aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/flow.py
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/flow.py')
-rw-r--r--libmproxy/flow.py128
1 files changed, 65 insertions, 63 deletions
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 9238cfbf..6d77fd88 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -23,6 +23,7 @@ import tnetstring, filt, script, utils, encoding, proxy
from email.utils import parsedate_tz, formatdate, mktime_tz
from netlib import odict, http, certutils
import controller, version
+import app
HDR_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_MISSING = 0
@@ -42,13 +43,13 @@ class ReplaceHooks:
def add(self, fpatt, rex, s):
"""
- Add a replacement hook.
+ add a replacement hook.
- fpatt: A string specifying a filter pattern.
- rex: A regular expression.
- s: The replacement string
+ fpatt: a string specifying a filter pattern.
+ rex: a regular expression.
+ s: the replacement string
- Returns True if hook was added, False if the pattern could not be
+ returns true if hook was added, false if the pattern could not be
parsed.
"""
cpatt = filt.parse(fpatt)
@@ -196,7 +197,15 @@ class decoded(object):
self.o.encode(self.ce)
-class HTTPMsg(controller.Msg):
+class StateObject:
+ def __eq__(self, other):
+ try:
+ return self._get_state() == other._get_state()
+ except AttributeError:
+ return False
+
+
+class HTTPMsg(StateObject):
def get_decoded_content(self):
"""
Returns the decoded content based on the current Content-Encoding header.
@@ -252,6 +261,7 @@ class HTTPMsg(controller.Msg):
return 0
return len(self.content)
+
class Request(HTTPMsg):
"""
An HTTP request.
@@ -289,7 +299,6 @@ class Request(HTTPMsg):
self.timestamp_start = timestamp_start or utils.timestamp()
self.timestamp_end = max(timestamp_end or utils.timestamp(), timestamp_start)
self.close = False
- controller.Msg.__init__(self)
# Have this request's cookies been modified by sticky cookies or auth?
self.stickycookie = False
@@ -388,15 +397,8 @@ class Request(HTTPMsg):
def __hash__(self):
return id(self)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
c.headers = self.headers.copy()
return c
@@ -458,11 +460,19 @@ class Request(HTTPMsg):
query = utils.urlencode(odict.lst)
self.set_url(urlparse.urlunparse([scheme, netloc, path, params, query, fragment]))
- def get_url(self):
+ def get_url(self, hostheader=False):
"""
Returns a URL string, constructed from the Request's URL compnents.
+
+ If hostheader is True, we use the value specified in the request
+ Host header to construct the URL.
"""
- return utils.unparse_url(self.scheme, self.host.decode("idna"), self.port, self.path).encode('ascii')
+ if hostheader:
+ host = self.headers.get_first("host") or self.host
+ else:
+ host = self.host
+ host = host.encode("idna")
+ return utils.unparse_url(self.scheme, host, self.port, self.path).encode('ascii')
def set_url(self, url):
"""
@@ -603,7 +613,6 @@ class Response(HTTPMsg):
self.cert = cert
self.timestamp_start = timestamp_start or utils.timestamp()
self.timestamp_end = max(timestamp_end or utils.timestamp(), timestamp_start)
- controller.Msg.__init__(self)
self.replay = False
def _refresh_cookie(self, c, delta):
@@ -700,15 +709,8 @@ class Response(HTTPMsg):
state["timestamp_end"],
)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
c.headers = self.headers.copy()
return c
@@ -773,7 +775,7 @@ class Response(HTTPMsg):
cookies.append((cookie_name, (cookie_value, cookie_parameters)))
return dict(cookies)
-class ClientDisconnect(controller.Msg):
+class ClientDisconnect:
"""
A client disconnection event.
@@ -782,11 +784,10 @@ class ClientDisconnect(controller.Msg):
client_conn: ClientConnect object.
"""
def __init__(self, client_conn):
- controller.Msg.__init__(self)
self.client_conn = client_conn
-class ClientConnect(controller.Msg):
+class ClientConnect(StateObject):
"""
A single client connection. Each connection can result in multiple HTTP
Requests.
@@ -807,10 +808,6 @@ class ClientConnect(controller.Msg):
self.close = False
self.requestcount = 0
self.error = None
- controller.Msg.__init__(self)
-
- def __eq__(self, other):
- return self._get_state() == other._get_state()
def _load_state(self, state):
self.close = True
@@ -834,15 +831,10 @@ class ClientConnect(controller.Msg):
return None
def copy(self):
- """
- Returns a copy of this object.
- """
- c = copy.copy(self)
- c.acked = True
- return c
+ return copy.copy(self)
-class Error(controller.Msg):
+class Error(StateObject):
"""
An Error.
@@ -860,18 +852,13 @@ class Error(controller.Msg):
def __init__(self, request, msg, timestamp=None):
self.request, self.msg = request, msg
self.timestamp = timestamp or utils.timestamp()
- controller.Msg.__init__(self)
def _load_state(self, state):
self.msg = state["msg"]
self.timestamp = state["timestamp"]
def copy(self):
- """
- Returns a copy of this object.
- """
c = copy.copy(self)
- c.acked = True
return c
def _get_state(self):
@@ -888,9 +875,6 @@ class Error(controller.Msg):
state["timestamp"],
)
- def __eq__(self, other):
- return self._get_state() == other._get_state()
-
def replace(self, pattern, repl, *args, **kwargs):
"""
Replaces a regular expression pattern with repl in both the headers
@@ -1180,10 +1164,11 @@ class Flow:
Kill this request.
"""
self.error = Error(self.request, "Connection killed")
- if self.request and not self.request.acked:
- self.request._ack(None)
- elif self.response and not self.response.acked:
- self.response._ack(None)
+ 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
@@ -1199,10 +1184,10 @@ class Flow:
Continue with the flow - called after an intercept().
"""
if self.request:
- if not self.request.acked:
- self.request._ack()
- elif self.response and not self.response.acked:
- self.response._ack()
+ 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):
@@ -1325,7 +1310,7 @@ class State(object):
if f.request in self._flow_map:
del self._flow_map[f.request]
self._flow_list.remove(f)
- if f.match(self._limit):
+ if f in self.view:
self.view.remove(f)
return True
@@ -1368,6 +1353,7 @@ class FlowMaster(controller.Master):
self.setheaders = SetHeaders()
self.stream = None
+ app.mapp.config["PMASTER"] = self
def add_event(self, e, level="info"):
"""
@@ -1464,7 +1450,7 @@ class FlowMaster(controller.Master):
flow.response = response
if self.refresh_server_playback:
response.refresh()
- flow.request._ack(response)
+ flow.request.reply(response)
if self.server_playback.count() == 0:
self.stop_server_playback()
return True
@@ -1491,10 +1477,13 @@ class FlowMaster(controller.Master):
Loads a flow, and returns a new flow object.
"""
if f.request:
+ f.request.reply = controller.DummyReply()
fr = self.handle_request(f.request)
if f.response:
+ f.response.reply = controller.DummyReply()
self.handle_response(f.response)
if f.error:
+ f.error.reply = controller.DummyReply()
self.handle_error(f.error)
return fr
@@ -1522,7 +1511,7 @@ class FlowMaster(controller.Master):
if self.kill_nonreplay:
f.kill(self)
else:
- f.request._ack()
+ f.request.reply()
def process_new_response(self, f):
if self.stickycookie_state:
@@ -1561,11 +1550,11 @@ class FlowMaster(controller.Master):
def handle_clientconnect(self, cc):
self.run_script_hook("clientconnect", cc)
- cc._ack()
+ cc.reply()
def handle_clientdisconnect(self, r):
self.run_script_hook("clientdisconnect", r)
- r._ack()
+ r.reply()
def handle_error(self, r):
f = self.state.add_error(r)
@@ -1573,7 +1562,7 @@ class FlowMaster(controller.Master):
self.run_script_hook("error", f)
if self.client_playback:
self.client_playback.clear(f)
- r._ack()
+ r.reply()
return f
def handle_request(self, r):
@@ -1596,7 +1585,7 @@ class FlowMaster(controller.Master):
if self.stream:
self.stream.add(f)
else:
- r._ack()
+ r.reply()
return f
def shutdown(self):
@@ -1609,8 +1598,8 @@ class FlowMaster(controller.Master):
self.stream.add(i)
self.stop_stream()
- def start_stream(self, fp):
- self.stream = FlowWriter(fp)
+ def start_stream(self, fp, filt):
+ self.stream = FilteredFlowWriter(fp, filt)
def stop_stream(self):
self.stream.fo.close()
@@ -1656,3 +1645,16 @@ class FlowReader:
return
raise FlowReadError("Invalid data format.")
+
+class FilteredFlowWriter:
+ def __init__(self, fo, filt):
+ self.fo = fo
+ self.filt = filt
+
+ def add(self, f):
+ if self.filt and not f.match(self.filt):
+ return
+ d = f._get_state()
+ tnetstring.dump(d, self.fo)
+
+