aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/conftest.py27
-rw-r--r--test/examples/test_xss_scanner.py8
-rw-r--r--test/filename_matching.py2
-rw-r--r--test/helper_tools/typehints_for_options.py34
-rw-r--r--test/mitmproxy/addons/test_allowremote.py38
-rw-r--r--test/mitmproxy/addons/test_browser.py31
-rw-r--r--test/mitmproxy/addons/test_check_alpn.py23
-rw-r--r--test/mitmproxy/addons/test_clientplayback.py11
-rw-r--r--test/mitmproxy/addons/test_core.py6
-rw-r--r--test/mitmproxy/addons/test_core_option_validation.py1
-rw-r--r--test/mitmproxy/addons/test_cut.py184
-rw-r--r--test/mitmproxy/addons/test_dumper.py2
-rw-r--r--test/mitmproxy/addons/test_eventstore.py15
-rw-r--r--test/mitmproxy/addons/test_intercept.py12
-rw-r--r--test/mitmproxy/addons/test_proxyauth.py421
-rw-r--r--test/mitmproxy/addons/test_save.py13
-rw-r--r--test/mitmproxy/addons/test_script.py101
-rw-r--r--test/mitmproxy/addons/test_streambodies.py6
-rw-r--r--test/mitmproxy/addons/test_termlog.py2
-rw-r--r--test/mitmproxy/addons/test_termstatus.py2
-rw-r--r--test/mitmproxy/addons/test_view.py37
-rw-r--r--test/mitmproxy/contentviews/image/test_image_parser.py23
-rw-r--r--test/mitmproxy/contentviews/image/test_view.py3
-rw-r--r--test/mitmproxy/contentviews/test_auto.py2
-rw-r--r--test/mitmproxy/contentviews/test_css.py61
-rw-r--r--test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css11
-rw-r--r--test/mitmproxy/contentviews/test_css_data/animation-keyframe.css3
-rw-r--r--test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css35
-rw-r--r--test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css30
-rw-r--r--test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css22
-rw-r--r--test/mitmproxy/contentviews/test_css_data/block-comment.css18
-rw-r--r--test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css2
-rw-r--r--test/mitmproxy/contentviews/test_css_data/empty-rule.css1
-rw-r--r--test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css8
-rw-r--r--test/mitmproxy/contentviews/test_css_data/import-directive.css2
-rw-r--r--test/mitmproxy/contentviews/test_css_data/indentation-formatted.css3
-rw-r--r--test/mitmproxy/contentviews/test_css_data/indentation.css3
-rw-r--r--test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css17
-rw-r--r--test/mitmproxy/contentviews/test_css_data/media-directive.css7
-rw-r--r--test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css7
-rw-r--r--test/mitmproxy/contentviews/test_css_data/quoted-string.css2
-rw-r--r--test/mitmproxy/contentviews/test_css_data/selectors-formatted.css19
-rw-r--r--test/mitmproxy/contentviews/test_css_data/selectors.css5
-rw-r--r--test/mitmproxy/contentviews/test_css_data/simple-formatted.css16
-rw-r--r--test/mitmproxy/contentviews/test_css_data/simple.css5
-rw-r--r--test/mitmproxy/contentviews/test_html_outline.py9
-rw-r--r--test/mitmproxy/contentviews/test_javascript.py24
-rw-r--r--test/mitmproxy/contentviews/test_js_data/simple-formatted.js68
-rw-r--r--test/mitmproxy/contentviews/test_js_data/simple.js8
-rw-r--r--test/mitmproxy/contentviews/test_protobuf.py65
-rw-r--r--test/mitmproxy/contentviews/test_protobuf_data/protobuf01 (renamed from test/mitmproxy/data/protobuf01)0
-rw-r--r--test/mitmproxy/contentviews/test_protobuf_data/protobuf02bin0 -> 213 bytes
-rw-r--r--test/mitmproxy/contentviews/test_protobuf_data/protobuf02-decoded65
-rw-r--r--test/mitmproxy/contentviews/test_protobuf_data/protobuf031
-rw-r--r--test/mitmproxy/contentviews/test_protobuf_data/protobuf03-decoded4
-rw-r--r--test/mitmproxy/contentviews/test_query.py2
-rw-r--r--test/mitmproxy/coretypes/__init__.py (renamed from test/mitmproxy/types/__init__.py)0
-rw-r--r--test/mitmproxy/coretypes/test_basethread.py (renamed from test/mitmproxy/types/test_basethread.py)2
-rw-r--r--test/mitmproxy/coretypes/test_bidi.py (renamed from test/mitmproxy/types/test_bidi.py)2
-rw-r--r--test/mitmproxy/coretypes/test_multidict.py (renamed from test/mitmproxy/types/test_multidict.py)2
-rw-r--r--test/mitmproxy/coretypes/test_serializable.py (renamed from test/mitmproxy/types/test_serializable.py)2
-rw-r--r--test/mitmproxy/data/addonscripts/concurrent_decorator_class.py2
-rw-r--r--test/mitmproxy/data/addonscripts/load_error.py2
-rw-r--r--test/mitmproxy/data/addonscripts/same_filename/addon.py1
-rw-r--r--test/mitmproxy/data/addonscripts/shutdown.py5
-rw-r--r--test/mitmproxy/data/no_common_name.pem96
-rw-r--r--test/mitmproxy/net/http/http1/test_read.py11
-rw-r--r--test/mitmproxy/net/http/test_response.py4
-rw-r--r--test/mitmproxy/net/test_tcp.py178
-rw-r--r--test/mitmproxy/net/test_tls.py55
-rw-r--r--test/mitmproxy/net/tools/getcertnames2
-rw-r--r--test/mitmproxy/net/tservers.py2
-rw-r--r--test/mitmproxy/platform/test_pf.py1
-rw-r--r--test/mitmproxy/proxy/protocol/test_http1.py32
-rw-r--r--test/mitmproxy/proxy/protocol/test_http2.py148
-rw-r--r--test/mitmproxy/proxy/protocol/test_tls.py3
-rw-r--r--test/mitmproxy/proxy/protocol/test_websocket.py209
-rw-r--r--test/mitmproxy/proxy/test_config.py39
-rw-r--r--test/mitmproxy/proxy/test_server.py110
-rw-r--r--test/mitmproxy/test_addonmanager.py26
-rw-r--r--test/mitmproxy/test_certs.py16
-rw-r--r--test/mitmproxy/test_command.py228
-rw-r--r--test/mitmproxy/test_connections.py16
-rw-r--r--test/mitmproxy/test_controller.py3
-rw-r--r--test/mitmproxy/test_flow.py49
-rw-r--r--test/mitmproxy/test_flowfilter.py28
-rw-r--r--test/mitmproxy/test_http.py9
-rw-r--r--test/mitmproxy/test_log.py5
-rw-r--r--test/mitmproxy/test_optmanager.py20
-rw-r--r--test/mitmproxy/test_typemanager.py0
-rw-r--r--test/mitmproxy/test_types.py237
-rw-r--r--test/mitmproxy/test_version.py28
-rw-r--r--test/mitmproxy/test_websocket.py21
-rw-r--r--test/mitmproxy/tools/console/test_commander.py98
-rw-r--r--test/mitmproxy/tools/console/test_common.py28
-rw-r--r--test/mitmproxy/tools/console/test_defaultkeys.py23
-rw-r--r--test/mitmproxy/tools/console/test_help.py11
-rw-r--r--test/mitmproxy/tools/console/test_keymap.py77
-rw-r--r--test/mitmproxy/tools/console/test_master.py23
-rw-r--r--test/mitmproxy/tools/console/test_pathedit.py72
-rw-r--r--test/mitmproxy/tools/console/test_statusbar.py33
-rw-r--r--test/mitmproxy/tools/test_cmdline.py8
-rw-r--r--test/mitmproxy/tools/test_dump.py9
-rw-r--r--test/mitmproxy/tools/test_main.py19
-rw-r--r--test/mitmproxy/tools/web/test_app.py67
-rw-r--r--test/mitmproxy/tools/web/test_master.py5
-rw-r--r--test/mitmproxy/tools/web/test_static_viewer.py69
-rw-r--r--test/mitmproxy/tservers.py29
-rw-r--r--test/mitmproxy/utils/test_arg_check.py36
-rw-r--r--test/mitmproxy/utils/test_debug.py9
-rw-r--r--test/mitmproxy/utils/test_human.py3
-rw-r--r--test/mitmproxy/utils/test_strutils.py30
-rw-r--r--test/mitmproxy/utils/test_typecheck.py30
-rw-r--r--test/mitmproxy/utils/test_version_check.py25
-rw-r--r--test/pathod/protocols/test_http2.py20
-rw-r--r--test/pathod/test_pathoc.py23
-rw-r--r--test/pathod/test_pathod.py12
117 files changed, 2846 insertions, 1034 deletions
diff --git a/test/conftest.py b/test/conftest.py
index bb913548..27918cf9 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,15 +1,10 @@
import os
-import pytest
-import OpenSSL
+import socket
-import mitmproxy.net.tcp
+import pytest
pytest_plugins = ('test.full_coverage_plugin',)
-requires_alpn = pytest.mark.skipif(
- not mitmproxy.net.tcp.HAS_ALPN,
- reason='requires OpenSSL with ALPN support')
-
skip_windows = pytest.mark.skipif(
os.name == "nt",
reason='Skipping due to Windows'
@@ -25,8 +20,16 @@ skip_appveyor = pytest.mark.skipif(
reason='Skipping due to Appveyor'
)
-
-@pytest.fixture()
-def disable_alpn(monkeypatch):
- monkeypatch.setattr(mitmproxy.net.tcp, 'HAS_ALPN', False)
- monkeypatch.setattr(OpenSSL.SSL._lib, 'Cryptography_HAS_ALPN', False)
+try:
+ s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ s.bind(("::1", 0))
+ s.close()
+except OSError:
+ no_ipv6 = True
+else:
+ no_ipv6 = False
+
+skip_no_ipv6 = pytest.mark.skipif(
+ no_ipv6,
+ reason='Host has no IPv6 support'
+)
diff --git a/test/examples/test_xss_scanner.py b/test/examples/test_xss_scanner.py
index 14ee6902..e15d7e10 100644
--- a/test/examples/test_xss_scanner.py
+++ b/test/examples/test_xss_scanner.py
@@ -314,7 +314,13 @@ class TestXSSScanner():
assert logger.args == []
xss.find_unclaimed_URLs("<html><script src=\"http://unclaimedDomainName.com\"></script></html>",
"https://example.com")
- assert logger.args[0] == 'XSS found in https://example.com due to unclaimed URL "http://unclaimedDomainName.com" in script tag.'
+ assert logger.args[0] == 'XSS found in https://example.com due to unclaimed URL "http://unclaimedDomainName.com".'
+ xss.find_unclaimed_URLs("<html><iframe src=\"http://unclaimedDomainName.com\"></iframe></html>",
+ "https://example.com")
+ assert logger.args[0] == 'XSS found in https://example.com due to unclaimed URL "http://unclaimedDomainName.com".'
+ xss.find_unclaimed_URLs("<html><link rel=\"stylesheet\" href=\"http://unclaimedDomainName.com\"></html>",
+ "https://example.com")
+ assert logger.args[0] == 'XSS found in https://example.com due to unclaimed URL "http://unclaimedDomainName.com".'
def test_log_XSS_data(self, monkeypatch, logger):
logger.args = []
diff --git a/test/filename_matching.py b/test/filename_matching.py
index 51cedf03..e74848d4 100644
--- a/test/filename_matching.py
+++ b/test/filename_matching.py
@@ -22,7 +22,7 @@ def check_src_files_have_test():
def check_test_files_have_src():
unknown_test_files = []
- excluded = ['test/mitmproxy/data/', 'test/mitmproxy/net/data/', '/tservers.py']
+ excluded = ['test/mitmproxy/data/', 'test/mitmproxy/net/data/', '/tservers.py', '/conftest.py']
test_files = glob.glob('test/mitmproxy/**/*.py', recursive=True) + glob.glob('test/pathod/**/*.py', recursive=True)
test_files = [f for f in test_files if os.path.basename(f) != '__init__.py']
test_files = [f for f in test_files if not any(os.path.normpath(p) in f for p in excluded)]
diff --git a/test/helper_tools/typehints_for_options.py b/test/helper_tools/typehints_for_options.py
new file mode 100644
index 00000000..8c7d006c
--- /dev/null
+++ b/test/helper_tools/typehints_for_options.py
@@ -0,0 +1,34 @@
+import typing
+from unittest import mock
+
+from mitmproxy import proxy, options
+from mitmproxy.tools import dump, console, web
+
+
+def print_typehints(opts):
+ for name, option in sorted(opts.items()):
+ print(
+ # For Python 3.6, we can just use "{}: {}".
+ "{} = None # type: {}".format(
+ name,
+ {
+ int: "int",
+ str: "str",
+ bool: "bool",
+ typing.Optional[str]: "Optional[str]",
+ typing.Sequence[str]: "Sequence[str]"
+ }[option.typespec]
+ )
+ )
+
+
+if __name__ == "__main__":
+ opts = options.Options()
+ server = proxy.server.DummyServer(None)
+
+ # initialize with all three tools here to capture tool-specific options defined in addons.
+ dump.DumpMaster(opts, server)
+ with mock.patch("sys.stdout.isatty", lambda: True):
+ console.master.ConsoleMaster(opts, server)
+ web.master.WebMaster(opts, server)
+ print_typehints(opts)
diff --git a/test/mitmproxy/addons/test_allowremote.py b/test/mitmproxy/addons/test_allowremote.py
new file mode 100644
index 00000000..9fc71525
--- /dev/null
+++ b/test/mitmproxy/addons/test_allowremote.py
@@ -0,0 +1,38 @@
+from unittest import mock
+import pytest
+
+from mitmproxy.addons import allowremote
+from mitmproxy.test import taddons
+
+
+@pytest.mark.parametrize("allow_remote, ip, should_be_killed", [
+ (True, "192.168.1.3", False),
+ (True, "122.176.243.101", False),
+ (False, "192.168.1.3", False),
+ (False, "122.176.243.101", True),
+ (True, "::ffff:1:2", False),
+ (True, "fe80::", False),
+ (True, "2001:4860:4860::8888", False),
+ (False, "::ffff:1:2", False),
+ (False, "fe80::", False),
+ (False, "2001:4860:4860::8888", True),
+])
+def test_allowremote(allow_remote, ip, should_be_killed):
+ ar = allowremote.AllowRemote()
+ with taddons.context() as tctx:
+ tctx.master.addons.register(ar)
+ tctx.options.allow_remote = allow_remote
+
+ with mock.patch('mitmproxy.proxy.protocol.base.Layer') as layer:
+ layer.client_conn.address = (ip, 12345)
+
+ ar.clientconnect(layer)
+ if should_be_killed:
+ assert tctx.master.has_log("Client connection was killed", "warn")
+ else:
+ assert tctx.master.logs == []
+ tctx.master.clear()
+
+ tctx.options.proxyauth = "any"
+ ar.clientconnect(layer)
+ assert tctx.master.logs == []
diff --git a/test/mitmproxy/addons/test_browser.py b/test/mitmproxy/addons/test_browser.py
new file mode 100644
index 00000000..407a3fe6
--- /dev/null
+++ b/test/mitmproxy/addons/test_browser.py
@@ -0,0 +1,31 @@
+from unittest import mock
+
+from mitmproxy.addons import browser
+from mitmproxy.test import taddons
+
+
+def test_browser():
+ with mock.patch("subprocess.Popen") as po, mock.patch("shutil.which") as which:
+ which.return_value = "chrome"
+ b = browser.Browser()
+ with taddons.context() as tctx:
+ b.start()
+ assert po.called
+ b.start()
+
+ assert not tctx.master.has_log("already running")
+ b.browser.poll = lambda: None
+ b.start()
+ assert tctx.master.has_log("already running")
+ b.done()
+ assert not b.browser
+
+
+def test_no_browser():
+ with mock.patch("shutil.which") as which:
+ which.return_value = False
+
+ b = browser.Browser()
+ with taddons.context() as tctx:
+ b.start()
+ assert tctx.master.has_log("platform is not supported")
diff --git a/test/mitmproxy/addons/test_check_alpn.py b/test/mitmproxy/addons/test_check_alpn.py
deleted file mode 100644
index 2b1d6058..00000000
--- a/test/mitmproxy/addons/test_check_alpn.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from mitmproxy.addons import check_alpn
-from mitmproxy.test import taddons
-from ...conftest import requires_alpn
-
-
-class TestCheckALPN:
-
- @requires_alpn
- def test_check_alpn(self):
- msg = 'ALPN support missing'
-
- with taddons.context() as tctx:
- a = check_alpn.CheckALPN()
- tctx.configure(a)
- assert not tctx.master.has_log(msg)
-
- def test_check_no_alpn(self, disable_alpn):
- msg = 'ALPN support missing'
-
- with taddons.context() as tctx:
- a = check_alpn.CheckALPN()
- tctx.configure(a)
- assert tctx.master.has_log(msg)
diff --git a/test/mitmproxy/addons/test_clientplayback.py b/test/mitmproxy/addons/test_clientplayback.py
index 6089b2d5..3f990668 100644
--- a/test/mitmproxy/addons/test_clientplayback.py
+++ b/test/mitmproxy/addons/test_clientplayback.py
@@ -36,9 +36,12 @@ class TestClientPlayback:
assert rp.called
assert cp.current_thread
- cp.flows = None
- cp.current_thread = None
+ cp.flows = []
+ cp.current_thread.is_alive.return_value = False
+ assert cp.count() == 1
cp.tick()
+ assert cp.count() == 0
+ assert tctx.master.has_event("update")
assert tctx.master.has_event("processing_complete")
cp.current_thread = MockThread()
@@ -49,6 +52,10 @@ class TestClientPlayback:
cp.stop_replay()
assert not cp.flows
+ df = tflow.DummyFlow(tflow.tclient_conn(), tflow.tserver_conn(), True)
+ with pytest.raises(exceptions.CommandError, match="Can't replay live flow."):
+ cp.start_replay([df])
+
def test_load_file(self, tmpdir):
cp = clientplayback.ClientPlayback()
with taddons.context():
diff --git a/test/mitmproxy/addons/test_core.py b/test/mitmproxy/addons/test_core.py
index c132d80a..5aa4ef37 100644
--- a/test/mitmproxy/addons/test_core.py
+++ b/test/mitmproxy/addons/test_core.py
@@ -69,9 +69,6 @@ def test_flow_set():
f = tflow.tflow(resp=True)
assert sa.flow_set_options()
- with pytest.raises(exceptions.CommandError):
- sa.flow_set([f], "flibble", "post")
-
assert f.request.method != "post"
sa.flow_set([f], "method", "post")
assert f.request.method == "POST"
@@ -126,9 +123,6 @@ def test_encoding():
sa.encode_toggle([f], "request")
assert "content-encoding" not in f.request.headers
- with pytest.raises(exceptions.CommandError):
- sa.encode([f], "request", "invalid")
-
def test_options(tmpdir):
p = str(tmpdir.join("path"))
diff --git a/test/mitmproxy/addons/test_core_option_validation.py b/test/mitmproxy/addons/test_core_option_validation.py
index 6d6d5ba4..cd5d4dfa 100644
--- a/test/mitmproxy/addons/test_core_option_validation.py
+++ b/test/mitmproxy/addons/test_core_option_validation.py
@@ -11,7 +11,6 @@ def test_simple():
with pytest.raises(exceptions.OptionsError):
tctx.configure(sa, body_size_limit = "invalid")
tctx.configure(sa, body_size_limit = "1m")
- assert tctx.master.options._processed["body_size_limit"]
with pytest.raises(exceptions.OptionsError, match="mutually exclusive"):
tctx.configure(
diff --git a/test/mitmproxy/addons/test_cut.py b/test/mitmproxy/addons/test_cut.py
index e028331f..c444b8ee 100644
--- a/test/mitmproxy/addons/test_cut.py
+++ b/test/mitmproxy/addons/test_cut.py
@@ -13,83 +13,51 @@ from unittest import mock
def test_extract():
tf = tflow.tflow(resp=True)
tests = [
- ["q.method", "GET"],
- ["q.scheme", "http"],
- ["q.host", "address"],
- ["q.port", "22"],
- ["q.path", "/path"],
- ["q.url", "http://address:22/path"],
- ["q.text", "content"],
- ["q.content", b"content"],
- ["q.raw_content", b"content"],
- ["q.header[header]", "qvalue"],
-
- ["s.status_code", "200"],
- ["s.reason", "OK"],
- ["s.text", "message"],
- ["s.content", b"message"],
- ["s.raw_content", b"message"],
- ["s.header[header-response]", "svalue"],
-
- ["cc.address.port", "22"],
- ["cc.address.host", "address"],
- ["cc.tls_version", "TLSv1.2"],
- ["cc.sni", "address"],
- ["cc.ssl_established", "false"],
-
- ["sc.address.port", "22"],
- ["sc.address.host", "address"],
- ["sc.ip_address.host", "192.168.0.1"],
- ["sc.tls_version", "TLSv1.2"],
- ["sc.sni", "address"],
- ["sc.ssl_established", "false"],
+ ["request.method", "GET"],
+ ["request.scheme", "http"],
+ ["request.host", "address"],
+ ["request.http_version", "HTTP/1.1"],
+ ["request.port", "22"],
+ ["request.path", "/path"],
+ ["request.url", "http://address:22/path"],
+ ["request.text", "content"],
+ ["request.content", b"content"],
+ ["request.raw_content", b"content"],
+ ["request.timestamp_start", "946681200"],
+ ["request.timestamp_end", "946681201"],
+ ["request.header[header]", "qvalue"],
+
+ ["response.status_code", "200"],
+ ["response.reason", "OK"],
+ ["response.text", "message"],
+ ["response.content", b"message"],
+ ["response.raw_content", b"message"],
+ ["response.header[header-response]", "svalue"],
+ ["response.timestamp_start", "946681202"],
+ ["response.timestamp_end", "946681203"],
+
+ ["client_conn.address.port", "22"],
+ ["client_conn.address.host", "127.0.0.1"],
+ ["client_conn.tls_version", "TLSv1.2"],
+ ["client_conn.sni", "address"],
+ ["client_conn.tls_established", "false"],
+
+ ["server_conn.address.port", "22"],
+ ["server_conn.address.host", "address"],
+ ["server_conn.ip_address.host", "192.168.0.1"],
+ ["server_conn.tls_version", "TLSv1.2"],
+ ["server_conn.sni", "address"],
+ ["server_conn.tls_established", "false"],
]
- for t in tests:
- ret = cut.extract(t[0], tf)
- if ret != t[1]:
- raise AssertionError("%s: Expected %s, got %s" % (t[0], t[1], ret))
+ for spec, expected in tests:
+ ret = cut.extract(spec, tf)
+ assert spec and ret == expected
with open(tutils.test_data.path("mitmproxy/net/data/text_cert"), "rb") as f:
d = f.read()
- c1 = certs.SSLCert.from_pem(d)
+ c1 = certs.Cert.from_pem(d)
tf.server_conn.cert = c1
- assert "CERTIFICATE" in cut.extract("sc.cert", tf)
-
-
-def test_parse_cutspec():
- tests = [
- ("", None, True),
- ("req.method", ("@all", ["req.method"]), False),
- (
- "req.method,req.host",
- ("@all", ["req.method", "req.host"]),
- False
- ),
- (
- "req.method,req.host|~b foo",
- ("~b foo", ["req.method", "req.host"]),
- False
- ),
- (
- "req.method,req.host|~b foo | ~b bar",
- ("~b foo | ~b bar", ["req.method", "req.host"]),
- False
- ),
- (
- "req.method, req.host | ~b foo | ~b bar",
- ("~b foo | ~b bar", ["req.method", "req.host"]),
- False
- ),
- ]
- for cutspec, output, err in tests:
- try:
- assert cut.parse_cutspec(cutspec) == output
- except exceptions.CommandError:
- if not err:
- raise
- else:
- if err:
- raise AssertionError("Expected error.")
+ assert "CERTIFICATE" in cut.extract("server_conn.cert", tf)
def test_headername():
@@ -110,69 +78,69 @@ def test_cut_clip():
v.add([tflow.tflow(resp=True)])
with mock.patch('pyperclip.copy') as pc:
- tctx.command(c.clip, "q.method|@all")
+ tctx.command(c.clip, "@all", "request.method")
assert pc.called
with mock.patch('pyperclip.copy') as pc:
- tctx.command(c.clip, "q.content|@all")
+ tctx.command(c.clip, "@all", "request.content")
assert pc.called
with mock.patch('pyperclip.copy') as pc:
- tctx.command(c.clip, "q.method,q.content|@all")
+ tctx.command(c.clip, "@all", "request.method,request.content")
assert pc.called
-def test_cut_file(tmpdir):
+def test_cut_save(tmpdir):
f = str(tmpdir.join("path"))
v = view.View()
c = cut.Cut()
with taddons.context() as tctx:
tctx.master.addons.add(v, c)
-
v.add([tflow.tflow(resp=True)])
- tctx.command(c.save, "q.method|@all", f)
+ tctx.command(c.save, "@all", "request.method", f)
assert qr(f) == b"GET"
- tctx.command(c.save, "q.content|@all", f)
+ tctx.command(c.save, "@all", "request.content", f)
assert qr(f) == b"content"
- tctx.command(c.save, "q.content|@all", "+" + f)
+ tctx.command(c.save, "@all", "request.content", "+" + f)
assert qr(f) == b"content\ncontent"
v.add([tflow.tflow(resp=True)])
- tctx.command(c.save, "q.method|@all", f)
+ tctx.command(c.save, "@all", "request.method", f)
assert qr(f).splitlines() == [b"GET", b"GET"]
- tctx.command(c.save, "q.method,q.content|@all", f)
+ tctx.command(c.save, "@all", "request.method,request.content", f)
assert qr(f).splitlines() == [b"GET,content", b"GET,content"]
def test_cut():
- v = view.View()
c = cut.Cut()
- with taddons.context() as tctx:
- v.add([tflow.tflow(resp=True)])
- tctx.master.addons.add(v, c)
- assert c.cut("q.method|@all") == [["GET"]]
- assert c.cut("q.scheme|@all") == [["http"]]
- assert c.cut("q.host|@all") == [["address"]]
- assert c.cut("q.port|@all") == [["22"]]
- assert c.cut("q.path|@all") == [["/path"]]
- assert c.cut("q.url|@all") == [["http://address:22/path"]]
- assert c.cut("q.content|@all") == [[b"content"]]
- assert c.cut("q.header[header]|@all") == [["qvalue"]]
- assert c.cut("q.header[unknown]|@all") == [[""]]
-
- assert c.cut("s.status_code|@all") == [["200"]]
- assert c.cut("s.reason|@all") == [["OK"]]
- assert c.cut("s.content|@all") == [[b"message"]]
- assert c.cut("s.header[header-response]|@all") == [["svalue"]]
- assert c.cut("moo") == [[""]]
+ with taddons.context():
+ tflows = [tflow.tflow(resp=True)]
+ assert c.cut(tflows, ["request.method"]) == [["GET"]]
+ assert c.cut(tflows, ["request.scheme"]) == [["http"]]
+ assert c.cut(tflows, ["request.host"]) == [["address"]]
+ assert c.cut(tflows, ["request.port"]) == [["22"]]
+ assert c.cut(tflows, ["request.path"]) == [["/path"]]
+ assert c.cut(tflows, ["request.url"]) == [["http://address:22/path"]]
+ assert c.cut(tflows, ["request.content"]) == [[b"content"]]
+ assert c.cut(tflows, ["request.header[header]"]) == [["qvalue"]]
+ assert c.cut(tflows, ["request.header[unknown]"]) == [[""]]
+
+ assert c.cut(tflows, ["response.status_code"]) == [["200"]]
+ assert c.cut(tflows, ["response.reason"]) == [["OK"]]
+ assert c.cut(tflows, ["response.content"]) == [[b"message"]]
+ assert c.cut(tflows, ["response.header[header-response]"]) == [["svalue"]]
+ assert c.cut(tflows, ["moo"]) == [[""]]
with pytest.raises(exceptions.CommandError):
- assert c.cut("__dict__") == [[""]]
+ assert c.cut(tflows, ["__dict__"]) == [[""]]
+
+ with taddons.context():
+ tflows = [tflow.tflow(resp=False)]
+ assert c.cut(tflows, ["response.reason"]) == [[""]]
+ assert c.cut(tflows, ["response.header[key]"]) == [[""]]
- v = view.View()
c = cut.Cut()
- with taddons.context() as tctx:
- tctx.master.addons.add(v, c)
- v.add([tflow.ttcpflow()])
- assert c.cut("q.method|@all") == [[""]]
- assert c.cut("s.status|@all") == [[""]]
+ with taddons.context():
+ tflows = [tflow.ttcpflow()]
+ assert c.cut(tflows, ["request.method"]) == [[""]]
+ assert c.cut(tflows, ["response.status"]) == [[""]]
diff --git a/test/mitmproxy/addons/test_dumper.py b/test/mitmproxy/addons/test_dumper.py
index d8aa593b..fb80f3ce 100644
--- a/test/mitmproxy/addons/test_dumper.py
+++ b/test/mitmproxy/addons/test_dumper.py
@@ -148,7 +148,7 @@ class TestContentView:
sio = io.StringIO()
d = dumper.Dumper(sio)
with taddons.context(options=options.Options()) as ctx:
- ctx.configure(d, flow_detail=4, verbosity=3)
+ ctx.configure(d, flow_detail=4, verbosity='debug')
d.response(tflow.tflow())
assert ctx.master.has_log("content viewer failed")
diff --git a/test/mitmproxy/addons/test_eventstore.py b/test/mitmproxy/addons/test_eventstore.py
index f54b9980..8ac26b05 100644
--- a/test/mitmproxy/addons/test_eventstore.py
+++ b/test/mitmproxy/addons/test_eventstore.py
@@ -30,3 +30,18 @@ def test_simple():
assert not sig_add.called
assert sig_refresh.called
+
+
+def test_max_size():
+ store = eventstore.EventStore(3)
+ assert store.size == 3
+ store.log(log.LogEntry("foo", "info"))
+ store.log(log.LogEntry("bar", "info"))
+ store.log(log.LogEntry("baz", "info"))
+ assert len(store.data) == 3
+ assert ["foo", "bar", "baz"] == [x.msg for x in store.data]
+
+ # overflow
+ store.log(log.LogEntry("boo", "info"))
+ assert len(store.data) == 3
+ assert ["bar", "baz", "boo"] == [x.msg for x in store.data]
diff --git a/test/mitmproxy/addons/test_intercept.py b/test/mitmproxy/addons/test_intercept.py
index f436a817..d4999eb5 100644
--- a/test/mitmproxy/addons/test_intercept.py
+++ b/test/mitmproxy/addons/test_intercept.py
@@ -13,10 +13,12 @@ def test_simple():
assert not r.filt
tctx.configure(r, intercept="~q")
assert r.filt
+ assert tctx.options.intercept_active
with pytest.raises(exceptions.OptionsError):
tctx.configure(r, intercept="~~")
tctx.configure(r, intercept=None)
assert not r.filt
+ assert not tctx.options.intercept_active
tctx.configure(r, intercept="~s")
@@ -31,3 +33,13 @@ def test_simple():
f = tflow.tflow(resp=True)
r.response(f)
assert f.intercepted
+
+ tctx.configure(r, intercept_active=False)
+ f = tflow.tflow(resp=True)
+ tctx.cycle(r, f)
+ assert not f.intercepted
+
+ tctx.configure(r, intercept_active=True)
+ f = tflow.tflow(resp=True)
+ tctx.cycle(r, f)
+ assert f.intercepted
diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py
index 40044bf0..97259d1c 100644
--- a/test/mitmproxy/addons/test_proxyauth.py
+++ b/test/mitmproxy/addons/test_proxyauth.py
@@ -10,197 +10,242 @@ from mitmproxy.test import tflow
from mitmproxy.test import tutils
-def test_parse_http_basic_auth():
- assert proxyauth.parse_http_basic_auth(
- proxyauth.mkauth("test", "test")
- ) == ("basic", "test", "test")
- with pytest.raises(ValueError):
- proxyauth.parse_http_basic_auth("")
- with pytest.raises(ValueError):
- proxyauth.parse_http_basic_auth("foo bar")
- with pytest.raises(ValueError):
- proxyauth.parse_http_basic_auth("basic abc")
- with pytest.raises(ValueError):
- v = "basic " + binascii.b2a_base64(b"foo").decode("ascii")
- proxyauth.parse_http_basic_auth(v)
-
-
-def test_configure():
- up = proxyauth.ProxyAuth()
- with taddons.context() as ctx:
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="foo")
-
- ctx.configure(up, proxyauth="foo:bar")
- assert up.singleuser == ["foo", "bar"]
-
- ctx.configure(up, proxyauth=None)
- assert up.singleuser is None
-
- ctx.configure(up, proxyauth="any")
- assert up.nonanonymous
- ctx.configure(up, proxyauth=None)
- assert not up.nonanonymous
-
- with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
- with mock.patch('ldap3.Connection', return_value="test"):
- ctx.configure(up, proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
- assert up.ldapserver
- ctx.configure(up, proxyauth="ldaps:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
- assert up.ldapserver
-
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="ldap:test:test:test")
-
- with pytest.raises(IndexError):
- ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person")
-
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="ldapssssssss:fake_server:dn:password:tree")
-
- with pytest.raises(exceptions.OptionsError):
+class TestMkauth:
+ def test_mkauth_scheme(self):
+ assert proxyauth.mkauth('username', 'password') == 'basic dXNlcm5hbWU6cGFzc3dvcmQ=\n'
+
+ @pytest.mark.parametrize('scheme, expected', [
+ ('', ' dXNlcm5hbWU6cGFzc3dvcmQ=\n'),
+ ('basic', 'basic dXNlcm5hbWU6cGFzc3dvcmQ=\n'),
+ ('foobar', 'foobar dXNlcm5hbWU6cGFzc3dvcmQ=\n'),
+ ])
+ def test_mkauth(self, scheme, expected):
+ assert proxyauth.mkauth('username', 'password', scheme) == expected
+
+
+class TestParseHttpBasicAuth:
+ @pytest.mark.parametrize('input', [
+ '',
+ 'foo bar',
+ 'basic abc',
+ 'basic ' + binascii.b2a_base64(b"foo").decode("ascii"),
+ ])
+ def test_parse_http_basic_auth_error(self, input):
+ with pytest.raises(ValueError):
+ proxyauth.parse_http_basic_auth(input)
+
+ def test_parse_http_basic_auth(self):
+ input = proxyauth.mkauth("test", "test")
+ assert proxyauth.parse_http_basic_auth(input) == ("basic", "test", "test")
+
+
+class TestProxyAuth:
+ @pytest.mark.parametrize('mode, expected', [
+ ('', False),
+ ('foobar', False),
+ ('regular', True),
+ ('upstream:', True),
+ ('upstream:foobar', True),
+ ])
+ def test_is_proxy_auth(self, mode, expected):
+ up = proxyauth.ProxyAuth()
+ with taddons.context() as ctx:
+ ctx.options.mode = mode
+ assert up.is_proxy_auth() is expected
+
+ @pytest.mark.parametrize('is_proxy_auth, expected', [
+ (True, 'Proxy-Authorization'),
+ (False, 'Authorization'),
+ ])
+ def test_which_auth_header(self, is_proxy_auth, expected):
+ up = proxyauth.ProxyAuth()
+ with mock.patch('mitmproxy.addons.proxyauth.ProxyAuth.is_proxy_auth', return_value=is_proxy_auth):
+ assert up.which_auth_header() == expected
+
+ @pytest.mark.parametrize('is_proxy_auth, expected_status_code, expected_header', [
+ (True, 407, 'Proxy-Authenticate'),
+ (False, 401, 'WWW-Authenticate'),
+ ])
+ def test_auth_required_response(self, is_proxy_auth, expected_status_code, expected_header):
+ up = proxyauth.ProxyAuth()
+ with mock.patch('mitmproxy.addons.proxyauth.ProxyAuth.is_proxy_auth', return_value=is_proxy_auth):
+ resp = up.auth_required_response()
+ assert resp.status_code == expected_status_code
+ assert expected_header in resp.headers.keys()
+
+ def test_check(self):
+ up = proxyauth.ProxyAuth()
+ with taddons.context() as ctx:
+ ctx.configure(up, proxyauth="any", mode="regular")
+ f = tflow.tflow()
+ assert not up.check(f)
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ assert up.check(f)
+
+ f.request.headers["Proxy-Authorization"] = "invalid"
+ assert not up.check(f)
+
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test", scheme="unknown"
+ )
+ assert not up.check(f)
+
+ ctx.configure(up, proxyauth="test:test")
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ assert up.check(f)
+ ctx.configure(up, proxyauth="test:foo")
+ assert not up.check(f)
+
ctx.configure(
up,
- proxyauth= "@" + tutils.test_data.path("mitmproxy/net/data/server.crt")
+ proxyauth="@" + tutils.test_data.path(
+ "mitmproxy/net/data/htpasswd"
+ )
)
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="@nonexistent")
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ assert up.check(f)
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "foo"
+ )
+ assert not up.check(f)
+
+ with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
+ with mock.patch('ldap3.Connection', search="test"):
+ with mock.patch('ldap3.Connection.search', return_value="test"):
+ ctx.configure(
+ up,
+ proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com"
+ )
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ assert up.check(f)
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "", ""
+ )
+ assert not up.check(f)
+
+ def test_authenticate(self):
+ up = proxyauth.ProxyAuth()
+ with taddons.context() as ctx:
+ ctx.configure(up, proxyauth="any", mode="regular")
+
+ f = tflow.tflow()
+ assert not f.response
+ up.authenticate(f)
+ assert f.response.status_code == 407
+
+ f = tflow.tflow()
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ up.authenticate(f)
+ assert not f.response
+ assert not f.request.headers.get("Proxy-Authorization")
+
+ f = tflow.tflow()
+ ctx.configure(up, mode="reverse")
+ assert not f.response
+ up.authenticate(f)
+ assert f.response.status_code == 401
+
+ f = tflow.tflow()
+ f.request.headers["Authorization"] = proxyauth.mkauth(
+ "test", "test"
+ )
+ up.authenticate(f)
+ assert not f.response
+ assert not f.request.headers.get("Authorization")
+
+ def test_configure(self):
+ up = proxyauth.ProxyAuth()
+ with taddons.context() as ctx:
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="foo")
+
+ ctx.configure(up, proxyauth="foo:bar")
+ assert up.singleuser == ["foo", "bar"]
+
+ ctx.configure(up, proxyauth=None)
+ assert up.singleuser is None
+
+ ctx.configure(up, proxyauth="any")
+ assert up.nonanonymous
+ ctx.configure(up, proxyauth=None)
+ assert not up.nonanonymous
+
+ with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
+ with mock.patch('ldap3.Connection', return_value="test"):
+ ctx.configure(up, proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
+ assert up.ldapserver
+ ctx.configure(up, proxyauth="ldaps:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com")
+ assert up.ldapserver
+
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="ldap:test:test:test")
+
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person")
+
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="ldapssssssss:fake_server:dn:password:tree")
+
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(
+ up,
+ proxyauth= "@" + tutils.test_data.path("mitmproxy/net/data/server.crt")
+ )
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="@nonexistent")
- ctx.configure(
- up,
- proxyauth= "@" + tutils.test_data.path(
- "mitmproxy/net/data/htpasswd"
+ ctx.configure(
+ up,
+ proxyauth= "@" + tutils.test_data.path(
+ "mitmproxy/net/data/htpasswd"
+ )
)
- )
- assert up.htpasswd
- assert up.htpasswd.check_password("test", "test")
- assert not up.htpasswd.check_password("test", "foo")
- ctx.configure(up, proxyauth=None)
- assert not up.htpasswd
-
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="any", mode="transparent")
- with pytest.raises(exceptions.OptionsError):
- ctx.configure(up, proxyauth="any", mode="socks5")
-
-
-def test_check(monkeypatch):
- up = proxyauth.ProxyAuth()
- with taddons.context() as ctx:
- ctx.configure(up, proxyauth="any", mode="regular")
- f = tflow.tflow()
- assert not up.check(f)
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- assert up.check(f)
-
- f.request.headers["Proxy-Authorization"] = "invalid"
- assert not up.check(f)
-
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test", scheme="unknown"
- )
- assert not up.check(f)
-
- ctx.configure(up, proxyauth="test:test")
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- assert up.check(f)
- ctx.configure(up, proxyauth="test:foo")
- assert not up.check(f)
-
- ctx.configure(
- up,
- proxyauth="@" + tutils.test_data.path(
- "mitmproxy/net/data/htpasswd"
+ assert up.htpasswd
+ assert up.htpasswd.check_password("test", "test")
+ assert not up.htpasswd.check_password("test", "foo")
+ ctx.configure(up, proxyauth=None)
+ assert not up.htpasswd
+
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="any", mode="transparent")
+ with pytest.raises(exceptions.OptionsError):
+ ctx.configure(up, proxyauth="any", mode="socks5")
+
+ def test_handlers(self):
+ up = proxyauth.ProxyAuth()
+ with taddons.context() as ctx:
+ ctx.configure(up, proxyauth="any", mode="regular")
+
+ f = tflow.tflow()
+ assert not f.response
+ up.requestheaders(f)
+ assert f.response.status_code == 407
+
+ f = tflow.tflow()
+ f.request.method = "CONNECT"
+ assert not f.response
+ up.http_connect(f)
+ assert f.response.status_code == 407
+
+ f = tflow.tflow()
+ f.request.method = "CONNECT"
+ f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
+ "test", "test"
)
- )
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- assert up.check(f)
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "foo"
- )
- assert not up.check(f)
-
- with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"):
- with mock.patch('ldap3.Connection', search="test"):
- with mock.patch('ldap3.Connection.search', return_value="test"):
- ctx.configure(
- up,
- proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com"
- )
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- assert up.check(f)
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "", ""
- )
- assert not up.check(f)
-
-
-def test_authenticate():
- up = proxyauth.ProxyAuth()
- with taddons.context() as ctx:
- ctx.configure(up, proxyauth="any", mode="regular")
-
- f = tflow.tflow()
- assert not f.response
- up.authenticate(f)
- assert f.response.status_code == 407
-
- f = tflow.tflow()
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- up.authenticate(f)
- assert not f.response
- assert not f.request.headers.get("Proxy-Authorization")
-
- f = tflow.tflow()
- ctx.configure(up, mode="reverse")
- assert not f.response
- up.authenticate(f)
- assert f.response.status_code == 401
-
- f = tflow.tflow()
- f.request.headers["Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- up.authenticate(f)
- assert not f.response
- assert not f.request.headers.get("Authorization")
-
-
-def test_handlers():
- up = proxyauth.ProxyAuth()
- with taddons.context() as ctx:
- ctx.configure(up, proxyauth="any", mode="regular")
-
- f = tflow.tflow()
- assert not f.response
- up.requestheaders(f)
- assert f.response.status_code == 407
-
- f = tflow.tflow()
- f.request.method = "CONNECT"
- assert not f.response
- up.http_connect(f)
- assert f.response.status_code == 407
-
- f = tflow.tflow()
- f.request.method = "CONNECT"
- f.request.headers["Proxy-Authorization"] = proxyauth.mkauth(
- "test", "test"
- )
- up.http_connect(f)
- assert not f.response
-
- f2 = tflow.tflow(client_conn=f.client_conn)
- up.requestheaders(f2)
- assert not f2.response
- assert f2.metadata["proxyauth"] == ('test', 'test')
+ up.http_connect(f)
+ assert not f.response
+
+ f2 = tflow.tflow(client_conn=f.client_conn)
+ up.requestheaders(f2)
+ assert not f2.response
+ assert f2.metadata["proxyauth"] == ('test', 'test')
diff --git a/test/mitmproxy/addons/test_save.py b/test/mitmproxy/addons/test_save.py
index a4e425cd..2dee708f 100644
--- a/test/mitmproxy/addons/test_save.py
+++ b/test/mitmproxy/addons/test_save.py
@@ -44,6 +44,19 @@ def test_tcp(tmpdir):
assert rd(p)
+def test_websocket(tmpdir):
+ sa = save.Save()
+ with taddons.context() as tctx:
+ p = str(tmpdir.join("foo"))
+ tctx.configure(sa, save_stream_file=p)
+
+ f = tflow.twebsocketflow()
+ sa.websocket_start(f)
+ sa.websocket_end(f)
+ tctx.configure(sa, save_stream_file=None)
+ assert rd(p)
+
+
def test_save_command(tmpdir):
sa = save.Save()
with taddons.context() as tctx:
diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py
index dd5349cb..c4fe6b43 100644
--- a/test/mitmproxy/addons/test_script.py
+++ b/test/mitmproxy/addons/test_script.py
@@ -1,46 +1,65 @@
-import traceback
+import os
import sys
-import time
+import traceback
+from unittest import mock
+
import pytest
-from unittest import mock
-from mitmproxy.test import tflow
-from mitmproxy.test import tutils
-from mitmproxy.test import taddons
from mitmproxy import addonmanager
from mitmproxy import exceptions
+from mitmproxy import log
from mitmproxy.addons import script
+from mitmproxy.test import taddons
+from mitmproxy.test import tflow
+from mitmproxy.test import tutils
def test_load_script():
- with taddons.context() as tctx:
- ns = script.load_script(
- tctx.ctx(),
- tutils.test_data.path(
- "mitmproxy/data/addonscripts/recorder/recorder.py"
- )
+ ns = script.load_script(
+ tutils.test_data.path(
+ "mitmproxy/data/addonscripts/recorder/recorder.py"
)
- assert ns.addons
+ )
+ assert ns.addons
- ns = script.load_script(
- tctx.ctx(),
+ with pytest.raises(FileNotFoundError):
+ script.load_script(
"nonexistent"
)
- assert not ns
+
+
+def test_load_fullname():
+ """
+ Test that loading two scripts at locations a/foo.py and b/foo.py works.
+ This only succeeds if they get assigned different basenames.
+
+ """
+ ns = script.load_script(
+ tutils.test_data.path(
+ "mitmproxy/data/addonscripts/addon.py"
+ )
+ )
+ assert ns.addons
+ ns2 = script.load_script(
+ tutils.test_data.path(
+ "mitmproxy/data/addonscripts/same_filename/addon.py"
+ )
+ )
+ assert ns.name != ns2.name
+ assert not hasattr(ns2, "addons")
def test_script_print_stdout():
with taddons.context() as tctx:
- with mock.patch('mitmproxy.ctx.log.warn') as mock_warn:
+ with mock.patch('mitmproxy.ctx.master.tell') as mock_warn:
with addonmanager.safecall():
ns = script.load_script(
- tctx.ctx(),
tutils.test_data.path(
"mitmproxy/data/addonscripts/print.py"
)
)
ns.load(addonmanager.Loader(tctx.master))
- mock_warn.assert_called_once_with("stdoutprint")
+ mock_warn.assert_called_once_with("log", log.LogEntry("stdoutprint", "warn"))
class TestScript:
@@ -78,11 +97,13 @@ class TestScript:
sc = script.Script(str(f))
tctx.configure(sc)
sc.tick()
- for _ in range(3):
- sc.last_load, sc.last_mtime = 0, 0
- sc.tick()
- time.sleep(0.1)
- tctx.master.has_log("Loading")
+ assert tctx.master.has_log("Loading")
+ tctx.master.clear()
+ assert not tctx.master.has_log("Loading")
+
+ sc.last_load, sc.last_mtime = 0, 0
+ sc.tick()
+ assert tctx.master.has_log("Loading")
def test_exception(self):
with taddons.context() as tctx:
@@ -96,8 +117,8 @@ class TestScript:
f = tflow.tflow(resp=True)
tctx.master.addons.trigger("request", f)
- tctx.master.has_log("ValueError: Error!")
- tctx.master.has_log("error.py")
+ assert tctx.master.has_log("ValueError: Error!")
+ assert tctx.master.has_log("error.py")
def test_addon(self):
with taddons.context() as tctx:
@@ -183,6 +204,34 @@ class TestScriptLoader:
scripts = ["one", "one"]
)
+ def test_script_deletion(self):
+ tdir = tutils.test_data.path("mitmproxy/data/addonscripts/")
+ with open(tdir + "/dummy.py", 'w') as f:
+ f.write("\n")
+ with taddons.context() as tctx:
+ sl = script.ScriptLoader()
+ tctx.master.addons.add(sl)
+ tctx.configure(sl, scripts=[tutils.test_data.path("mitmproxy/data/addonscripts/dummy.py")])
+
+ os.remove(tutils.test_data.path("mitmproxy/data/addonscripts/dummy.py"))
+ tctx.invoke(sl, "tick")
+ assert not tctx.options.scripts
+ assert not sl.addons
+
+ def test_load_err(self):
+ sc = script.ScriptLoader()
+ with taddons.context() as tctx:
+ tctx.configure(sc, scripts=[
+ tutils.test_data.path("mitmproxy/data/addonscripts/load_error.py")
+ ])
+ try:
+ tctx.invoke(sc, "tick")
+ except ValueError:
+ pass # this is expected and normally guarded.
+ # on the next tick we should not fail however.
+ tctx.invoke(sc, "tick")
+ assert len(tctx.master.addons) == 0
+
def test_order(self):
rec = tutils.test_data.path("mitmproxy/data/addonscripts/recorder")
sc = script.ScriptLoader()
diff --git a/test/mitmproxy/addons/test_streambodies.py b/test/mitmproxy/addons/test_streambodies.py
index c6ce5e81..426ec9ae 100644
--- a/test/mitmproxy/addons/test_streambodies.py
+++ b/test/mitmproxy/addons/test_streambodies.py
@@ -29,3 +29,9 @@ def test_simple():
f = tflow.tflow(resp=True)
f.response.headers["content-length"] = "invalid"
tctx.cycle(sa, f)
+
+ tctx.configure(sa, stream_websockets = True)
+ f = tflow.twebsocketflow()
+ assert not f.stream
+ sa.websocket_start(f)
+ assert f.stream
diff --git a/test/mitmproxy/addons/test_termlog.py b/test/mitmproxy/addons/test_termlog.py
index 2133b74d..027bdfeb 100644
--- a/test/mitmproxy/addons/test_termlog.py
+++ b/test/mitmproxy/addons/test_termlog.py
@@ -16,7 +16,7 @@ class TestTermLog:
])
def test_output(self, outfile, expected_out, expected_err, capfd):
t = termlog.TermLog(outfile=outfile)
- with taddons.context(options=Options(verbosity=2)) as tctx:
+ with taddons.context(options=Options(verbosity='info')) as tctx:
tctx.configure(t)
t.log(log.LogEntry("one", "info"))
t.log(log.LogEntry("two", "debug"))
diff --git a/test/mitmproxy/addons/test_termstatus.py b/test/mitmproxy/addons/test_termstatus.py
index 2debaff5..5f960a1c 100644
--- a/test/mitmproxy/addons/test_termstatus.py
+++ b/test/mitmproxy/addons/test_termstatus.py
@@ -1,3 +1,4 @@
+from mitmproxy import proxy
from mitmproxy.addons import termstatus
from mitmproxy.test import taddons
@@ -5,6 +6,7 @@ from mitmproxy.test import taddons
def test_configure():
ts = termstatus.TermStatus()
with taddons.context() as ctx:
+ ctx.master.server = proxy.DummyServer()
ctx.configure(ts, server=False)
ts.running()
assert not ctx.master.logs
diff --git a/test/mitmproxy/addons/test_view.py b/test/mitmproxy/addons/test_view.py
index d5a3a456..a95d059d 100644
--- a/test/mitmproxy/addons/test_view.py
+++ b/test/mitmproxy/addons/test_view.py
@@ -7,6 +7,7 @@ from mitmproxy import flowfilter
from mitmproxy import exceptions
from mitmproxy import io
from mitmproxy.test import taddons
+from mitmproxy.tools.console import consoleaddons
def tft(*, method="get", start=0):
@@ -27,9 +28,9 @@ def test_order_refresh():
tf = tflow.tflow(resp=True)
with taddons.context() as tctx:
- tctx.configure(v, console_order="time")
+ tctx.configure(v, view_order="time")
v.add([tf])
- tf.request.timestamp_start = 1
+ tf.request.timestamp_start = 10
assert not sargs
v.update([tf])
assert sargs
@@ -40,7 +41,7 @@ def test_order_generators():
tf = tflow.tflow(resp=True)
rs = view.OrderRequestStart(v)
- assert rs.generate(tf) == 0
+ assert rs.generate(tf) == 946681200
rm = view.OrderRequestMethod(v)
assert rm.generate(tf) == tf.request.method
@@ -146,6 +147,10 @@ def test_create():
assert v[0].request.url == "http://foo.com/"
v.create("get", "http://foo.com")
assert len(v) == 2
+ with pytest.raises(exceptions.CommandError, match="Invalid URL"):
+ v.create("get", "http://foo.com\\")
+ with pytest.raises(exceptions.CommandError, match="Invalid URL"):
+ v.create("get", "http://")
def test_orders():
@@ -170,6 +175,10 @@ def test_load(tmpdir):
assert len(v) == 2
v.load_file(path)
assert len(v) == 4
+ try:
+ v.load_file("nonexistent_file_path")
+ except IOError:
+ assert False
def test_resolve():
@@ -262,6 +271,16 @@ def test_duplicate():
assert v.focus.index == 2
+def test_remove():
+ v = view.View()
+ with taddons.context():
+ f = [tflow.tflow(), tflow.tflow()]
+ v.add(f)
+ assert len(v) == 2
+ v.remove(f)
+ assert len(v) == 0
+
+
def test_setgetval():
v = view.View()
with taddons.context():
@@ -286,12 +305,12 @@ def test_order():
v.request(tft(method="put", start=4))
assert [i.request.timestamp_start for i in v] == [1, 2, 3, 4]
- tctx.configure(v, console_order="method")
+ tctx.configure(v, view_order="method")
assert [i.request.method for i in v] == ["GET", "GET", "PUT", "PUT"]
v.set_reversed(True)
assert [i.request.method for i in v] == ["PUT", "PUT", "GET", "GET"]
- tctx.configure(v, console_order="time")
+ tctx.configure(v, view_order="time")
assert [i.request.timestamp_start for i in v] == [4, 3, 2, 1]
v.set_reversed(False)
@@ -411,6 +430,8 @@ def test_signals():
def test_focus_follow():
v = view.View()
with taddons.context() as tctx:
+ console_addon = consoleaddons.ConsoleAddon(tctx.master)
+ tctx.configure(console_addon)
tctx.configure(v, console_focus_follow=True, view_filter="~m get")
v.add([tft(start=5)])
@@ -532,11 +553,11 @@ def test_configure():
with pytest.raises(Exception, match="Invalid interception filter"):
tctx.configure(v, view_filter="~~")
- tctx.configure(v, console_order="method")
+ tctx.configure(v, view_order="method")
with pytest.raises(Exception, match="Unknown flow order"):
- tctx.configure(v, console_order="no")
+ tctx.configure(v, view_order="no")
- tctx.configure(v, console_order_reversed=True)
+ tctx.configure(v, view_order_reversed=True)
tctx.configure(v, console_focus_follow=True)
assert v.focus_follow
diff --git a/test/mitmproxy/contentviews/image/test_image_parser.py b/test/mitmproxy/contentviews/image/test_image_parser.py
index 3cb44ca6..fdc72165 100644
--- a/test/mitmproxy/contentviews/image/test_image_parser.py
+++ b/test/mitmproxy/contentviews/image/test_image_parser.py
@@ -167,3 +167,26 @@ def test_parse_gif(filename, metadata):
def test_parse_jpeg(filename, metadata):
with open(tutils.test_data.path(filename), 'rb') as f:
assert metadata == image_parser.parse_jpeg(f.read())
+
+
+@pytest.mark.parametrize("filename, metadata", {
+ "mitmproxy/data/image.ico": [
+ ('Format', 'ICO'),
+ ('Number of images', '3'),
+ ('Image 1', "Size: {} x {}\n"
+ "{: >18}Bits per pixel: {}\n"
+ "{: >18}PNG: {}".format(48, 48, '', 24, '', False)
+ ),
+ ('Image 2', "Size: {} x {}\n"
+ "{: >18}Bits per pixel: {}\n"
+ "{: >18}PNG: {}".format(32, 32, '', 24, '', False)
+ ),
+ ('Image 3', "Size: {} x {}\n"
+ "{: >18}Bits per pixel: {}\n"
+ "{: >18}PNG: {}".format(16, 16, '', 24, '', False)
+ )
+ ]
+}.items())
+def test_ico(filename, metadata):
+ with open(tutils.test_data.path(filename), 'rb') as f:
+ assert metadata == image_parser.parse_ico(f.read())
diff --git a/test/mitmproxy/contentviews/image/test_view.py b/test/mitmproxy/contentviews/image/test_view.py
index 34f655a1..6da5b1d0 100644
--- a/test/mitmproxy/contentviews/image/test_view.py
+++ b/test/mitmproxy/contentviews/image/test_view.py
@@ -9,8 +9,7 @@ def test_view_image():
"mitmproxy/data/image.png",
"mitmproxy/data/image.gif",
"mitmproxy/data/all.jpeg",
- # https://bugs.python.org/issue21574
- # "mitmproxy/data/image.ico",
+ "mitmproxy/data/image.ico",
]:
with open(tutils.test_data.path(img), "rb") as f:
viewname, lines = v(f.read())
diff --git a/test/mitmproxy/contentviews/test_auto.py b/test/mitmproxy/contentviews/test_auto.py
index 2ff43139..cd888a2d 100644
--- a/test/mitmproxy/contentviews/test_auto.py
+++ b/test/mitmproxy/contentviews/test_auto.py
@@ -1,6 +1,6 @@
from mitmproxy.contentviews import auto
from mitmproxy.net import http
-from mitmproxy.types import multidict
+from mitmproxy.coretypes import multidict
from . import full_eval
diff --git a/test/mitmproxy/contentviews/test_css.py b/test/mitmproxy/contentviews/test_css.py
index ecb9259b..814f6e83 100644
--- a/test/mitmproxy/contentviews/test_css.py
+++ b/test/mitmproxy/contentviews/test_css.py
@@ -1,29 +1,42 @@
+import pytest
+
from mitmproxy.contentviews import css
from mitmproxy.test import tutils
from . import full_eval
-try:
- import cssutils
-except:
- cssutils = None
-
-
-def test_view_css():
+data = tutils.test_data.push("mitmproxy/contentviews/test_css_data/")
+
+
+@pytest.mark.parametrize("filename", [
+ "animation-keyframe.css",
+ "blank-lines-and-spaces.css",
+ "block-comment.css",
+ "empty-rule.css",
+ "import-directive.css",
+ "indentation.css",
+ "media-directive.css",
+ "quoted-string.css",
+ "selectors.css",
+ "simple.css",
+])
+def test_beautify(filename):
+ path = data.path(filename)
+ with open(path) as f:
+ input = f.read()
+ with open("-formatted.".join(path.rsplit(".", 1))) as f:
+ expected = f.read()
+ formatted = css.beautify(input)
+ assert formatted == expected
+
+
+def test_simple():
v = full_eval(css.ViewCSS())
-
- with open(tutils.test_data.path('mitmproxy/data/1.css'), 'r') as fp:
- fixture_1 = fp.read()
-
- result = v('a')
-
- if cssutils:
- assert len(list(result[1])) == 0
- else:
- assert len(list(result[1])) == 1
-
- result = v(fixture_1)
-
- if cssutils:
- assert len(list(result[1])) > 1
- else:
- assert len(list(result[1])) == 1
+ assert v(b"#foo{color:red}") == ('CSS', [
+ [('text', '#foo {')],
+ [('text', ' color: red')],
+ [('text', '}')]
+ ])
+ assert v(b"") == ('CSS', [[('text', '')]])
+ assert v(b"console.log('not really css')") == (
+ 'CSS', [[('text', "console.log('not really css')")]]
+ )
diff --git a/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css b/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css
new file mode 100644
index 00000000..3f91d508
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/animation-keyframe-formatted.css
@@ -0,0 +1,11 @@
+@-webkit-keyframes anim {
+0% {
+ -webkit-transform: translate3d(0px, 0px, 0px);
+}
+
+100% {
+ -webkit-transform: translate3d(150px, 0px, 0px)
+}
+
+
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css b/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css
new file mode 100644
index 00000000..ce63da5c
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/animation-keyframe.css
@@ -0,0 +1,3 @@
+@-webkit-keyframes anim {
+0% { -webkit-transform: translate3d(0px, 0px, 0px); }
+100% { -webkit-transform: translate3d(150px, 0px, 0px) }}
diff --git a/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css
new file mode 100644
index 00000000..de6bd045
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces-formatted.css
@@ -0,0 +1,35 @@
+/* only one blank line between */
+menu {
+ color: red
+}
+
+navi {
+ color: black
+}
+
+/* automatically insert a blank line */
+button {
+ border: 1px
+}
+
+sidebar {
+ color: #ffe
+}
+
+/* always whitespace before { */
+hidden {
+ opacity: 0%
+}
+
+/* no blank lines inside ruleset */
+imprint {
+ color: blue;
+ opacity: 0.5;
+ font-size: small
+}
+
+/* before colon: no space, after colon: one space only */
+footer {
+ font-family: Arial;
+ float: right;
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css
new file mode 100644
index 00000000..c6892105
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/blank-lines-and-spaces.css
@@ -0,0 +1,30 @@
+/* only one blank line between */
+menu { color: red }
+
+
+
+
+navi { color: black }
+
+/* automatically insert a blank line */
+button { border: 1px } sidebar { color: #ffe }
+
+/* always whitespace before { */
+hidden{opacity:0%}
+
+/* no blank lines inside ruleset */
+imprint {
+ color: blue;
+
+
+ opacity: 0.5;
+
+ font-size: small
+}
+
+/* before colon: no space, after colon: one space only */
+footer {
+ font-family: Arial;
+
+ float :right;
+ }
diff --git a/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css b/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css
new file mode 100644
index 00000000..83e0f4e6
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/block-comment-formatted.css
@@ -0,0 +1,22 @@
+/* line comment */
+navigation {
+ color: blue
+}
+
+menu {
+ /* line comment inside */
+ border: 2px
+}
+
+/* block
+comment */
+sidebar {
+ color: red
+}
+
+invisible {
+ /* block
+ * comment
+ * inside */
+ color: #eee
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/block-comment.css b/test/mitmproxy/contentviews/test_css_data/block-comment.css
new file mode 100644
index 00000000..3ba26540
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/block-comment.css
@@ -0,0 +1,18 @@
+/* line comment */
+navigation { color: blue }
+
+menu {
+ /* line comment inside */
+ border: 2px
+}
+
+/* block
+ comment */
+sidebar { color: red }
+
+invisible {
+ /* block
+ * comment
+ * inside */
+ color: #eee
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css b/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css
new file mode 100644
index 00000000..7c0a78f4
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/empty-rule-formatted.css
@@ -0,0 +1,2 @@
+menu {
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/empty-rule.css b/test/mitmproxy/contentviews/test_css_data/empty-rule.css
new file mode 100644
index 00000000..7d6ecfcd
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/empty-rule.css
@@ -0,0 +1 @@
+menu{}
diff --git a/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css b/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css
new file mode 100644
index 00000000..08a0ad57
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/import-directive-formatted.css
@@ -0,0 +1,8 @@
+menu {
+ background-color: red
+}
+
+@import url('foobar.css') screen;
+nav {
+ margin: 0
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/import-directive.css b/test/mitmproxy/contentviews/test_css_data/import-directive.css
new file mode 100644
index 00000000..61979f0a
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/import-directive.css
@@ -0,0 +1,2 @@
+menu{background-color:red} @import url('foobar.css') screen;
+nav{margin:0}
diff --git a/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css b/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css
new file mode 100644
index 00000000..18ea527d
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/indentation-formatted.css
@@ -0,0 +1,3 @@
+navigation {
+ color: blue
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/indentation.css b/test/mitmproxy/contentviews/test_css_data/indentation.css
new file mode 100644
index 00000000..77e00f83
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/indentation.css
@@ -0,0 +1,3 @@
+ navigation {
+ color: blue
+ }
diff --git a/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css b/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css
new file mode 100644
index 00000000..84d95421
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/media-directive-formatted.css
@@ -0,0 +1,17 @@
+@import "subs.css";
+@import "print-main.css" print;
+@media print {
+body {
+ font-size: 10pt
+}
+
+nav {
+ color: blue;
+}
+
+
+}
+
+h1 {
+ color: red;
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/media-directive.css b/test/mitmproxy/contentviews/test_css_data/media-directive.css
new file mode 100644
index 00000000..ddf67c58
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/media-directive.css
@@ -0,0 +1,7 @@
+@import "subs.css";
+@import "print-main.css" print;
+@media print {
+ body { font-size: 10pt }
+ nav { color: blue; }
+}
+h1 {color: red; }
diff --git a/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css b/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css
new file mode 100644
index 00000000..ab4c3412
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/quoted-string-formatted.css
@@ -0,0 +1,7 @@
+nav:after {
+ content: '}'
+}
+
+nav:before {
+ content: "}"
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/quoted-string.css b/test/mitmproxy/contentviews/test_css_data/quoted-string.css
new file mode 100644
index 00000000..f5f3279e
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/quoted-string.css
@@ -0,0 +1,2 @@
+nav:after{content:'}'}
+nav:before{content:"}"}
diff --git a/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css b/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css
new file mode 100644
index 00000000..166251cb
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/selectors-formatted.css
@@ -0,0 +1,19 @@
+* {
+ border: 0px solid blue;
+}
+
+div[class="{}"] {
+ color: red;
+}
+
+a[id=\"foo"] {
+ padding: 0;
+}
+
+[id=\"foo"] {
+ margin: 0;
+}
+
+#menu, #nav, #footer {
+ color: royalblue;
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/selectors.css b/test/mitmproxy/contentviews/test_css_data/selectors.css
new file mode 100644
index 00000000..dc36f9e5
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/selectors.css
@@ -0,0 +1,5 @@
+* { border: 0px solid blue; }
+div[class="{}"] { color: red; }
+a[id=\"foo"] { padding: 0; }
+[id=\"foo"] { margin: 0; }
+#menu, #nav, #footer { color: royalblue; }
diff --git a/test/mitmproxy/contentviews/test_css_data/simple-formatted.css b/test/mitmproxy/contentviews/test_css_data/simple-formatted.css
new file mode 100644
index 00000000..9435236b
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/simple-formatted.css
@@ -0,0 +1,16 @@
+menu {
+ color: blue;
+}
+
+box {
+ border-radius: 4px;
+ background-color: red
+}
+
+a {
+ color: green
+}
+
+b {
+ color: red
+}
diff --git a/test/mitmproxy/contentviews/test_css_data/simple.css b/test/mitmproxy/contentviews/test_css_data/simple.css
new file mode 100644
index 00000000..33b29a03
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_css_data/simple.css
@@ -0,0 +1,5 @@
+menu { color: blue; }
+
+box { border-radius: 4px; background-color: red }
+a { color: green }
+b { color: red }
diff --git a/test/mitmproxy/contentviews/test_html_outline.py b/test/mitmproxy/contentviews/test_html_outline.py
deleted file mode 100644
index 9e664e52..00000000
--- a/test/mitmproxy/contentviews/test_html_outline.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from mitmproxy.contentviews import html_outline
-from test.mitmproxy.contentviews import full_eval
-
-
-def test_view_html_outline():
- v = full_eval(html_outline.ViewHTMLOutline())
- s = b"<html><br><br></br><p>one</p></html>"
- assert v(s)
- assert v(b'\xfe')
diff --git a/test/mitmproxy/contentviews/test_javascript.py b/test/mitmproxy/contentviews/test_javascript.py
index 43039c93..23dd106e 100644
--- a/test/mitmproxy/contentviews/test_javascript.py
+++ b/test/mitmproxy/contentviews/test_javascript.py
@@ -1,10 +1,32 @@
+import pytest
+
from mitmproxy.contentviews import javascript
+from mitmproxy.test import tutils
from . import full_eval
+data = tutils.test_data.push("mitmproxy/contentviews/test_js_data/")
+
def test_view_javascript():
v = full_eval(javascript.ViewJavaScript())
assert v(b"[1, 2, 3]")
assert v(b"[1, 2, 3")
- assert v(b"function(a){[1, 2, 3]}")
+ assert v(b"function(a){[1, 2, 3]}") == ("JavaScript", [
+ [('text', 'function(a) {')],
+ [('text', ' [1, 2, 3]')],
+ [('text', '}')]
+ ])
assert v(b"\xfe") # invalid utf-8
+
+
+@pytest.mark.parametrize("filename", [
+ "simple.js",
+])
+def test_format_xml(filename):
+ path = data.path(filename)
+ with open(path) as f:
+ input = f.read()
+ with open("-formatted.".join(path.rsplit(".", 1))) as f:
+ expected = f.read()
+ js = javascript.beautify(input)
+ assert js == expected
diff --git a/test/mitmproxy/contentviews/test_js_data/simple-formatted.js b/test/mitmproxy/contentviews/test_js_data/simple-formatted.js
new file mode 100644
index 00000000..2b665f02
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_js_data/simple-formatted.js
@@ -0,0 +1,68 @@
+/* _GlobalPrefix_ */
+this.gbar_=this.gbar_||{};
+(function(_) {
+ var window=this;
+
+/* _Module_:sy25 */
+ try {
+ var Mn=function(){};
+ _.y(Mn,Error);
+ _.Nn=function() {
+ this.b="pending";
+ this.B=[];
+ this.w=this.C=void 0
+ };
+ _.fe(_.Nn);
+ var On=function() {
+ _.qa.call(this,"Multiple attempts to set the state of this Result")
+ };
+ _.y(On,_.qa);
+ _.Nn.prototype.ta=function() {
+ return this.C
+ };
+ _.Pn=function(a,c,d) {
+ "pending"==a.b?a.B.push( {
+ hb:c,scope:d||null
+ }
+ ):c.call(d,a)
+ };
+ _.Nn.prototype.A=function(a) {
+ if("pending"==this.b)this.C=a,this.b="success",Qn(this);
+ else if(!Rn(this))throw new On;
+ };
+ _.Nn.prototype.o=function(a) {
+ if("pending"==this.b)this.w=a,this.b="error",Qn(this);
+ else if(!Rn(this))throw new On;
+ };
+ var Qn=function(a) {
+ var c=a.B;
+ a.B=[];
+ for(var d=0;d<c.length;d++) {
+ var e=c[d];
+ e.hb.call(e.scope,a)
+ }
+
+ };
+ _.Nn.prototype.cancel=function() {
+ return"pending"==this.b?(this.o(new Mn),!0):!1
+ };
+ var Rn=function(a) {
+ return"error"==a.b&&a.w instanceof Mn
+ };
+ _.Nn.prototype.then=function(a,c,d) {
+ var e,f,g=new _.ie(function(a,c) {
+ e=a;
+ f=c
+ }
+ );
+ _.Pn(this,function(a) {
+ Rn(a)?g.cancel():"success"==a.b?e(a.ta()):"error"==a.b&&f(a.w)
+ }
+ );
+ return g.then(a,c,d)
+ };
+
+ }
+ catch(e) {
+ _._DumpException(e)
+ }
diff --git a/test/mitmproxy/contentviews/test_js_data/simple.js b/test/mitmproxy/contentviews/test_js_data/simple.js
new file mode 100644
index 00000000..66a56d8b
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_js_data/simple.js
@@ -0,0 +1,8 @@
+/* _GlobalPrefix_ */
+this.gbar_=this.gbar_||{};(function(_){var window=this;
+/* _Module_:sy25 */
+try{
+var Mn=function(){};_.y(Mn,Error);_.Nn=function(){this.b="pending";this.B=[];this.w=this.C=void 0};_.fe(_.Nn);var On=function(){_.qa.call(this,"Multiple attempts to set the state of this Result")};_.y(On,_.qa);_.Nn.prototype.ta=function(){return this.C};_.Pn=function(a,c,d){"pending"==a.b?a.B.push({hb:c,scope:d||null}):c.call(d,a)};_.Nn.prototype.A=function(a){if("pending"==this.b)this.C=a,this.b="success",Qn(this);else if(!Rn(this))throw new On;};
+_.Nn.prototype.o=function(a){if("pending"==this.b)this.w=a,this.b="error",Qn(this);else if(!Rn(this))throw new On;};var Qn=function(a){var c=a.B;a.B=[];for(var d=0;d<c.length;d++){var e=c[d];e.hb.call(e.scope,a)}};_.Nn.prototype.cancel=function(){return"pending"==this.b?(this.o(new Mn),!0):!1};var Rn=function(a){return"error"==a.b&&a.w instanceof Mn}; _.Nn.prototype.then=function(a,c,d){var e,f,g=new _.ie(function(a,c){e=a;f=c});_.Pn(this,function(a){Rn(a)?g.cancel():"success"==a.b?e(a.ta()):"error"==a.b&&f(a.w)});return g.then(a,c,d)};
+
+}catch(e){_._DumpException(e)}
diff --git a/test/mitmproxy/contentviews/test_protobuf.py b/test/mitmproxy/contentviews/test_protobuf.py
index 71e51576..6c6e37f2 100644
--- a/test/mitmproxy/contentviews/test_protobuf.py
+++ b/test/mitmproxy/contentviews/test_protobuf.py
@@ -1,52 +1,31 @@
-from unittest import mock
import pytest
from mitmproxy.contentviews import protobuf
from mitmproxy.test import tutils
from . import full_eval
+data = tutils.test_data.push("mitmproxy/contentviews/test_protobuf_data/")
+
def test_view_protobuf_request():
v = full_eval(protobuf.ViewProtobuf())
- p = tutils.test_data.path("mitmproxy/data/protobuf01")
-
- with mock.patch('mitmproxy.contentviews.protobuf.ViewProtobuf.is_available'):
- with mock.patch('subprocess.Popen') as n:
- m = mock.Mock()
- attrs = {'communicate.return_value': (b'1: "3bbc333c-e61c-433b-819a-0b9a8cc103b8"', True)}
- m.configure_mock(**attrs)
- n.return_value = m
-
- with open(p, "rb") as f:
- data = f.read()
- content_type, output = v(data)
- assert content_type == "Protobuf"
- assert output[0] == [('text', b'1: "3bbc333c-e61c-433b-819a-0b9a8cc103b8"')]
-
- m.communicate = mock.MagicMock()
- m.communicate.return_value = (None, None)
- with pytest.raises(ValueError, matches="Failed to parse input."):
- v(b'foobar')
-
-
-def test_view_protobuf_availability():
- with mock.patch('subprocess.Popen') as n:
- m = mock.Mock()
- attrs = {'communicate.return_value': (b'libprotoc fake version', True)}
- m.configure_mock(**attrs)
- n.return_value = m
- assert protobuf.ViewProtobuf().is_available()
-
- m = mock.Mock()
- attrs = {'communicate.return_value': (b'command not found', True)}
- m.configure_mock(**attrs)
- n.return_value = m
- assert not protobuf.ViewProtobuf().is_available()
-
-
-def test_view_protobuf_fallback():
- with mock.patch('subprocess.Popen.communicate') as m:
- m.side_effect = OSError()
- v = full_eval(protobuf.ViewProtobuf())
- with pytest.raises(NotImplementedError, matches='protoc not found'):
- v(b'foobar')
+ p = data.path("protobuf01")
+
+ with open(p, "rb") as f:
+ raw = f.read()
+ content_type, output = v(raw)
+ assert content_type == "Protobuf"
+ assert output == [[('text', '1: 3bbc333c-e61c-433b-819a-0b9a8cc103b8')]]
+ with pytest.raises(ValueError, matches="Failed to parse input."):
+ v(b'foobar')
+
+
+@pytest.mark.parametrize("filename", ["protobuf02", "protobuf03"])
+def test_format_pbuf(filename):
+ path = data.path(filename)
+ with open(path, "rb") as f:
+ input = f.read()
+ with open(path + "-decoded") as f:
+ expected = f.read()
+
+ assert protobuf.format_pbuf(input) == expected
diff --git a/test/mitmproxy/data/protobuf01 b/test/mitmproxy/contentviews/test_protobuf_data/protobuf01
index fbfdbff3..fbfdbff3 100644
--- a/test/mitmproxy/data/protobuf01
+++ b/test/mitmproxy/contentviews/test_protobuf_data/protobuf01
diff --git a/test/mitmproxy/contentviews/test_protobuf_data/protobuf02 b/test/mitmproxy/contentviews/test_protobuf_data/protobuf02
new file mode 100644
index 00000000..a47c45d5
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_protobuf_data/protobuf02
Binary files differ
diff --git a/test/mitmproxy/contentviews/test_protobuf_data/protobuf02-decoded b/test/mitmproxy/contentviews/test_protobuf_data/protobuf02-decoded
new file mode 100644
index 00000000..9be61e28
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_protobuf_data/protobuf02-decoded
@@ -0,0 +1,65 @@
+1 {
+ 1: tpbuf
+ 4 {
+ 1: Person
+ 2 {
+ 1: name
+ 3: 1
+ 4: 2
+ 5: 9
+ }
+ 2 {
+ 1: id
+ 3: 2
+ 4: 2
+ 5: 5
+ }
+ 2 {
+ 1 {
+ 12: 1818845549
+ }
+ 3: 3
+ 4: 1
+ 5: 9
+ }
+ 2 {
+ 1: phone
+ 3: 4
+ 4: 3
+ 5: 11
+ 6: .Person.PhoneNumber
+ }
+ 3 {
+ 1: PhoneNumber
+ 2 {
+ 1: number
+ 3: 1
+ 4: 2
+ 5: 9
+ }
+ 2 {
+ 1: type
+ 3: 2
+ 4: 1
+ 5: 14
+ 6: .Person.PhoneType
+ 7: HOME
+ }
+ }
+ 4 {
+ 1: PhoneType
+ 2 {
+ 1: MOBILE
+ 2: 0
+ }
+ 2 {
+ 1: HOME
+ 2: 1
+ }
+ 2 {
+ 1: WORK
+ 2: 2
+ }
+ }
+ }
+}
diff --git a/test/mitmproxy/contentviews/test_protobuf_data/protobuf03 b/test/mitmproxy/contentviews/test_protobuf_data/protobuf03
new file mode 100644
index 00000000..9fb230b3
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_protobuf_data/protobuf03
@@ -0,0 +1 @@
+€ ð \ No newline at end of file
diff --git a/test/mitmproxy/contentviews/test_protobuf_data/protobuf03-decoded b/test/mitmproxy/contentviews/test_protobuf_data/protobuf03-decoded
new file mode 100644
index 00000000..3d3392e1
--- /dev/null
+++ b/test/mitmproxy/contentviews/test_protobuf_data/protobuf03-decoded
@@ -0,0 +1,4 @@
+2 {
+3: 3840
+4: 2160
+}
diff --git a/test/mitmproxy/contentviews/test_query.py b/test/mitmproxy/contentviews/test_query.py
index d2bddd05..741b23f1 100644
--- a/test/mitmproxy/contentviews/test_query.py
+++ b/test/mitmproxy/contentviews/test_query.py
@@ -1,5 +1,5 @@
from mitmproxy.contentviews import query
-from mitmproxy.types import multidict
+from mitmproxy.coretypes import multidict
from . import full_eval
diff --git a/test/mitmproxy/types/__init__.py b/test/mitmproxy/coretypes/__init__.py
index e69de29b..e69de29b 100644
--- a/test/mitmproxy/types/__init__.py
+++ b/test/mitmproxy/coretypes/__init__.py
diff --git a/test/mitmproxy/types/test_basethread.py b/test/mitmproxy/coretypes/test_basethread.py
index a91588eb..4a383fea 100644
--- a/test/mitmproxy/types/test_basethread.py
+++ b/test/mitmproxy/coretypes/test_basethread.py
@@ -1,5 +1,5 @@
import re
-from mitmproxy.types import basethread
+from mitmproxy.coretypes import basethread
def test_basethread():
diff --git a/test/mitmproxy/types/test_bidi.py b/test/mitmproxy/coretypes/test_bidi.py
index e3a259fd..3bdad3c2 100644
--- a/test/mitmproxy/types/test_bidi.py
+++ b/test/mitmproxy/coretypes/test_bidi.py
@@ -1,5 +1,5 @@
import pytest
-from mitmproxy.types import bidi
+from mitmproxy.coretypes import bidi
def test_bidi():
diff --git a/test/mitmproxy/types/test_multidict.py b/test/mitmproxy/coretypes/test_multidict.py
index c76cd753..273d8ca2 100644
--- a/test/mitmproxy/types/test_multidict.py
+++ b/test/mitmproxy/coretypes/test_multidict.py
@@ -1,6 +1,6 @@
import pytest
-from mitmproxy.types import multidict
+from mitmproxy.coretypes import multidict
class _TMulti:
diff --git a/test/mitmproxy/types/test_serializable.py b/test/mitmproxy/coretypes/test_serializable.py
index 390d17e1..a316f876 100644
--- a/test/mitmproxy/types/test_serializable.py
+++ b/test/mitmproxy/coretypes/test_serializable.py
@@ -1,6 +1,6 @@
import copy
-from mitmproxy.types import serializable
+from mitmproxy.coretypes import serializable
class SerializableDummy(serializable.Serializable):
diff --git a/test/mitmproxy/data/addonscripts/concurrent_decorator_class.py b/test/mitmproxy/data/addonscripts/concurrent_decorator_class.py
index 2a7d300c..b52f55c5 100644
--- a/test/mitmproxy/data/addonscripts/concurrent_decorator_class.py
+++ b/test/mitmproxy/data/addonscripts/concurrent_decorator_class.py
@@ -5,7 +5,7 @@ from mitmproxy.script import concurrent
class ConcurrentClass:
@concurrent
- def request(flow):
+ def request(self, flow):
time.sleep(0.1)
diff --git a/test/mitmproxy/data/addonscripts/load_error.py b/test/mitmproxy/data/addonscripts/load_error.py
new file mode 100644
index 00000000..4c05e9ed
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/load_error.py
@@ -0,0 +1,2 @@
+def load(_):
+ raise ValueError()
diff --git a/test/mitmproxy/data/addonscripts/same_filename/addon.py b/test/mitmproxy/data/addonscripts/same_filename/addon.py
new file mode 100644
index 00000000..c84a9b13
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/same_filename/addon.py
@@ -0,0 +1 @@
+foo = 42
diff --git a/test/mitmproxy/data/addonscripts/shutdown.py b/test/mitmproxy/data/addonscripts/shutdown.py
new file mode 100644
index 00000000..51a99b5c
--- /dev/null
+++ b/test/mitmproxy/data/addonscripts/shutdown.py
@@ -0,0 +1,5 @@
+from mitmproxy import ctx
+
+
+def running():
+ ctx.master.shutdown()
diff --git a/test/mitmproxy/data/no_common_name.pem b/test/mitmproxy/data/no_common_name.pem
index fc271a0e..d46448f5 100644
--- a/test/mitmproxy/data/no_common_name.pem
+++ b/test/mitmproxy/data/no_common_name.pem
@@ -1,20 +1,84 @@
-----BEGIN RSA PRIVATE KEY-----
-MIIBOQIBAAJBAKVJ43C+8SjOvN9/pP/8HwzmHGQmRvdK/R6KlWdr7He6iiXDQNfH
-RAp+gqX0hBRT80eRjGhSmTTBLCWiXVny4UUCAwEAAQJAUQ8nZ0d85VJd9g2XUaLH
-Z4ACNGtBKk2wTKYSFyIqWZxsF5qhh7HGshJIAP6tYiX8ZW+mMSfme+zsJzWe8ChL
-gQIhAM8QpAgUHnNteZvkv0XqceX1GILEWifMt+hO9yTp4dY5AiEAzFnKr77CKCri
-/DPig4R/5q4KMpMx9EqJufHdGNmIA20CICMARxnufK86RCIr6oEg/hvG8Fu6YRr1
-Kekk3/XnavtRAiBVLVQ7vwKE5aNpRmMzOKZrS736aLpYvjz8IaFr+zgjXQIgdad5
-QZoTD49NTyMEgyZp70gTXcXQLrX2PgQKL4uNmoU=
+MIIJKgIBAAKCAgEA4Jut+R51opC773ToeUhwJOVGnpxNqzZTDMImO141WPvKMjMs
+i15f0U3OKqK8YERDfDzaAbgqz6MNgqc8QbNJ0e9VxtMUzTkCwSlbDHMFgZNyXVRX
+OQEBJ/fTMlU+LimOH38QY0orifdAHH+kPUYIiqTBzgJvCy8w1o4hGSlzf2HW400d
+mlRSJEVgBj6nXQENbVxmxf6f9H19eWpnLuP5aJIwP4LEsGdqLP0ESnWfZVPIAiEs
+LuYkhbRXqiuJfnc1am8LexzLi4VQMCw0K4Tm1lTbapcnOUakO6orQvX7MOEKYBU+
+ogGGby0MyOTaCkuUFi0YdTtU4DEdWJ9HfogRO/uG1325/8/T+tq8RgOkp9cUvjU3
+ONqpLZC6zs0YR8OzlTbXClmV+Mr6d1qEb3jk6zWlLykLVozOS3z5vdVxpbJqM/Ct
+HporAA0cSer2EGtN68OB3Hy3Bh7MPjqpSFSJ1uTQ755jS3qOzwggoQFCz2dBmfyi
+7nOGHFW4h/5NaF1mnIlJJVnJIZajSNl9e6klGeXmJv4ZtiqJd/CG0jTUnGWOTimu
+kSmxVNcWs2vFjwQhuRJwawzGo7O1gZPkq3/0/F+yLp5karmRJs8sQ/JDvGL4rW5Y
+qW7u1WuYQeHYHscfPwf0be8teKWcURIqBoHPxdJV3s9zf8Y/AN9OFFdPqwcCAwEA
+AQKCAgEAtdHQV2Ws3FhFimYc+nEFNxjSvfrRdNOZDy7rPAvbK5lH6LM8T+WpswlE
+54as71DTQHMSF2o6XbMkcKtoP9ce3u7bhQPCRw7rh+ouZjmGL4pofdyUbvS9NtmL
+Aae3mi7RefWmEnosHJcmMuuwzFkw+Oq+aEHYGjmtU0Hi0TeY43kUNxRp7lBr3ii6
+vtNhMAx2Dh1KpOSmH4imVe8ob/DkKR6OKBt3lUVh0eFP4+arjZ7wvaiU17I9xm5i
+uMJdnx5pAyu5I4P/0YWtkBF4efIv2zj+FZ8ehWMF97adJqtxF/RULcuE1Chf5weU
+3dtEFilwSzNeJShOYN3hX6gwe+Ex8NQ7EIcbkJBaxzteEbcCJJf2rUq5oHwt3MFR
+H1m1iZ/H3erNHOIqr+LW5+ZXI9Lyyn9z1YTodP2KIfB6Rg8ldsaEnkuCD/S+iikO
+xNo/OwJf3rHtbeRtpAfSqkSMhT57hadRTvPlBMAoFEsX3hdw1XuRLOp9YAhsjdhU
+GBzD+U/kB8FlYRhPjvjT97lJ7uE2AosGgIZBwpyv/UqPU75u/3SgubLmIHoXYWBn
+jPig0H3zqpT+1D+88umMP5Ka+V/KsofUvObSjipdw3U1ZekI+08SLlGQqLvA7tk/
+3WvT7QN3k0OUx261pJNHmVhPZysJT0DNpJX9x/4jFiLVBxYyomECggEBAPNtLge0
+YJGvKTTpQoKlIb/a47wncBizyeVRhjcYNRQn6JcSzPPao8x1/ARoKPGeTXqDWiHt
+o8RllF1aEKbO0gTXtBM7jjwf1ueco1F4IQYc7EnA1TwNyQ0JeA5RFcpOzLOx4SK5
+67jMaAI/gQ2Q4H3uISWF+hPlckRySpwouz+xS3QP6PZ8S6lZwHF5S9LMrxTlwbPj
+yAjgIllFIvs91J6mDljll1ZG601CU8piaY5zPoJVQIesA/YRK+rlHBPICOGXJBm7
+G8kbYT9EFibxklWjiz9C9G70WPulPCZpAqaqIp70nOFR+Kp5g8VuPaMKFx8p3qc/
+AQJGlO06lFVaL0UCggEBAOw1qkzBFTdBmZW9VSBHBQdTJuuuWjoLCp1tfPUYluqG
+VrzIKN0dlnB2Qy5uOHd0fVTq88+EadnXwt2hOkp4uGeWOIZfE0welWmNZ1ym9vkg
+PSLgPYBDSL1rDntLeAgEUYl4gnFL8zyD0uKw/+oMoMaW2jNEqmGJH9y5uG2lkFGC
+A6MbEw32sfJKoWB2GvnlIPGAMfZy9Varxq/r24OeFi7J6ja89DBL9OEWsB49/CP0
+92dkS2+7KekTN9go3cFaqnseDHNUuRhHsIJBpaWSaxlUwpVP6QQtn5NMEW44YpnE
+PDk/EsxwOVAGRVaS5+pBJSUbLplXRHlzVZW4lEc+f9sCggEAe0ZuSh6RzRVck9wQ
+/6JqzgMm03FRdmEOPKCljJ8oujVft6ogutmdm/ygDQdGvN3DNOjyKz5ychJTKVdk
+GWWhvCwUmKzPYilpps+PccGZT8Qz8UHDeu8sQvrpnq53j4WKavIJJpHrCyIRBhps
+25bj6UI/7QXFWHAZBwquOBj0gtPhdzxbaQAXPQMjzxNzT6Syga29A8G12rDPFFBL
+39o3I8TKfUB//IRbwzt0vYhLFoXMQSq1TD/Tnbiieglex7HEtaHZ+WHlN1ozTFvJ
+sB0kU1RIP1hD+zCpI39RT85cNlTwxXjxPbZKbOKu1bv3YOrKPNDyXdYtR57A6saA
+uhy61QKCAQEAnax5AIFGyzrD7duTjlc5+Ri9e0dIPUSPkmS6q9T9MJH6JkwqUudk
+O7AFymGS2dJtsxifJV/LVLoc/tqX0Yxh8+un0bJ3bDFiJTJZ09Q0OjoV9UjgZNUF
+IkPrR8wp1JglYXGLCVvcgwGv7NigC7jgPZAHGX/1h+QD29AxVyfUfUQfb2osPv70
+67p7nKtZ+IPFiM+9CjjUokVJ/LahMmt9fUAVUvKwweiCDxqY96cCv3HPEDo3zN6P
+7GCCv40P8fi2ojZ9syLT52w7W8e8bhid2yvkM81CyyI1ShrV69BBqUj/tmru/n7P
+EycMc+zeWFWiGPHbGkrRj4y4jZfHiwMiTwKCAQEAk77dpDHxxTbmW0sdRYsEgwbz
+hhbi4vhaEsJODDg0WsqZYOxsfbjTXYYSClCel3QHuJGHmEqCTcj8SqWXwpQhTeW1
+Ngs1tOprfLAo+XCbUTy51b9Acx6l/EgP1n5lrjNnGgcygfAYSG34zXSnXpiJzqQP
+vKXntjJii5U8d+Qzvvwf6taU5v0JqO1J2ZWmvmgZN9qgVR42KVvdBc1F6qsN4F68
+ZrY9lULqi776Z60Dfl9S/7E4BCSEyZm4PjJ39zQYMYc4B77YYv/aO4WFZBm7gbc/
+0UP8WkOzc8wYOqgLF2KatJkaOLBfbhMGaKPvjvWDfjyVfZ8I4Gqx2rI8ILHEuQ==
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
-MIIBgTCCASugAwIBAgIJAKlcXsPLQAQuMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
-BAYTAkFVMB4XDTEzMTIxMjAxMzA1NVoXDTE0MDExMTAxMzA1NVowDTELMAkGA1UE
-BhMCQVUwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApUnjcL7xKM6833+k//wfDOYc
-ZCZG90r9HoqVZ2vsd7qKJcNA18dECn6CpfSEFFPzR5GMaFKZNMEsJaJdWfLhRQID
-AQABo24wbDAdBgNVHQ4EFgQUJm8BXcVRsROy0PVt5stkB3eVnEgwPQYDVR0jBDYw
-NIAUJm8BXcVRsROy0PVt5stkB3eVnEihEaQPMA0xCzAJBgNVBAYTAkFVggkAqVxe
-w8tABC4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAHHxcBEpWrIqtLVH
-m6Yn1hgqrAbfMj9IK6zY9C5Cbad/DfUj3AZMb5u758WJK0x9brmckgqdrQsuf9He
-Ef51/SU=
+MIIFtTCCA52gAwIBAgIJAM/qkBYP5ExSMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTcwNzI1MDg0MDAwWhcNMTgwNzI1MDg0MDAwWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEA4Jut+R51opC773ToeUhwJOVGnpxNqzZTDMImO141WPvKMjMsi15f0U3O
+KqK8YERDfDzaAbgqz6MNgqc8QbNJ0e9VxtMUzTkCwSlbDHMFgZNyXVRXOQEBJ/fT
+MlU+LimOH38QY0orifdAHH+kPUYIiqTBzgJvCy8w1o4hGSlzf2HW400dmlRSJEVg
+Bj6nXQENbVxmxf6f9H19eWpnLuP5aJIwP4LEsGdqLP0ESnWfZVPIAiEsLuYkhbRX
+qiuJfnc1am8LexzLi4VQMCw0K4Tm1lTbapcnOUakO6orQvX7MOEKYBU+ogGGby0M
+yOTaCkuUFi0YdTtU4DEdWJ9HfogRO/uG1325/8/T+tq8RgOkp9cUvjU3ONqpLZC6
+zs0YR8OzlTbXClmV+Mr6d1qEb3jk6zWlLykLVozOS3z5vdVxpbJqM/CtHporAA0c
+Ser2EGtN68OB3Hy3Bh7MPjqpSFSJ1uTQ755jS3qOzwggoQFCz2dBmfyi7nOGHFW4
+h/5NaF1mnIlJJVnJIZajSNl9e6klGeXmJv4ZtiqJd/CG0jTUnGWOTimukSmxVNcW
+s2vFjwQhuRJwawzGo7O1gZPkq3/0/F+yLp5karmRJs8sQ/JDvGL4rW5YqW7u1WuY
+QeHYHscfPwf0be8teKWcURIqBoHPxdJV3s9zf8Y/AN9OFFdPqwcCAwEAAaOBpzCB
+pDAdBgNVHQ4EFgQUZrQUSE9A8i0N4ZuQZq/F4I74QlwwdQYDVR0jBG4wbIAUZrQU
+SE9A8i0N4ZuQZq/F4I74QlyhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
+b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDP
+6pAWD+RMUjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQDPeylCUY4k
+nG1KoT139g5T5G1/lxgmYnDqQB1+5JYCQWsPK7sy19tD58bq3+2N2Tozu2/f/GkG
+LZtouLyRciFtcAWy4LQlSR4hTLAWeik2WV/h+ovfv4XwvRuwS5PYVQNHQsOAO3ea
+hX+W+w+rwI+MFlgEHJO85P61ijcNVbiTpgd0s47RViKyJVDqfhCmpobzS5eTbbXn
+F1oFlV84lgEt84BE4RJxlr6fSIxZn6rQPjdbY65snol7Zs2oAt7nLb3hpZgWKobF
+3xAfkC9m19nQHeYz3JlNC7sf80top2H2HEZMVVOAD+MxXkcAjNbjBRT3/KAIyWex
+2fmoGRbvCIU0LFyyyk7/tG1xTgxNuBmd4Byz1LI25uz6eK4Ey8LeZmp5mvaewI53
+t65sAGBkx+LIRt0yGmMCRRFl735Ya4SJD7je1GTiw9I3Yd63dtaJTVd65kkFkLOk
+LD56iJHSyCY6JkDXd8RjozIVoaXkVQh2wFq/ZfXzAgIx/u5cJQCMG2DAu6/WI74+
+7invOv7dbYfoI02N4iB57iRbPxE4gSrRayYxUVdH1R/tlXbN9Fkd30fl2WfSO897
+QC/ODA9w86FSFANhn6nv2KuKIMUSEW+5ZhBowSFIBEdAaMS7yj9uuBWmQKrWNfOh
+mZJF1YiFmgRybkdKHPrlCSZyvVBdmnmM6g==
-----END CERTIFICATE-----
diff --git a/test/mitmproxy/net/http/http1/test_read.py b/test/mitmproxy/net/http/http1/test_read.py
index b3589c92..4084c360 100644
--- a/test/mitmproxy/net/http/http1/test_read.py
+++ b/test/mitmproxy/net/http/http1/test_read.py
@@ -194,6 +194,17 @@ def test_expected_http_body_size():
treq(headers=Headers(content_length="42"))
) == 42
+ # more than 1 content-length headers with same value
+ assert expected_http_body_size(
+ treq(headers=Headers([(b'content-length', b'42'), (b'content-length', b'42')]))
+ ) == 42
+
+ # more than 1 content-length headers with conflicting value
+ with pytest.raises(exceptions.HttpSyntaxException):
+ expected_http_body_size(
+ treq(headers=Headers([(b'content-length', b'42'), (b'content-length', b'45')]))
+ )
+
# no length
assert expected_http_body_size(
treq(headers=Headers())
diff --git a/test/mitmproxy/net/http/test_response.py b/test/mitmproxy/net/http/test_response.py
index fa1770fe..af35bab3 100644
--- a/test/mitmproxy/net/http/test_response.py
+++ b/test/mitmproxy/net/http/test_response.py
@@ -150,10 +150,10 @@ class TestResponseUtils:
n = time.time()
r.headers["date"] = email.utils.formatdate(n)
pre = r.headers["date"]
- r.refresh(n)
+ r.refresh(946681202)
assert pre == r.headers["date"]
- r.refresh(n + 60)
+ r.refresh(946681262)
d = email.utils.parsedate_tz(r.headers["date"])
d = email.utils.mktime_tz(d)
# Weird that this is not exact...
diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py
index 73de0879..8c012e42 100644
--- a/test/mitmproxy/net/test_tcp.py
+++ b/test/mitmproxy/net/test_tcp.py
@@ -1,9 +1,9 @@
from io import BytesIO
+import re
import queue
import time
import socket
import random
-import os
import threading
import pytest
from unittest import mock
@@ -13,9 +13,9 @@ from mitmproxy import certs
from mitmproxy.net import tcp
from mitmproxy import exceptions
from mitmproxy.test import tutils
+from ...conftest import skip_no_ipv6
from . import tservers
-from ...conftest import requires_alpn
class EchoHandler(tcp.BaseHandler):
@@ -96,7 +96,13 @@ class TestServerBind(tservers.ServerTestBase):
class handler(tcp.BaseHandler):
def handle(self):
- self.wfile.write(str(self.connection.getpeername()).encode())
+ # We may get an ipv4-mapped ipv6 address here, e.g. ::ffff:127.0.0.1.
+ # Those still appear as "127.0.0.1" in the table, so we need to strip the prefix.
+ peername = self.connection.getpeername()
+ address = re.sub("^::ffff:(?=\d+.\d+.\d+.\d+$)", "", peername[0])
+ port = peername[1]
+
+ self.wfile.write(str((address, port)).encode())
self.wfile.flush()
def test_bind(self):
@@ -114,6 +120,7 @@ class TestServerBind(tservers.ServerTestBase):
pass
+@skip_no_ipv6
class TestServerIPv6(tservers.ServerTestBase):
handler = EchoHandler
addr = ("::1", 0)
@@ -171,7 +178,7 @@ class TestServerSSL(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(sni="foo.com", options=SSL.OP_ALL)
+ c.convert_to_tls(sni="foo.com", options=SSL.OP_ALL)
testval = b"echo!\n"
c.wfile.write(testval)
c.wfile.flush()
@@ -181,7 +188,7 @@ class TestServerSSL(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
assert not c.get_current_cipher()
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
ret = c.get_current_cipher()
assert ret
assert "AES" in ret[0]
@@ -198,7 +205,7 @@ class TestSSLv3Only(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
class TestInvalidTrustFile(tservers.ServerTestBase):
@@ -206,9 +213,9 @@ class TestInvalidTrustFile(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl(
+ c.convert_to_tls(
sni="example.mitmproxy.org",
- verify_options=SSL.VERIFY_PEER,
+ verify=SSL.VERIFY_PEER,
ca_pemfile=tutils.test_data.path("mitmproxy/net/data/verificationcerts/generate.py")
)
@@ -224,7 +231,7 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase):
def test_mode_default_should_pass(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
# Verification errors should be saved even if connection isn't aborted
# aborted
@@ -238,7 +245,7 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase):
def test_mode_none_should_pass(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(verify_options=SSL.VERIFY_NONE)
+ c.convert_to_tls(verify=SSL.VERIFY_NONE)
# Verification errors should be saved even if connection isn't aborted
assert c.ssl_verification_error
@@ -252,9 +259,9 @@ class TestSSLUpstreamCertVerificationWBadServerCert(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.InvalidCertificateException):
- c.convert_to_ssl(
+ c.convert_to_tls(
sni="example.mitmproxy.org",
- verify_options=SSL.VERIFY_PEER,
+ verify=SSL.VERIFY_PEER,
ca_pemfile=tutils.test_data.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
)
@@ -277,18 +284,28 @@ class TestSSLUpstreamCertVerificationWBadHostname(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl(
- verify_options=SSL.VERIFY_PEER,
+ c.convert_to_tls(
+ verify=SSL.VERIFY_PEER,
ca_pemfile=tutils.test_data.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
)
+ def test_mode_none_should_pass_without_sni(self):
+ c = tcp.TCPClient(("127.0.0.1", self.port))
+ with c.connect():
+ c.convert_to_tls(
+ verify=SSL.VERIFY_NONE,
+ ca_path=tutils.test_data.path("mitmproxy/net/data/verificationcerts/")
+ )
+
+ assert "'no-hostname' doesn't match" in str(c.ssl_verification_error)
+
def test_should_fail(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.InvalidCertificateException):
- c.convert_to_ssl(
+ c.convert_to_tls(
sni="mitmproxy.org",
- verify_options=SSL.VERIFY_PEER,
+ verify=SSL.VERIFY_PEER,
ca_pemfile=tutils.test_data.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
)
assert c.ssl_verification_error
@@ -305,9 +322,9 @@ class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase):
def test_mode_strict_w_pemfile_should_pass(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(
+ c.convert_to_tls(
sni="example.mitmproxy.org",
- verify_options=SSL.VERIFY_PEER,
+ verify=SSL.VERIFY_PEER,
ca_pemfile=tutils.test_data.path("mitmproxy/net/data/verificationcerts/trusted-root.crt")
)
@@ -321,9 +338,9 @@ class TestSSLUpstreamCertVerificationWValidCertChain(tservers.ServerTestBase):
def test_mode_strict_w_cadir_should_pass(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(
+ c.convert_to_tls(
sni="example.mitmproxy.org",
- verify_options=SSL.VERIFY_PEER,
+ verify=SSL.VERIFY_PEER,
ca_path=tutils.test_data.path("mitmproxy/net/data/verificationcerts/")
)
@@ -355,7 +372,7 @@ class TestSSLClientCert(tservers.ServerTestBase):
def test_clientcert(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(
+ c.convert_to_tls(
cert=tutils.test_data.path("mitmproxy/net/data/clientcert/client.pem"))
assert c.rfile.readline().strip() == b"1"
@@ -363,7 +380,7 @@ class TestSSLClientCert(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl(cert=tutils.test_data.path("mitmproxy/net/data/clientcert/make"))
+ c.convert_to_tls(cert=tutils.test_data.path("mitmproxy/net/data/clientcert/make"))
class TestSNI(tservers.ServerTestBase):
@@ -383,15 +400,15 @@ class TestSNI(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
assert c.sni == "foo.com"
assert c.rfile.readline() == b"foo.com"
def test_idn(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(sni="mitmproxyäöüß.example.com")
- assert c.ssl_established
+ c.convert_to_tls(sni="mitmproxyäöüß.example.com")
+ assert c.tls_established
assert "doesn't match" not in str(c.ssl_verification_error)
@@ -404,7 +421,7 @@ class TestServerCipherList(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
expected = b"['AES256-GCM-SHA384']"
assert c.rfile.read(len(expected) + 2) == expected
@@ -425,7 +442,7 @@ class TestServerCurrentCipher(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
assert b'AES256-GCM-SHA384' in c.rfile.readline()
@@ -439,7 +456,7 @@ class TestServerCipherListError(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(Exception, match="handshake error"):
- c.convert_to_ssl(sni="foo.com")
+ c.convert_to_tls(sni="foo.com")
class TestClientCipherListError(tservers.ServerTestBase):
@@ -452,7 +469,7 @@ class TestClientCipherListError(tservers.ServerTestBase):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
with pytest.raises(Exception, match="cipher specification"):
- c.convert_to_ssl(sni="foo.com", cipher_list="bogus")
+ c.convert_to_tls(sni="foo.com", cipher_list="bogus")
class TestSSLDisconnect(tservers.ServerTestBase):
@@ -467,7 +484,7 @@ class TestSSLDisconnect(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
# Excercise SSL.ZeroReturnError
c.rfile.read(10)
c.close()
@@ -484,7 +501,7 @@ class TestSSLHardDisconnect(tservers.ServerTestBase):
def test_echo(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
# Exercise SSL.SysCallError
c.rfile.read(10)
c.close()
@@ -534,39 +551,21 @@ class TestTimeOut(tservers.ServerTestBase):
c.rfile.read(10)
-class TestCryptographyALPN:
-
- def test_has_alpn(self):
- if os.environ.get("OPENSSL") == "with-alpn":
- assert tcp.HAS_ALPN
- assert SSL._lib.Cryptography_HAS_ALPN
- elif os.environ.get("OPENSSL") == "old":
- assert not tcp.HAS_ALPN
- assert not SSL._lib.Cryptography_HAS_ALPN
-
-
class TestALPNClient(tservers.ServerTestBase):
handler = ALPNHandler
ssl = dict(
alpn_select=b"bar"
)
- @requires_alpn
- @pytest.mark.parametrize('has_alpn,alpn_protos, expected_negotiated, expected_response', [
- (True, [b"foo", b"bar", b"fasel"], b'bar', b'bar'),
- (True, [], b'', b'NONE'),
- (True, None, b'', b'NONE'),
- (False, [b"foo", b"bar", b"fasel"], b'', b'NONE'),
- (False, [], b'', b'NONE'),
- (False, None, b'', b'NONE'),
+ @pytest.mark.parametrize('alpn_protos, expected_negotiated, expected_response', [
+ ([b"foo", b"bar", b"fasel"], b'bar', b'bar'),
+ ([], b'', b'NONE'),
+ (None, b'', b'NONE'),
])
- def test_alpn(self, monkeypatch, has_alpn, alpn_protos, expected_negotiated, expected_response):
- monkeypatch.setattr(tcp, 'HAS_ALPN', has_alpn)
- monkeypatch.setattr(SSL._lib, 'Cryptography_HAS_ALPN', has_alpn)
-
+ def test_alpn(self, monkeypatch, alpn_protos, expected_negotiated, expected_response):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(alpn_protos=alpn_protos)
+ c.convert_to_tls(alpn_protos=alpn_protos)
assert c.get_alpn_proto_negotiated() == expected_negotiated
assert c.rfile.readline().strip() == expected_response
@@ -574,7 +573,7 @@ class TestALPNClient(tservers.ServerTestBase):
class TestNoSSLNoALPNClient(tservers.ServerTestBase):
handler = ALPNHandler
- def test_no_ssl_no_alpn(self, disable_alpn):
+ def test_no_ssl_no_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
assert c.get_alpn_proto_negotiated() == b""
@@ -588,7 +587,7 @@ class TestSSLTimeOut(tservers.ServerTestBase):
def test_timeout_client(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
c.settimeout(0.1)
with pytest.raises(exceptions.TcpTimeout):
c.rfile.read(10)
@@ -606,7 +605,7 @@ class TestDHParams(tservers.ServerTestBase):
def test_dhparams(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
ret = c.get_current_cipher()
assert ret[0] == "DHE-RSA-AES256-SHA"
@@ -794,10 +793,7 @@ class TestPeek(tservers.ServerTestBase):
c.close()
with pytest.raises(exceptions.NetlibException):
- if c.rfile.peek(1) == b"":
- # Workaround for Python 2 on Unix:
- # Peeking a closed connection does not raise an exception here.
- raise exceptions.NetlibException()
+ c.rfile.peek(1)
class TestPeekSSL(TestPeek):
@@ -805,61 +801,5 @@ class TestPeekSSL(TestPeek):
def _connect(self, c):
with c.connect() as conn:
- c.convert_to_ssl()
+ c.convert_to_tls()
return conn.pop()
-
-
-class TestSSLKeyLogger(tservers.ServerTestBase):
- handler = EchoHandler
- ssl = dict(
- cipher_list="AES256-SHA"
- )
-
- def test_log(self, tmpdir):
- testval = b"echo!\n"
- _logfun = tcp.log_ssl_key
-
- logfile = str(tmpdir.join("foo", "bar", "logfile"))
- tcp.log_ssl_key = tcp.SSLKeyLogger(logfile)
-
- c = tcp.TCPClient(("127.0.0.1", self.port))
- with c.connect():
- c.convert_to_ssl()
- c.wfile.write(testval)
- c.wfile.flush()
- assert c.rfile.readline() == testval
- c.finish()
-
- tcp.log_ssl_key.close()
- with open(logfile, "rb") as f:
- assert f.read().count(b"CLIENT_RANDOM") == 2
-
- tcp.log_ssl_key = _logfun
-
- def test_create_logfun(self):
- assert isinstance(
- tcp.SSLKeyLogger.create_logfun("test"),
- tcp.SSLKeyLogger)
- assert not tcp.SSLKeyLogger.create_logfun(False)
-
-
-class TestSSLInvalid(tservers.ServerTestBase):
- handler = EchoHandler
- ssl = True
-
- def test_invalid_ssl_method_should_fail(self):
- fake_ssl_method = 100500
- c = tcp.TCPClient(("127.0.0.1", self.port))
- with c.connect():
- with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl(method=fake_ssl_method)
-
- def test_alpn_error(self):
- c = tcp.TCPClient(("127.0.0.1", self.port))
- with c.connect():
- if tcp.HAS_ALPN:
- with pytest.raises(exceptions.TlsException, match="must be a function"):
- c.create_ssl_context(alpn_select_callback="foo")
-
- with pytest.raises(exceptions.TlsException, match="ALPN error"):
- c.create_ssl_context(alpn_select="foo", alpn_select_callback="bar")
diff --git a/test/mitmproxy/net/test_tls.py b/test/mitmproxy/net/test_tls.py
new file mode 100644
index 00000000..00782064
--- /dev/null
+++ b/test/mitmproxy/net/test_tls.py
@@ -0,0 +1,55 @@
+import pytest
+
+from mitmproxy import exceptions
+from mitmproxy.net import tls
+from mitmproxy.net.tcp import TCPClient
+from test.mitmproxy.net.test_tcp import EchoHandler
+from . import tservers
+
+
+class TestMasterSecretLogger(tservers.ServerTestBase):
+ handler = EchoHandler
+ ssl = dict(
+ cipher_list="AES256-SHA"
+ )
+
+ def test_log(self, tmpdir):
+ testval = b"echo!\n"
+ _logfun = tls.log_master_secret
+
+ logfile = str(tmpdir.join("foo", "bar", "logfile"))
+ tls.log_master_secret = tls.MasterSecretLogger(logfile)
+
+ c = TCPClient(("127.0.0.1", self.port))
+ with c.connect():
+ c.convert_to_tls()
+ c.wfile.write(testval)
+ c.wfile.flush()
+ assert c.rfile.readline() == testval
+ c.finish()
+
+ tls.log_master_secret.close()
+ with open(logfile, "rb") as f:
+ assert f.read().count(b"CLIENT_RANDOM") == 2
+
+ tls.log_master_secret = _logfun
+
+ def test_create_logfun(self):
+ assert isinstance(
+ tls.MasterSecretLogger.create_logfun("test"),
+ tls.MasterSecretLogger)
+ assert not tls.MasterSecretLogger.create_logfun(False)
+
+
+class TestTLSInvalid:
+ def test_invalid_ssl_method_should_fail(self):
+ fake_ssl_method = 100500
+ with pytest.raises(exceptions.TlsException):
+ tls.create_client_context(method=fake_ssl_method)
+
+ def test_alpn_error(self):
+ with pytest.raises(exceptions.TlsException, match="must be a function"):
+ tls.create_client_context(alpn_select_callback="foo")
+
+ with pytest.raises(exceptions.TlsException, match="ALPN error"):
+ tls.create_client_context(alpn_select="foo", alpn_select_callback="bar")
diff --git a/test/mitmproxy/net/tools/getcertnames b/test/mitmproxy/net/tools/getcertnames
index d64e5ff5..9349415f 100644
--- a/test/mitmproxy/net/tools/getcertnames
+++ b/test/mitmproxy/net/tools/getcertnames
@@ -7,7 +7,7 @@ from mitmproxy.net import tcp
def get_remote_cert(host, port, sni):
c = tcp.TCPClient((host, port))
c.connect()
- c.convert_to_ssl(sni=sni)
+ c.convert_to_tls(sni=sni)
return c.cert
if len(sys.argv) > 2:
diff --git a/test/mitmproxy/net/tservers.py b/test/mitmproxy/net/tservers.py
index 44701aa5..22e195e3 100644
--- a/test/mitmproxy/net/tservers.py
+++ b/test/mitmproxy/net/tservers.py
@@ -60,7 +60,7 @@ class _TServer(tcp.TCPServer):
else:
method = OpenSSL.SSL.SSLv23_METHOD
options = None
- h.convert_to_ssl(
+ h.convert_to_tls(
cert,
key,
method=method,
diff --git a/test/mitmproxy/platform/test_pf.py b/test/mitmproxy/platform/test_pf.py
index 3292d345..b048a697 100644
--- a/test/mitmproxy/platform/test_pf.py
+++ b/test/mitmproxy/platform/test_pf.py
@@ -15,6 +15,7 @@ class TestLookup:
d = f.read()
assert pf.lookup("192.168.1.111", 40000, d) == ("5.5.5.5", 80)
+ assert pf.lookup("::ffff:192.168.1.111", 40000, d) == ("5.5.5.5", 80)
with pytest.raises(Exception, match="Could not resolve original destination"):
pf.lookup("192.168.1.112", 40000, d)
with pytest.raises(Exception, match="Could not resolve original destination"):
diff --git a/test/mitmproxy/proxy/protocol/test_http1.py b/test/mitmproxy/proxy/protocol/test_http1.py
index b642afb3..4cca370c 100644
--- a/test/mitmproxy/proxy/protocol/test_http1.py
+++ b/test/mitmproxy/proxy/protocol/test_http1.py
@@ -1,3 +1,6 @@
+from unittest import mock
+import pytest
+
from mitmproxy.test import tflow
from mitmproxy.net.http import http1
from mitmproxy.net.tcp import TCPClient
@@ -77,3 +80,32 @@ class TestHeadContentLength(tservers.HTTPProxyTest):
"""head:'%s/p/200:h"Content-Length"="42"'""" % self.server.urlbase
)
assert resp.headers["Content-Length"] == "42"
+
+
+class TestStreaming(tservers.HTTPProxyTest):
+
+ @pytest.mark.parametrize('streaming', [True, False])
+ def test_streaming(self, streaming):
+
+ class Stream:
+ def requestheaders(self, f):
+ f.request.stream = streaming
+
+ def responseheaders(self, f):
+ f.response.stream = streaming
+
+ def assert_write(self, v):
+ if streaming:
+ assert len(v) <= 4096
+ return self.o.write(v)
+
+ self.master.addons.add(Stream())
+ p = self.pathoc()
+ with p.connect():
+ with mock.patch("mitmproxy.net.tcp.Writer.write", side_effect=assert_write, autospec=True):
+ # response with 10000 bytes
+ r = p.request("post:'%s/p/200:b@10000'" % self.server.urlbase)
+ assert len(r.content) == 10000
+
+ # request with 10000 bytes
+ assert p.request("post:'%s/p/200':b@10000" % self.server.urlbase)
diff --git a/test/mitmproxy/proxy/protocol/test_http2.py b/test/mitmproxy/proxy/protocol/test_http2.py
index 261f8415..194a57c9 100644
--- a/test/mitmproxy/proxy/protocol/test_http2.py
+++ b/test/mitmproxy/proxy/protocol/test_http2.py
@@ -8,15 +8,14 @@ import pytest
import h2
from mitmproxy import options
-from mitmproxy.proxy.config import ProxyConfig
import mitmproxy.net
from ...net import tservers as net_tservers
from mitmproxy import exceptions
from mitmproxy.net.http import http1, http2
+from pathod.language import generators
from ... import tservers
-from ....conftest import requires_alpn
import logging
logging.getLogger("hyper.packages.hpack.hpack").setLevel(logging.WARNING)
@@ -89,10 +88,8 @@ class _Http2TestBase:
@classmethod
def setup_class(cls):
- opts = cls.get_options()
- cls.config = ProxyConfig(opts)
-
- tmaster = tservers.TestMaster(opts, cls.config)
+ cls.options = cls.get_options()
+ tmaster = tservers.TestMaster(cls.options)
cls.proxy = tservers.ProxyThread(tmaster)
cls.proxy.start()
@@ -144,7 +141,7 @@ class _Http2TestBase:
while self.client.rfile.readline() != b"\r\n":
pass
- self.client.convert_to_ssl(alpn_protos=[b'h2'])
+ self.client.convert_to_tls(alpn_protos=[b'h2'])
config = h2.config.H2Configuration(
client_side=True,
@@ -166,7 +163,8 @@ class _Http2TestBase:
end_stream=None,
priority_exclusive=None,
priority_depends_on=None,
- priority_weight=None):
+ priority_weight=None,
+ streaming=False):
if headers is None:
headers = []
if end_stream is None:
@@ -182,7 +180,8 @@ class _Http2TestBase:
)
if body:
h2_conn.send_data(stream_id, body)
- h2_conn.end_stream(stream_id)
+ if not streaming:
+ h2_conn.end_stream(stream_id)
wfile.write(h2_conn.data_to_send())
wfile.flush()
@@ -200,7 +199,6 @@ class _Http2Test(_Http2TestBase, _Http2ServerBase):
_Http2ServerBase.teardown_class()
-@requires_alpn
class TestSimple(_Http2Test):
request_body_buffer = b''
@@ -283,7 +281,6 @@ class TestSimple(_Http2Test):
assert response_body_buffer == b'response body'
-@requires_alpn
class TestRequestWithPriority(_Http2Test):
@classmethod
@@ -319,7 +316,7 @@ class TestRequestWithPriority(_Http2Test):
(False, (None, None, None), (None, None, None)),
])
def test_request_with_priority(self, http2_priority_enabled, priority, expected_priority):
- self.config.options.http2_priority = http2_priority_enabled
+ self.options.http2_priority = http2_priority_enabled
h2_conn = self.setup_connection()
@@ -365,7 +362,6 @@ class TestRequestWithPriority(_Http2Test):
assert resp.headers.get('priority_weight', None) == expected_priority[2]
-@requires_alpn
class TestPriority(_Http2Test):
@classmethod
@@ -398,7 +394,7 @@ class TestPriority(_Http2Test):
(False, (True, 42424242, 42), []),
])
def test_priority(self, prioritize_before, http2_priority_enabled, priority, expected_priority):
- self.config.options.http2_priority = http2_priority_enabled
+ self.options.http2_priority = http2_priority_enabled
self.__class__.priority_data = []
h2_conn = self.setup_connection()
@@ -450,7 +446,6 @@ class TestPriority(_Http2Test):
assert self.priority_data == expected_priority
-@requires_alpn
class TestStreamResetFromServer(_Http2Test):
@classmethod
@@ -501,7 +496,6 @@ class TestStreamResetFromServer(_Http2Test):
assert self.master.state.flows[0].response is None
-@requires_alpn
class TestBodySizeLimit(_Http2Test):
@classmethod
@@ -511,8 +505,7 @@ class TestBodySizeLimit(_Http2Test):
return True
def test_body_size_limit(self):
- self.config.options.body_size_limit = "20"
- self.config.options._processed["body_size_limit"] = 20
+ self.options.body_size_limit = "20"
h2_conn = self.setup_connection()
@@ -551,7 +544,6 @@ class TestBodySizeLimit(_Http2Test):
assert len(self.master.state.flows) == 0
-@requires_alpn
class TestPushPromise(_Http2Test):
@classmethod
@@ -720,7 +712,6 @@ class TestPushPromise(_Http2Test):
# the other two bodies might not be transmitted before the reset
-@requires_alpn
class TestConnectionLost(_Http2Test):
@classmethod
@@ -762,7 +753,6 @@ class TestConnectionLost(_Http2Test):
assert self.master.state.flows[0].response is None
-@requires_alpn
class TestMaxConcurrentStreams(_Http2Test):
@classmethod
@@ -823,7 +813,6 @@ class TestMaxConcurrentStreams(_Http2Test):
assert b"Stream-ID " in flow.response.content
-@requires_alpn
class TestConnectionTerminated(_Http2Test):
@classmethod
@@ -862,3 +851,118 @@ class TestConnectionTerminated(_Http2Test):
assert connection_terminated_event.error_code == 5
assert connection_terminated_event.last_stream_id == 42
assert connection_terminated_event.additional_data == b'foobar'
+
+
+class TestRequestStreaming(_Http2Test):
+
+ @classmethod
+ def handle_server_event(cls, event, h2_conn, rfile, wfile):
+ if isinstance(event, h2.events.ConnectionTerminated):
+ return False
+ elif isinstance(event, h2.events.DataReceived):
+ data = event.data
+ assert data
+ h2_conn.close_connection(error_code=5, last_stream_id=42, additional_data=data)
+ wfile.write(h2_conn.data_to_send())
+ wfile.flush()
+
+ return True
+
+ @pytest.mark.parametrize('streaming', [True, False])
+ def test_request_streaming(self, streaming):
+ class Stream:
+ def requestheaders(self, f):
+ f.request.stream = streaming
+
+ self.master.addons.add(Stream())
+ h2_conn = self.setup_connection()
+ body = generators.RandomGenerator("bytes", 100)[:]
+ self._send_request(
+ self.client.wfile,
+ h2_conn,
+ headers=[
+ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])),
+ (':method', 'GET'),
+ (':scheme', 'https'),
+ (':path', '/'),
+
+ ],
+ body=body,
+ streaming=True
+ )
+ done = False
+ connection_terminated_event = None
+ self.client.rfile.o.settimeout(2)
+ while not done:
+ try:
+ raw = b''.join(http2.read_raw_frame(self.client.rfile))
+ events = h2_conn.receive_data(raw)
+
+ for event in events:
+ if isinstance(event, h2.events.ConnectionTerminated):
+ connection_terminated_event = event
+ done = True
+ except:
+ break
+
+ if streaming:
+ assert connection_terminated_event.additional_data == body
+ else:
+ assert connection_terminated_event is None
+
+
+class TestResponseStreaming(_Http2Test):
+
+ @classmethod
+ def handle_server_event(cls, event, h2_conn, rfile, wfile):
+ if isinstance(event, h2.events.ConnectionTerminated):
+ return False
+ elif isinstance(event, h2.events.RequestReceived):
+ data = generators.RandomGenerator("bytes", 100)[:]
+ h2_conn.send_headers(event.stream_id, [
+ (':status', '200'),
+ ('content-length', '100')
+ ])
+ h2_conn.send_data(event.stream_id, data)
+ wfile.write(h2_conn.data_to_send())
+ wfile.flush()
+ return True
+
+ @pytest.mark.parametrize('streaming', [True, False])
+ def test_response_streaming(self, streaming):
+ class Stream:
+ def responseheaders(self, f):
+ f.response.stream = streaming
+
+ self.master.addons.add(Stream())
+ h2_conn = self.setup_connection()
+ self._send_request(
+ self.client.wfile,
+ h2_conn,
+ headers=[
+ (':authority', "127.0.0.1:{}".format(self.server.server.address[1])),
+ (':method', 'GET'),
+ (':scheme', 'https'),
+ (':path', '/'),
+
+ ]
+ )
+ done = False
+ self.client.rfile.o.settimeout(2)
+ data = None
+ while not done:
+ try:
+ raw = b''.join(http2.read_raw_frame(self.client.rfile))
+ events = h2_conn.receive_data(raw)
+
+ for event in events:
+ if isinstance(event, h2.events.DataReceived):
+ data = event.data
+ done = True
+ except:
+ break
+
+ if streaming:
+ assert data
+ else:
+ assert data is None
diff --git a/test/mitmproxy/proxy/protocol/test_tls.py b/test/mitmproxy/proxy/protocol/test_tls.py
index 980ba7bd..e17ee46f 100644
--- a/test/mitmproxy/proxy/protocol/test_tls.py
+++ b/test/mitmproxy/proxy/protocol/test_tls.py
@@ -23,5 +23,4 @@ class TestClientHello:
)
c = TlsClientHello(data)
assert c.sni == 'example.com'
- assert c.alpn_protocols[0].name == b'h2'
- assert c.alpn_protocols[1].name == b'http/1.1'
+ assert c.alpn_protocols == [b'h2', b'http/1.1']
diff --git a/test/mitmproxy/proxy/protocol/test_websocket.py b/test/mitmproxy/proxy/protocol/test_websocket.py
index f78e173f..5cd9601c 100644
--- a/test/mitmproxy/proxy/protocol/test_websocket.py
+++ b/test/mitmproxy/proxy/protocol/test_websocket.py
@@ -1,5 +1,6 @@
import pytest
import os
+import struct
import tempfile
import traceback
@@ -7,7 +8,6 @@ from mitmproxy import options
from mitmproxy import exceptions
from mitmproxy.http import HTTPFlow
from mitmproxy.websocket import WebSocketFlow
-from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.net import tcp
from mitmproxy.net import http
@@ -34,6 +34,7 @@ class _WebSocketServerBase(net_tservers.ServerTestBase):
connection='upgrade',
upgrade='websocket',
sec_websocket_accept=b'',
+ sec_websocket_extensions='permessage-deflate' if "permessage-deflate" in request.headers.values() else ''
),
content=b'',
)
@@ -49,10 +50,8 @@ class _WebSocketTestBase:
@classmethod
def setup_class(cls):
- opts = cls.get_options()
- cls.config = ProxyConfig(opts)
-
- tmaster = tservers.TestMaster(opts, cls.config)
+ cls.options = cls.get_options()
+ tmaster = tservers.TestMaster(cls.options)
cls.proxy = tservers.ProxyThread(tmaster)
cls.proxy.start()
@@ -83,7 +82,7 @@ class _WebSocketTestBase:
if self.client:
self.client.close()
- def setup_connection(self):
+ def setup_connection(self, extension=False):
self.client = tcp.TCPClient(("127.0.0.1", self.proxy.port))
self.client.connect()
@@ -102,8 +101,8 @@ class _WebSocketTestBase:
response = http.http1.read_response(self.client.rfile, request)
if self.ssl:
- self.client.convert_to_ssl()
- assert self.client.ssl_established
+ self.client.convert_to_tls()
+ assert self.client.tls_established
request = http.Request(
"relative",
@@ -118,6 +117,7 @@ class _WebSocketTestBase:
upgrade="websocket",
sec_websocket_version="13",
sec_websocket_key="1234",
+ sec_websocket_extensions="permessage-deflate" if extension else ""
),
content=b'')
self.client.wfile.write(http.http1.assemble_request(request))
@@ -148,49 +148,97 @@ class TestSimple(_WebSocketTest):
wfile.flush()
frame = websockets.Frame.from_file(rfile)
- wfile.write(bytes(frame))
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=frame.header.opcode, payload=frame.payload)))
wfile.flush()
frame = websockets.Frame.from_file(rfile)
- wfile.write(bytes(frame))
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=frame.header.opcode, payload=frame.payload)))
wfile.flush()
- def test_simple(self):
+ @pytest.mark.parametrize('streaming', [True, False])
+ def test_simple(self, streaming):
+ class Stream:
+ def websocket_start(self, f):
+ f.stream = streaming
+
+ self.master.addons.add(Stream())
self.setup_connection()
frame = websockets.Frame.from_file(self.client.rfile)
assert frame.payload == b'server-foobar'
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'self.client-foobar')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.TEXT, payload=b'self.client-foobar')))
self.client.wfile.flush()
frame = websockets.Frame.from_file(self.client.rfile)
assert frame.payload == b'self.client-foobar'
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.BINARY, payload=b'\xde\xad\xbe\xef')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.BINARY, payload=b'\xde\xad\xbe\xef')))
self.client.wfile.flush()
frame = websockets.Frame.from_file(self.client.rfile)
assert frame.payload == b'\xde\xad\xbe\xef'
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE)))
self.client.wfile.flush()
assert len(self.master.state.flows) == 2
assert isinstance(self.master.state.flows[0], HTTPFlow)
assert isinstance(self.master.state.flows[1], WebSocketFlow)
assert len(self.master.state.flows[1].messages) == 5
- assert self.master.state.flows[1].messages[0].content == b'server-foobar'
+ assert self.master.state.flows[1].messages[0].content == 'server-foobar'
assert self.master.state.flows[1].messages[0].type == websockets.OPCODE.TEXT
- assert self.master.state.flows[1].messages[1].content == b'self.client-foobar'
+ assert self.master.state.flows[1].messages[1].content == 'self.client-foobar'
assert self.master.state.flows[1].messages[1].type == websockets.OPCODE.TEXT
- assert self.master.state.flows[1].messages[2].content == b'self.client-foobar'
+ assert self.master.state.flows[1].messages[2].content == 'self.client-foobar'
assert self.master.state.flows[1].messages[2].type == websockets.OPCODE.TEXT
assert self.master.state.flows[1].messages[3].content == b'\xde\xad\xbe\xef'
assert self.master.state.flows[1].messages[3].type == websockets.OPCODE.BINARY
assert self.master.state.flows[1].messages[4].content == b'\xde\xad\xbe\xef'
assert self.master.state.flows[1].messages[4].type == websockets.OPCODE.BINARY
+ def test_change_payload(self):
+ class Addon:
+ def websocket_message(self, f):
+ f.messages[-1].content = "foo"
+
+ self.master.addons.add(Addon())
+ self.setup_connection()
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.payload == b'foo'
+
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.TEXT, payload=b'self.client-foobar')))
+ self.client.wfile.flush()
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.payload == b'foo'
+
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.BINARY, payload=b'\xde\xad\xbe\xef')))
+ self.client.wfile.flush()
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.payload == b'foo'
+
+
+class TestKillFlow(_WebSocketTest):
+
+ @classmethod
+ def handle_websockets(cls, rfile, wfile):
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'server-foobar')))
+ wfile.flush()
+
+ def test_kill(self):
+ class KillFlow:
+ def websocket_message(self, f):
+ f.kill()
+
+ self.master.addons.add(KillFlow())
+ self.setup_connection()
+
+ with pytest.raises(exceptions.TcpDisconnect):
+ websockets.Frame.from_file(self.client.rfile)
+
class TestSimpleTLS(_WebSocketTest):
ssl = True
@@ -201,7 +249,7 @@ class TestSimpleTLS(_WebSocketTest):
wfile.flush()
frame = websockets.Frame.from_file(rfile)
- wfile.write(bytes(frame))
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=frame.header.opcode, payload=frame.payload)))
wfile.flush()
def test_simple_tls(self):
@@ -210,13 +258,13 @@ class TestSimpleTLS(_WebSocketTest):
frame = websockets.Frame.from_file(self.client.rfile)
assert frame.payload == b'server-foobar'
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'self.client-foobar')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.TEXT, payload=b'self.client-foobar')))
self.client.wfile.flush()
frame = websockets.Frame.from_file(self.client.rfile)
assert frame.payload == b'self.client-foobar'
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE)))
self.client.wfile.flush()
@@ -231,22 +279,24 @@ class TestPing(_WebSocketTest):
assert frame.header.opcode == websockets.OPCODE.PONG
assert frame.payload == b'foobar'
- wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.TEXT, payload=b'pong-received')))
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PONG, payload=b'done')))
wfile.flush()
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
+ wfile.flush()
+ websockets.Frame.from_file(rfile)
+
def test_ping(self):
self.setup_connection()
frame = websockets.Frame.from_file(self.client.rfile)
- assert frame.header.opcode == websockets.OPCODE.PING
- assert frame.payload == b'foobar'
-
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PONG, payload=frame.payload)))
+ websockets.Frame.from_file(self.client.rfile)
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE)))
self.client.wfile.flush()
+ assert frame.header.opcode == websockets.OPCODE.PING
+ assert frame.payload == b'' # We don't send payload to other end
- frame = websockets.Frame.from_file(self.client.rfile)
- assert frame.header.opcode == websockets.OPCODE.TEXT
- assert frame.payload == b'pong-received'
+ assert self.master.has_log("Pong Received from server", "info")
class TestPong(_WebSocketTest):
@@ -255,20 +305,29 @@ class TestPong(_WebSocketTest):
def handle_websockets(cls, rfile, wfile):
frame = websockets.Frame.from_file(rfile)
assert frame.header.opcode == websockets.OPCODE.PING
- assert frame.payload == b'foobar'
+ assert frame.payload == b''
wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PONG, payload=frame.payload)))
wfile.flush()
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
+ wfile.flush()
+ websockets.Frame.from_file(rfile)
+
def test_pong(self):
self.setup_connection()
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.PING, payload=b'foobar')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.PING, payload=b'foobar')))
self.client.wfile.flush()
frame = websockets.Frame.from_file(self.client.rfile)
+ websockets.Frame.from_file(self.client.rfile)
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE)))
+ self.client.wfile.flush()
+
assert frame.header.opcode == websockets.OPCODE.PONG
assert frame.payload == b'foobar'
+ assert self.master.has_log("Pong Received from server", "info")
class TestClose(_WebSocketTest):
@@ -276,7 +335,7 @@ class TestClose(_WebSocketTest):
@classmethod
def handle_websockets(cls, rfile, wfile):
frame = websockets.Frame.from_file(rfile)
- wfile.write(bytes(frame))
+ wfile.write(bytes(websockets.Frame(fin=1, opcode=frame.header.opcode, payload=frame.payload)))
wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
wfile.flush()
@@ -286,7 +345,7 @@ class TestClose(_WebSocketTest):
def test_close(self):
self.setup_connection()
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE)))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE)))
self.client.wfile.flush()
websockets.Frame.from_file(self.client.rfile)
@@ -296,7 +355,7 @@ class TestClose(_WebSocketTest):
def test_close_payload_1(self):
self.setup_connection()
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42')))
self.client.wfile.flush()
websockets.Frame.from_file(self.client.rfile)
@@ -306,7 +365,7 @@ class TestClose(_WebSocketTest):
def test_close_payload_2(self):
self.setup_connection()
- self.client.wfile.write(bytes(websockets.Frame(fin=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42foobar')))
+ self.client.wfile.write(bytes(websockets.Frame(fin=1, mask=1, opcode=websockets.OPCODE.CLOSE, payload=b'\00\42foobar')))
self.client.wfile.flush()
websockets.Frame.from_file(self.client.rfile)
@@ -326,5 +385,83 @@ class TestInvalidFrame(_WebSocketTest):
# with pytest.raises(exceptions.TcpDisconnect):
frame = websockets.Frame.from_file(self.client.rfile)
- assert frame.header.opcode == 15
- assert frame.payload == b'foobar'
+ code, = struct.unpack('!H', frame.payload[:2])
+ assert code == 1002
+ assert frame.payload[2:].startswith(b'Invalid opcode')
+
+
+class TestStreaming(_WebSocketTest):
+
+ @classmethod
+ def handle_websockets(cls, rfile, wfile):
+ wfile.write(bytes(websockets.Frame(opcode=websockets.OPCODE.TEXT, payload=b'server-foobar')))
+ wfile.flush()
+
+ @pytest.mark.parametrize('streaming', [True, False])
+ def test_streaming(self, streaming):
+ class Stream:
+ def websocket_start(self, f):
+ f.stream = streaming
+
+ self.master.addons.add(Stream())
+ self.setup_connection()
+
+ frame = None
+ if not streaming:
+ with pytest.raises(exceptions.TcpDisconnect): # Reader.safe_read get nothing as result
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame is None
+
+ else:
+ frame = websockets.Frame.from_file(self.client.rfile)
+
+ assert frame
+ assert self.master.state.flows[1].messages == [] # Message not appended as the final frame isn't received
+
+
+class TestExtension(_WebSocketTest):
+
+ @classmethod
+ def handle_websockets(cls, rfile, wfile):
+ wfile.write(b'\xc1\x0f*N-*K-\xd2M\xcb\xcfOJ,\x02\x00')
+ wfile.flush()
+
+ frame = websockets.Frame.from_file(rfile)
+ assert frame.header.rsv1
+ wfile.write(b'\xc1\nJ\xce\xc9L\xcd+\x81r\x00\x00')
+ wfile.flush()
+
+ frame = websockets.Frame.from_file(rfile)
+ assert frame.header.rsv1
+ wfile.write(b'\xc2\x07\xba\xb7v\xdf{\x00\x00')
+ wfile.flush()
+
+ def test_extension(self):
+ self.setup_connection(True)
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.header.rsv1
+
+ self.client.wfile.write(b'\xc1\x8fQ\xb7vX\x1by\xbf\x14\x9c\x9c\xa7\x15\x9ax9\x12}\xb5v')
+ self.client.wfile.flush()
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.header.rsv1
+
+ self.client.wfile.write(b'\xc2\x87\xeb\xbb\x0csQ\x0cz\xac\x90\xbb\x0c')
+ self.client.wfile.flush()
+
+ frame = websockets.Frame.from_file(self.client.rfile)
+ assert frame.header.rsv1
+
+ assert len(self.master.state.flows[1].messages) == 5
+ assert self.master.state.flows[1].messages[0].content == 'server-foobar'
+ assert self.master.state.flows[1].messages[0].type == websockets.OPCODE.TEXT
+ assert self.master.state.flows[1].messages[1].content == 'client-foobar'
+ assert self.master.state.flows[1].messages[1].type == websockets.OPCODE.TEXT
+ assert self.master.state.flows[1].messages[2].content == 'client-foobar'
+ assert self.master.state.flows[1].messages[2].type == websockets.OPCODE.TEXT
+ assert self.master.state.flows[1].messages[3].content == b'\xde\xad\xbe\xef'
+ assert self.master.state.flows[1].messages[3].type == websockets.OPCODE.BINARY
+ assert self.master.state.flows[1].messages[4].content == b'\xde\xad\xbe\xef'
+ assert self.master.state.flows[1].messages[4].type == websockets.OPCODE.BINARY
diff --git a/test/mitmproxy/proxy/test_config.py b/test/mitmproxy/proxy/test_config.py
index 777ab4dd..a7da980b 100644
--- a/test/mitmproxy/proxy/test_config.py
+++ b/test/mitmproxy/proxy/test_config.py
@@ -1 +1,38 @@
-# TODO: write tests
+import pytest
+
+from mitmproxy import options
+from mitmproxy import exceptions
+from mitmproxy.proxy.config import ProxyConfig
+from mitmproxy.test import tutils
+
+
+class TestProxyConfig:
+ def test_upstream_cert_insecure(self):
+ opts = options.Options()
+ opts.add_upstream_certs_to_client_chain = True
+ with pytest.raises(exceptions.OptionsError, match="verify-upstream-cert"):
+ ProxyConfig(opts)
+
+ def test_invalid_cadir(self):
+ opts = options.Options()
+ opts.cadir = "foo"
+ with pytest.raises(exceptions.OptionsError, match="parent directory does not exist"):
+ ProxyConfig(opts)
+
+ def test_invalid_client_certs(self):
+ opts = options.Options()
+ opts.client_certs = "foo"
+ with pytest.raises(exceptions.OptionsError, match="certificate path does not exist"):
+ ProxyConfig(opts)
+
+ def test_valid_client_certs(self):
+ opts = options.Options()
+ opts.client_certs = tutils.test_data.path("mitmproxy/data/clientcert/")
+ p = ProxyConfig(opts)
+ assert p.client_certs
+
+ def test_invalid_certificate(self):
+ opts = options.Options()
+ opts.certs = [tutils.test_data.path("mitmproxy/data/dumpfile-011")]
+ with pytest.raises(exceptions.OptionsError, match="Invalid certificate format"):
+ ProxyConfig(opts)
diff --git a/test/mitmproxy/proxy/test_server.py b/test/mitmproxy/proxy/test_server.py
index bd61f600..56b7b4c9 100644
--- a/test/mitmproxy/proxy/test_server.py
+++ b/test/mitmproxy/proxy/test_server.py
@@ -117,13 +117,12 @@ class TcpMixin:
def _ignore_on(self):
assert not hasattr(self, "_ignore_backup")
- self._ignore_backup = self.config.check_ignore
- self.config.check_ignore = HostMatcher(
- [".+:%s" % self.server.port] + self.config.check_ignore.patterns)
+ self._ignore_backup = self.options.ignore_hosts
+ self.options.ignore_hosts = [".+:%s" % self.server.port] + self.options.ignore_hosts
def _ignore_off(self):
assert hasattr(self, "_ignore_backup")
- self.config.check_ignore = self._ignore_backup
+ self.options.ignore_hosts = self._ignore_backup
del self._ignore_backup
def test_ignore(self):
@@ -144,9 +143,9 @@ class TcpMixin:
# Test that we get the original SSL cert
if self.ssl:
- i_cert = certs.SSLCert(i.sslinfo.certchain[0])
- i2_cert = certs.SSLCert(i2.sslinfo.certchain[0])
- n_cert = certs.SSLCert(n.sslinfo.certchain[0])
+ i_cert = certs.Cert(i.sslinfo.certchain[0])
+ i2_cert = certs.Cert(i2.sslinfo.certchain[0])
+ n_cert = certs.Cert(n.sslinfo.certchain[0])
assert i_cert == i2_cert
assert i_cert != n_cert
@@ -163,13 +162,12 @@ class TcpMixin:
def _tcpproxy_on(self):
assert not hasattr(self, "_tcpproxy_backup")
- self._tcpproxy_backup = self.config.check_tcp
- self.config.check_tcp = HostMatcher(
- [".+:%s" % self.server.port] + self.config.check_tcp.patterns)
+ self._tcpproxy_backup = self.options.tcp_hosts
+ self.options.tcp_hosts = [".+:%s" % self.server.port] + self.options.tcp_hosts
def _tcpproxy_off(self):
assert hasattr(self, "_tcpproxy_backup")
- self.config.check_tcp = self._tcpproxy_backup
+ self.options.tcp_hosts = self._tcpproxy_backup
del self._tcpproxy_backup
def test_tcp(self):
@@ -190,11 +188,12 @@ class TcpMixin:
# Test that we get the original SSL cert
if self.ssl:
- i_cert = certs.SSLCert(i.sslinfo.certchain[0])
- i2_cert = certs.SSLCert(i2.sslinfo.certchain[0])
- n_cert = certs.SSLCert(n.sslinfo.certchain[0])
+ i_cert = certs.Cert(i.sslinfo.certchain[0])
+ i2_cert = certs.Cert(i2.sslinfo.certchain[0])
+ n_cert = certs.Cert(n.sslinfo.certchain[0])
- assert i_cert == i2_cert == n_cert
+ assert i_cert == i2_cert
+ assert i_cert != n_cert
# Make sure that TCP messages are in the event log.
# Re-enable and fix this when we start keeping TCPFlows in the state.
@@ -239,13 +238,28 @@ class TestHTTP(tservers.HTTPProxyTest, CommonMixin):
p.request("get:'%s'" % response)
def test_reconnect(self):
- req = "get:'%s/p/200:b@1:da'" % self.server.urlbase
+ req = "get:'%s/p/200:b@1'" % self.server.urlbase
p = self.pathoc()
+
+ class MockOnce:
+ call = 0
+
+ def mock_once(self, http1obj, req):
+ self.call += 1
+ if self.call == 1:
+ raise exceptions.TcpDisconnect
+ else:
+ headers = http1.assemble_request_head(req)
+ http1obj.server_conn.wfile.write(headers)
+ http1obj.server_conn.wfile.flush()
+
with p.connect():
- assert p.request(req)
- # Server has disconnected. Mitmproxy should detect this, and reconnect.
- assert p.request(req)
- assert p.request(req)
+ with mock.patch("mitmproxy.proxy.protocol.http1.Http1Layer.send_request_headers",
+ side_effect=MockOnce().mock_once, autospec=True):
+ # Server disconnects while sending headers but mitmproxy reconnects
+ resp = p.request(req)
+ assert resp
+ assert resp.status_code == 200
def test_get_connection_switching(self):
req = "get:'%s/p/200:b@1'"
@@ -338,22 +352,22 @@ class TestHTTPS(tservers.HTTPProxyTest, CommonMixin, TcpMixin):
def test_clientcert_file(self):
try:
- self.config.client_certs = os.path.join(
+ self.options.client_certs = os.path.join(
tutils.test_data.path("mitmproxy/data/clientcert"), "client.pem")
f = self.pathod("304")
assert f.status_code == 304
assert self.server.last_log()["request"]["clientcert"]["keyinfo"]
finally:
- self.config.client_certs = None
+ self.options.client_certs = None
def test_clientcert_dir(self):
try:
- self.config.client_certs = tutils.test_data.path("mitmproxy/data/clientcert")
+ self.options.client_certs = tutils.test_data.path("mitmproxy/data/clientcert")
f = self.pathod("304")
assert f.status_code == 304
assert self.server.last_log()["request"]["clientcert"]["keyinfo"]
finally:
- self.config.client_certs = None
+ self.options.client_certs = None
def test_error_post_connect(self):
p = self.pathoc()
@@ -397,7 +411,7 @@ class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest):
return p.request("get:/p/242")
def test_verification_w_cadir(self):
- self.config.options.update(
+ self.options.update(
ssl_insecure=False,
ssl_verify_upstream_trusted_cadir=tutils.test_data.path(
"mitmproxy/data/servercert/"
@@ -407,7 +421,7 @@ class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest):
assert self._request().status_code == 242
def test_verification_w_pemfile(self):
- self.config.options.update(
+ self.options.update(
ssl_insecure=False,
ssl_verify_upstream_trusted_cadir=None,
ssl_verify_upstream_trusted_ca=tutils.test_data.path(
@@ -443,7 +457,7 @@ class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest):
return opts
def test_no_verification_w_bad_cert(self):
- self.config.options.ssl_insecure = True
+ self.options.ssl_insecure = True
r = self._request()
assert r.status_code == 242
@@ -451,10 +465,10 @@ class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest):
# We only test for a single invalid cert here.
# Actual testing of different root-causes (invalid hostname, expired, ...)
# is done in mitmproxy.net.
- self.config.options.ssl_insecure = False
+ self.options.ssl_insecure = False
r = self._request()
assert r.status_code == 502
- assert b"Certificate Verification Error" in r.raw_content
+ assert b"Certificate verification error" in r.raw_content
class TestHTTPSNoCommonName(tservers.HTTPProxyTest):
@@ -465,7 +479,7 @@ class TestHTTPSNoCommonName(tservers.HTTPProxyTest):
ssl = True
ssloptions = pathod.SSLOptions(
certs=[
- (b"*", tutils.test_data.path("mitmproxy/data/no_common_name.pem"))
+ ("*", tutils.test_data.path("mitmproxy/data/no_common_name.pem"))
]
)
@@ -478,7 +492,7 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin):
reverse = True
def test_host_header(self):
- self.config.options.keep_host_header = True
+ self.options.keep_host_header = True
p = self.pathoc()
with p.connect():
resp = p.request("get:/p/200:h'Host'='example.com'")
@@ -488,7 +502,7 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin):
assert req.host_header == "example.com"
def test_overridden_host_header(self):
- self.config.options.keep_host_header = False # default value
+ self.options.keep_host_header = False # default value
p = self.pathoc()
with p.connect():
resp = p.request("get:/p/200:h'Host'='example.com'")
@@ -565,7 +579,7 @@ class TestSocks5SSL(tservers.SocksModeTest):
p = self.pathoc_raw()
with p.connect():
p.socks_connect(("localhost", self.server.port))
- p.convert_to_ssl()
+ p.convert_to_tls()
f = p.request("get:/p/200")
assert f.status_code == 200
@@ -695,7 +709,7 @@ class TestProxy(tservers.HTTPProxyTest):
first_flow = self.master.state.flows[0]
second_flow = self.master.state.flows[1]
assert first_flow.server_conn.timestamp_tcp_setup
- assert first_flow.server_conn.timestamp_ssl_setup is None
+ assert first_flow.server_conn.timestamp_tls_setup is None
assert second_flow.server_conn.timestamp_tcp_setup
assert first_flow.server_conn.timestamp_tcp_setup == second_flow.server_conn.timestamp_tcp_setup
@@ -714,7 +728,7 @@ class TestProxySSL(tservers.HTTPProxyTest):
f = self.pathod("304:b@10k")
assert f.status_code == 304
first_flow = self.master.state.flows[0]
- assert first_flow.server_conn.timestamp_ssl_setup
+ assert first_flow.server_conn.timestamp_tls_setup
def test_via(self):
# tests that the ssl timestamp is present when ssl is used
@@ -1072,6 +1086,23 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
proxified to an upstream http proxy, we need to send the CONNECT
request again.
"""
+
+ class MockOnce:
+ call = 0
+
+ def mock_once(self, http1obj, req):
+ self.call += 1
+
+ if self.call == 2:
+ headers = http1.assemble_request_head(req)
+ http1obj.server_conn.wfile.write(headers)
+ http1obj.server_conn.wfile.flush()
+ raise exceptions.TcpDisconnect
+ else:
+ headers = http1.assemble_request_head(req)
+ http1obj.server_conn.wfile.write(headers)
+ http1obj.server_conn.wfile.flush()
+
self.chain[0].tmaster.addons.add(RequestKiller([1, 2]))
self.chain[1].tmaster.addons.add(RequestKiller([1]))
@@ -1086,7 +1117,10 @@ class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxyTest):
assert len(self.chain[0].tmaster.state.flows) == 1
assert len(self.chain[1].tmaster.state.flows) == 1
- req = p.request("get:'/p/418:b\"content2\"'")
+ with mock.patch("mitmproxy.proxy.protocol.http1.Http1Layer.send_request_headers",
+ side_effect=MockOnce().mock_once, autospec=True):
+ req = p.request("get:'/p/418:b\"content2\"'")
+
assert req.status_code == 502
assert len(self.proxy.tmaster.state.flows) == 2
@@ -1108,14 +1142,14 @@ class AddUpstreamCertsToClientChainMixin:
ssloptions = pathod.SSLOptions(
cn=b"example.mitmproxy.org",
certs=[
- (b"example.mitmproxy.org", servercert)
+ ("example.mitmproxy.org", servercert)
]
)
def test_add_upstream_certs_to_client_chain(self):
with open(self.servercert, "rb") as f:
d = f.read()
- upstreamCert = certs.SSLCert.from_pem(d)
+ upstreamCert = certs.Cert.from_pem(d)
p = self.pathoc()
with p.connect():
upstream_cert_found_in_client_chain = False
diff --git a/test/mitmproxy/test_addonmanager.py b/test/mitmproxy/test_addonmanager.py
index 678bc1b7..274d2d90 100644
--- a/test/mitmproxy/test_addonmanager.py
+++ b/test/mitmproxy/test_addonmanager.py
@@ -6,7 +6,6 @@ from mitmproxy import exceptions
from mitmproxy import options
from mitmproxy import command
from mitmproxy import master
-from mitmproxy import proxy
from mitmproxy.test import taddons
from mitmproxy.test import tflow
@@ -51,7 +50,7 @@ def test_command():
def test_halt():
o = options.Options()
- m = master.Master(o, proxy.DummyServer(o))
+ m = master.Master(o)
a = addonmanager.AddonManager(m)
halt = THalt()
end = TAddon("end")
@@ -68,7 +67,7 @@ def test_halt():
def test_lifecycle():
o = options.Options()
- m = master.Master(o, proxy.DummyServer(o))
+ m = master.Master(o)
a = addonmanager.AddonManager(m)
a.add(TAddon("one"))
@@ -91,7 +90,15 @@ def test_loader():
with taddons.context() as tctx:
l = addonmanager.Loader(tctx.master)
l.add_option("custom_option", bool, False, "help")
+ assert "custom_option" in l.master.options
+
+ # calling this again with the same signature is a no-op.
l.add_option("custom_option", bool, False, "help")
+ assert not tctx.master.has_log("Over-riding existing option")
+
+ # a different signature should emit a warning though.
+ l.add_option("custom_option", bool, True, "help")
+ assert tctx.master.has_log("Over-riding existing option")
def cmd(a: str) -> str:
return "foo"
@@ -115,7 +122,12 @@ def test_simple():
a.add(TAddon("one"))
a.trigger("done")
a.trigger("tick")
- tctx.master.has_log("not callable")
+ assert tctx.master.has_log("not callable")
+
+ tctx.master.clear()
+ a.get("one").tick = addons
+ a.trigger("tick")
+ assert not tctx.master.has_log("not callable")
a.remove(a.get("one"))
assert not a.get("one")
@@ -125,10 +137,12 @@ def test_simple():
a.trigger("custom")
assert ta.custom_called
+ assert ta in a
+
def test_load_option():
o = options.Options()
- m = master.Master(o, proxy.DummyServer(o))
+ m = master.Master(o)
a = addonmanager.AddonManager(m)
a.add(AOption())
assert "custom_option" in m.options._options
@@ -136,7 +150,7 @@ def test_load_option():
def test_nesting():
o = options.Options()
- m = master.Master(o, proxy.DummyServer(o))
+ m = master.Master(o)
a = addonmanager.AddonManager(m)
a.add(
diff --git a/test/mitmproxy/test_certs.py b/test/mitmproxy/test_certs.py
index 88c49561..dcc185c0 100644
--- a/test/mitmproxy/test_certs.py
+++ b/test/mitmproxy/test_certs.py
@@ -102,7 +102,7 @@ class TestCertStore:
dc = ca2.get_cert(b"foo.com", [b"sans.example.com"])
dcp = tmpdir.join("dc")
dcp.write(dc[0].to_pem())
- ca1.add_cert_file(b"foo.com", str(dcp))
+ ca1.add_cert_file("foo.com", str(dcp))
ret = ca1.get_cert(b"foo.com", [])
assert ret[0].serial == dc[0].serial
@@ -136,18 +136,18 @@ class TestDummyCert:
assert r.altnames == []
-class TestSSLCert:
+class TestCert:
def test_simple(self):
with open(tutils.test_data.path("mitmproxy/net/data/text_cert"), "rb") as f:
d = f.read()
- c1 = certs.SSLCert.from_pem(d)
+ c1 = certs.Cert.from_pem(d)
assert c1.cn == b"google.com"
assert len(c1.altnames) == 436
with open(tutils.test_data.path("mitmproxy/net/data/text_cert_2"), "rb") as f:
d = f.read()
- c2 = certs.SSLCert.from_pem(d)
+ c2 = certs.Cert.from_pem(d)
assert c2.cn == b"www.inode.co.nz"
assert len(c2.altnames) == 2
assert c2.digest("sha1")
@@ -165,20 +165,20 @@ class TestSSLCert:
def test_err_broken_sans(self):
with open(tutils.test_data.path("mitmproxy/net/data/text_cert_weird1"), "rb") as f:
d = f.read()
- c = certs.SSLCert.from_pem(d)
+ c = certs.Cert.from_pem(d)
# This breaks unless we ignore a decoding error.
assert c.altnames is not None
def test_der(self):
with open(tutils.test_data.path("mitmproxy/net/data/dercert"), "rb") as f:
d = f.read()
- s = certs.SSLCert.from_der(d)
+ s = certs.Cert.from_der(d)
assert s.cn
def test_state(self):
with open(tutils.test_data.path("mitmproxy/net/data/text_cert"), "rb") as f:
d = f.read()
- c = certs.SSLCert.from_pem(d)
+ c = certs.Cert.from_pem(d)
c.get_state()
c2 = c.copy()
@@ -188,6 +188,6 @@ class TestSSLCert:
assert c == c2
assert c is not c2
- x = certs.SSLCert('')
+ x = certs.Cert('')
x.set_state(a)
assert x == c
diff --git a/test/mitmproxy/test_command.py b/test/mitmproxy/test_command.py
index 958328b2..c777192d 100644
--- a/test/mitmproxy/test_command.py
+++ b/test/mitmproxy/test_command.py
@@ -4,27 +4,56 @@ from mitmproxy import flow
from mitmproxy import exceptions
from mitmproxy.test import tflow
from mitmproxy.test import taddons
+import mitmproxy.types
import io
import pytest
class TAddon:
+ @command.command("cmd1")
def cmd1(self, foo: str) -> str:
"""cmd1 help"""
return "ret " + foo
+ @command.command("cmd2")
def cmd2(self, foo: str) -> str:
return 99
+ @command.command("cmd3")
def cmd3(self, foo: int) -> int:
return foo
+ @command.command("cmd4")
+ def cmd4(self, a: int, b: str, c: mitmproxy.types.Path) -> str:
+ return "ok"
+
+ @command.command("subcommand")
+ def subcommand(self, cmd: mitmproxy.types.Cmd, *args: mitmproxy.types.Arg) -> str:
+ return "ok"
+
+ @command.command("empty")
def empty(self) -> None:
pass
- def varargs(self, one: str, *var: typing.Sequence[str]) -> typing.Sequence[str]:
+ @command.command("varargs")
+ def varargs(self, one: str, *var: str) -> typing.Sequence[str]:
return list(var)
+ def choices(self) -> typing.Sequence[str]:
+ return ["one", "two", "three"]
+
+ @command.argument("arg", type=mitmproxy.types.Choice("choices"))
+ def choose(self, arg: str) -> typing.Sequence[str]:
+ return ["one", "two", "three"]
+
+ @command.command("path")
+ def path(self, arg: mitmproxy.types.Path) -> None:
+ pass
+
+ @command.command("flow")
+ def flow(self, f: flow.Flow, s: str) -> None:
+ pass
+
class TestCommand:
def test_varargs(self):
@@ -52,6 +81,144 @@ class TestCommand:
c = command.Command(cm, "cmd.three", a.cmd3)
assert c.call(["1"]) == 1
+ def test_parse_partial(self):
+ tests = [
+ [
+ "foo bar",
+ [
+ command.ParseResult(
+ value = "foo", type = mitmproxy.types.Cmd, valid = False
+ ),
+ command.ParseResult(
+ value = "bar", type = mitmproxy.types.Unknown, valid = False
+ )
+ ],
+ [],
+ ],
+ [
+ "cmd1 'bar",
+ [
+ command.ParseResult(value = "cmd1", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "'bar", type = str, valid = True)
+ ],
+ [],
+ ],
+ [
+ "a",
+ [command.ParseResult(value = "a", type = mitmproxy.types.Cmd, valid = False)],
+ [],
+ ],
+ [
+ "",
+ [command.ParseResult(value = "", type = mitmproxy.types.Cmd, valid = False)],
+ []
+ ],
+ [
+ "cmd3 1",
+ [
+ command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "1", type = int, valid = True),
+ ],
+ []
+ ],
+ [
+ "cmd3 ",
+ [
+ command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "", type = int, valid = False),
+ ],
+ []
+ ],
+ [
+ "subcommand ",
+ [
+ command.ParseResult(
+ value = "subcommand", type = mitmproxy.types.Cmd, valid = True,
+ ),
+ command.ParseResult(value = "", type = mitmproxy.types.Cmd, valid = False),
+ ],
+ ["arg"],
+ ],
+ [
+ "subcommand cmd3 ",
+ [
+ command.ParseResult(value = "subcommand", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "cmd3", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "", type = int, valid = False),
+ ],
+ []
+ ],
+ [
+ "cmd4",
+ [
+ command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
+ ],
+ ["int", "str", "path"]
+ ],
+ [
+ "cmd4 ",
+ [
+ command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "", type = int, valid = False),
+ ],
+ ["str", "path"]
+ ],
+ [
+ "cmd4 1",
+ [
+ command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "1", type = int, valid = True),
+ ],
+ ["str", "path"]
+ ],
+ [
+ "cmd4 1",
+ [
+ command.ParseResult(value = "cmd4", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "1", type = int, valid = True),
+ ],
+ ["str", "path"]
+ ],
+ [
+ "flow",
+ [
+ command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ ],
+ ["flow", "str"]
+ ],
+ [
+ "flow ",
+ [
+ command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "", type = flow.Flow, valid = False),
+ ],
+ ["str"]
+ ],
+ [
+ "flow x",
+ [
+ command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "x", type = flow.Flow, valid = False),
+ ],
+ ["str"]
+ ],
+ [
+ "flow x ",
+ [
+ command.ParseResult(value = "flow", type = mitmproxy.types.Cmd, valid = True),
+ command.ParseResult(value = "x", type = flow.Flow, valid = False),
+ command.ParseResult(value = "", type = str, valid = True),
+ ],
+ []
+ ],
+ ]
+ with taddons.context() as tctx:
+ tctx.master.addons.add(TAddon())
+ for s, expected, expectedremain in tests:
+ current, remain = tctx.master.commands.parse_partial(s)
+ assert current == expected
+ assert expectedremain == remain
+
def test_simple():
with taddons.context() as tctx:
@@ -64,7 +231,7 @@ def test_simple():
c.call("nonexistent")
with pytest.raises(exceptions.CommandError, match="Invalid"):
c.call("")
- with pytest.raises(exceptions.CommandError, match="Usage"):
+ with pytest.raises(exceptions.CommandError, match="argument mismatch"):
c.call("one.two too many args")
c.add("empty", a.empty)
@@ -76,15 +243,18 @@ def test_simple():
def test_typename():
- assert command.typename(str, True) == "str"
- assert command.typename(typing.Sequence[flow.Flow], True) == "[flow]"
- assert command.typename(typing.Sequence[flow.Flow], False) == "flowspec"
+ assert command.typename(str) == "str"
+ assert command.typename(typing.Sequence[flow.Flow]) == "[flow]"
- assert command.typename(command.Cuts, False) == "cutspec"
- assert command.typename(command.Cuts, True) == "[cuts]"
+ assert command.typename(mitmproxy.types.Data) == "[data]"
+ assert command.typename(mitmproxy.types.CutSpec) == "[cut]"
- assert command.typename(flow.Flow, False) == "flow"
- assert command.typename(typing.Sequence[str], False) == "[str]"
+ assert command.typename(flow.Flow) == "flow"
+ assert command.typename(typing.Sequence[str]) == "[str]"
+
+ assert command.typename(mitmproxy.types.Choice("foo")) == "choice"
+ assert command.typename(mitmproxy.types.Path) == "path"
+ assert command.typename(mitmproxy.types.Cmd) == "cmd"
class DummyConsole:
@@ -94,7 +264,7 @@ class DummyConsole:
return [tflow.tflow(resp=True)] * n
@command.command("cut")
- def cut(self, spec: str) -> command.Cuts:
+ def cut(self, spec: str) -> mitmproxy.types.Data:
return [["test"]]
@@ -102,38 +272,11 @@ def test_parsearg():
with taddons.context() as tctx:
tctx.master.addons.add(DummyConsole())
assert command.parsearg(tctx.master.commands, "foo", str) == "foo"
-
- assert command.parsearg(tctx.master.commands, "1", int) == 1
+ with pytest.raises(exceptions.CommandError, match="Unsupported"):
+ command.parsearg(tctx.master.commands, "foo", type)
with pytest.raises(exceptions.CommandError):
command.parsearg(tctx.master.commands, "foo", int)
- assert command.parsearg(tctx.master.commands, "true", bool) is True
- assert command.parsearg(tctx.master.commands, "false", bool) is False
- with pytest.raises(exceptions.CommandError):
- command.parsearg(tctx.master.commands, "flobble", bool)
-
- assert len(command.parsearg(
- tctx.master.commands, "2", typing.Sequence[flow.Flow]
- )) == 2
- assert command.parsearg(tctx.master.commands, "1", flow.Flow)
- with pytest.raises(exceptions.CommandError):
- command.parsearg(tctx.master.commands, "2", flow.Flow)
- with pytest.raises(exceptions.CommandError):
- command.parsearg(tctx.master.commands, "0", flow.Flow)
- with pytest.raises(exceptions.CommandError):
- command.parsearg(tctx.master.commands, "foo", Exception)
-
- assert command.parsearg(
- tctx.master.commands, "foo", command.Cuts
- ) == [["test"]]
-
- assert command.parsearg(
- tctx.master.commands, "foo", typing.Sequence[str]
- ) == ["foo"]
- assert command.parsearg(
- tctx.master.commands, "foo, bar", typing.Sequence[str]
- ) == ["foo", "bar"]
-
class TDec:
@command.command("cmd1")
@@ -163,3 +306,10 @@ def test_decorator():
with taddons.context() as tctx:
tctx.master.addons.add(a)
assert tctx.master.commands.call("cmd1 bar") == "ret bar"
+
+
+def test_verify_arg_signature():
+ with pytest.raises(exceptions.CommandError):
+ command.verify_arg_signature(lambda: None, [1, 2], {})
+ print('hello there')
+ command.verify_arg_signature(lambda a, b: None, [1, 2], {})
diff --git a/test/mitmproxy/test_connections.py b/test/mitmproxy/test_connections.py
index 99367bb6..9e5d89f1 100644
--- a/test/mitmproxy/test_connections.py
+++ b/test/mitmproxy/test_connections.py
@@ -29,7 +29,7 @@ class TestClientConnection:
def test_repr(self):
c = tflow.tclient_conn()
- assert 'address:22' in repr(c)
+ assert '127.0.0.1:22' in repr(c)
assert 'ALPN' in repr(c)
assert 'TLS' not in repr(c)
@@ -41,10 +41,10 @@ class TestClientConnection:
def test_tls_established_property(self):
c = tflow.tclient_conn()
c.tls_established = True
- assert c.ssl_established
+ assert c.tls_established
assert c.tls_established
c.tls_established = False
- assert not c.ssl_established
+ assert not c.tls_established
assert not c.tls_established
def test_make_dummy(self):
@@ -113,10 +113,10 @@ class TestServerConnection:
def test_tls_established_property(self):
c = tflow.tserver_conn()
c.tls_established = True
- assert c.ssl_established
+ assert c.tls_established
assert c.tls_established
c.tls_established = False
- assert not c.ssl_established
+ assert not c.tls_established
assert not c.tls_established
def test_make_dummy(self):
@@ -155,7 +155,7 @@ class TestServerConnection:
def test_sni(self):
c = connections.ServerConnection(('', 1234))
with pytest.raises(ValueError, matches='sni must be str, not '):
- c.establish_ssl(None, b'foobar')
+ c.establish_tls(None, b'foobar')
def test_state(self):
c = tflow.tserver_conn()
@@ -206,7 +206,7 @@ class TestClientConnectionTLS:
key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM,
raw_key)
- c.convert_to_ssl(cert, key)
+ c.convert_to_tls(cert, key)
assert c.connected()
assert c.sni == sni
assert c.tls_established
@@ -230,7 +230,7 @@ class TestServerConnectionTLS(tservers.ServerTestBase):
def test_tls(self, clientcert):
c = connections.ServerConnection(("127.0.0.1", self.port))
c.connect()
- c.establish_ssl(clientcert, "foo.com")
+ c.establish_tls(clientcert, "foo.com")
assert c.connected()
assert c.sni == "foo.com"
assert c.tls_established
diff --git a/test/mitmproxy/test_controller.py b/test/mitmproxy/test_controller.py
index 2e13d298..e840380a 100644
--- a/test/mitmproxy/test_controller.py
+++ b/test/mitmproxy/test_controller.py
@@ -30,7 +30,8 @@ class TestMaster:
assert ctx.master.should_exit.is_set()
def test_server_simple(self):
- m = master.Master(None, proxy.DummyServer(None))
+ m = master.Master(None)
+ m.server = proxy.DummyServer()
m.start()
m.shutdown()
m.start()
diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py
index 9f6ed585..8cc11a16 100644
--- a/test/mitmproxy/test_flow.py
+++ b/test/mitmproxy/test_flow.py
@@ -1,16 +1,16 @@
import io
+from unittest import mock
import pytest
-from mitmproxy.test import tflow
+from mitmproxy.test import tflow, tutils
import mitmproxy.io
from mitmproxy import flowfilter
from mitmproxy import options
-from mitmproxy.proxy import config
from mitmproxy.io import tnetstring
from mitmproxy.exceptions import FlowReadException, ReplayException, ControlException
from mitmproxy import flow
from mitmproxy import http
-from mitmproxy.proxy.server import DummyServer
+from mitmproxy.net import http as net_http
from mitmproxy import master
from . import tservers
@@ -84,22 +84,47 @@ class TestSerialize:
with pytest.raises(Exception, match="version"):
list(r.stream())
+ def test_copy(self):
+ """
+ _backup may be shared across instances. That should not raise errors.
+ """
+ f = tflow.tflow()
+ f.backup()
+ f.request.path = "/foo"
+ f2 = f.copy()
+ f2.revert()
+ f.revert()
+
class TestFlowMaster:
- def test_load_flow_reverse(self):
+ def test_load_http_flow_reverse(self):
s = tservers.TestState()
opts = options.Options(
mode="reverse:https://use-this-domain"
)
- conf = config.ProxyConfig(opts)
- fm = master.Master(opts, DummyServer(conf))
+ fm = master.Master(opts)
fm.addons.add(s)
f = tflow.tflow(resp=True)
fm.load_flow(f)
assert s.flows[0].request.host == "use-this-domain"
+ def test_load_websocket_flow(self):
+ s = tservers.TestState()
+ opts = options.Options(
+ mode="reverse:https://use-this-domain"
+ )
+ fm = master.Master(opts)
+ fm.addons.add(s)
+ f = tflow.twebsocketflow()
+ fm.load_flow(f.handshake_flow)
+ fm.load_flow(f)
+ assert s.flows[0].request.host == "use-this-domain"
+ assert s.flows[1].handshake_flow == f.handshake_flow
+ assert len(s.flows[1].messages) == len(f.messages)
+
def test_replay(self):
- fm = master.Master(None, DummyServer())
+ opts = options.Options()
+ fm = master.Master(opts)
f = tflow.tflow(resp=True)
f.request.content = None
with pytest.raises(ReplayException, match="missing"):
@@ -117,9 +142,17 @@ class TestFlowMaster:
with pytest.raises(ReplayException, match="live"):
fm.replay_request(f)
+ req = tutils.treq(headers=net_http.Headers(((b":authority", b"foo"), (b"header", b"qvalue"), (b"content-length", b"7"))))
+ f = tflow.tflow(req=req)
+ f.request.http_version = "HTTP/2.0"
+ with mock.patch('mitmproxy.proxy.protocol.http_replay.RequestReplayThread.run'):
+ rt = fm.replay_request(f)
+ assert rt.f.request.http_version == "HTTP/1.1"
+ assert ":authority" not in rt.f.request.headers
+
def test_all(self):
s = tservers.TestState()
- fm = master.Master(None, DummyServer())
+ fm = master.Master(None)
fm.addons.add(s)
f = tflow.tflow(req=None)
fm.addons.handle_lifecycle("clientconnect", f.client_conn)
diff --git a/test/mitmproxy/test_flowfilter.py b/test/mitmproxy/test_flowfilter.py
index fe9b2408..4eb37d81 100644
--- a/test/mitmproxy/test_flowfilter.py
+++ b/test/mitmproxy/test_flowfilter.py
@@ -225,11 +225,11 @@ class TestMatchingHTTPFlow:
def test_src(self):
q = self.req()
- assert self.q("~src address", q)
+ assert self.q("~src 127.0.0.1", q)
assert not self.q("~src foobar", q)
assert self.q("~src :22", q)
assert not self.q("~src :99", q)
- assert self.q("~src address:22", q)
+ assert self.q("~src 127.0.0.1:22", q)
q.client_conn.address = None
assert not self.q('~src address:22', q)
@@ -315,11 +315,11 @@ class TestMatchingTCPFlow:
def test_src(self):
f = self.flow()
- assert self.q("~src address", f)
+ assert self.q("~src 127.0.0.1", f)
assert not self.q("~src foobar", f)
assert self.q("~src :22", f)
assert not self.q("~src :99", f)
- assert self.q("~src address:22", f)
+ assert self.q("~src 127.0.0.1:22", f)
def test_dst(self):
f = self.flow()
@@ -420,6 +420,20 @@ class TestMatchingWebSocketFlow:
e = self.err()
assert self.q("~e", e)
+ def test_domain(self):
+ q = self.flow()
+ assert self.q("~d example.com", q)
+ assert not self.q("~d none", q)
+
+ def test_url(self):
+ q = self.flow()
+ assert self.q("~u example.com", q)
+ assert self.q("~u example.com/ws", q)
+ assert not self.q("~u moo/path", q)
+
+ q.handshake_flow = None
+ assert not self.q("~u example.com", q)
+
def test_body(self):
f = self.flow()
@@ -440,11 +454,11 @@ class TestMatchingWebSocketFlow:
def test_src(self):
f = self.flow()
- assert self.q("~src address", f)
+ assert self.q("~src 127.0.0.1", f)
assert not self.q("~src foobar", f)
assert self.q("~src :22", f)
assert not self.q("~src :99", f)
- assert self.q("~src address:22", f)
+ assert self.q("~src 127.0.0.1:22", f)
def test_dst(self):
f = self.flow()
@@ -524,7 +538,7 @@ class TestMatchingDummyFlow:
assert not self.q("~s", f)
- assert self.q("~src address", f)
+ assert self.q("~src 127.0.0.1", f)
assert not self.q("~src nonexistent", f)
assert not self.q("~tcp", f)
diff --git a/test/mitmproxy/test_http.py b/test/mitmproxy/test_http.py
index 4463961a..49e61e25 100644
--- a/test/mitmproxy/test_http.py
+++ b/test/mitmproxy/test_http.py
@@ -203,6 +203,15 @@ class TestHTTPFlow:
f.resume()
assert f.reply.state == "committed"
+ def test_resume_duplicated(self):
+ f = tflow.tflow()
+ f.intercept()
+ f2 = f.copy()
+ assert f.intercepted is f2.intercepted is True
+ f.resume()
+ f2.resume()
+ assert f.intercepted is f2.intercepted is False
+
def test_replace_unicode(self):
f = tflow.tflow(resp=True)
f.response.content = b"\xc2foo"
diff --git a/test/mitmproxy/test_log.py b/test/mitmproxy/test_log.py
index cde679ed..349e3ac8 100644
--- a/test/mitmproxy/test_log.py
+++ b/test/mitmproxy/test_log.py
@@ -4,3 +4,8 @@ from mitmproxy import log
def test_logentry():
e = log.LogEntry("foo", "info")
assert repr(e) == "LogEntry(foo, info)"
+
+ f = log.LogEntry("foo", "warning")
+ assert e == e
+ assert e != f
+ assert e != 42
diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py
index 04ec7ded..d9b93227 100644
--- a/test/mitmproxy/test_optmanager.py
+++ b/test/mitmproxy/test_optmanager.py
@@ -73,6 +73,11 @@ def test_required_int():
o.parse_setval("required_int", None)
+def test_deepcopy():
+ o = TD()
+ copy.deepcopy(o)
+
+
def test_options():
o = TO()
assert o.keys() == {"bool", "one", "two", "required_int"}
@@ -229,6 +234,10 @@ def test_simple():
assert "one" in TO()
+def test_items():
+ assert TO().items()
+
+
def test_serialize():
o = TD2()
o.three = "set"
@@ -240,6 +249,7 @@ def test_serialize():
o2 = TD2()
optmanager.load(o2, data)
assert o2 == o
+ assert not o == 42
t = """
unknown: foo
@@ -257,6 +267,10 @@ def test_serialize():
with pytest.raises(Exception, match="Config error"):
optmanager.load(o2, t)
+ t = "# a comment"
+ optmanager.load(o2, t)
+ assert optmanager.load(o2, "foobar: '123'") == {"foobar": "123"}
+
t = ""
optmanager.load(o2, t)
assert optmanager.load(o2, "foobar: '123'") == {"foobar": "123"}
@@ -334,6 +348,12 @@ def test_dump_defaults():
assert optmanager.dump_defaults(o)
+def test_dump_dicts():
+ o = options.Options()
+ assert optmanager.dump_dicts(o)
+ assert optmanager.dump_dicts(o, ['http2', 'anticomp'])
+
+
class TTypes(optmanager.OptManager):
def __init__(self):
super().__init__()
diff --git a/test/mitmproxy/test_typemanager.py b/test/mitmproxy/test_typemanager.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/test/mitmproxy/test_typemanager.py
diff --git a/test/mitmproxy/test_types.py b/test/mitmproxy/test_types.py
new file mode 100644
index 00000000..72492fa9
--- /dev/null
+++ b/test/mitmproxy/test_types.py
@@ -0,0 +1,237 @@
+import pytest
+import os
+import typing
+import contextlib
+
+from mitmproxy.test import tutils
+import mitmproxy.exceptions
+import mitmproxy.types
+from mitmproxy.test import taddons
+from mitmproxy.test import tflow
+from mitmproxy import command
+from mitmproxy import flow
+
+from . import test_command
+
+
+@contextlib.contextmanager
+def chdir(path: str):
+ old_dir = os.getcwd()
+ os.chdir(path)
+ yield
+ os.chdir(old_dir)
+
+
+def test_bool():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._BoolType()
+ assert b.completion(tctx.master.commands, bool, "b") == ["false", "true"]
+ assert b.parse(tctx.master.commands, bool, "true") is True
+ assert b.parse(tctx.master.commands, bool, "false") is False
+ assert b.is_valid(tctx.master.commands, bool, True) is True
+ assert b.is_valid(tctx.master.commands, bool, "foo") is False
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, bool, "foo")
+
+
+def test_str():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._StrType()
+ assert b.is_valid(tctx.master.commands, str, "foo") is True
+ assert b.is_valid(tctx.master.commands, str, 1) is False
+ assert b.completion(tctx.master.commands, str, "") == []
+ assert b.parse(tctx.master.commands, str, "foo") == "foo"
+
+
+def test_unknown():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._UnknownType()
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Unknown, "foo") is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Unknown, 1) is False
+ assert b.completion(tctx.master.commands, mitmproxy.types.Unknown, "") == []
+ assert b.parse(tctx.master.commands, mitmproxy.types.Unknown, "foo") == "foo"
+
+
+def test_int():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._IntType()
+ assert b.is_valid(tctx.master.commands, int, "foo") is False
+ assert b.is_valid(tctx.master.commands, int, 1) is True
+ assert b.completion(tctx.master.commands, int, "b") == []
+ assert b.parse(tctx.master.commands, int, "1") == 1
+ assert b.parse(tctx.master.commands, int, "999") == 999
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, int, "foo")
+
+
+def test_path():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._PathType()
+ assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/foo") == "/foo"
+ assert b.parse(tctx.master.commands, mitmproxy.types.Path, "/bar") == "/bar"
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, "foo") is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Path, 3) is False
+
+ def normPathOpts(prefix, match):
+ ret = []
+ for s in b.completion(tctx.master.commands, mitmproxy.types.Path, match):
+ s = s[len(prefix):]
+ s = s.replace(os.sep, "/")
+ ret.append(s)
+ return ret
+
+ cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
+ assert normPathOpts(cd, cd) == ['/aaa', '/aab', '/aac', '/bbb/']
+ assert normPathOpts(cd, os.path.join(cd, "a")) == ['/aaa', '/aab', '/aac']
+ with chdir(cd):
+ assert normPathOpts("", "./") == ['./aaa', './aab', './aac', './bbb/']
+ assert normPathOpts("", "") == ['./aaa', './aab', './aac', './bbb/']
+ assert b.completion(
+ tctx.master.commands, mitmproxy.types.Path, "nonexistent"
+ ) == ["nonexistent"]
+
+
+def test_cmd():
+ with taddons.context() as tctx:
+ tctx.master.addons.add(test_command.TAddon())
+ b = mitmproxy.types._CmdType()
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "foo") is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Cmd, "cmd1") is True
+ assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "cmd1") == "cmd1"
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ assert b.parse(tctx.master.commands, mitmproxy.types.Cmd, "foo")
+ assert len(
+ b.completion(tctx.master.commands, mitmproxy.types.Cmd, "")
+ ) == len(tctx.master.commands.commands.keys())
+
+
+def test_cutspec():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._CutSpecType()
+ b.parse(tctx.master.commands, mitmproxy.types.CutSpec, "foo,bar") == ["foo", "bar"]
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, 1) is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "foo") is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.CutSpec, "request.path") is True
+
+ assert b.completion(
+ tctx.master.commands, mitmproxy.types.CutSpec, "request.p"
+ ) == b.valid_prefixes
+ ret = b.completion(tctx.master.commands, mitmproxy.types.CutSpec, "request.port,f")
+ assert ret[0].startswith("request.port,")
+ assert len(ret) == len(b.valid_prefixes)
+
+
+def test_arg():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._ArgType()
+ assert b.completion(tctx.master.commands, mitmproxy.types.Arg, "") == []
+ assert b.parse(tctx.master.commands, mitmproxy.types.Arg, "foo") == "foo"
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, "foo") is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Arg, 1) is False
+
+
+def test_strseq():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._StrSeqType()
+ assert b.completion(tctx.master.commands, typing.Sequence[str], "") == []
+ assert b.parse(tctx.master.commands, typing.Sequence[str], "foo") == ["foo"]
+ assert b.parse(tctx.master.commands, typing.Sequence[str], "foo,bar") == ["foo", "bar"]
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], ["foo"]) is True
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], ["a", "b", 3]) is False
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], 1) is False
+ assert b.is_valid(tctx.master.commands, typing.Sequence[str], "foo") is False
+
+
+class DummyConsole:
+ @command.command("view.resolve")
+ def resolve(self, spec: str) -> typing.Sequence[flow.Flow]:
+ if spec == "err":
+ raise mitmproxy.exceptions.CommandError()
+ n = int(spec)
+ return [tflow.tflow(resp=True)] * n
+
+ @command.command("cut")
+ def cut(self, spec: str) -> mitmproxy.types.Data:
+ return [["test"]]
+
+ @command.command("options")
+ def options(self) -> typing.Sequence[str]:
+ return ["one", "two", "three"]
+
+
+def test_flow():
+ with taddons.context() as tctx:
+ tctx.master.addons.add(DummyConsole())
+ b = mitmproxy.types._FlowType()
+ assert len(b.completion(tctx.master.commands, flow.Flow, "")) == len(b.valid_prefixes)
+ assert b.parse(tctx.master.commands, flow.Flow, "1")
+ assert b.is_valid(tctx.master.commands, flow.Flow, tflow.tflow()) is True
+ assert b.is_valid(tctx.master.commands, flow.Flow, "xx") is False
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, flow.Flow, "0")
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, flow.Flow, "2")
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, flow.Flow, "err")
+
+
+def test_flows():
+ with taddons.context() as tctx:
+ tctx.master.addons.add(DummyConsole())
+ b = mitmproxy.types._FlowsType()
+ assert len(
+ b.completion(tctx.master.commands, typing.Sequence[flow.Flow], "")
+ ) == len(b.valid_prefixes)
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], [tflow.tflow()]) is True
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], "xx") is False
+ assert b.is_valid(tctx.master.commands, typing.Sequence[flow.Flow], 0) is False
+ assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "0")) == 0
+ assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "1")) == 1
+ assert len(b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "2")) == 2
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, typing.Sequence[flow.Flow], "err")
+
+
+def test_data():
+ with taddons.context() as tctx:
+ b = mitmproxy.types._DataType()
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, 0) is False
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, []) is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [["x"]]) is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [[b"x"]]) is True
+ assert b.is_valid(tctx.master.commands, mitmproxy.types.Data, [[1]]) is False
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, mitmproxy.types.Data, "foo")
+
+
+def test_choice():
+ with taddons.context() as tctx:
+ tctx.master.addons.add(DummyConsole())
+ b = mitmproxy.types._ChoiceType()
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("options"),
+ "one",
+ ) is True
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("options"),
+ "invalid",
+ ) is False
+ assert b.is_valid(
+ tctx.master.commands,
+ mitmproxy.types.Choice("nonexistent"),
+ "invalid",
+ ) is False
+ comp = b.completion(tctx.master.commands, mitmproxy.types.Choice("options"), "")
+ assert comp == ["one", "two", "three"]
+ assert b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "one") == "one"
+ with pytest.raises(mitmproxy.exceptions.TypeError):
+ b.parse(tctx.master.commands, mitmproxy.types.Choice("options"), "invalid")
+
+
+def test_typemanager():
+ assert mitmproxy.types.CommandTypes.get(bool, None)
+ assert mitmproxy.types.CommandTypes.get(mitmproxy.types.Choice("choide"), None)
diff --git a/test/mitmproxy/test_version.py b/test/mitmproxy/test_version.py
index f87b0851..8c176542 100644
--- a/test/mitmproxy/test_version.py
+++ b/test/mitmproxy/test_version.py
@@ -1,10 +1,36 @@
+import pathlib
import runpy
+import subprocess
+from unittest import mock
from mitmproxy import version
def test_version(capsys):
- runpy.run_module('mitmproxy.version', run_name='__main__')
+ here = pathlib.Path(__file__).absolute().parent
+ version_file = here / ".." / ".." / "mitmproxy" / "version.py"
+ runpy.run_path(str(version_file), run_name='__main__')
stdout, stderr = capsys.readouterr()
assert len(stdout) > 0
assert stdout.strip() == version.VERSION
+
+
+def test_get_version_hardcoded():
+ version.VERSION = "3.0.0.dev123-0xcafebabe"
+ assert version.get_version() == "3.0.0"
+ assert version.get_version(True) == "3.0.0.dev123"
+ assert version.get_version(True, True) == "3.0.0.dev123-0xcafebabe"
+
+
+def test_get_version():
+ version.VERSION = "3.0.0"
+
+ with mock.patch('subprocess.check_output') as m:
+ m.return_value = b"tag-0-cafecafe"
+ assert version.get_version(True, True) == "3.0.0"
+
+ m.return_value = b"tag-2-cafecafe"
+ assert version.get_version(True, True) == "3.0.0.dev2-0xcafecaf"
+
+ m.side_effect = subprocess.CalledProcessError(-1, 'git describe --long')
+ assert version.get_version(True, True) == "3.0.0"
diff --git a/test/mitmproxy/test_websocket.py b/test/mitmproxy/test_websocket.py
index 7c53a4b0..fcacec36 100644
--- a/test/mitmproxy/test_websocket.py
+++ b/test/mitmproxy/test_websocket.py
@@ -3,6 +3,7 @@ import pytest
from mitmproxy.io import tnetstring
from mitmproxy import flowfilter
+from mitmproxy.exceptions import Kill, ControlException
from mitmproxy.test import tflow
@@ -42,6 +43,20 @@ class TestWebSocketFlow:
assert f.error.get_state() == f2.error.get_state()
assert f.error is not f2.error
+ def test_kill(self):
+ f = tflow.twebsocketflow()
+ with pytest.raises(ControlException):
+ f.intercept()
+ f.resume()
+ f.kill()
+
+ f = tflow.twebsocketflow()
+ f.intercept()
+ assert f.killable
+ f.kill()
+ assert not f.killable
+ assert f.reply.value == Kill
+
def test_match(self):
f = tflow.twebsocketflow()
assert not flowfilter.match("~b nonexistent", f)
@@ -71,3 +86,9 @@ class TestWebSocketFlow:
d = tflow.twebsocketflow().handshake_flow.get_state()
tnetstring.dump(d, b)
assert b.getvalue()
+
+ def test_message_kill(self):
+ f = tflow.twebsocketflow()
+ assert not f.messages[-1].killed
+ f.messages[-1].kill()
+ assert f.messages[-1].killed
diff --git a/test/mitmproxy/tools/console/test_commander.py b/test/mitmproxy/tools/console/test_commander.py
new file mode 100644
index 00000000..2a96995d
--- /dev/null
+++ b/test/mitmproxy/tools/console/test_commander.py
@@ -0,0 +1,98 @@
+
+from mitmproxy.tools.console.commander import commander
+from mitmproxy.test import taddons
+
+
+class TestListCompleter:
+ def test_cycle(self):
+ tests = [
+ [
+ "",
+ ["a", "b", "c"],
+ ["a", "b", "c", "a"]
+ ],
+ [
+ "xxx",
+ ["a", "b", "c"],
+ ["xxx", "xxx", "xxx"]
+ ],
+ [
+ "b",
+ ["a", "b", "ba", "bb", "c"],
+ ["b", "ba", "bb", "b"]
+ ],
+ ]
+ for start, options, cycle in tests:
+ c = commander.ListCompleter(start, options)
+ for expected in cycle:
+ assert c.cycle() == expected
+
+
+class TestCommandBuffer:
+
+ def test_backspace(self):
+ tests = [
+ [("", 0), ("", 0)],
+ [("1", 0), ("1", 0)],
+ [("1", 1), ("", 0)],
+ [("123", 3), ("12", 2)],
+ [("123", 2), ("13", 1)],
+ [("123", 0), ("123", 0)],
+ ]
+ with taddons.context() as tctx:
+ for start, output in tests:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text, cb.cursor = start[0], start[1]
+ cb.backspace()
+ assert cb.text == output[0]
+ assert cb.cursor == output[1]
+
+ def test_left(self):
+ cursors = [3, 2, 1, 0, 0]
+ with taddons.context() as tctx:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text, cb.cursor = "abcd", 4
+ for c in cursors:
+ cb.left()
+ assert cb.cursor == c
+
+ def test_right(self):
+ cursors = [1, 2, 3, 4, 4]
+ with taddons.context() as tctx:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text, cb.cursor = "abcd", 0
+ for c in cursors:
+ cb.right()
+ assert cb.cursor == c
+
+ def test_insert(self):
+ tests = [
+ [("", 0), ("x", 1)],
+ [("a", 0), ("xa", 1)],
+ [("xa", 2), ("xax", 3)],
+ ]
+ with taddons.context() as tctx:
+ for start, output in tests:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text, cb.cursor = start[0], start[1]
+ cb.insert("x")
+ assert cb.text == output[0]
+ assert cb.cursor == output[1]
+
+ def test_cycle_completion(self):
+ with taddons.context() as tctx:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text = "foo bar"
+ cb.cursor = len(cb.text)
+ cb.cycle_completion()
+
+ def test_render(self):
+ with taddons.context() as tctx:
+ cb = commander.CommandBuffer(tctx.master)
+ cb.text = "foo"
+ assert cb.render()
+
+ def test_flatten(self):
+ with taddons.context() as tctx:
+ cb = commander.CommandBuffer(tctx.master)
+ assert cb.flatten("foo bar") == "foo bar"
diff --git a/test/mitmproxy/tools/console/test_common.py b/test/mitmproxy/tools/console/test_common.py
index 3ab4fd67..72438c49 100644
--- a/test/mitmproxy/tools/console/test_common.py
+++ b/test/mitmproxy/tools/console/test_common.py
@@ -1,12 +1,34 @@
+import urwid
+
from mitmproxy.test import tflow
from mitmproxy.tools.console import common
-from ....conftest import skip_appveyor
-
-@skip_appveyor
def test_format_flow():
f = tflow.tflow(resp=True)
assert common.format_flow(f, True)
assert common.format_flow(f, True, hostheader=True)
assert common.format_flow(f, True, extended=True)
+
+
+def test_format_keyvals():
+ assert common.format_keyvals(
+ [
+ ("aa", "bb"),
+ ("cc", "dd"),
+ ("ee", None),
+ ]
+ )
+ wrapped = urwid.BoxAdapter(
+ urwid.ListBox(
+ urwid.SimpleFocusListWalker(
+ common.format_keyvals([("foo", "bar")])
+ )
+ ), 1
+ )
+ assert wrapped.render((30, ))
+ assert common.format_keyvals(
+ [
+ ("aa", wrapped)
+ ]
+ )
diff --git a/test/mitmproxy/tools/console/test_defaultkeys.py b/test/mitmproxy/tools/console/test_defaultkeys.py
new file mode 100644
index 00000000..1f17c888
--- /dev/null
+++ b/test/mitmproxy/tools/console/test_defaultkeys.py
@@ -0,0 +1,23 @@
+from mitmproxy.test.tflow import tflow
+from mitmproxy.tools.console import defaultkeys
+from mitmproxy.tools.console import keymap
+from mitmproxy.tools.console import master
+from mitmproxy import command
+
+
+def test_commands_exist():
+ km = keymap.Keymap(None)
+ defaultkeys.map(km)
+ assert km.bindings
+ m = master.ConsoleMaster(None)
+ m.load_flow(tflow())
+
+ for binding in km.bindings:
+ cmd, *args = command.lexer(binding.command)
+ assert cmd in m.commands.commands
+
+ cmd_obj = m.commands.commands[cmd]
+ try:
+ cmd_obj.prepare_args(args)
+ except Exception as e:
+ raise ValueError("Invalid command: {}".format(binding.command)) from e
diff --git a/test/mitmproxy/tools/console/test_help.py b/test/mitmproxy/tools/console/test_help.py
deleted file mode 100644
index 0ebc2d6a..00000000
--- a/test/mitmproxy/tools/console/test_help.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import mitmproxy.tools.console.help as help
-
-from ....conftest import skip_appveyor
-
-
-@skip_appveyor
-class TestHelp:
-
- def test_helptext(self):
- h = help.HelpView(None)
- assert h.helptext()
diff --git a/test/mitmproxy/tools/console/test_keymap.py b/test/mitmproxy/tools/console/test_keymap.py
index 6a75800e..00e64991 100644
--- a/test/mitmproxy/tools/console/test_keymap.py
+++ b/test/mitmproxy/tools/console/test_keymap.py
@@ -4,26 +4,69 @@ from unittest import mock
import pytest
+def test_binding():
+ b = keymap.Binding("space", "cmd", ["options"], "")
+ assert b.keyspec() == " "
+
+
def test_bind():
- with taddons.context() as tctx:
- km = keymap.Keymap(tctx.master)
- km.executor = mock.Mock()
+ with taddons.context() as tctx:
+ km = keymap.Keymap(tctx.master)
+ km.executor = mock.Mock()
+
+ with pytest.raises(ValueError):
+ km.add("foo", "bar", ["unsupported"])
+
+ km.add("key", "str", ["options", "commands"])
+ assert km.get("options", "key")
+ assert km.get("commands", "key")
+ assert not km.get("flowlist", "key")
+ assert len((km.list("commands"))) == 1
+
+ km.handle("unknown", "unknown")
+ assert not km.executor.called
+
+ km.handle("options", "key")
+ assert km.executor.called
+
+ km.add("glob", "str", ["global"])
+ km.executor = mock.Mock()
+ km.handle("options", "glob")
+ assert km.executor.called
+
+ assert len((km.list("global"))) == 1
+
+
+def test_join():
+ with taddons.context() as tctx:
+ km = keymap.Keymap(tctx.master)
+ km.add("key", "str", ["options"], "help1")
+ km.add("key", "str", ["commands"])
+ return
+ assert len(km.bindings) == 1
+ assert len(km.bindings[0].contexts) == 2
+ assert km.bindings[0].help == "help1"
+ km.add("key", "str", ["commands"], "help2")
+ assert len(km.bindings) == 1
+ assert len(km.bindings[0].contexts) == 2
+ assert km.bindings[0].help == "help2"
- with pytest.raises(ValueError):
- km.add("foo", "bar", ["unsupported"])
+ assert km.get("commands", "key")
+ km.unbind(km.bindings[0])
+ assert len(km.bindings) == 0
+ assert not km.get("commands", "key")
- km.add("key", "str", ["options", "commands"])
- assert km.get("options", "key")
- assert km.get("commands", "key")
- assert not km.get("flowlist", "key")
- km.handle("unknown", "unknown")
- assert not km.executor.called
+def test_remove():
+ with taddons.context() as tctx:
+ km = keymap.Keymap(tctx.master)
+ km.add("key", "str", ["options", "commands"], "help1")
+ assert len(km.bindings) == 1
+ assert "options" in km.bindings[0].contexts
- km.handle("options", "key")
- assert km.executor.called
+ km.remove("key", ["options"])
+ assert len(km.bindings) == 1
+ assert "options" not in km.bindings[0].contexts
- km.add("glob", "str", ["global"])
- km.executor = mock.Mock()
- km.handle("options", "glob")
- assert km.executor.called
+ km.remove("key", ["commands"])
+ assert len(km.bindings) == 0
diff --git a/test/mitmproxy/tools/console/test_master.py b/test/mitmproxy/tools/console/test_master.py
index c87c9e83..9779a482 100644
--- a/test/mitmproxy/tools/console/test_master.py
+++ b/test/mitmproxy/tools/console/test_master.py
@@ -1,23 +1,10 @@
+import urwid
+
+from mitmproxy import options
from mitmproxy.test import tflow
from mitmproxy.test import tutils
from mitmproxy.tools import console
-from mitmproxy import proxy
-from mitmproxy import options
-from mitmproxy.tools.console import common
from ... import tservers
-import urwid
-
-
-def test_format_keyvals():
- assert common.format_keyvals(
- [
- ("aa", "bb"),
- None,
- ("cc", "dd"),
- (None, "dd"),
- (None, "dd"),
- ]
- )
def test_options():
@@ -27,9 +14,9 @@ def test_options():
class TestMaster(tservers.MasterTest):
def mkmaster(self, **opts):
if "verbosity" not in opts:
- opts["verbosity"] = 1
+ opts["verbosity"] = 'warn'
o = options.Options(**opts)
- m = console.master.ConsoleMaster(o, proxy.DummyServer())
+ m = console.master.ConsoleMaster(o)
m.addons.trigger("configure", o.keys())
return m
diff --git a/test/mitmproxy/tools/console/test_pathedit.py b/test/mitmproxy/tools/console/test_pathedit.py
deleted file mode 100644
index b9f51f5a..00000000
--- a/test/mitmproxy/tools/console/test_pathedit.py
+++ /dev/null
@@ -1,72 +0,0 @@
-import os
-from os.path import normpath
-from unittest import mock
-
-from mitmproxy.tools.console import pathedit
-from mitmproxy.test import tutils
-
-
-class TestPathCompleter:
-
- def test_lookup_construction(self):
- c = pathedit._PathCompleter()
-
- cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
- ca = os.path.join(cd, "a")
- assert c.complete(ca).endswith(normpath("/completion/aaa"))
- assert c.complete(ca).endswith(normpath("/completion/aab"))
- c.reset()
- ca = os.path.join(cd, "aaa")
- assert c.complete(ca).endswith(normpath("/completion/aaa"))
- assert c.complete(ca).endswith(normpath("/completion/aaa"))
- c.reset()
- assert c.complete(cd).endswith(normpath("/completion/aaa"))
-
- def test_completion(self):
- c = pathedit._PathCompleter(True)
- c.reset()
- c.lookup = [
- ("a", "x/a"),
- ("aa", "x/aa"),
- ]
- assert c.complete("a") == "a"
- assert c.final == "x/a"
- assert c.complete("a") == "aa"
- assert c.complete("a") == "a"
-
- c = pathedit._PathCompleter(True)
- r = c.complete("l")
- assert c.final.endswith(r)
-
- c.reset()
- assert c.complete("/nonexistent") == "/nonexistent"
- assert c.final == "/nonexistent"
- c.reset()
- assert c.complete("~") != "~"
-
- c.reset()
- s = "thisisatotallynonexistantpathforsure"
- assert c.complete(s) == s
- assert c.final == s
-
-
-class TestPathEdit:
-
- def test_keypress(self):
-
- pe = pathedit.PathEdit("", "")
-
- with mock.patch('urwid.widget.Edit.get_edit_text') as get_text, \
- mock.patch('urwid.widget.Edit.set_edit_text') as set_text:
-
- cd = os.path.normpath(tutils.test_data.path("mitmproxy/completion"))
- get_text.return_value = os.path.join(cd, "a")
-
- # Pressing tab should set completed path
- pe.keypress((1,), "tab")
- set_text_called_with = set_text.call_args[0][0]
- assert set_text_called_with.endswith(normpath("/completion/aaa"))
-
- # Pressing any other key should reset
- pe.keypress((1,), "a")
- assert pe.lookup is None
diff --git a/test/mitmproxy/tools/console/test_statusbar.py b/test/mitmproxy/tools/console/test_statusbar.py
new file mode 100644
index 00000000..b131a5d3
--- /dev/null
+++ b/test/mitmproxy/tools/console/test_statusbar.py
@@ -0,0 +1,33 @@
+from mitmproxy import options
+from mitmproxy.tools.console import statusbar, master
+
+
+def test_statusbar(monkeypatch):
+ o = options.Options(
+ setheaders=[":~q:foo:bar"],
+ replacements=[":~q:foo:bar"],
+ ignore_hosts=["example.com", "example.org"],
+ tcp_hosts=["example.tcp"],
+ intercept="~q",
+ view_filter="~dst example.com",
+ stickycookie="~dst example.com",
+ stickyauth="~dst example.com",
+ default_contentview="javascript",
+ anticache=True,
+ anticomp=True,
+ showhost=True,
+ refresh_server_playback=False,
+ replay_kill_extra=True,
+ upstream_cert=False,
+ stream_large_bodies="3m",
+ mode="transparent",
+ scripts=["nonexistent"],
+ save_stream_file="foo",
+ )
+ m = master.ConsoleMaster(o)
+ m.options.update(view_order='url', console_focus_follow=True)
+ monkeypatch.setattr(m.addons.get("clientplayback"), "count", lambda: 42)
+ monkeypatch.setattr(m.addons.get("serverplayback"), "count", lambda: 42)
+
+ bar = statusbar.StatusBar(m) # this already causes a redraw
+ assert bar.ib._w
diff --git a/test/mitmproxy/tools/test_cmdline.py b/test/mitmproxy/tools/test_cmdline.py
index 65cfeb07..e247dc1d 100644
--- a/test/mitmproxy/tools/test_cmdline.py
+++ b/test/mitmproxy/tools/test_cmdline.py
@@ -1,7 +1,8 @@
import argparse
-from mitmproxy.tools import cmdline
-from mitmproxy.tools import main
+
from mitmproxy import options
+from mitmproxy.tools import cmdline, web, dump, console
+from mitmproxy.tools import main
def test_common():
@@ -14,17 +15,20 @@ def test_common():
def test_mitmproxy():
opts = options.Options()
+ console.master.ConsoleMaster(opts)
ap = cmdline.mitmproxy(opts)
assert ap
def test_mitmdump():
opts = options.Options()
+ dump.DumpMaster(opts)
ap = cmdline.mitmdump(opts)
assert ap
def test_mitmweb():
opts = options.Options()
+ web.master.WebMaster(opts)
ap = cmdline.mitmweb(opts)
assert ap
diff --git a/test/mitmproxy/tools/test_dump.py b/test/mitmproxy/tools/test_dump.py
index 69a76d2e..952c3f4f 100644
--- a/test/mitmproxy/tools/test_dump.py
+++ b/test/mitmproxy/tools/test_dump.py
@@ -1,7 +1,6 @@
import pytest
from unittest import mock
-from mitmproxy import proxy
from mitmproxy import log
from mitmproxy import controller
from mitmproxy import options
@@ -12,8 +11,8 @@ from .. import tservers
class TestDumpMaster(tservers.MasterTest):
def mkmaster(self, flt, **opts):
- o = options.Options(view_filter=flt, verbosity=-1, flow_detail=0, **opts)
- m = dump.DumpMaster(o, proxy.DummyServer(), with_termlog=False, with_dumper=False)
+ o = options.Options(view_filter=flt, verbosity='error', flow_detail=0, **opts)
+ m = dump.DumpMaster(o, with_termlog=False, with_dumper=False)
return m
def test_has_error(self):
@@ -27,12 +26,12 @@ class TestDumpMaster(tservers.MasterTest):
def test_addons_termlog(self, termlog):
with mock.patch('sys.stdout'):
o = options.Options()
- m = dump.DumpMaster(o, proxy.DummyServer(), with_termlog=termlog)
+ m = dump.DumpMaster(o, with_termlog=termlog)
assert (m.addons.get('termlog') is not None) == termlog
@pytest.mark.parametrize("dumper", [False, True])
def test_addons_dumper(self, dumper):
with mock.patch('sys.stdout'):
o = options.Options()
- m = dump.DumpMaster(o, proxy.DummyServer(), with_dumper=dumper)
+ m = dump.DumpMaster(o, with_dumper=dumper)
assert (m.addons.get('dumper') is not None) == dumper
diff --git a/test/mitmproxy/tools/test_main.py b/test/mitmproxy/tools/test_main.py
new file mode 100644
index 00000000..88e2fe86
--- /dev/null
+++ b/test/mitmproxy/tools/test_main.py
@@ -0,0 +1,19 @@
+from mitmproxy.test import tutils
+from mitmproxy.tools import main
+
+shutdown_script = tutils.test_data.path("mitmproxy/data/addonscripts/shutdown.py")
+
+
+def test_mitmweb():
+ main.mitmweb([
+ "--no-web-open-browser",
+ "-q",
+ "-s", shutdown_script
+ ])
+
+
+def test_mitmdump():
+ main.mitmdump([
+ "-q",
+ "-s", shutdown_script
+ ])
diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py
index 5427b995..5afc0bca 100644
--- a/test/mitmproxy/tools/web/test_app.py
+++ b/test/mitmproxy/tools/web/test_app.py
@@ -1,27 +1,40 @@
import json as _json
+import logging
from unittest import mock
import os
+import pytest
import tornado.testing
from tornado import httpclient
from tornado import websocket
from mitmproxy import exceptions
-from mitmproxy import proxy
from mitmproxy import options
from mitmproxy.test import tflow
from mitmproxy.tools.web import app
from mitmproxy.tools.web import master as webmaster
+@pytest.fixture(scope="module")
+def no_tornado_logging():
+ logging.getLogger('tornado.access').disabled = True
+ logging.getLogger('tornado.application').disabled = True
+ logging.getLogger('tornado.general').disabled = True
+ yield
+ logging.getLogger('tornado.access').disabled = False
+ logging.getLogger('tornado.application').disabled = False
+ logging.getLogger('tornado.general').disabled = False
+
+
def json(resp: httpclient.HTTPResponse):
return _json.loads(resp.body.decode())
+@pytest.mark.usefixtures("no_tornado_logging")
class TestApp(tornado.testing.AsyncHTTPTestCase):
def get_app(self):
o = options.Options(http2=False)
- m = webmaster.WebMaster(o, proxy.DummyServer(), with_termlog=False)
+ m = webmaster.WebMaster(o, with_termlog=False)
f = tflow.tflow(resp=True)
f.id = "42"
m.view.add([f])
@@ -187,7 +200,7 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
f.response.headers["Content-Encoding"] = "ran\x00dom"
f.response.headers["Content-Disposition"] = 'inline; filename="filename.jpg"'
- r = self.fetch("/flows/42/response/content")
+ r = self.fetch("/flows/42/response/content.data")
assert r.body == b"message"
assert r.headers["Content-Encoding"] == "random"
assert r.headers["Content-Disposition"] == 'attachment; filename="filename.jpg"'
@@ -195,17 +208,17 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
del f.response.headers["Content-Disposition"]
f.request.path = "/foo/bar.jpg"
assert self.fetch(
- "/flows/42/response/content"
+ "/flows/42/response/content.data"
).headers["Content-Disposition"] == 'attachment; filename=bar.jpg'
f.response.content = b""
- assert self.fetch("/flows/42/response/content").code == 400
+ assert self.fetch("/flows/42/response/content.data").code == 400
f.revert()
def test_update_flow_content(self):
assert self.fetch(
- "/flows/42/request/content",
+ "/flows/42/request/content.data",
method="POST",
body="new"
).code == 200
@@ -223,7 +236,7 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
b'--somefancyboundary--\r\n'
)
assert self.fetch(
- "/flows/42/request/content",
+ "/flows/42/request/content.data",
method="POST",
headers={"Content-Type": 'multipart/form-data; boundary="somefancyboundary"'},
body=body
@@ -253,6 +266,19 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
assert self.put_json("/settings", {"anticache": True}).code == 200
assert self.put_json("/settings", {"wtf": True}).code == 400
+ def test_options(self):
+ j = json(self.fetch("/options"))
+ assert type(j) == dict
+ assert type(j['anticache']) == dict
+
+ def test_option_update(self):
+ assert self.put_json("/options", {"anticache": True}).code == 200
+ assert self.put_json("/options", {"wtf": True}).code == 400
+ assert self.put_json("/options", {"anticache": "foo"}).code == 400
+
+ def test_option_save(self):
+ assert self.fetch("/options/save", method="POST").code == 200
+
def test_err(self):
with mock.patch("mitmproxy.tools.web.app.IndexHandler.get") as f:
f.side_effect = RuntimeError
@@ -265,19 +291,38 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
ws_client = yield websocket.websocket_connect(ws_url)
self.master.options.anticomp = True
- response = yield ws_client.read_message()
- assert _json.loads(response) == {
+ r1 = yield ws_client.read_message()
+ r2 = yield ws_client.read_message()
+ j1 = _json.loads(r1)
+ j2 = _json.loads(r2)
+ response = dict()
+ response[j1['resource']] = j1
+ response[j2['resource']] = j2
+ assert response['settings'] == {
"resource": "settings",
"cmd": "update",
"data": {"anticomp": True},
}
+ assert response['options'] == {
+ "resource": "options",
+ "cmd": "update",
+ "data": {
+ "anticomp": {
+ "value": True,
+ "choices": None,
+ "default": False,
+ "help": "Try to convince servers to send us un-compressed data.",
+ "type": "bool",
+ }
+ }
+ }
ws_client.close()
# trigger on_close by opening a second connection.
ws_client2 = yield websocket.websocket_connect(ws_url)
ws_client2.close()
- def test_generate_tflow_js(self):
+ def _test_generate_tflow_js(self):
_tflow = app.flow_to_json(tflow.tflow(resp=True, err=True))
# Set some value as constant, so that _tflow.js would not change every time.
_tflow['client_conn']['id'] = "4a18d1a0-50a1-48dd-9aa6-d45d74282939"
@@ -291,5 +336,5 @@ class TestApp(tornado.testing.AsyncHTTPTestCase):
web_root = os.path.join(here, os.pardir, os.pardir, os.pardir, os.pardir, 'web')
tflow_path = os.path.join(web_root, 'src/js/__tests__/ducks/_tflow.js')
content = """export default function(){{\n return {tflow_json}\n}}""".format(tflow_json=tflow_json)
- with open(tflow_path, 'w') as f:
+ with open(tflow_path, 'w', newline="\n") as f:
f.write(content)
diff --git a/test/mitmproxy/tools/web/test_master.py b/test/mitmproxy/tools/web/test_master.py
index 27f99a18..2bceb5ca 100644
--- a/test/mitmproxy/tools/web/test_master.py
+++ b/test/mitmproxy/tools/web/test_master.py
@@ -1,7 +1,5 @@
from mitmproxy.tools.web import master
-from mitmproxy import proxy
from mitmproxy import options
-from mitmproxy.proxy.config import ProxyConfig
from ... import tservers
@@ -9,8 +7,7 @@ from ... import tservers
class TestWebMaster(tservers.MasterTest):
def mkmaster(self, **opts):
o = options.Options(**opts)
- c = ProxyConfig(o)
- return master.WebMaster(o, proxy.DummyServer(c))
+ return master.WebMaster(o)
def test_basic(self):
m = self.mkmaster()
diff --git a/test/mitmproxy/tools/web/test_static_viewer.py b/test/mitmproxy/tools/web/test_static_viewer.py
new file mode 100644
index 00000000..cfe6cd7f
--- /dev/null
+++ b/test/mitmproxy/tools/web/test_static_viewer.py
@@ -0,0 +1,69 @@
+import json
+from unittest import mock
+
+from mitmproxy.test import taddons
+from mitmproxy.test import tflow
+
+from mitmproxy import flowfilter
+from mitmproxy.tools.web.app import flow_to_json
+
+from mitmproxy.tools.web import static_viewer
+from mitmproxy.addons import save
+
+
+def test_save_static(tmpdir):
+ tmpdir.mkdir('static')
+ static_viewer.save_static(tmpdir)
+ assert len(tmpdir.listdir()) == 2
+ assert tmpdir.join('index.html').check(file=1)
+ assert tmpdir.join('static/static.js').read() == 'MITMWEB_STATIC = true;'
+
+
+def test_save_filter_help(tmpdir):
+ static_viewer.save_filter_help(tmpdir)
+ f = tmpdir.join('/filter-help.json')
+ assert f.check(file=1)
+ assert f.read() == json.dumps(dict(commands=flowfilter.help))
+
+
+def test_save_settings(tmpdir):
+ static_viewer.save_settings(tmpdir)
+ f = tmpdir.join('/settings.json')
+ assert f.check(file=1)
+
+
+def test_save_flows(tmpdir):
+ flows = [tflow.tflow(req=True, resp=None), tflow.tflow(req=True, resp=True)]
+ static_viewer.save_flows(tmpdir, flows)
+ assert tmpdir.join('flows.json').check(file=1)
+ assert tmpdir.join('flows.json').read() == json.dumps([flow_to_json(f) for f in flows])
+
+
+@mock.patch('mitmproxy.ctx.log')
+def test_save_flows_content(ctx, tmpdir):
+ flows = [tflow.tflow(req=True, resp=None), tflow.tflow(req=True, resp=True)]
+ with mock.patch('time.time', mock.Mock(side_effect=[1, 2, 2] * 4)):
+ static_viewer.save_flows_content(tmpdir, flows)
+ flows_path = tmpdir.join('flows')
+ assert len(flows_path.listdir()) == len(flows)
+ for p in flows_path.listdir():
+ assert p.join('request').check(dir=1)
+ assert p.join('response').check(dir=1)
+ assert p.join('request/content.data').check(file=1)
+ assert p.join('request/content').check(dir=1)
+ assert p.join('response/content.data').check(file=1)
+ assert p.join('response/content').check(dir=1)
+ assert p.join('request/content/Auto.json').check(file=1)
+ assert p.join('response/content/Auto.json').check(file=1)
+
+
+def test_static_viewer(tmpdir):
+ s = static_viewer.StaticViewer()
+ sa = save.Save()
+ with taddons.context() as tctx:
+ sa.save([tflow.tflow(resp=True)], str(tmpdir.join('foo')))
+ tctx.master.addons.add(s)
+ tctx.configure(s, web_static_viewer=str(tmpdir), rfile=str(tmpdir.join('foo')))
+ assert tmpdir.join('index.html').check(file=1)
+ assert tmpdir.join('static').check(dir=1)
+ assert tmpdir.join('flows').check(dir=1)
diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py
index 3a2050e1..dd5bb327 100644
--- a/test/mitmproxy/tservers.py
+++ b/test/mitmproxy/tservers.py
@@ -2,6 +2,7 @@ import os.path
import threading
import tempfile
import sys
+from unittest import mock
import mitmproxy.platform
from mitmproxy.proxy.config import ProxyConfig
@@ -23,10 +24,13 @@ class MasterTest:
def cycle(self, master, content):
f = tflow.tflow(req=tutils.treq(content=content))
- master.addons.handle_lifecycle("clientconnect", f.client_conn)
+ layer = mock.Mock("mitmproxy.proxy.protocol.base.Layer")
+ layer.client_conn = f.client_conn
+ layer.reply = controller.DummyReply()
+ master.addons.handle_lifecycle("clientconnect", layer)
for i in eventsequence.iterate(f):
master.addons.handle_lifecycle(*i)
- master.addons.handle_lifecycle("clientdisconnect", f.client_conn)
+ master.addons.handle_lifecycle("clientdisconnect", layer)
return f
def dummy_cycle(self, master, n, content):
@@ -65,9 +69,10 @@ class TestState:
class TestMaster(taddons.RecordingMaster):
- def __init__(self, opts, config):
- s = ProxyServer(config)
- super().__init__(opts, s)
+ def __init__(self, opts):
+ super().__init__(opts)
+ config = ProxyConfig(opts)
+ self.server = ProxyServer(config)
def clear_addons(self, addons):
self.addons.clear()
@@ -125,9 +130,8 @@ class ProxyTestBase:
ssl=cls.ssl,
ssloptions=cls.ssloptions)
- opts = cls.get_options()
- cls.config = ProxyConfig(opts)
- tmaster = cls.masterclass(opts, cls.config)
+ cls.options = cls.get_options()
+ tmaster = cls.masterclass(cls.options)
cls.proxy = ProxyThread(tmaster)
cls.proxy.start()
@@ -334,19 +338,16 @@ class ChainProxyTest(ProxyTestBase):
@classmethod
def setup_class(cls):
+ # We need to initialize the chain first so that the normal server gets a correct config.
cls.chain = []
- super().setup_class()
for _ in range(cls.n):
opts = cls.get_options()
- config = ProxyConfig(opts)
- tmaster = cls.masterclass(opts, config)
+ tmaster = cls.masterclass(opts)
proxy = ProxyThread(tmaster)
proxy.start()
cls.chain.insert(0, proxy)
- # Patch the orginal proxy to upstream mode
- opts = cls.get_options()
- cls.config = cls.proxy.tmaster.config = cls.proxy.tmaster.server.config = ProxyConfig(opts)
+ super().setup_class()
@classmethod
def teardown_class(cls):
diff --git a/test/mitmproxy/utils/test_arg_check.py b/test/mitmproxy/utils/test_arg_check.py
new file mode 100644
index 00000000..72913955
--- /dev/null
+++ b/test/mitmproxy/utils/test_arg_check.py
@@ -0,0 +1,36 @@
+import io
+import contextlib
+from unittest import mock
+
+import pytest
+
+from mitmproxy.utils import arg_check
+
+
+@pytest.mark.parametrize('arg, output', [
+ (["-T"], "-T is deprecated, please use --mode transparent instead"),
+ (["-U"], "-U is deprecated, please use --mode upstream:SPEC instead"),
+ (["--cadir"], "--cadir is deprecated.\n"
+ "Please use `--set cadir=value` instead.\n"
+ "To show all options and their default values use --options"),
+ (["--palette"], "--palette is deprecated.\n"
+ "Please use `--set console_palette=value` instead.\n"
+ "To show all options and their default values use --options"),
+ (["--wfile"], "--wfile is deprecated.\n"
+ "Please use `--save-stream-file` instead."),
+ (["--eventlog"], "--eventlog has been removed."),
+ (["--nonanonymous"], '--nonanonymous is deprecated.\n'
+ 'Please use `--proxyauth SPEC` instead.\n'
+ 'SPEC Format: "username:pass", "any" to accept any user/pass combination,\n'
+ '"@path" to use an Apache htpasswd file, or\n'
+ '"ldap[s]:url_server_ldap:dn_auth:password:dn_subtree" '
+ 'for LDAP authentication.')
+
+])
+def test_check_args(arg, output):
+ f = io.StringIO()
+ with contextlib.redirect_stdout(f):
+ with mock.patch('sys.argv') as m:
+ m.__getitem__.return_value = arg
+ arg_check.check()
+ assert f.getvalue().strip() == output
diff --git a/test/mitmproxy/utils/test_debug.py b/test/mitmproxy/utils/test_debug.py
index 4371ef70..0ca6ead0 100644
--- a/test/mitmproxy/utils/test_debug.py
+++ b/test/mitmproxy/utils/test_debug.py
@@ -1,5 +1,4 @@
import io
-import subprocess
import sys
from unittest import mock
import pytest
@@ -11,13 +10,7 @@ from mitmproxy.utils import debug
def test_dump_system_info_precompiled(precompiled):
sys.frozen = None
with mock.patch.object(sys, 'frozen', precompiled):
- assert ("Precompiled Binary" in debug.dump_system_info()) == precompiled
-
-
-def test_dump_system_info_version():
- with mock.patch('subprocess.check_output') as m:
- m.side_effect = subprocess.CalledProcessError(-1, 'git describe --tags --long')
- assert 'release version' in debug.dump_system_info()
+ assert ("binary" in debug.dump_system_info()) == precompiled
def test_dump_info():
diff --git a/test/mitmproxy/utils/test_human.py b/test/mitmproxy/utils/test_human.py
index 76dc2f88..947cfa4a 100644
--- a/test/mitmproxy/utils/test_human.py
+++ b/test/mitmproxy/utils/test_human.py
@@ -22,6 +22,7 @@ def test_parse_size():
human.parse_size("1f")
with pytest.raises(ValueError):
human.parse_size("ak")
+ assert human.parse_size(None) is None
def test_pretty_size():
@@ -53,3 +54,5 @@ def test_format_address():
assert human.format_address(("::ffff:127.0.0.1", "54010", "0", "0")) == "127.0.0.1:54010"
assert human.format_address(("127.0.0.1", "54010")) == "127.0.0.1:54010"
assert human.format_address(("example.com", "54010")) == "example.com:54010"
+ assert human.format_address(("::", "8080")) == "*:8080"
+ assert human.format_address(("0.0.0.0", "8080")) == "*:8080"
diff --git a/test/mitmproxy/utils/test_strutils.py b/test/mitmproxy/utils/test_strutils.py
index bacd7f62..dfe2c620 100644
--- a/test/mitmproxy/utils/test_strutils.py
+++ b/test/mitmproxy/utils/test_strutils.py
@@ -96,3 +96,33 @@ def test_clean_hanging_newline():
def test_hexdump():
assert list(strutils.hexdump(b"one\0" * 10))
+
+
+ESCAPE_QUOTES = [
+ "'" + strutils.SINGLELINE_CONTENT + strutils.NO_ESCAPE + "'",
+ '"' + strutils.SINGLELINE_CONTENT + strutils.NO_ESCAPE + '"'
+]
+
+
+def test_split_special_areas():
+ assert strutils.split_special_areas("foo", ESCAPE_QUOTES) == ["foo"]
+ assert strutils.split_special_areas("foo 'bar' baz", ESCAPE_QUOTES) == ["foo ", "'bar'", " baz"]
+ assert strutils.split_special_areas(
+ """foo 'b\\'a"r' baz""",
+ ESCAPE_QUOTES
+ ) == ["foo ", "'b\\'a\"r'", " baz"]
+ assert strutils.split_special_areas(
+ "foo\n/*bar\nbaz*/\nqux",
+ [r'/\*[\s\S]+?\*/']
+ ) == ["foo\n", "/*bar\nbaz*/", "\nqux"]
+ assert strutils.split_special_areas(
+ "foo\n//bar\nbaz",
+ [r'//.+$']
+ ) == ["foo\n", "//bar", "\nbaz"]
+
+
+def test_escape_special_areas():
+ assert strutils.escape_special_areas('foo "bar" baz', ESCAPE_QUOTES, "*") == 'foo "bar" baz'
+ esc = strutils.escape_special_areas('foo "b*r" b*z', ESCAPE_QUOTES, "*")
+ assert esc == 'foo "b\ue02ar" b*z'
+ assert strutils.unescape_special_areas(esc) == 'foo "b*r" b*z'
diff --git a/test/mitmproxy/utils/test_typecheck.py b/test/mitmproxy/utils/test_typecheck.py
index fe33070e..5295fff5 100644
--- a/test/mitmproxy/utils/test_typecheck.py
+++ b/test/mitmproxy/utils/test_typecheck.py
@@ -4,7 +4,6 @@ from unittest import mock
import pytest
from mitmproxy.utils import typecheck
-from mitmproxy import command
class TBase:
@@ -88,26 +87,9 @@ def test_check_any():
typecheck.check_option_type("foo", None, typing.Any)
-def test_check_command_type():
- assert(typecheck.check_command_type("foo", str))
- assert(typecheck.check_command_type(["foo"], typing.Sequence[str]))
- assert(not typecheck.check_command_type(["foo", 1], typing.Sequence[str]))
- assert(typecheck.check_command_type(None, None))
- assert(not typecheck.check_command_type(["foo"], typing.Sequence[int]))
- assert(not typecheck.check_command_type("foo", typing.Sequence[int]))
- assert(typecheck.check_command_type([["foo", b"bar"]], command.Cuts))
- assert(not typecheck.check_command_type(["foo", b"bar"], command.Cuts))
- assert(not typecheck.check_command_type([["foo", 22]], command.Cuts))
-
- # Python 3.5 only defines __parameters__
- m = mock.Mock()
- m.__str__ = lambda self: "typing.Sequence"
- m.__parameters__ = (int,)
-
- typecheck.check_command_type([10], m)
-
- # Python 3.5 only defines __union_params__
- m = mock.Mock()
- m.__str__ = lambda self: "typing.Union"
- m.__union_params__ = (int,)
- assert not typecheck.check_command_type([22], m)
+def test_typesec_to_str():
+ assert(typecheck.typespec_to_str(str)) == "str"
+ assert(typecheck.typespec_to_str(typing.Sequence[str])) == "sequence of str"
+ assert(typecheck.typespec_to_str(typing.Optional[str])) == "optional str"
+ with pytest.raises(NotImplementedError):
+ typecheck.typespec_to_str(dict)
diff --git a/test/mitmproxy/utils/test_version_check.py b/test/mitmproxy/utils/test_version_check.py
deleted file mode 100644
index d7929378..00000000
--- a/test/mitmproxy/utils/test_version_check.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import io
-from unittest import mock
-from mitmproxy.utils import version_check
-
-
-@mock.patch("sys.exit")
-def test_check_pyopenssl_version(sexit):
- fp = io.StringIO()
- version_check.check_pyopenssl_version(fp=fp)
- assert not fp.getvalue()
- assert not sexit.called
-
- version_check.check_pyopenssl_version((9999,), fp=fp)
- assert "outdated" in fp.getvalue()
- assert sexit.called
-
-
-@mock.patch("sys.exit")
-@mock.patch("OpenSSL.__version__")
-def test_unparseable_pyopenssl_version(version, sexit):
- version.split.return_value = ["foo", "bar"]
- fp = io.StringIO()
- version_check.check_pyopenssl_version(fp=fp)
- assert "Cannot parse" in fp.getvalue()
- assert not sexit.called
diff --git a/test/pathod/protocols/test_http2.py b/test/pathod/protocols/test_http2.py
index c16a6d40..95965cee 100644
--- a/test/pathod/protocols/test_http2.py
+++ b/test/pathod/protocols/test_http2.py
@@ -11,8 +11,6 @@ from ...mitmproxy.net import tservers as net_tservers
from pathod.protocols.http2 import HTTP2StateProtocol, TCPHandler
-from ...conftest import requires_alpn
-
class TestTCPHandlerWrapper:
def test_wrapped(self):
@@ -68,7 +66,6 @@ class TestProtocol:
assert mock_server_method.called
-@requires_alpn
class TestCheckALPNMatch(net_tservers.ServerTestBase):
handler = EchoHandler
ssl = dict(
@@ -78,12 +75,11 @@ class TestCheckALPNMatch(net_tservers.ServerTestBase):
def test_check_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(alpn_protos=[b'h2'])
+ c.convert_to_tls(alpn_protos=[b'h2'])
protocol = HTTP2StateProtocol(c)
assert protocol.check_alpn()
-@requires_alpn
class TestCheckALPNMismatch(net_tservers.ServerTestBase):
handler = EchoHandler
ssl = dict(
@@ -93,7 +89,7 @@ class TestCheckALPNMismatch(net_tservers.ServerTestBase):
def test_check_alpn(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl(alpn_protos=[b'h2'])
+ c.convert_to_tls(alpn_protos=[b'h2'])
protocol = HTTP2StateProtocol(c)
with pytest.raises(NotImplementedError):
protocol.check_alpn()
@@ -211,7 +207,7 @@ class TestApplySettings(net_tservers.ServerTestBase):
def test_apply_settings(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c)
protocol._apply_settings({
@@ -306,7 +302,7 @@ class TestReadRequest(net_tservers.ServerTestBase):
def test_read_request(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c, is_server=True)
protocol.connection_preface_performed = True
@@ -332,7 +328,7 @@ class TestReadRequestRelative(net_tservers.ServerTestBase):
def test_asterisk_form(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c, is_server=True)
protocol.connection_preface_performed = True
@@ -355,7 +351,7 @@ class TestReadRequestAbsolute(net_tservers.ServerTestBase):
def test_absolute_form(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c, is_server=True)
protocol.connection_preface_performed = True
@@ -382,7 +378,7 @@ class TestReadResponse(net_tservers.ServerTestBase):
def test_read_response(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c)
protocol.connection_preface_performed = True
@@ -408,7 +404,7 @@ class TestReadEmptyResponse(net_tservers.ServerTestBase):
def test_read_empty_response(self):
c = tcp.TCPClient(("127.0.0.1", self.port))
with c.connect():
- c.convert_to_ssl()
+ c.convert_to_tls()
protocol = HTTP2StateProtocol(c)
protocol.connection_preface_performed = True
diff --git a/test/pathod/test_pathoc.py b/test/pathod/test_pathoc.py
index 2dd29e20..297b54d4 100644
--- a/test/pathod/test_pathoc.py
+++ b/test/pathod/test_pathoc.py
@@ -11,7 +11,6 @@ from pathod.protocols.http2 import HTTP2StateProtocol
from mitmproxy.test import tutils
from . import tservers
-from ..conftest import requires_alpn
def test_response():
@@ -216,7 +215,6 @@ class TestDaemonHTTP2(PathocTestDaemon):
ssl = True
explain = False
- @requires_alpn
def test_http2(self):
c = pathoc.Pathoc(
("127.0.0.1", self.d.port),
@@ -231,7 +229,6 @@ class TestDaemonHTTP2(PathocTestDaemon):
)
assert c.protocol == http1
- @requires_alpn
def test_http2_alpn(self):
c = pathoc.Pathoc(
("127.0.0.1", self.d.port),
@@ -241,14 +238,13 @@ class TestDaemonHTTP2(PathocTestDaemon):
http2_skip_connection_preface=True,
)
- tmp_convert_to_ssl = c.convert_to_ssl
- c.convert_to_ssl = Mock()
- c.convert_to_ssl.side_effect = tmp_convert_to_ssl
+ tmp_convert_to_tls = c.convert_to_tls
+ c.convert_to_tls = Mock()
+ c.convert_to_tls.side_effect = tmp_convert_to_tls
with c.connect():
- _, kwargs = c.convert_to_ssl.call_args
+ _, kwargs = c.convert_to_tls.call_args
assert set(kwargs['alpn_protos']) == set([b'http/1.1', b'h2'])
- @requires_alpn
def test_request(self):
c = pathoc.Pathoc(
("127.0.0.1", self.d.port),
@@ -259,14 +255,3 @@ class TestDaemonHTTP2(PathocTestDaemon):
with c.connect():
resp = c.request("get:/p/200")
assert resp.status_code == 200
-
- def test_failing_request(self, disable_alpn):
- c = pathoc.Pathoc(
- ("127.0.0.1", self.d.port),
- fp=None,
- ssl=True,
- use_http2=True,
- )
- with pytest.raises(NotImplementedError):
- with c.connect():
- c.request("get:/p/200")
diff --git a/test/pathod/test_pathod.py b/test/pathod/test_pathod.py
index 88480a59..d6522cb6 100644
--- a/test/pathod/test_pathod.py
+++ b/test/pathod/test_pathod.py
@@ -8,7 +8,6 @@ from mitmproxy import exceptions
from mitmproxy.test import tutils
from . import tservers
-from ..conftest import requires_alpn
class TestPathod:
@@ -58,7 +57,7 @@ class TestNotAfterConnect(tservers.DaemonTests):
class TestCustomCert(tservers.DaemonTests):
ssl = True
ssloptions = dict(
- certs=[(b"*", tutils.test_data.path("pathod/data/testkey.pem"))],
+ certs=[("*", tutils.test_data.path("pathod/data/testkey.pem"))],
)
def test_connect(self):
@@ -154,7 +153,7 @@ class CommonTests(tservers.DaemonTests):
c = tcp.TCPClient(("localhost", self.d.port))
with c.connect():
if self.ssl:
- c.convert_to_ssl()
+ c.convert_to_tls()
c.wfile.write(b"foo\n\n\n")
c.wfile.flush()
l = self.d.last_log()
@@ -242,7 +241,7 @@ class TestDaemonSSL(CommonTests):
with c.connect():
c.wfile.write(b"\0\0\0\0")
with pytest.raises(exceptions.TlsException):
- c.convert_to_ssl()
+ c.convert_to_tls()
l = self.d.last_log()
assert l["type"] == "error"
assert "SSL" in l["msg"]
@@ -257,11 +256,6 @@ class TestHTTP2(tservers.DaemonTests):
ssl = True
nohang = True
- @requires_alpn
def test_http2(self):
r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True)
assert r[0].status_code == 800
-
- def test_no_http2(self, disable_alpn):
- with pytest.raises(NotImplementedError):
- r, _ = self.pathoc(["GET:/"], ssl=True, use_http2=True)