aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@nullcube.com>2016-07-14 12:43:17 +1200
committerAldo Cortesi <aldo@nullcube.com>2016-07-14 13:52:09 +1200
commitb2c4f301cb834ecdf6e5b0063e86be877d3ece6d (patch)
tree3496f88cbcfa1e7c2470e3e1bed9ac67bb563bdb
parent703c05066ec0bc05c680e24d368606791dd1c958 (diff)
downloadmitmproxy-b2c4f301cb834ecdf6e5b0063e86be877d3ece6d.tar.gz
mitmproxy-b2c4f301cb834ecdf6e5b0063e86be877d3ece6d.tar.bz2
mitmproxy-b2c4f301cb834ecdf6e5b0063e86be877d3ece6d.zip
Stream to file -> addon
This commit also clarifies a confusion about the "outfile" attribute and its use in testing in the mitmdump master.
-rw-r--r--mitmproxy/builtins/__init__.py2
-rw-r--r--mitmproxy/builtins/stream.py54
-rw-r--r--mitmproxy/dump.py22
-rw-r--r--mitmproxy/flow/master.py28
-rw-r--r--test/mitmproxy/builtins/test_stream.py46
-rw-r--r--test/mitmproxy/mastertest.py13
-rw-r--r--test/mitmproxy/test_dump.py89
-rw-r--r--test/mitmproxy/test_flow.py31
8 files changed, 167 insertions, 118 deletions
diff --git a/mitmproxy/builtins/__init__.py b/mitmproxy/builtins/__init__.py
index 75326712..8021c20f 100644
--- a/mitmproxy/builtins/__init__.py
+++ b/mitmproxy/builtins/__init__.py
@@ -4,6 +4,7 @@ from mitmproxy.builtins import anticache
from mitmproxy.builtins import anticomp
from mitmproxy.builtins import stickyauth
from mitmproxy.builtins import stickycookie
+from mitmproxy.builtins import stream
def default_addons():
@@ -12,4 +13,5 @@ def default_addons():
anticomp.AntiComp(),
stickyauth.StickyAuth(),
stickycookie.StickyCookie(),
+ stream.Stream(),
]
diff --git a/mitmproxy/builtins/stream.py b/mitmproxy/builtins/stream.py
new file mode 100644
index 00000000..821a71f1
--- /dev/null
+++ b/mitmproxy/builtins/stream.py
@@ -0,0 +1,54 @@
+from __future__ import absolute_import, print_function, division
+import os.path
+
+from mitmproxy import ctx
+from mitmproxy import exceptions
+from mitmproxy.flow import io
+
+
+class Stream:
+ def __init__(self):
+ self.stream = None
+
+ def start_stream_to_path(self, path, mode, filt):
+ path = os.path.expanduser(path)
+ try:
+ f = open(path, mode)
+ except IOError as v:
+ return str(v)
+ self.stream = io.FilteredFlowWriter(f, filt)
+
+ def configure(self, options):
+ # We're already streaming - stop the previous stream and restart
+ if self.stream:
+ self.done()
+
+ if options.outfile:
+ filt = None
+ if options.get("filtstr"):
+ filt = filt.parse(options.filtstr)
+ if not filt:
+ raise exceptions.OptionsError(
+ "Invalid filter specification: %s" % options.filtstr
+ )
+ path, mode = options.outfile
+ if mode not in ("wb", "ab"):
+ raise exceptions.OptionsError("Invalid mode.")
+ err = self.start_stream_to_path(path, mode, filt)
+ if err:
+ raise exceptions.OptionsError(err)
+
+ def done(self):
+ if self.stream:
+ for flow in ctx.master.active_flows:
+ self.stream.add(flow)
+ self.stream.fo.close()
+ self.stream = None
+
+ def tcp_close(self, flow):
+ if self.stream:
+ self.stream.add(flow)
+
+ def response(self, flow):
+ if self.stream:
+ self.stream.add(flow)
diff --git a/mitmproxy/dump.py b/mitmproxy/dump.py
index d9142a4d..296419db 100644
--- a/mitmproxy/dump.py
+++ b/mitmproxy/dump.py
@@ -52,16 +52,17 @@ class Options(options.Options):
"replay_ignore_content",
"replay_ignore_params",
"replay_ignore_payload_params",
- "replay_ignore_host"
+ "replay_ignore_host",
+
+ "tfile"
]
class DumpMaster(flow.FlowMaster):
- def __init__(self, server, options, outfile=None):
+ def __init__(self, server, options):
flow.FlowMaster.__init__(self, options, server, flow.State())
self.addons.add(*builtins.default_addons())
- self.outfile = outfile
self.o = options
self.showhost = options.showhost
self.replay_ignore_params = options.replay_ignore_params
@@ -82,15 +83,6 @@ class DumpMaster(flow.FlowMaster):
else:
self.filt = None
- if options.outfile:
- err = self.start_stream_to_path(
- options.outfile[0],
- options.outfile[1],
- self.filt
- )
- if err:
- raise DumpError(err)
-
if options.replacements:
for i in options.replacements:
self.replacehooks.add(*i)
@@ -163,7 +155,7 @@ class DumpMaster(flow.FlowMaster):
def echo(self, text, indent=None, **style):
if indent:
text = self.indent(indent, text)
- click.secho(text, file=self.outfile, **style)
+ click.secho(text, file=self.options.tfile, **style)
def _echo_message(self, message):
if self.options.flow_detail >= 2 and hasattr(message, "headers"):
@@ -312,8 +304,8 @@ class DumpMaster(flow.FlowMaster):
if f.error:
self.echo(" << {}".format(f.error.msg), bold=True, fg="red")
- if self.outfile:
- self.outfile.flush()
+ if self.options.tfile:
+ self.options.tfile.flush()
def _process_flow(self, f):
if self.filt and not f.match(self.filt):
diff --git a/mitmproxy/flow/master.py b/mitmproxy/flow/master.py
index d67ee7cc..27ceee87 100644
--- a/mitmproxy/flow/master.py
+++ b/mitmproxy/flow/master.py
@@ -46,7 +46,6 @@ class FlowMaster(controller.Master):
self.replay_ignore_content = None
self.replay_ignore_host = False
- self.stream = None
self.apps = modules.AppRegistry()
def start_app(self, host, port):
@@ -409,8 +408,6 @@ class FlowMaster(controller.Master):
if not f.reply.acked:
if self.client_playback:
self.client_playback.clear(f)
- if self.stream:
- self.stream.add(f)
return f
def handle_intercept(self, f):
@@ -471,33 +468,8 @@ class FlowMaster(controller.Master):
@controller.handler
def tcp_close(self, flow):
self.active_flows.discard(flow)
- if self.stream:
- self.stream.add(flow)
self.run_scripts("tcp_close", flow)
def shutdown(self):
super(FlowMaster, self).shutdown()
-
- # Add all flows that are still active
- if self.stream:
- for flow in self.active_flows:
- self.stream.add(flow)
- self.stop_stream()
-
self.unload_scripts()
-
- def start_stream(self, fp, filt):
- self.stream = io.FilteredFlowWriter(fp, filt)
-
- def stop_stream(self):
- self.stream.fo.close()
- self.stream = None
-
- def start_stream_to_path(self, path, mode="wb", filt=None):
- path = os.path.expanduser(path)
- try:
- f = open(path, mode)
- self.start_stream(f, filt)
- except IOError as v:
- return str(v)
- self.stream_path = path
diff --git a/test/mitmproxy/builtins/test_stream.py b/test/mitmproxy/builtins/test_stream.py
new file mode 100644
index 00000000..54e4f7d9
--- /dev/null
+++ b/test/mitmproxy/builtins/test_stream.py
@@ -0,0 +1,46 @@
+from __future__ import absolute_import, print_function, division
+
+from .. import tutils, mastertest
+
+import os.path
+
+from mitmproxy.builtins import stream
+from mitmproxy.flow import master, FlowReader
+from mitmproxy.flow import state
+from mitmproxy import options
+
+
+class TestStream(mastertest.MasterTest):
+ def test_stream(self):
+ with tutils.tmpdir() as tdir:
+ p = os.path.join(tdir, "foo")
+
+ def r():
+ r = FlowReader(open(p, "rb"))
+ return list(r.stream())
+
+ s = state.State()
+ m = master.FlowMaster(
+ options.Options(
+ outfile = (p, "wb")
+ ),
+ None,
+ s
+ )
+ sa = stream.Stream()
+
+ m.addons.add(sa)
+ f = tutils.tflow(resp=True)
+ self.invoke(m, "request", f)
+ self.invoke(m, "response", f)
+ m.addons.remove(sa)
+
+ assert r()[0].response
+
+ m.options.outfile = (p, "ab")
+
+ m.addons.add(sa)
+ f = tutils.tflow()
+ self.invoke(m, "request", f)
+ m.addons.remove(sa)
+ assert not r()[1].response
diff --git a/test/mitmproxy/mastertest.py b/test/mitmproxy/mastertest.py
index 06854e25..9754d3a9 100644
--- a/test/mitmproxy/mastertest.py
+++ b/test/mitmproxy/mastertest.py
@@ -18,15 +18,14 @@ class MasterTest:
l = proxy.Log("connect")
l.reply = mock.MagicMock()
master.log(l)
- master.clientconnect(f.client_conn)
- master.serverconnect(f.server_conn)
- master.request(f)
+ self.invoke(master, "clientconnect", f.client_conn)
+ self.invoke(master, "clientconnect", f.client_conn)
+ self.invoke(master, "serverconnect", f.server_conn)
+ self.invoke(master, "request", f)
if not f.error:
f.response = models.HTTPResponse.wrap(netlib.tutils.tresp(content=content))
- f.reply.acked = False
- f = master.response(f)
- f.client_conn.reply.acked = False
- master.clientdisconnect(f.client_conn)
+ self.invoke(master, "response", f)
+ self.invoke(master, "clientdisconnect", f)
return f
def dummy_cycle(self, master, n, content):
diff --git a/test/mitmproxy/test_dump.py b/test/mitmproxy/test_dump.py
index aa73b5a4..9686be84 100644
--- a/test/mitmproxy/test_dump.py
+++ b/test/mitmproxy/test_dump.py
@@ -4,31 +4,33 @@ from mitmproxy.exceptions import ContentViewException
import netlib.tutils
-from mitmproxy import dump, flow, models
+from mitmproxy import dump, flow, models, exceptions
from . import tutils, mastertest
import mock
def test_strfuncs():
- o = dump.Options()
+ o = dump.Options(
+ tfile = StringIO(),
+ flow_detail = 0,
+ )
m = dump.DumpMaster(None, o)
- m.outfile = StringIO()
m.o.flow_detail = 0
m.echo_flow(tutils.tflow())
- assert not m.outfile.getvalue()
+ assert not o.tfile.getvalue()
m.o.flow_detail = 4
m.echo_flow(tutils.tflow())
- assert m.outfile.getvalue()
+ assert o.tfile.getvalue()
- m.outfile = StringIO()
+ o.tfile = StringIO()
m.echo_flow(tutils.tflow(resp=True))
- assert "<<" in m.outfile.getvalue()
+ assert "<<" in o.tfile.getvalue()
- m.outfile = StringIO()
+ o.tfile = StringIO()
m.echo_flow(tutils.tflow(err=True))
- assert "<<" in m.outfile.getvalue()
+ assert "<<" in o.tfile.getvalue()
flow = tutils.tflow()
flow.request = netlib.tutils.treq()
@@ -50,25 +52,32 @@ def test_strfuncs():
def test_contentview(get_content_view):
get_content_view.side_effect = ContentViewException(""), ("x", iter([]))
- o = dump.Options(flow_detail=4, verbosity=3)
- m = dump.DumpMaster(None, o, StringIO())
+ o = dump.Options(
+ flow_detail=4,
+ verbosity=3,
+ tfile=StringIO(),
+ )
+ m = dump.DumpMaster(None, o)
m.echo_flow(tutils.tflow())
- assert "Content viewer failed" in m.outfile.getvalue()
+ assert "Content viewer failed" in m.options.tfile.getvalue()
class TestDumpMaster(mastertest.MasterTest):
def dummy_cycle(self, master, n, content):
mastertest.MasterTest.dummy_cycle(self, master, n, content)
- return master.outfile.getvalue()
+ return master.options.tfile.getvalue()
def mkmaster(self, filt, **options):
- cs = StringIO()
if "verbosity" not in options:
options["verbosity"] = 0
if "flow_detail" not in options:
options["flow_detail"] = 0
- o = dump.Options(filtstr=filt, **options)
- return dump.DumpMaster(None, o, outfile=cs)
+ o = dump.Options(
+ filtstr=filt,
+ tfile=StringIO(),
+ **options
+ )
+ return dump.DumpMaster(None, o)
def test_basic(self):
for i in (1, 2, 3):
@@ -89,31 +98,33 @@ class TestDumpMaster(mastertest.MasterTest):
)
def test_error(self):
- cs = StringIO()
- o = dump.Options(flow_detail=1)
- m = dump.DumpMaster(None, o, outfile=cs)
+ o = dump.Options(
+ tfile=StringIO(),
+ flow_detail=1
+ )
+ m = dump.DumpMaster(None, o)
f = tutils.tflow(err=True)
m.request(f)
assert m.error(f)
- assert "error" in cs.getvalue()
+ assert "error" in o.tfile.getvalue()
def test_missing_content(self):
- cs = StringIO()
- o = dump.Options(flow_detail=3)
- m = dump.DumpMaster(None, o, outfile=cs)
+ o = dump.Options(
+ flow_detail=3,
+ tfile=StringIO(),
+ )
+ m = dump.DumpMaster(None, o)
f = tutils.tflow()
f.request.content = None
m.request(f)
f.response = models.HTTPResponse.wrap(netlib.tutils.tresp())
f.response.content = None
m.response(f)
- assert "content missing" in cs.getvalue()
+ assert "content missing" in o.tfile.getvalue()
def test_replay(self):
- cs = StringIO()
-
o = dump.Options(server_replay=["nonexistent"], kill=True)
- tutils.raises(dump.DumpError, dump.DumpMaster, None, o, outfile=cs)
+ tutils.raises(dump.DumpError, dump.DumpMaster, None, o)
with tutils.tmpdir() as t:
p = os.path.join(t, "rep")
@@ -122,7 +133,7 @@ class TestDumpMaster(mastertest.MasterTest):
o = dump.Options(server_replay=[p], kill=True)
o.verbosity = 0
o.flow_detail = 0
- m = dump.DumpMaster(None, o, outfile=cs)
+ m = dump.DumpMaster(None, o)
self.cycle(m, b"content")
self.cycle(m, b"content")
@@ -130,13 +141,13 @@ class TestDumpMaster(mastertest.MasterTest):
o = dump.Options(server_replay=[p], kill=False)
o.verbosity = 0
o.flow_detail = 0
- m = dump.DumpMaster(None, o, outfile=cs)
+ m = dump.DumpMaster(None, o)
self.cycle(m, b"nonexistent")
o = dump.Options(client_replay=[p], kill=False)
o.verbosity = 0
o.flow_detail = 0
- m = dump.DumpMaster(None, o, outfile=cs)
+ m = dump.DumpMaster(None, o)
def test_read(self):
with tutils.tmpdir() as t:
@@ -172,20 +183,24 @@ class TestDumpMaster(mastertest.MasterTest):
assert len(m.apps.apps) == 1
def test_replacements(self):
- cs = StringIO()
- o = dump.Options(replacements=[(".*", "content", "foo")])
+ o = dump.Options(
+ replacements=[(".*", "content", "foo")],
+ tfile = StringIO(),
+ )
o.verbosity = 0
o.flow_detail = 0
- m = dump.DumpMaster(None, o, outfile=cs)
+ m = dump.DumpMaster(None, o)
f = self.cycle(m, b"content")
assert f.request.content == b"foo"
def test_setheader(self):
- cs = StringIO()
- o = dump.Options(setheaders=[(".*", "one", "two")])
+ o = dump.Options(
+ setheaders=[(".*", "one", "two")],
+ tfile=StringIO()
+ )
o.verbosity = 0
o.flow_detail = 0
- m = dump.DumpMaster(None, o, outfile=cs)
+ m = dump.DumpMaster(None, o)
f = self.cycle(m, b"content")
assert f.request.headers["one"] == "two"
@@ -212,7 +227,7 @@ class TestDumpMaster(mastertest.MasterTest):
def test_write_err(self):
tutils.raises(
- dump.DumpError,
+ exceptions.OptionsError,
self.mkmaster, None, outfile = ("nonexistentdir/foo", "wb")
)
diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py
index 91342e58..1a07f74d 100644
--- a/test/mitmproxy/test_flow.py
+++ b/test/mitmproxy/test_flow.py
@@ -1,5 +1,3 @@
-import os.path
-
import mock
import io
@@ -887,35 +885,6 @@ class TestFlowMaster:
fm.process_new_request(f)
assert "killed" in f.error.msg
- def test_stream(self):
- with tutils.tmpdir() as tdir:
- p = os.path.join(tdir, "foo")
-
- def read():
- with open(p, "rb") as f:
- r = flow.FlowReader(f)
- return list(r.stream())
-
- s = flow.State()
- fm = flow.FlowMaster(None, None, s)
- f = tutils.tflow(resp=True)
-
- with open(p, "ab") as tmpfile:
- fm.start_stream(tmpfile, None)
- fm.request(f)
- fm.response(f)
- fm.stop_stream()
-
- assert read()[0].response
-
- with open(p, "ab") as tmpfile:
- f = tutils.tflow()
- fm.start_stream(tmpfile, None)
- fm.request(f)
- fm.shutdown()
-
- assert not read()[1].response
-
class TestRequest: