aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2014-12-23 00:15:08 +0100
committerMaximilian Hils <git@maximilianhils.com>2014-12-23 00:15:08 +0100
commit72ab44ef54910d384403ac541b877814464c15c5 (patch)
tree9f00b67f70d079038c34bf7034a26ce6ef3379f2
parentd2471592d23f592dfa484bc6e49ad73bc060bda6 (diff)
parentebae1ebe5dfe08bde978ae24f0346d3b1f045a7e (diff)
downloadmitmproxy-72ab44ef54910d384403ac541b877814464c15c5.tar.gz
mitmproxy-72ab44ef54910d384403ac541b877814464c15c5.tar.bz2
mitmproxy-72ab44ef54910d384403ac541b877814464c15c5.zip
Merge branch 'master' of github.com:mitmproxy/mitmproxy
-rw-r--r--libmproxy/cmdline.py16
-rw-r--r--libmproxy/dump.py5
-rw-r--r--libmproxy/flow.py39
-rw-r--r--test/test_flow.py76
4 files changed, 109 insertions, 27 deletions
diff --git a/libmproxy/cmdline.py b/libmproxy/cmdline.py
index bf5add33..ec03d63e 100644
--- a/libmproxy/cmdline.py
+++ b/libmproxy/cmdline.py
@@ -183,7 +183,8 @@ def get_common_options(options):
verbosity=options.verbose,
nopop=options.nopop,
replay_ignore_content = options.replay_ignore_content,
- replay_ignore_params = options.replay_ignore_params
+ replay_ignore_params = options.replay_ignore_params,
+ replay_ignore_payload_params = options.replay_ignore_payload_params
)
@@ -438,13 +439,24 @@ def common_options(parser):
help="Disable response pop from response flow. "
"This makes it possible to replay same response multiple times."
)
- group.add_argument(
+ payload = group.add_mutually_exclusive_group()
+ payload.add_argument(
"--replay-ignore-content",
action="store_true", dest="replay_ignore_content", default=False,
help="""
Ignore request's content while searching for a saved flow to replay
"""
)
+ payload.add_argument(
+ "--replay-ignore-payload-param",
+ action="append", dest="replay_ignore_payload_params", type=str,
+ help="""
+ Request's payload parameters (application/x-www-form-urlencoded) to
+ be ignored while searching for a saved flow to replay.
+ Can be passed multiple times.
+ """
+ )
+
group.add_argument(
"--replay-ignore-param",
action="append", dest="replay_ignore_params", type=str,
diff --git a/libmproxy/dump.py b/libmproxy/dump.py
index 8f260745..731592dc 100644
--- a/libmproxy/dump.py
+++ b/libmproxy/dump.py
@@ -39,6 +39,7 @@ class Options(object):
"outfile",
"replay_ignore_content",
"replay_ignore_params",
+ "replay_ignore_payload_params",
]
def __init__(self, **kwargs):
@@ -78,6 +79,7 @@ class DumpMaster(flow.FlowMaster):
self.replay_ignore_params = options.replay_ignore_params
self.replay_ignore_content = options.replay_ignore_content
self.refresh_server_playback = options.refresh_server_playback
+ self.replay_ignore_payload_params = options.replay_ignore_payload_params
self.set_stream_large_bodies(options.stream_large_bodies)
@@ -115,7 +117,8 @@ class DumpMaster(flow.FlowMaster):
not options.keepserving,
options.nopop,
options.replay_ignore_params,
- options.replay_ignore_content
+ options.replay_ignore_content,
+ options.replay_ignore_payload_params,
)
if options.client_replay:
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index d3ae383e..904a64b1 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -201,12 +201,12 @@ class ClientPlaybackState:
class ServerPlaybackState:
- def __init__(self, headers, flows, exit, nopop, ignore_params, ignore_content):
+ def __init__(self, headers, flows, exit, nopop, ignore_params, ignore_content, ignore_payload_params):
"""
headers: Case-insensitive list of request headers that should be
included in request-response matching.
"""
- self.headers, self.exit, self.nopop, self.ignore_params, self.ignore_content = headers, exit, nopop, ignore_params, ignore_content
+ self.headers, self.exit, self.nopop, self.ignore_params, self.ignore_content, self.ignore_payload_params = headers, exit, nopop, ignore_params, ignore_content, ignore_payload_params
self.fmap = {}
for i in flows:
if i.response:
@@ -225,22 +225,37 @@ class ServerPlaybackState:
_, _, path, _, query, _ = urlparse.urlparse(r.url)
queriesArray = urlparse.parse_qsl(query)
- filtered = []
- ignore_params = self.ignore_params or []
- for p in queriesArray:
- if p[0] not in ignore_params:
- filtered.append(p)
-
key = [
str(r.host),
str(r.port),
str(r.scheme),
str(r.method),
str(path),
- ]
+ ]
+
if not self.ignore_content:
- key.append(str(r.content))
+ ignore_payload_params = self.ignore_payload_params or []
+ ct = r.headers["Content-Type"]
+ if len(ct) > 0:
+ ct = ct[0]
+ if len(ignore_payload_params) > 0 and ct == "application/x-www-form-urlencoded":
+ parsedContent = urlparse.parse_qsl(r.content)
+ filtered = []
+ for p in parsedContent:
+ if p[0] not in ignore_payload_params:
+ filtered.append(p)
+
+ for p in filtered:
+ key.append(p[0])
+ key.append(p[1])
+ else:
+ key.append(str(r.content))
+ filtered = []
+ ignore_params = self.ignore_params or []
+ for p in queriesArray:
+ if p[0] not in ignore_params:
+ filtered.append(p)
for p in filtered:
key.append(p[0])
key.append(p[1])
@@ -697,14 +712,14 @@ class FlowMaster(controller.Master):
def stop_client_playback(self):
self.client_playback = None
- def start_server_playback(self, flows, kill, headers, exit, nopop, ignore_params, ignore_content):
+ def start_server_playback(self, flows, kill, headers, exit, nopop, ignore_params, ignore_content, ignore_payload_params):
"""
flows: List of flows.
kill: Boolean, should we kill requests not part of the replay?
ignore_params: list of parameters to ignore in server replay
ignore_content: true if request content should be ignored in server replay
"""
- self.server_playback = ServerPlaybackState(headers, flows, exit, nopop, ignore_params, ignore_content)
+ self.server_playback = ServerPlaybackState(headers, flows, exit, nopop, ignore_params, ignore_content, ignore_payload_params)
self.kill_nonreplay = kill
def stop_server_playback(self):
diff --git a/test/test_flow.py b/test/test_flow.py
index fdfac62f..48f5ba55 100644
--- a/test/test_flow.py
+++ b/test/test_flow.py
@@ -114,7 +114,7 @@ class TestClientPlaybackState:
class TestServerPlaybackState:
def test_hash(self):
- s = flow.ServerPlaybackState(None, [], False, False, None, False)
+ s = flow.ServerPlaybackState(None, [], False, False, None, False, None)
r = tutils.tflow()
r2 = tutils.tflow()
@@ -126,7 +126,7 @@ class TestServerPlaybackState:
assert s._hash(r) != s._hash(r2)
def test_headers(self):
- s = flow.ServerPlaybackState(["foo"], [], False, False, None, False)
+ s = flow.ServerPlaybackState(["foo"], [], False, False, None, False, None)
r = tutils.tflow(resp=True)
r.request.headers["foo"] = ["bar"]
r2 = tutils.tflow(resp=True)
@@ -147,7 +147,7 @@ class TestServerPlaybackState:
r2 = tutils.tflow(resp=True)
r2.request.headers["key"] = ["two"]
- s = flow.ServerPlaybackState(None, [r, r2], False, False, None, False)
+ s = flow.ServerPlaybackState(None, [r, r2], False, False, None, False, None)
assert s.count() == 2
assert len(s.fmap.keys()) == 1
@@ -168,7 +168,7 @@ class TestServerPlaybackState:
r2 = tutils.tflow(resp=True)
r2.request.headers["key"] = ["two"]
- s = flow.ServerPlaybackState(None, [r, r2], False, True, None, False)
+ s = flow.ServerPlaybackState(None, [r, r2], False, True, None, False, None)
assert s.count() == 2
s.next_flow(r)
@@ -176,7 +176,7 @@ class TestServerPlaybackState:
def test_ignore_params(self):
- s = flow.ServerPlaybackState(None, [], False, False, ["param1", "param2"], False)
+ s = flow.ServerPlaybackState(None, [], False, False, ["param1", "param2"], False, None)
r = tutils.tflow(resp=True)
r.request.path="/test?param1=1"
r2 = tutils.tflow(resp=True)
@@ -189,8 +189,60 @@ class TestServerPlaybackState:
r2.request.path="/test?param3=2"
assert not s._hash(r) == s._hash(r2)
+ def test_ignore_payload_params(self):
+ s = flow.ServerPlaybackState(None, [], False, False, None, False, ["param1", "param2"])
+ r = tutils.tflow(resp=True)
+ r.request.headers["Content-Type"] = ["application/x-www-form-urlencoded"]
+ r.request.content = "paramx=x&param1=1"
+ r2 = tutils.tflow(resp=True)
+ r2.request.headers["Content-Type"] = ["application/x-www-form-urlencoded"]
+ r2.request.content = "paramx=x&param1=1"
+ # same parameters
+ assert s._hash(r) == s._hash(r2)
+ # ignored parameters !=
+ r2.request.content = "paramx=x&param1=2"
+ assert s._hash(r) == s._hash(r2)
+ # missing parameter
+ r2.request.content="paramx=x"
+ assert s._hash(r) == s._hash(r2)
+ # ignorable parameter added
+ r2.request.content="paramx=x&param1=2"
+ assert s._hash(r) == s._hash(r2)
+ # not ignorable parameter changed
+ r2.request.content="paramx=y&param1=1"
+ assert not s._hash(r) == s._hash(r2)
+ # not ignorable parameter missing
+ r2.request.content="param1=1"
+ assert not s._hash(r) == s._hash(r2)
+
+ def test_ignore_payload_params_other_content_type(self):
+ s = flow.ServerPlaybackState(None, [], False, False, None, False, ["param1", "param2"])
+ r = tutils.tflow(resp=True)
+ r.request.headers["Content-Type"] = ["application/json"]
+ r.request.content = '{"param1":"1"}'
+ r2 = tutils.tflow(resp=True)
+ r2.request.headers["Content-Type"] = ["application/json"]
+ r2.request.content = '{"param1":"1"}'
+ # same content
+ assert s._hash(r) == s._hash(r2)
+ # distint content (note only x-www-form-urlencoded payload is analysed)
+ r2.request.content = '{"param1":"2"}'
+ assert not s._hash(r) == s._hash(r2)
+
+ def test_ignore_payload_wins_over_params(self):
+ #NOTE: parameters are mutually exclusive in options
+ s = flow.ServerPlaybackState(None, [], False, False, None, True, ["param1", "param2"])
+ r = tutils.tflow(resp=True)
+ r.request.headers["Content-Type"] = ["application/x-www-form-urlencoded"]
+ r.request.content = "paramx=y"
+ r2 = tutils.tflow(resp=True)
+ r2.request.headers["Content-Type"] = ["application/x-www-form-urlencoded"]
+ r2.request.content = "paramx=x"
+ # same parameters
+ assert s._hash(r) == s._hash(r2)
+
def test_ignore_content(self):
- s = flow.ServerPlaybackState(None, [], False, False, None, False)
+ s = flow.ServerPlaybackState(None, [], False, False, None, False, None)
r = tutils.tflow(resp=True)
r2 = tutils.tflow(resp=True)
@@ -201,7 +253,7 @@ class TestServerPlaybackState:
assert not s._hash(r) == s._hash(r2)
#now ignoring content
- s = flow.ServerPlaybackState(None, [], False, False, None, True)
+ s = flow.ServerPlaybackState(None, [], False, False, None, True, None)
r = tutils.tflow(resp=True)
r2 = tutils.tflow(resp=True)
r.request.content = "foo"
@@ -695,7 +747,7 @@ class TestFlowMaster:
pb = [tutils.tflow(resp=True), f]
fm = flow.FlowMaster(DummyServer(ProxyConfig()), s)
- assert not fm.start_server_playback(pb, False, [], False, False, None, False)
+ assert not fm.start_server_playback(pb, False, [], False, False, None, False, None)
assert not fm.start_client_playback(pb, False)
fm.client_playback.testing = True
@@ -718,16 +770,16 @@ class TestFlowMaster:
fm.refresh_server_playback = True
assert not fm.do_server_playback(tutils.tflow())
- fm.start_server_playback(pb, False, [], False, False, None, False)
+ fm.start_server_playback(pb, False, [], False, False, None, False, None)
assert fm.do_server_playback(tutils.tflow())
- fm.start_server_playback(pb, False, [], True, False, None, False)
+ fm.start_server_playback(pb, False, [], True, False, None, False, None)
r = tutils.tflow()
r.request.content = "gibble"
assert not fm.do_server_playback(r)
assert fm.do_server_playback(tutils.tflow())
- fm.start_server_playback(pb, False, [], True, False, None, False)
+ fm.start_server_playback(pb, False, [], True, False, None, False, None)
q = Queue.Queue()
fm.tick(q, 0)
assert fm.should_exit.is_set()
@@ -742,7 +794,7 @@ class TestFlowMaster:
pb = [f]
fm = flow.FlowMaster(None, s)
fm.refresh_server_playback = True
- fm.start_server_playback(pb, True, [], False, False, None, False)
+ fm.start_server_playback(pb, True, [], False, False, None, False, None)
f = tutils.tflow()
f.request.host = "nonexistent"