aboutsummaryrefslogtreecommitdiffstats
path: root/libmproxy/flow.py
diff options
context:
space:
mode:
Diffstat (limited to 'libmproxy/flow.py')
-rw-r--r--libmproxy/flow.py135
1 files changed, 70 insertions, 65 deletions
diff --git a/libmproxy/flow.py b/libmproxy/flow.py
index 6aa26f4a..e5fdf424 100644
--- a/libmproxy/flow.py
+++ b/libmproxy/flow.py
@@ -2,16 +2,17 @@
This module provides more sophisticated flow tracking and provides filtering and interception facilities.
"""
from __future__ import absolute_import
-import base64
-import hashlib, Cookie, cookielib, re, threading
-import os
-import flask
-import requests
+import hashlib
+import Cookie
+import cookielib
+import re
from netlib import odict, wsgi
import netlib.http
-from . import controller, protocol, tnetstring, filt, script, version, app
+from . import controller, protocol, tnetstring, filt, script, version
+from .onboarding import app
from .protocol import http, handle
-from .proxy.config import parse_host_pattern
+from .proxy.config import HostMatcher
+import urlparse
ODict = odict.ODict
ODictCaseless = odict.ODictCaseless
@@ -150,10 +151,13 @@ class StreamLargeBodies(object):
def run(self, flow, is_request):
r = flow.request if is_request else flow.response
code = flow.response.code if flow.response else None
- expected_size = netlib.http.expected_http_body_size(r.headers, is_request, flow.request.method, code)
+ expected_size = netlib.http.expected_http_body_size(
+ r.headers, is_request, flow.request.method, code
+ )
if not (0 <= expected_size <= self.max_size):
r.stream = True
+
class ClientPlaybackState:
def __init__(self, flows, exit):
self.flows, self.exit = flows, exit
@@ -190,12 +194,12 @@ class ClientPlaybackState:
class ServerPlaybackState:
- def __init__(self, headers, flows, exit, nopop):
+ def __init__(self, headers, flows, exit, nopop, ignore_params, ignore_content):
"""
headers: Case-insensitive list of request headers that should be
included in request-response matching.
"""
- self.headers, self.exit, self.nopop = headers, exit, nopop
+ self.headers, self.exit, self.nopop, self.ignore_params, self.ignore_content = headers, exit, nopop, ignore_params, ignore_content
self.fmap = {}
for i in flows:
if i.response:
@@ -210,14 +214,29 @@ class ServerPlaybackState:
Calculates a loose hash of the flow request.
"""
r = flow.request
+
+ _, _, path, _, query, _ = urlparse.urlparse(r.url)
+ queriesArray = urlparse.parse_qsl(query)
+
+ filtered = []
+ for p in queriesArray:
+ if p[0] not in self.ignore_params:
+ filtered.append(p)
+
key = [
str(r.host),
str(r.port),
str(r.scheme),
str(r.method),
- str(r.path),
- str(r.content),
- ]
+ str(path),
+ ]
+ if not self.ignore_content:
+ key.append(str(r.content))
+
+ for p in filtered:
+ key.append(p[0])
+ key.append(p[1])
+
if self.headers:
hdrs = []
for i in self.headers:
@@ -446,43 +465,19 @@ class FlowMaster(controller.Master):
self.refresh_server_playback = False
self.replacehooks = ReplaceHooks()
self.setheaders = SetHeaders()
+ self.replay_ignore_params = False
+ self.replay_ignore_content = None
+
self.stream = None
self.apps = AppRegistry()
- def start_app(self, host, port, external):
- if not external:
- self.apps.add(
- app.mapp,
- host,
- port
- )
- else:
- @app.mapp.before_request
- def patch_environ(*args, **kwargs):
- flask.request.environ["mitmproxy.master"] = self
-
- # the only absurd way to shut down a flask/werkzeug server.
- # http://flask.pocoo.org/snippets/67/
- shutdown_secret = base64.b32encode(os.urandom(30))
-
- @app.mapp.route('/shutdown/<secret>')
- def shutdown(secret):
- if secret == shutdown_secret:
- flask.request.environ.get('werkzeug.server.shutdown')()
-
- # Workaround: Monkey-patch shutdown function to stop the app.
- # Improve this when we switch werkzeugs http server for something useful.
- _shutdown = self.shutdown
- def _shutdownwrap():
- _shutdown()
- requests.get("http://%s:%s/shutdown/%s" % (host, port, shutdown_secret))
- self.shutdown = _shutdownwrap
-
- threading.Thread(target=app.mapp.run, kwargs={
- "use_reloader": False,
- "host": host,
- "port": port}).start()
+ def start_app(self, host, port):
+ self.apps.add(
+ app.mapp,
+ host,
+ port
+ )
def add_event(self, e, level="info"):
"""
@@ -520,11 +515,17 @@ class FlowMaster(controller.Master):
for script in self.scripts:
self.run_single_script_hook(script, name, *args, **kwargs)
- def get_ignore(self):
- return [i.pattern for i in self.server.config.ignore]
+ def get_ignore_filter(self):
+ return self.server.config.check_ignore.patterns
+
+ def set_ignore_filter(self, host_patterns):
+ self.server.config.check_ignore = HostMatcher(host_patterns)
- def set_ignore(self, ignore):
- self.server.config.ignore = parse_host_pattern(ignore)
+ def get_tcp_filter(self):
+ return self.server.config.check_tcp.patterns
+
+ def set_tcp_filter(self, host_patterns):
+ self.server.config.check_tcp = HostMatcher(host_patterns)
def set_stickycookie(self, txt):
if txt:
@@ -563,12 +564,14 @@ class FlowMaster(controller.Master):
def stop_client_playback(self):
self.client_playback = None
- def start_server_playback(self, flows, kill, headers, exit, nopop):
+ def start_server_playback(self, flows, kill, headers, exit, nopop, ignore_params, ignore_content):
"""
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)
+ self.server_playback = ServerPlaybackState(headers, flows, exit, nopop, ignore_params, ignore_content)
self.kill_nonreplay = kill
def stop_server_playback(self):
@@ -585,7 +588,7 @@ class FlowMaster(controller.Master):
rflow = self.server_playback.next_flow(flow)
if not rflow:
return None
- response = http.HTTPResponse._from_state(rflow.response._get_state())
+ response = http.HTTPResponse.from_state(rflow.response.get_state())
response.is_replay = True
if self.refresh_server_playback:
response.refresh()
@@ -595,7 +598,7 @@ class FlowMaster(controller.Master):
return True
return None
- def tick(self, q):
+ def tick(self, q, timeout):
if self.client_playback:
e = [
self.client_playback.done(),
@@ -604,9 +607,9 @@ class FlowMaster(controller.Master):
]
if all(e):
self.shutdown()
- self.client_playback.tick(self)
+ self.client_playback.tick(self, timeout)
- return controller.Master.tick(self, q)
+ return controller.Master.tick(self, q, timeout)
def duplicate_flow(self, f):
return self.load_flow(f.copy())
@@ -664,6 +667,8 @@ class FlowMaster(controller.Master):
"""
Returns None if successful, or error message if not.
"""
+ if f.live:
+ return "Can't replay request which is still live..."
if f.intercepting:
return "Can't replay while intercepting..."
if f.request.content == http.CONTENT_MISSING:
@@ -676,11 +681,11 @@ class FlowMaster(controller.Master):
f.error = None
self.process_new_request(f)
rt = http.RequestReplayThread(
- self.server.config,
- f,
- self.masterq,
- self.should_exit
- )
+ self.server.config,
+ f,
+ self.masterq,
+ self.should_exit
+ )
rt.start() # pragma: no cover
if block:
rt.join()
@@ -732,7 +737,7 @@ class FlowMaster(controller.Master):
self.stream_large_bodies.run(f, False)
f.reply()
- return f
+ return f
def handle_response(self, f):
self.state.add_response(f)
@@ -769,7 +774,7 @@ class FlowWriter:
self.fo = fo
def add(self, flow):
- d = flow._get_state()
+ d = flow.get_state()
tnetstring.dump(d, self.fo)
@@ -795,7 +800,7 @@ class FlowReader:
v = ".".join(str(i) for i in data["version"])
raise FlowReadError("Incompatible serialized data version: %s"%v)
off = self.fo.tell()
- yield handle.protocols[data["conntype"]]["flow"]._from_state(data)
+ yield handle.protocols[data["type"]]["flow"].from_state(data)
except ValueError, v:
# Error is due to EOF
if self.fo.tell() == off and self.fo.read() == '':
@@ -811,5 +816,5 @@ class FilteredFlowWriter:
def add(self, f):
if self.filt and not f.match(self.filt):
return
- d = f._get_state()
+ d = f.get_state()
tnetstring.dump(d, self.fo)