diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/mitmproxy/builtins/test_dumper.py | 86 | ||||
-rw-r--r-- | test/mitmproxy/builtins/test_filestreamer.py (renamed from test/mitmproxy/builtins/test_stream.py) | 4 | ||||
-rw-r--r-- | test/mitmproxy/builtins/test_setheaders.py | 64 | ||||
-rw-r--r-- | test/mitmproxy/mastertest.py | 7 | ||||
-rw-r--r-- | test/mitmproxy/test_dump.py | 83 | ||||
-rw-r--r-- | test/mitmproxy/test_flow.py | 60 | ||||
-rw-r--r-- | test/mitmproxy/test_options.py | 11 | ||||
-rw-r--r-- | test/mitmproxy/test_protocol_http2.py | 177 | ||||
-rw-r--r-- | test/netlib/http/http1/test_read.py | 26 |
9 files changed, 363 insertions, 155 deletions
diff --git a/test/mitmproxy/builtins/test_dumper.py b/test/mitmproxy/builtins/test_dumper.py new file mode 100644 index 00000000..57e3d036 --- /dev/null +++ b/test/mitmproxy/builtins/test_dumper.py @@ -0,0 +1,86 @@ +from .. import tutils, mastertest +from six.moves import cStringIO as StringIO + +from mitmproxy.builtins import dumper +from mitmproxy.flow import state +from mitmproxy import exceptions +from mitmproxy import dump +from mitmproxy import models +import netlib.tutils +import mock + + +class TestDumper(mastertest.MasterTest): + def test_simple(self): + d = dumper.Dumper() + sio = StringIO() + + d.configure(dump.Options(tfile = sio, flow_detail = 0)) + d.response(tutils.tflow()) + assert not sio.getvalue() + + d.configure(dump.Options(tfile = sio, flow_detail = 4)) + d.response(tutils.tflow()) + assert sio.getvalue() + + sio = StringIO() + d.configure(dump.Options(tfile = sio, flow_detail = 4)) + d.response(tutils.tflow(resp=True)) + assert "<<" in sio.getvalue() + + sio = StringIO() + d.configure(dump.Options(tfile = sio, flow_detail = 4)) + d.response(tutils.tflow(err=True)) + assert "<<" in sio.getvalue() + + sio = StringIO() + d.configure(dump.Options(tfile = sio, flow_detail = 4)) + flow = tutils.tflow() + flow.request = netlib.tutils.treq() + flow.request.stickycookie = True + flow.client_conn = mock.MagicMock() + flow.client_conn.address.host = "foo" + flow.response = netlib.tutils.tresp(content=None) + flow.response.is_replay = True + flow.response.status_code = 300 + d.response(flow) + assert sio.getvalue() + + sio = StringIO() + d.configure(dump.Options(tfile = sio, flow_detail = 4)) + flow = tutils.tflow(resp=netlib.tutils.tresp(content=b"{")) + flow.response.headers["content-type"] = "application/json" + flow.response.status_code = 400 + d.response(flow) + assert sio.getvalue() + + sio = StringIO() + d.configure(dump.Options(tfile = sio)) + flow = tutils.tflow() + flow.request.content = None + flow.response = models.HTTPResponse.wrap(netlib.tutils.tresp()) + flow.response.content = None + d.response(flow) + assert "content missing" in sio.getvalue() + + +class TestContentView(mastertest.MasterTest): + @mock.patch("mitmproxy.contentviews.get_content_view") + def test_contentview(self, get_content_view): + se = exceptions.ContentViewException(""), ("x", iter([])) + get_content_view.side_effect = se + + s = state.State() + sio = StringIO() + m = mastertest.RecordingMaster( + dump.Options( + flow_detail=4, + verbosity=3, + tfile=sio, + ), + None, s + ) + d = dumper.Dumper() + m.addons.add(d) + self.invoke(m, "response", tutils.tflow()) + assert "Content viewer failed" in m.event_log[0][1] diff --git a/test/mitmproxy/builtins/test_stream.py b/test/mitmproxy/builtins/test_filestreamer.py index edaa41d2..002006b7 100644 --- a/test/mitmproxy/builtins/test_stream.py +++ b/test/mitmproxy/builtins/test_filestreamer.py @@ -4,7 +4,7 @@ from .. import tutils, mastertest import os.path -from mitmproxy.builtins import stream +from mitmproxy.builtins import filestreamer from mitmproxy.flow import master, FlowReader from mitmproxy.flow import state from mitmproxy.flow import options @@ -27,7 +27,7 @@ class TestStream(mastertest.MasterTest): None, s ) - sa = stream.Stream() + sa = filestreamer.FileStreamer() m.addons.add(sa) f = tutils.tflow(resp=True) diff --git a/test/mitmproxy/builtins/test_setheaders.py b/test/mitmproxy/builtins/test_setheaders.py new file mode 100644 index 00000000..1a8d048c --- /dev/null +++ b/test/mitmproxy/builtins/test_setheaders.py @@ -0,0 +1,64 @@ +from .. import tutils, mastertest + +from mitmproxy.builtins import setheaders +from mitmproxy.flow import state +from mitmproxy.flow import options + + +class TestSetHeaders(mastertest.MasterTest): + def mkmaster(self, **opts): + s = state.State() + m = mastertest.RecordingMaster(options.Options(**opts), None, s) + sh = setheaders.SetHeaders() + m.addons.add(sh) + return m, sh + + def test_configure(self): + sh = setheaders.SetHeaders() + tutils.raises( + "invalid setheader filter pattern", + sh.configure, + options.Options( + setheaders = [("~b", "one", "two")] + ) + ) + + def test_setheaders(self): + m, sh = self.mkmaster( + setheaders = [ + ("~q", "one", "two"), + ("~s", "one", "three") + ] + ) + f = tutils.tflow() + f.request.headers["one"] = "xxx" + self.invoke(m, "request", f) + assert f.request.headers["one"] == "two" + + f = tutils.tflow(resp=True) + f.response.headers["one"] = "xxx" + self.invoke(m, "response", f) + assert f.response.headers["one"] == "three" + + m, sh = self.mkmaster( + setheaders = [ + ("~s", "one", "two"), + ("~s", "one", "three") + ] + ) + f = tutils.tflow(resp=True) + f.request.headers["one"] = "xxx" + f.response.headers["one"] = "xxx" + self.invoke(m, "response", f) + assert f.response.headers.get_all("one") == ["two", "three"] + + m, sh = self.mkmaster( + setheaders = [ + ("~q", "one", "two"), + ("~q", "one", "three") + ] + ) + f = tutils.tflow() + f.request.headers["one"] = "xxx" + self.invoke(m, "request", f) + assert f.request.headers.get_all("one") == ["two", "three"] diff --git a/test/mitmproxy/mastertest.py b/test/mitmproxy/mastertest.py index d1fe8cb4..dcc0dc48 100644 --- a/test/mitmproxy/mastertest.py +++ b/test/mitmproxy/mastertest.py @@ -8,11 +8,12 @@ from mitmproxy import flow, proxy, models, controller class MasterTest: - def invoke(self, master, handler, message): + def invoke(self, master, handler, *message): with master.handlecontext(): func = getattr(master, handler) - func(message) - message.reply = controller.DummyReply() + func(*message) + if message: + message[0].reply = controller.DummyReply() def cycle(self, master, content): f = tutils.tflow(req=netlib.tutils.treq(content=content)) diff --git a/test/mitmproxy/test_dump.py b/test/mitmproxy/test_dump.py index c94630a9..90f33264 100644 --- a/test/mitmproxy/test_dump.py +++ b/test/mitmproxy/test_dump.py @@ -1,67 +1,11 @@ import os from six.moves import cStringIO as StringIO -from mitmproxy.exceptions import ContentViewException -import netlib.tutils - -from mitmproxy import dump, flow, models, exceptions +from mitmproxy import dump, flow, exceptions from . import tutils, mastertest import mock -def test_strfuncs(): - o = dump.Options( - tfile = StringIO(), - flow_detail = 0, - ) - m = dump.DumpMaster(None, o) - - m.o.flow_detail = 0 - m.echo_flow(tutils.tflow()) - assert not o.tfile.getvalue() - - m.o.flow_detail = 4 - m.echo_flow(tutils.tflow()) - assert o.tfile.getvalue() - - o.tfile = StringIO() - m.echo_flow(tutils.tflow(resp=True)) - assert "<<" in o.tfile.getvalue() - - o.tfile = StringIO() - m.echo_flow(tutils.tflow(err=True)) - assert "<<" in o.tfile.getvalue() - - flow = tutils.tflow() - flow.request = netlib.tutils.treq() - flow.request.stickycookie = True - flow.client_conn = mock.MagicMock() - flow.client_conn.address.host = "foo" - flow.response = netlib.tutils.tresp(content=None) - flow.response.is_replay = True - flow.response.status_code = 300 - m.echo_flow(flow) - - flow = tutils.tflow(resp=netlib.tutils.tresp(content=b"{")) - flow.response.headers["content-type"] = "application/json" - flow.response.status_code = 400 - m.echo_flow(flow) - - -@mock.patch("mitmproxy.contentviews.get_content_view") -def test_contentview(get_content_view): - get_content_view.side_effect = ContentViewException(""), ("x", iter([])) - - 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.options.tfile.getvalue() - - class TestDumpMaster(mastertest.MasterTest): def dummy_cycle(self, master, n, content): mastertest.MasterTest.dummy_cycle(self, master, n, content) @@ -72,11 +16,7 @@ class TestDumpMaster(mastertest.MasterTest): options["verbosity"] = 0 if "flow_detail" not in options: options["flow_detail"] = 0 - o = dump.Options( - filtstr=filt, - tfile=StringIO(), - **options - ) + o = dump.Options(filtstr=filt, tfile=StringIO(), **options) return dump.DumpMaster(None, o) def test_basic(self): @@ -104,24 +44,10 @@ class TestDumpMaster(mastertest.MasterTest): ) m = dump.DumpMaster(None, o) f = tutils.tflow(err=True) - m.request(f) + m.error(f) assert m.error(f) assert "error" in o.tfile.getvalue() - def test_missing_content(self): - 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 o.tfile.getvalue() - def test_replay(self): o = dump.Options(server_replay=["nonexistent"], kill=True) tutils.raises(dump.DumpError, dump.DumpMaster, None, o) @@ -155,9 +81,8 @@ class TestDumpMaster(mastertest.MasterTest): self.flowfile(p) assert "GET" in self.dummy_cycle( self.mkmaster(None, flow_detail=1, rfile=p), - 0, b"", + 1, b"", ) - tutils.raises( dump.DumpError, self.mkmaster, None, verbosity=1, rfile="/nonexistent" diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 10163401..90f7f915 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -751,7 +751,11 @@ class TestFlowMaster: f = tutils.tflow(resp=True) pb = [tutils.tflow(resp=True), f] - fm = flow.FlowMaster(None, DummyServer(ProxyConfig()), s) + fm = flow.FlowMaster( + flow.options.Options(), + DummyServer(ProxyConfig()), + s + ) assert not fm.start_server_playback( pb, False, @@ -779,7 +783,7 @@ class TestFlowMaster: f.response = HTTPResponse.wrap(netlib.tutils.tresp(content=f.request)) pb = [f] - fm = flow.FlowMaster(None, None, s) + fm = flow.FlowMaster(flow.options.Options(), None, s) fm.refresh_server_playback = True assert not fm.do_server_playback(tutils.tflow()) @@ -961,55 +965,3 @@ class TestClientConnection: assert c3.get_state() == c.get_state() assert str(c) - - -def test_setheaders(): - h = flow.SetHeaders() - h.add("~q", "foo", "bar") - assert h.lst - - h.set( - [ - (".*", "one", "two"), - (".*", "three", "four"), - ] - ) - assert h.count() == 2 - - h.clear() - assert not h.lst - - h.add("~q", "foo", "bar") - h.add("~s", "foo", "bar") - - v = h.get_specs() - assert v == [('~q', 'foo', 'bar'), ('~s', 'foo', 'bar')] - assert h.count() == 2 - h.clear() - assert h.count() == 0 - - f = tutils.tflow() - f.request.content = b"foo" - h.add("~s", "foo", "bar") - h.run(f) - assert f.request.content == b"foo" - - h.clear() - h.add("~s", "one", "two") - h.add("~s", "one", "three") - f = tutils.tflow(resp=True) - f.request.headers["one"] = "xxx" - f.response.headers["one"] = "xxx" - h.run(f) - assert f.request.headers["one"] == "xxx" - assert f.response.headers.get_all("one") == ["two", "three"] - - h.clear() - h.add("~q", "one", "two") - h.add("~q", "one", "three") - f = tutils.tflow() - f.request.headers["one"] = "xxx" - h.run(f) - assert f.request.headers.get_all("one") == ["two", "three"] - - assert not h.add("~", "foo", "bar") diff --git a/test/mitmproxy/test_options.py b/test/mitmproxy/test_options.py index cdb0d765..af619b27 100644 --- a/test/mitmproxy/test_options.py +++ b/test/mitmproxy/test_options.py @@ -52,6 +52,17 @@ def test_setter(): o.setter("nonexistent") +def test_toggler(): + o = TO(two=True) + f = o.toggler("two") + f() + assert o.two is False + f() + assert o.two is True + with tutils.raises("no such option"): + o.toggler("nonexistent") + + def test_rollback(): o = TO(one="two") diff --git a/test/mitmproxy/test_protocol_http2.py b/test/mitmproxy/test_protocol_http2.py index a100ac2d..b8f724bd 100644 --- a/test/mitmproxy/test_protocol_http2.py +++ b/test/mitmproxy/test_protocol_http2.py @@ -87,31 +87,31 @@ class _Http2ServerBase(netlib_tservers.ServerTestBase): class _Http2TestBase(object): @classmethod - def setup_class(self): - self.config = ProxyConfig(**self.get_proxy_config()) + def setup_class(cls): + cls.config = ProxyConfig(**cls.get_proxy_config()) - tmaster = tservers.TestMaster(self.config) + tmaster = tservers.TestMaster(cls.config) tmaster.start_app(APP_HOST, APP_PORT) - self.proxy = tservers.ProxyThread(tmaster) - self.proxy.start() + cls.proxy = tservers.ProxyThread(tmaster) + cls.proxy.start() @classmethod def teardown_class(cls): cls.proxy.shutdown() - @property - def master(self): - return self.proxy.tmaster - @classmethod def get_proxy_config(cls): cls.cadir = os.path.join(tempfile.gettempdir(), "mitmproxy") return dict( - no_upstream_cert = False, - cadir = cls.cadir, - authenticator = None, + no_upstream_cert=False, + cadir=cls.cadir, + authenticator=None, ) + @property + def master(self): + return self.proxy.tmaster + def setup(self): self.master.clear_log() self.master.state.clear() @@ -150,13 +150,17 @@ class _Http2TestBase(object): stream_id=1, headers=[], body=b'', + end_stream=None, priority_exclusive=None, priority_depends_on=None, priority_weight=None): + if end_stream is None: + end_stream = (len(body) == 0) + h2_conn.send_headers( stream_id=stream_id, headers=headers, - end_stream=(len(body) == 0), + end_stream=end_stream, priority_exclusive=priority_exclusive, priority_depends_on=priority_depends_on, priority_weight=priority_weight, @@ -376,6 +380,153 @@ class TestRequestWithPriority(_Http2Test): @requires_alpn +class TestPriority(_Http2Test): + priority_data = None + + @classmethod + def handle_server_event(self, event, h2_conn, rfile, wfile): + if isinstance(event, h2.events.ConnectionTerminated): + return False + elif isinstance(event, h2.events.PriorityUpdated): + self.priority_data = (event.exclusive, event.depends_on, event.weight) + elif isinstance(event, h2.events.RequestReceived): + import warnings + with warnings.catch_warnings(): + # Ignore UnicodeWarning: + # h2/utilities.py:64: UnicodeWarning: Unicode equal comparison + # failed to convert both arguments to Unicode - interpreting + # them as being unequal. + # elif header[0] in (b'cookie', u'cookie') and len(header[1]) < 20: + + warnings.simplefilter("ignore") + + headers = [(':status', '200')] + h2_conn.send_headers(event.stream_id, headers) + h2_conn.end_stream(event.stream_id) + wfile.write(h2_conn.data_to_send()) + wfile.flush() + return True + + def test_priority(self): + client, h2_conn = self._setup_connection() + + h2_conn.prioritize(1, exclusive=True, depends_on=0, weight=42) + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + self._send_request( + client.wfile, + h2_conn, + headers=[ + (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':method', 'GET'), + (':scheme', 'https'), + (':path', '/'), + ], + ) + + done = False + while not done: + try: + raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) + events = h2_conn.receive_data(raw) + except HttpException: + print(traceback.format_exc()) + assert False + + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + for event in events: + if isinstance(event, h2.events.StreamEnded): + done = True + + h2_conn.close_connection() + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + assert len(self.master.state.flows) == 1 + assert self.priority_data == (True, 0, 42) + + +@requires_alpn +class TestPriorityWithExistingStream(_Http2Test): + priority_data = [] + + @classmethod + def handle_server_event(self, event, h2_conn, rfile, wfile): + if isinstance(event, h2.events.ConnectionTerminated): + return False + elif isinstance(event, h2.events.PriorityUpdated): + self.priority_data.append((event.exclusive, event.depends_on, event.weight)) + elif isinstance(event, h2.events.RequestReceived): + assert not event.priority_updated + + import warnings + with warnings.catch_warnings(): + # Ignore UnicodeWarning: + # h2/utilities.py:64: UnicodeWarning: Unicode equal comparison + # failed to convert both arguments to Unicode - interpreting + # them as being unequal. + # elif header[0] in (b'cookie', u'cookie') and len(header[1]) < 20: + + warnings.simplefilter("ignore") + + headers = [(':status', '200')] + h2_conn.send_headers(event.stream_id, headers) + wfile.write(h2_conn.data_to_send()) + wfile.flush() + elif isinstance(event, h2.events.StreamEnded): + h2_conn.end_stream(event.stream_id) + wfile.write(h2_conn.data_to_send()) + wfile.flush() + return True + + def test_priority_with_existing_stream(self): + client, h2_conn = self._setup_connection() + + self._send_request( + client.wfile, + h2_conn, + headers=[ + (':authority', "127.0.0.1:%s" % self.server.server.address.port), + (':method', 'GET'), + (':scheme', 'https'), + (':path', '/'), + ], + end_stream=False, + ) + + h2_conn.prioritize(1, exclusive=True, depends_on=0, weight=42) + h2_conn.end_stream(1) + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + done = False + while not done: + try: + raw = b''.join(framereader.http2_read_raw_frame(client.rfile)) + events = h2_conn.receive_data(raw) + except HttpException: + print(traceback.format_exc()) + assert False + + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + for event in events: + if isinstance(event, h2.events.StreamEnded): + done = True + + h2_conn.close_connection() + client.wfile.write(h2_conn.data_to_send()) + client.wfile.flush() + + assert len(self.master.state.flows) == 1 + assert self.priority_data == [(True, 0, 42)] + + +@requires_alpn class TestStreamResetFromServer(_Http2Test): @classmethod diff --git a/test/netlib/http/http1/test_read.py b/test/netlib/http/http1/test_read.py index 5285ac1d..c8a40ecb 100644 --- a/test/netlib/http/http1/test_read.py +++ b/test/netlib/http/http1/test_read.py @@ -1,6 +1,9 @@ from __future__ import absolute_import, print_function, division + from io import BytesIO from mock import Mock +import pytest + from netlib.exceptions import HttpException, HttpSyntaxException, HttpReadDisconnect, TcpDisconnect from netlib.http import Headers from netlib.http.http1.read import ( @@ -23,11 +26,18 @@ def test_get_header_tokens(): assert get_header_tokens(headers, "foo") == ["bar", "voing", "oink"] -def test_read_request(): - rfile = BytesIO(b"GET / HTTP/1.1\r\n\r\nskip") +@pytest.mark.parametrize("input", [ + b"GET / HTTP/1.1\r\n\r\nskip", + b"GET / HTTP/1.1\r\n\r\nskip", + b"GET / HTTP/1.1\r\n\r\nskip", + b"GET / HTTP/1.1 \r\n\r\nskip", +]) +def test_read_request(input): + rfile = BytesIO(input) r = read_request(rfile) assert r.method == "GET" assert r.content == b"" + assert r.http_version == "HTTP/1.1" assert r.timestamp_end assert rfile.read() == b"skip" @@ -50,11 +60,19 @@ def test_read_request_head(): assert rfile.read() == b"skip" -def test_read_response(): +@pytest.mark.parametrize("input", [ + b"HTTP/1.1 418 I'm a teapot\r\n\r\nbody", + b"HTTP/1.1 418 I'm a teapot\r\n\r\nbody", + b"HTTP/1.1 418 I'm a teapot\r\n\r\nbody", + b"HTTP/1.1 418 I'm a teapot \r\n\r\nbody", +]) +def test_read_response(input): req = treq() - rfile = BytesIO(b"HTTP/1.1 418 I'm a teapot\r\n\r\nbody") + rfile = BytesIO(input) r = read_response(rfile, req) + assert r.http_version == "HTTP/1.1" assert r.status_code == 418 + assert r.reason == "I'm a teapot" assert r.content == b"body" assert r.timestamp_end |