diff options
-rw-r--r-- | .travis.yml | 6 | ||||
-rw-r--r-- | mitmproxy/net/tcp.py | 21 | ||||
-rw-r--r-- | pathod/protocols/http2.py | 8 | ||||
-rw-r--r-- | setup.cfg | 6 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | test/full_coverage_plugin.py | 7 | ||||
-rw-r--r-- | test/mitmproxy/contentviews/test_api.py | 15 | ||||
-rw-r--r-- | test/mitmproxy/contentviews/test_xml_html.py | 8 | ||||
-rw-r--r-- | test/mitmproxy/contentviews/test_xml_html_data/test-formatted.html | 44 | ||||
-rw-r--r-- | test/mitmproxy/contentviews/test_xml_html_data/test.html | 14 | ||||
-rw-r--r-- | test/mitmproxy/net/test_tcp.py | 43 | ||||
-rw-r--r-- | test/mitmproxy/test_controller.py | 2 | ||||
-rw-r--r-- | tox.ini | 2 |
13 files changed, 145 insertions, 33 deletions
diff --git a/.travis.yml b/.travis.yml index d411ac0b..26894eee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,13 +18,13 @@ matrix: language: generic env: TOXENV=py35 BDIST=1 - python: 3.5 - env: TOXENV=py35 OPENSSL_OLD + env: TOXENV=py35 OPENSSL=old addons: apt: packages: - libssl-dev - python: 3.5 - env: TOXENV=py35 BDIST=1 OPENSSL_ALPN + env: TOXENV=py35 BDIST=1 OPENSSL=with-alpn addons: apt: sources: @@ -34,7 +34,7 @@ matrix: packages: - libssl-dev - python: 3.6 - env: TOXENV=py36 OPENSSL_ALPN + env: TOXENV=py36 OPENSSL=with-alpn addons: apt: sources: diff --git a/mitmproxy/net/tcp.py b/mitmproxy/net/tcp.py index 372329d9..81568d24 100644 --- a/mitmproxy/net/tcp.py +++ b/mitmproxy/net/tcp.py @@ -511,7 +511,7 @@ class _Connection: if log_ssl_key: context.set_info_callback(log_ssl_key) - if HAS_ALPN: + if HAS_ALPN: # pragma: openssl-old no cover if alpn_protos is not None: # advertise application layer protocols context.set_alpn_protos(alpn_protos) @@ -520,13 +520,15 @@ class _Connection: def alpn_select_callback(conn_, options): if alpn_select in options: return bytes(alpn_select) - else: # pragma no cover + else: # pragma: no cover return options[0] context.set_alpn_select_callback(alpn_select_callback) elif alpn_select_callback is not None and alpn_select is None: + if not callable(alpn_select_callback): + raise exceptions.TlsException("ALPN error: alpn_select_callback must be a function.") context.set_alpn_select_callback(alpn_select_callback) elif alpn_select_callback is not None and alpn_select is not None: - raise exceptions.TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (method).") + raise exceptions.TlsException("ALPN error: only define alpn_select (string) OR alpn_select_callback (function).") return context @@ -669,11 +671,11 @@ class TCPClient(_Connection): if self.spoof_source_address: try: if not sock.getsockopt(socket.SOL_IP, socket.IP_TRANSPARENT): - sock.setsockopt(socket.SOL_IP, socket.IP_TRANSPARENT, 1) + sock.setsockopt(socket.SOL_IP, socket.IP_TRANSPARENT, 1) # pragma: windows no cover pragma: osx no cover except Exception as e: # socket.IP_TRANSPARENT might not be available on every OS and Python version raise exceptions.TcpException( - "Failed to spoof the source address: " + e.strerror + "Failed to spoof the source address: " + str(e) ) sock.connect(sa) return sock @@ -686,7 +688,7 @@ class TCPClient(_Connection): if err is not None: raise err else: - raise socket.error("getaddrinfo returns an empty list") + raise socket.error("getaddrinfo returns an empty list") # pragma: no cover def connect(self): try: @@ -709,7 +711,7 @@ class TCPClient(_Connection): return self.connection.gettimeout() def get_alpn_proto_negotiated(self): - if HAS_ALPN and self.ssl_established: + if HAS_ALPN and self.ssl_established: # pragma: openssl-old no cover return self.connection.get_alpn_proto_negotiated() else: return b"" @@ -816,7 +818,7 @@ class BaseHandler(_Connection): self.connection.settimeout(n) def get_alpn_proto_negotiated(self): - if HAS_ALPN and self.ssl_established: + if HAS_ALPN and self.ssl_established: # pragma: openssl-old no cover return self.connection.get_alpn_proto_negotiated() else: return b"" @@ -847,9 +849,10 @@ class TCPServer: def __init__(self, address): self.address = address self.__is_shut_down = threading.Event() + self.__is_shut_down.set() self.__shutdown_request = False - if self.address == 'localhost': + if self.address[0] == 'localhost': raise socket.error("Binding to 'localhost' is prohibited. Please use '::1' or '127.0.0.1' directly.") try: diff --git a/pathod/protocols/http2.py b/pathod/protocols/http2.py index 7c88c5c7..cfc71650 100644 --- a/pathod/protocols/http2.py +++ b/pathod/protocols/http2.py @@ -247,13 +247,13 @@ class HTTP2StateProtocol: raw_bytes = frm.serialize() self.tcp_handler.wfile.write(raw_bytes) self.tcp_handler.wfile.flush() - if not hide and self.dump_frames: # pragma no cover + if not hide and self.dump_frames: # pragma: no cover print(">> " + repr(frm)) def read_frame(self, hide=False): while True: frm = http2.parse_frame(*http2.read_raw_frame(self.tcp_handler.rfile)) - if not hide and self.dump_frames: # pragma no cover + if not hide and self.dump_frames: # pragma: no cover print("<< " + repr(frm)) if isinstance(frm, hyperframe.frame.PingFrame): @@ -337,7 +337,7 @@ class HTTP2StateProtocol: if end_stream: frms[0].flags.add('END_STREAM') - if self.dump_frames: # pragma no cover + if self.dump_frames: # pragma: no cover for frm in frms: print(">> ", repr(frm)) @@ -355,7 +355,7 @@ class HTTP2StateProtocol: data=body[i:i + chunk_size]) for i in chunks] frms[-1].flags.add('END_STREAM') - if self.dump_frames: # pragma no cover + if self.dump_frames: # pragma: no cover for frm in frms: print(">> ", repr(frm)) @@ -21,17 +21,12 @@ exclude_lines = [tool:full_coverage] exclude = - mitmproxy/contentviews/__init__.py mitmproxy/contentviews/wbxml.py - mitmproxy/contentviews/xml_html.py - mitmproxy/net/tcp.py - mitmproxy/net/http/encoding.py mitmproxy/proxy/protocol/ mitmproxy/proxy/config.py mitmproxy/proxy/root_context.py mitmproxy/proxy/server.py mitmproxy/tools/ - mitmproxy/controller.py mitmproxy/flow.py mitmproxy/master.py pathod/pathoc.py @@ -45,7 +40,6 @@ exclude = mitmproxy/addons/termlog.py mitmproxy/contentviews/base.py mitmproxy/contentviews/wbxml.py - mitmproxy/contentviews/xml_html.py mitmproxy/controller.py mitmproxy/ctx.py mitmproxy/exceptions.py @@ -104,7 +104,7 @@ setup( "pytest-timeout>=1.0.0, <2", "pytest-xdist>=1.14, <2", "pytest-faulthandler>=1.3.0, <2", - "sphinx>=1.3.5, <1.6", + "sphinx>=1.3.5, <1.7", "sphinx-autobuild>=0.5.2, <0.7", "sphinxcontrib-documentedlist>=0.5.0, <0.7", "sphinx_rtd_theme>=0.1.9, <0.3", diff --git a/test/full_coverage_plugin.py b/test/full_coverage_plugin.py index d98c29d6..ec30a9f9 100644 --- a/test/full_coverage_plugin.py +++ b/test/full_coverage_plugin.py @@ -1,6 +1,7 @@ import os import configparser import pytest +import sys here = os.path.abspath(os.path.dirname(__file__)) @@ -59,6 +60,12 @@ def pytest_runtestloop(session): if os.name == 'nt': cov.exclude('pragma: windows no cover') + if sys.platform == 'darwin': + cov.exclude('pragma: osx no cover') + + if os.environ.get("OPENSSL") == "old": + cov.exclude('pragma: openssl-old no cover') + yield coverage_values = dict([(name, 0) for name in pytest.config.option.full_cov]) diff --git a/test/mitmproxy/contentviews/test_api.py b/test/mitmproxy/contentviews/test_api.py index 95d83af9..c072c86f 100644 --- a/test/mitmproxy/contentviews/test_api.py +++ b/test/mitmproxy/contentviews/test_api.py @@ -9,23 +9,28 @@ from mitmproxy.test import tutils class TestContentView(contentviews.View): name = "test" - prompt = ("t", "test") + prompt = ("test", "t") content_types = ["test/123"] def test_add_remove(): tcv = TestContentView() contentviews.add(tcv) + assert tcv in contentviews.views # repeated addition causes exception - with pytest.raises(ContentViewException): + with pytest.raises(ContentViewException, match="Duplicate view"): contentviews.add(tcv) + tcv2 = TestContentView() + tcv2.name = "test2" + tcv2.prompt = ("test2", "t") # Same shortcut doesn't work either. - with pytest.raises(ContentViewException): - contentviews.add(TestContentView()) + with pytest.raises(ContentViewException, match="Duplicate view shortcut"): + contentviews.add(tcv2) contentviews.remove(tcv) + assert tcv not in contentviews.views def test_get_content_view(): @@ -43,6 +48,7 @@ def test_get_content_view(): headers=Headers(content_type="application/json") ) assert desc == "JSON" + assert list(lines) desc, lines, err = contentviews.get_content_view( contentviews.get("JSON"), @@ -84,3 +90,4 @@ def test_get_message_content_view(): def test_get_by_shortcut(): assert contentviews.get_by_shortcut("s") + assert not contentviews.get_by_shortcut("b") diff --git a/test/mitmproxy/contentviews/test_xml_html.py b/test/mitmproxy/contentviews/test_xml_html.py index 2b0aee4d..8148fd4c 100644 --- a/test/mitmproxy/contentviews/test_xml_html.py +++ b/test/mitmproxy/contentviews/test_xml_html.py @@ -11,6 +11,13 @@ def test_simple(): v = full_eval(xml_html.ViewXmlHtml()) assert v(b"foo") == ('XML', [[('text', 'foo')]]) assert v(b"<html></html>") == ('HTML', [[('text', '<html></html>')]]) + assert v(b"<>") == ('XML', [[('text', '<>')]]) + assert v(b"<p") == ('XML', [[('text', '<p')]]) + + with open(data.path("simple.html")) as f: + input = f.read() + tokens = xml_html.tokenize(input) + assert str(next(tokens)) == "Tag(<!DOCTYPE html>)" @pytest.mark.parametrize("filename", [ @@ -18,6 +25,7 @@ def test_simple(): "cdata.xml", "comment.xml", "inline.html", + "test.html" ]) def test_format_xml(filename): path = data.path(filename) diff --git a/test/mitmproxy/contentviews/test_xml_html_data/test-formatted.html b/test/mitmproxy/contentviews/test_xml_html_data/test-formatted.html new file mode 100644 index 00000000..0eb60004 --- /dev/null +++ b/test/mitmproxy/contentviews/test_xml_html_data/test-formatted.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Title</title> +</head> +<body> + <p> + Lorem ipsum dolor + <p> + sit amet, consectetur + <p> + adipiscing elit, sed + <p> + do eiusmod tempor + <p> + incididunt ut + <p> + labore et dolore + <p> + magna aliqua. + <p> + Ut enim ad minim + <p> + veniam, quis nostrud + <p> + exercitation + <p> + ullamco laboris + <p> + nisi ut aliquip ex ea + <p> + commodo consequat. + <p> + Duis aute irure + <p> + dolor in reprehenderit + <p> + in voluptate velit + <p> + esse cillum dolore + <p>eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> +</body> +</html> diff --git a/test/mitmproxy/contentviews/test_xml_html_data/test.html b/test/mitmproxy/contentviews/test_xml_html_data/test.html new file mode 100644 index 00000000..e74ac314 --- /dev/null +++ b/test/mitmproxy/contentviews/test_xml_html_data/test.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title>Title</title> +</head> +<body> +<p>Lorem ipsum dolor<p>sit amet, consectetur <p>adipiscing elit, sed<p>do eiusmod tempor<p> incididunt ut<p> labore et dolore<p> magna aliqua. + <p>Ut enim ad minim <p>veniam, quis nostrud <p>exercitation <p>ullamco laboris <p> + nisi ut aliquip ex ea <p>commodo consequat.<p>Duis aute irure <p>dolor in reprehenderit <p>in voluptate velit<p> esse cillum dolore <p>eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p> + + +</body> +</html>
\ No newline at end of file diff --git a/test/mitmproxy/net/test_tcp.py b/test/mitmproxy/net/test_tcp.py index 8b26784a..81d51888 100644 --- a/test/mitmproxy/net/test_tcp.py +++ b/test/mitmproxy/net/test_tcp.py @@ -529,10 +529,10 @@ class TestTimeOut(tservers.ServerTestBase): class TestCryptographyALPN: def test_has_alpn(self): - if 'OPENSSL_ALPN' in os.environ: + if os.environ.get("OPENSSL") == "with-alpn": assert tcp.HAS_ALPN assert SSL._lib.Cryptography_HAS_ALPN - elif 'OPENSSL_OLD' in os.environ: + elif os.environ.get("OPENSSL") == "old": assert not tcp.HAS_ALPN assert not SSL._lib.Cryptography_HAS_ALPN @@ -603,13 +603,36 @@ class TestDHParams(tservers.ServerTestBase): assert ret[0] == "DHE-RSA-AES256-SHA" -class TestTCPClient: +class TestTCPClient(tservers.ServerTestBase): def test_conerr(self): c = tcp.TCPClient(("127.0.0.1", 0)) - with pytest.raises(exceptions.TcpException): + with pytest.raises(exceptions.TcpException, match="Error connecting"): c.connect() + def test_timeout(self): + c = tcp.TCPClient(("127.0.0.1", self.port)) + with c.create_connection(timeout=20) as conn: + assert conn.gettimeout() == 20 + + def test_spoof_address(self): + c = tcp.TCPClient(("127.0.0.1", self.port), spoof_source_address=("127.0.0.1", 0)) + with pytest.raises(exceptions.TcpException, match="Failed to spoof"): + c.connect() + + +class TestTCPServer: + + def test_binderr(self): + with pytest.raises(socket.error, match="prohibited"): + tcp.TCPServer(("localhost", 8080)) + + def test_wait_for_silence(self): + s = tcp.TCPServer(("127.0.0.1", 0)) + with s.handler_counter: + with pytest.raises(exceptions.Timeout): + s.wait_for_silence() + class TestFileLike: @@ -811,7 +834,7 @@ class TestSSLKeyLogger(tservers.ServerTestBase): assert not tcp.SSLKeyLogger.create_logfun(False) -class TestSSLInvalidMethod(tservers.ServerTestBase): +class TestSSLInvalid(tservers.ServerTestBase): handler = EchoHandler ssl = True @@ -821,3 +844,13 @@ class TestSSLInvalidMethod(tservers.ServerTestBase): 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/test_controller.py b/test/mitmproxy/test_controller.py index ccc8bf35..2e13d298 100644 --- a/test/mitmproxy/test_controller.py +++ b/test/mitmproxy/test_controller.py @@ -176,6 +176,8 @@ class TestDummyReply: reply = controller.DummyReply() reply.ack() reply.take() + with pytest.raises(ControlException): + reply.mark_reset() reply.commit() reply.mark_reset() assert reply.state == "committed" @@ -7,7 +7,7 @@ toxworkdir={env:TOX_WORK_DIR:.tox} deps = {env:CI_DEPS:} -rrequirements.txt -passenv = CODECOV_TOKEN CI CI_* TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* SNAPSHOT_* OPENSSL_* RTOOL_* +passenv = CODECOV_TOKEN CI CI_* TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* SNAPSHOT_* OPENSSL RTOOL_* setenv = HOME = {envtmpdir} commands = mitmdump --version |