aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/flow.py
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2014-01-30 05:21:53 +0100
committerMaximilian Hils <git@maximilianhils.com>2014-01-30 05:21:53 +0100
commite00bbccfd6b0e2e4710db96bd133748eb594b10e (patch)
tree79109137cbfbf5d5cb5677c3a57af34f6dd5b684 /libmproxy/flow.py
parent40bf42f14a7ec386024a8925502fa3c6e6f0657e (diff)
downloadmitmproxy-e00bbccfd6b0e2e4710db96bd133748eb594b10e.tar.gz
mitmproxy-e00bbccfd6b0e2e4710db96bd133748eb594b10e.tar.bz2
mitmproxy-e00bbccfd6b0e2e4710db96bd133748eb594b10e.zip
remove old classes
Diffstat (limited to 'libmproxy/flow.py')
-rw-r--r--libmproxy/flow.py480
1 files changed, 4 insertions, 476 deletions
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 0f6204cf..4baee3ee 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -163,13 +163,12 @@ class StateObject:
class SimpleStateObject(StateObject):
"""
- A StateObject with opionated conventions that tries to keep everything DRY.y
+ A StateObject with opionated conventions that tries to keep everything DRY.
"""
_stateobject_attributes = None
"""
- A dict where the keys represent the attributes to be serialized.
- The values represent the attribute class or type.
+ An attribute-name -> class-or-type dict containing all attributes that should be serialized
If the attribute is a class, this class must be a subclass of StateObject.
"""
@@ -204,316 +203,6 @@ class SimpleStateObject(StateObject):
return f
-class Response(object):
- """
- An HTTP response.
-
- Exposes the following attributes:
-
- request: Request object.
-
- code: HTTP response code
-
- msg: HTTP response message
-
- headers: ODict object
-
- content: Content of the request, None, or CONTENT_MISSING if there
- is content associated, but not present. CONTENT_MISSING evaluates
- to False to make checking for the presence of content natural.
-
- timestamp_start: Seconds since the epoch signifying response transmission started
-
- timestamp_end: Seconds since the epoch signifying response transmission ended
- """
- def __init__(self, request, httpversion, code, msg, headers, content, cert, timestamp_start=None, timestamp_end=None):
- assert isinstance(headers, ODictCaseless)
- self.request = request
- self.httpversion, self.code, self.msg = httpversion, code, msg
- self.headers, self.content = headers, content
- self.cert = cert
- self.timestamp_start = timestamp_start or utils.timestamp()
- self.timestamp_end = timestamp_end or utils.timestamp()
- self.replay = False
-
- def _refresh_cookie(self, c, delta):
- """
- Takes a cookie string c and a time delta in seconds, and returns
- a refreshed cookie string.
- """
- c = Cookie.SimpleCookie(str(c))
- for i in c.values():
- if "expires" in i:
- d = parsedate_tz(i["expires"])
- if d:
- d = mktime_tz(d) + delta
- i["expires"] = formatdate(d)
- else:
- # This can happen when the expires tag is invalid.
- # reddit.com sends a an expires tag like this: "Thu, 31 Dec
- # 2037 23:59:59 GMT", which is valid RFC 1123, but not
- # strictly correct according tot he cookie spec. Browsers
- # appear to parse this tolerantly - maybe we should too.
- # For now, we just ignore this.
- del i["expires"]
- return c.output(header="").strip()
-
- def refresh(self, now=None):
- """
- This fairly complex and heuristic function refreshes a server
- response for replay.
-
- - It adjusts date, expires and last-modified headers.
- - It adjusts cookie expiration.
- """
- if not now:
- now = time.time()
- delta = now - self.timestamp_start
- refresh_headers = [
- "date",
- "expires",
- "last-modified",
- ]
- for i in refresh_headers:
- if i in self.headers:
- d = parsedate_tz(self.headers[i][0])
- if d:
- new = mktime_tz(d) + delta
- self.headers[i] = [formatdate(new)]
- c = []
- for i in self.headers["set-cookie"]:
- c.append(self._refresh_cookie(i, delta))
- if c:
- self.headers["set-cookie"] = c
-
- def _set_replay(self):
- self.replay = True
-
- def is_replay(self):
- """
- Is this response a replay?
- """
- return self.replay
-
- def _load_state(self, state):
- self.code = state["code"]
- self.msg = state["msg"]
- self.headers = ODictCaseless._from_state(state["headers"])
- self.content = state["content"]
- self.timestamp_start = state["timestamp_start"]
- self.timestamp_end = state["timestamp_end"]
- self.cert = certutils.SSLCert.from_pem(state["cert"]) if state["cert"] else None
-
- def _get_state(self):
- return dict(
- httpversion = self.httpversion,
- code = self.code,
- msg = self.msg,
- headers = self.headers._get_state(),
- timestamp_start = self.timestamp_start,
- timestamp_end = self.timestamp_end,
- cert = self.cert.to_pem() if self.cert else None,
- content = self.content,
- )
-
- @classmethod
- def _from_state(klass, request, state):
- return klass(
- request,
- state["httpversion"],
- state["code"],
- str(state["msg"]),
- ODictCaseless._from_state(state["headers"]),
- state["content"],
- certutils.SSLCert.from_pem(state["cert"]) if state["cert"] else None,
- state["timestamp_start"],
- state["timestamp_end"],
- )
-
- def copy(self):
- c = copy.copy(self)
- c.headers = self.headers.copy()
- return c
-
- def _assemble_head(self):
- FMT = '%s\r\n%s\r\n'
- headers = self.headers.copy()
- utils.del_all(
- headers,
- ['proxy-connection', 'transfer-encoding']
- )
- if self.content:
- headers["Content-Length"] = [str(len(self.content))]
- elif 'Transfer-Encoding' in self.headers:
- headers["Content-Length"] = ["0"]
- proto = "HTTP/%s.%s %s %s"%(self.httpversion[0], self.httpversion[1], self.code, str(self.msg))
- data = (proto, str(headers))
- return FMT%data
-
- def _assemble(self):
- """
- Assembles the response for transmission to the client. We make some
- modifications to make sure interception works properly.
-
- Returns None if the request cannot be assembled.
- """
- if self.content == CONTENT_MISSING:
- return None
- head = self._assemble_head()
- if self.content:
- return head + self.content
- else:
- return head
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in both the headers
- and the body of the response. Encoded content will be decoded
- before replacement, and re-encoded afterwards.
-
- Returns the number of replacements made.
- """
- with decoded(self):
- self.content, c = utils.safe_subn(pattern, repl, self.content, *args, **kwargs)
- c += self.headers.replace(pattern, repl, *args, **kwargs)
- return c
-
- def get_header_size(self):
- FMT = '%s\r\n%s\r\n'
- proto = "HTTP/%s.%s %s %s"%(self.httpversion[0], self.httpversion[1], self.code, str(self.msg))
- assembled_header = FMT % (proto, str(self.headers))
- return len(assembled_header)
-
- def get_cookies(self):
- cookie_headers = self.headers.get("set-cookie")
- if not cookie_headers:
- return None
-
- cookies = []
- for header in cookie_headers:
- pairs = [pair.partition("=") for pair in header.split(';')]
- cookie_name = pairs[0][0] # the key of the first key/value pairs
- cookie_value = pairs[0][2] # the value of the first key/value pairs
- cookie_parameters = {key.strip().lower():value.strip() for key,sep,value in pairs[1:]}
- cookies.append((cookie_name, (cookie_value, cookie_parameters)))
- return dict(cookies)
-
-
-class ClientDisconnect:
- """
- A client disconnection event.
-
- Exposes the following attributes:
-
- client_conn: ClientConnect object.
- """
- def __init__(self, client_conn):
- self.client_conn = client_conn
-
-
-class ClientConnect(StateObject):
- """
- A single client connection. Each connection can result in multiple HTTP
- Requests.
-
- Exposes the following attributes:
-
- address: (address, port) tuple, or None if the connection is replayed.
- requestcount: Number of requests created by this client connection.
- close: Is the client connection closed?
- error: Error string or None.
- """
- def __init__(self, address):
- """
- address is an (address, port) tuple, or None if this connection has
- been replayed from within mitmproxy.
- """
- self.address = address
- self.close = False
- self.error = None
-
- def __str__(self):
- if self.address:
- return "%s:%d"%(self.address[0],self.address[1])
-
- def _load_state(self, state):
- self.close = True
- self.error = state["error"]
- self.requestcount = state["requestcount"]
-
- def _get_state(self):
- return dict(
- address = list(self.address),
- requestcount = -1, # FIXME self.requestcount,
- error = self.error,
- )
-
- @classmethod
- def _from_state(klass, state):
- if state:
- k = klass(state["address"])
- k._load_state(state)
- return k
- else:
- return None
-
- def copy(self):
- return copy.copy(self)
-
-
-class Error(StateObject):
- """
- An Error.
-
- This is distinct from an HTTP error response (say, a code 500), which
- is represented by a normal Response object. This class is responsible
- for indicating errors that fall outside of normal HTTP communications,
- like interrupted connections, timeouts, protocol errors.
-
- Exposes the following attributes:
-
- request: Request object
- msg: Message describing the error
- timestamp: Seconds since the epoch
- """
- def __init__(self, request, msg, timestamp=None):
- self.request, self.msg = request, msg
- self.timestamp = timestamp or utils.timestamp()
-
- def _load_state(self, state):
- self.msg = state["msg"]
- self.timestamp = state["timestamp"]
-
- def copy(self):
- c = copy.copy(self)
- return c
-
- def _get_state(self):
- return dict(
- msg = self.msg,
- timestamp = self.timestamp,
- )
-
- @classmethod
- def _from_state(klass, request, state):
- return klass(
- request,
- state["msg"],
- state["timestamp"],
- )
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in both the headers
- and the body of the request. Returns the number of replacements
- made.
-
- FIXME: Is replace useful on an Error object??
- """
- self.msg, c = utils.safe_subn(pattern, repl, self.msg, *args, **kwargs)
- return c
-
-
class ClientPlaybackState:
def __init__(self, flows, exit):
self.flows, self.exit = flows, exit
@@ -673,167 +362,6 @@ class StickyAuthState:
f.request.headers["authorization"] = self.hosts[f.request.host]
-class Flow:
- """
- A Flow is a collection of objects representing a single HTTP
- transaction. The main attributes are:
-
- request: Request object
- response: Response object
- error: Error object
-
- Note that it's possible for a Flow to have both a response and an error
- object. This might happen, for instance, when a response was received
- from the server, but there was an error sending it back to the client.
-
- The following additional attributes are exposed:
-
- intercepting: Is this flow currently being intercepted?
- """
- def __init__(self, request):
- self.request = request
- self.response, self.error = None, None
- self.intercepting = False
- self._backup = None
-
- def copy(self):
- rc = self.request.copy()
- f = Flow(rc)
- if self.response:
- f.response = self.response.copy()
- f.response.request = rc
- if self.error:
- f.error = self.error.copy()
- f.error.request = rc
- return f
-
- @classmethod
- def _from_state(klass, state):
- f = klass(None)
- f._load_state(state)
- return f
-
- def _get_state(self):
- d = dict(
- request = self.request._get_state() if self.request else None,
- response = self.response._get_state() if self.response else None,
- error = self.error._get_state() if self.error else None,
- version = version.IVERSION
- )
- return d
-
- def _load_state(self, state):
- if self.request:
- self.request._load_state(state["request"])
- else:
- self.request = Request._from_state(state["request"])
-
- if state["response"]:
- if self.response:
- self.response._load_state(state["response"])
- else:
- self.response = Response._from_state(self.request, state["response"])
- else:
- self.response = None
-
- if state["error"]:
- if self.error:
- self.error._load_state(state["error"])
- else:
- self.error = Error._from_state(self.request, state["error"])
- else:
- self.error = None
-
- def modified(self):
- """
- Has this Flow been modified?
- """
- # FIXME: Save a serialization in backup, compare current with
- # backup to detect if flow has _really_ been modified.
- if self._backup:
- return True
- else:
- return False
-
- def backup(self, force=False):
- """
- Save a backup of this Flow, which can be reverted to using a
- call to .revert().
- """
- if not self._backup:
- self._backup = self._get_state()
-
- def revert(self):
- """
- Revert to the last backed up state.
- """
- if self._backup:
- self._load_state(self._backup)
- self._backup = None
-
- def match(self, f):
- """
- Match this flow against a compiled filter expression. Returns True
- if matched, False if not.
-
- If f is a string, it will be compiled as a filter expression. If
- the expression is invalid, ValueError is raised.
- """
- if isinstance(f, basestring):
- f = filt.parse(f)
- if not f:
- raise ValueError("Invalid filter expression.")
- if f:
- return f(self)
- return True
-
- def kill(self, master):
- """
- Kill this request.
- """
- self.error = Error(self.request, "Connection killed")
- self.error.reply = controller.DummyReply()
- if self.request and not self.request.reply.acked:
- self.request.reply(proxy.KILL)
- elif self.response and not self.response.reply.acked:
- self.response.reply(proxy.KILL)
- master.handle_error(self.error)
- self.intercepting = False
-
- def intercept(self):
- """
- Intercept this Flow. Processing will stop until accept_intercept is
- called.
- """
- self.intercepting = True
-
- def accept_intercept(self):
- """
- Continue with the flow - called after an intercept().
- """
- if self.request:
- if not self.request.reply.acked:
- self.request.reply()
- elif self.response and not self.response.reply.acked:
- self.response.reply()
- self.intercepting = False
-
- def replace(self, pattern, repl, *args, **kwargs):
- """
- Replaces a regular expression pattern with repl in all parts of the
- flow. Encoded content will be decoded before replacement, and
- re-encoded afterwards.
-
- Returns the number of replacements made.
- """
- c = self.request.replace(pattern, repl, *args, **kwargs)
- if self.response:
- c += self.response.replace(pattern, repl, *args, **kwargs)
- if self.error:
- c += self.error.replace(pattern, repl, *args, **kwargs)
- return c
-
-
class State(object):
def __init__(self):
self._flow_map = {}
@@ -866,7 +394,7 @@ class State(object):
"""
Add a request to the state. Returns the matching flow.
"""
- f = Flow(req)
+ f = req.flow
self._flow_list.append(f)
self._flow_map[req] = f
assert len(self._flow_list) == len(self._flow_map)
@@ -891,7 +419,7 @@ class State(object):
Add an error response to the state. Returns the matching flow, or
None if there isn't one.
"""
- f = self._flow_map.get(err.request)
+ f = self._flow_map.get(err.flow)
if not f:
return None
f.error = err