aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@corte.si>2014-09-07 12:59:35 +1200
committerAldo Cortesi <aldo@corte.si>2014-09-07 12:59:35 +1200
commit3d62e90dbf7ea05283e16752531a261e53a4bb47 (patch)
treec3f5aed62bcf13378522968a1c77375dc8102b53 /test
parent0e0cff638c1e055275e77e2af0ae540542f77197 (diff)
parentfdd7b2f108717900e39e3d0ab220ee65b79304ef (diff)
downloadmitmproxy-3d62e90dbf7ea05283e16752531a261e53a4bb47.tar.gz
mitmproxy-3d62e90dbf7ea05283e16752531a261e53a4bb47.tar.bz2
mitmproxy-3d62e90dbf7ea05283e16752531a261e53a4bb47.zip
Merge pull request #342 from mitmproxy/server_change_api
Server change api
Diffstat (limited to 'test')
-rw-r--r--test/test_examples.py2
-rw-r--r--test/test_flow.py98
-rw-r--r--test/test_protocol_http.py173
-rw-r--r--test/test_proxy.py17
-rw-r--r--test/test_script.py2
-rw-r--r--test/test_server.py239
-rw-r--r--test/tservers.py107
7 files changed, 361 insertions, 277 deletions
diff --git a/test/test_examples.py b/test/test_examples.py
index d18b5862..d557080e 100644
--- a/test/test_examples.py
+++ b/test/test_examples.py
@@ -12,6 +12,8 @@ def test_load_scripts():
tmaster = tservers.TestMaster(config.ProxyConfig())
for f in scripts:
+ if "iframe_injector" in f:
+ f += " foo" # one argument required
if "modify_response_body" in f:
f += " foo bar" # two arguments required
script.Script(f, tmaster) # Loads the script file. \ No newline at end of file
diff --git a/test/test_flow.py b/test/test_flow.py
index 6e9464e7..914138c9 100644
--- a/test/test_flow.py
+++ b/test/test_flow.py
@@ -481,7 +481,7 @@ class TestSerialize:
f2 = l[0]
assert f2._get_state() == f._get_state()
- assert f2.request._assemble() == f.request._assemble()
+ assert f2.request.assemble() == f.request.assemble()
def test_load_flows(self):
r = self._treader()
@@ -753,63 +753,61 @@ class TestRequest:
def test_simple(self):
f = tutils.tflow()
r = f.request
- u = r.get_url(False, f)
- assert r.set_url(u, f)
- assert not r.set_url("", f)
- assert r.get_url(False, f) == u
- assert r._assemble()
- assert r.size() == len(r._assemble())
+ u = r.url
+ r.url = u
+ tutils.raises(ValueError, setattr, r, "url", "")
+ assert r.url == u
+ assert r.assemble()
+ assert r.size() == len(r.assemble())
r2 = r.copy()
assert r == r2
r.content = None
- assert r._assemble()
- assert r.size() == len(r._assemble())
+ assert r.assemble()
+ assert r.size() == len(r.assemble())
r.content = CONTENT_MISSING
- tutils.raises("Cannot assemble flow with CONTENT_MISSING", r._assemble)
+ tutils.raises("Cannot assemble flow with CONTENT_MISSING", r.assemble)
def test_get_url(self):
- f = tutils.tflow()
- r = f.request
+ r = tutils.treq()
- assert r.get_url(False, f) == "http://address:22/path"
+ assert r.url == "http://address:22/path"
r.scheme = "https"
- assert r.get_url(False, f) == "https://address:22/path"
+ assert r.url == "https://address:22/path"
r.host = "host"
r.port = 42
- assert r.get_url(False, f) == "https://host:42/path"
+ assert r.url == "https://host:42/path"
r.host = "address"
r.port = 22
- assert r.get_url(False, f) == "https://address:22/path"
+ assert r.url== "https://address:22/path"
- assert r.get_url(True, f) == "https://address:22/path"
+ assert r.pretty_url(True) == "https://address:22/path"
r.headers["Host"] = ["foo.com"]
- assert r.get_url(False, f) == "https://address:22/path"
- assert r.get_url(True, f) == "https://foo.com:22/path"
+ assert r.pretty_url(False) == "https://address:22/path"
+ assert r.pretty_url(True) == "https://foo.com:22/path"
def test_path_components(self):
- f = tutils.tflow()
- r = f.request
+ r = tutils.treq()
r.path = "/"
- assert r.get_path_components(f) == []
+ assert r.get_path_components() == []
r.path = "/foo/bar"
- assert r.get_path_components(f) == ["foo", "bar"]
+ assert r.get_path_components() == ["foo", "bar"]
q = flow.ODict()
q["test"] = ["123"]
- r.set_query(q, f)
- assert r.get_path_components(f) == ["foo", "bar"]
-
- r.set_path_components([], f)
- assert r.get_path_components(f) == []
- r.set_path_components(["foo"], f)
- assert r.get_path_components(f) == ["foo"]
- r.set_path_components(["/oo"], f)
- assert r.get_path_components(f) == ["/oo"]
+ r.set_query(q)
+ assert r.get_path_components() == ["foo", "bar"]
+
+ r.set_path_components([])
+ assert r.get_path_components() == []
+ r.set_path_components(["foo"])
+ assert r.get_path_components() == ["foo"]
+ r.set_path_components(["/oo"])
+ assert r.get_path_components() == ["/oo"]
assert "%2F" in r.path
def test_getset_form_urlencoded(self):
@@ -828,26 +826,26 @@ class TestRequest:
def test_getset_query(self):
h = flow.ODictCaseless()
- f = tutils.tflow()
- f.request.path = "/foo?x=y&a=b"
- q = f.request.get_query(f)
+ r = tutils.treq()
+ r.path = "/foo?x=y&a=b"
+ q = r.get_query()
assert q.lst == [("x", "y"), ("a", "b")]
- f.request.path = "/"
- q = f.request.get_query(f)
+ r.path = "/"
+ q = r.get_query()
assert not q
- f.request.path = "/?adsfa"
- q = f.request.get_query(f)
+ r.path = "/?adsfa"
+ q = r.get_query()
assert q.lst == [("adsfa", "")]
- f.request.path = "/foo?x=y&a=b"
- assert f.request.get_query(f)
- f.request.set_query(flow.ODict([]), f)
- assert not f.request.get_query(f)
+ r.path = "/foo?x=y&a=b"
+ assert r.get_query()
+ r.set_query(flow.ODict([]))
+ assert not r.get_query()
qv = flow.ODict([("a", "b"), ("c", "d")])
- f.request.set_query(qv, f)
- assert f.request.get_query(f) == qv
+ r.set_query(qv)
+ assert r.get_query() == qv
def test_anticache(self):
h = flow.ODictCaseless()
@@ -968,18 +966,18 @@ class TestResponse:
def test_simple(self):
f = tutils.tflow(resp=True)
resp = f.response
- assert resp._assemble()
- assert resp.size() == len(resp._assemble())
+ assert resp.assemble()
+ assert resp.size() == len(resp.assemble())
resp2 = resp.copy()
assert resp2 == resp
resp.content = None
- assert resp._assemble()
- assert resp.size() == len(resp._assemble())
+ assert resp.assemble()
+ assert resp.size() == len(resp.assemble())
resp.content = CONTENT_MISSING
- tutils.raises("Cannot assemble flow with CONTENT_MISSING", resp._assemble)
+ tutils.raises("Cannot assemble flow with CONTENT_MISSING", resp.assemble)
def test_refresh(self):
r = tutils.tresp()
diff --git a/test/test_protocol_http.py b/test/test_protocol_http.py
index c2ff7b44..ea6cf3fd 100644
--- a/test/test_protocol_http.py
+++ b/test/test_protocol_http.py
@@ -1,5 +1,4 @@
from libmproxy.protocol.http import *
-from libmproxy.protocol import KILL
from cStringIO import StringIO
import tutils, tservers
@@ -32,11 +31,27 @@ class TestHTTPRequest:
f.request.host = f.server_conn.address.host
f.request.port = f.server_conn.address.port
f.request.scheme = "http"
- assert f.request._assemble() == "OPTIONS * HTTP/1.1\r\nHost: address:22\r\n\r\n"
+ assert f.request.assemble() == "OPTIONS * HTTP/1.1\r\nHost: address:22\r\n\r\n"
def test_origin_form(self):
s = StringIO("GET /foo\xff HTTP/1.1")
tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s)
+ s = StringIO("GET /foo HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: h2c")
+ r = HTTPRequest.from_stream(s)
+ assert r.headers["Upgrade"] == ["h2c"]
+
+ raw = r._assemble_headers()
+ assert "Upgrade" not in raw
+ assert "Host" not in raw
+
+ r.url = "http://example.com/foo"
+
+ raw = r._assemble_headers()
+ assert "Host" in raw
+ assert not "Host" in r.headers
+ r.update_host_header()
+ assert "Host" in r.headers
+
def test_authority_form(self):
s = StringIO("CONNECT oops-no-port.com HTTP/1.1")
@@ -44,26 +59,31 @@ class TestHTTPRequest:
s = StringIO("CONNECT address:22 HTTP/1.1")
r = HTTPRequest.from_stream(s)
r.scheme, r.host, r.port = "http", "address", 22
- assert r._assemble() == "CONNECT address:22 HTTP/1.1\r\nHost: address:22\r\n\r\n"
+ assert r.assemble() == "CONNECT address:22 HTTP/1.1\r\nHost: address:22\r\n\r\n"
+ assert r.pretty_url(False) == "address:22"
def test_absolute_form(self):
s = StringIO("GET oops-no-protocol.com HTTP/1.1")
tutils.raises("Bad HTTP request line", HTTPRequest.from_stream, s)
s = StringIO("GET http://address:22/ HTTP/1.1")
r = HTTPRequest.from_stream(s)
- assert r._assemble() == "GET http://address:22/ HTTP/1.1\r\nHost: address:22\r\n\r\n"
+ assert r.assemble() == "GET http://address:22/ HTTP/1.1\r\nHost: address:22\r\n\r\n"
def test_assemble_unknown_form(self):
r = tutils.treq()
- tutils.raises("Invalid request form", r._assemble, "antiauthority")
+ tutils.raises("Invalid request form", r.assemble, "antiauthority")
def test_set_url(self):
- f = tutils.tflow(req=tutils.treq_absolute())
- f.request.set_url("https://otheraddress:42/ORLY", f)
- assert f.request.scheme == "https"
- assert f.request.host == "otheraddress"
- assert f.request.port == 42
- assert f.request.path == "/ORLY"
+ r = tutils.treq_absolute()
+ r.url = "https://otheraddress:42/ORLY"
+ assert r.scheme == "https"
+ assert r.host == "otheraddress"
+ assert r.port == 42
+ assert r.path == "/ORLY"
+
+ def test_repr(self):
+ r = tutils.treq()
+ assert repr(r)
class TestHTTPResponse:
@@ -86,6 +106,19 @@ class TestHTTPResponse:
assert r.content == ""
tutils.raises("Invalid server response: 'content", HTTPResponse.from_stream, s, "GET")
+ def test_repr(self):
+ r = tutils.tresp()
+ assert "unknown content type" in repr(r)
+ r.headers["content-type"] = ["foo"]
+ assert "foo" in repr(r)
+ assert repr(tutils.tresp(content=CONTENT_MISSING))
+
+
+class TestHTTPFlow(object):
+ def test_repr(self):
+ f = tutils.tflow(resp=True, err=True)
+ assert repr(f)
+
class TestInvalidRequests(tservers.HTTPProxTest):
ssl = True
@@ -100,120 +133,4 @@ class TestInvalidRequests(tservers.HTTPProxTest):
p.connect()
r = p.request("get:/p/200")
assert r.status_code == 400
- assert "Invalid HTTP request form" in r.content
-
-
-class TestProxyChaining(tservers.HTTPChainProxyTest):
- def test_all(self):
- self.chain[1].tmaster.replacehooks.add("~q", "foo", "bar") # replace in request
- self.chain[0].tmaster.replacehooks.add("~q", "foo", "oh noes!")
- self.proxy.tmaster.replacehooks.add("~q", "bar", "baz")
- self.chain[0].tmaster.replacehooks.add("~s", "baz", "ORLY") # replace in response
-
- p = self.pathoc()
- req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
- assert req.content == "ORLY"
- assert req.status_code == 418
-
-class TestProxyChainingSSL(tservers.HTTPChainProxyTest):
- ssl = True
- def test_simple(self):
- p = self.pathoc()
- req = p.request("get:'/p/418:b\"content\"'")
- assert req.content == "content"
- assert req.status_code == 418
-
- assert self.chain[1].tmaster.state.flow_count() == 2 # CONNECT from pathoc to chain[0],
- # request from pathoc to chain[0]
- assert self.chain[0].tmaster.state.flow_count() == 2 # CONNECT from chain[1] to proxy,
- # request from chain[1] to proxy
- assert self.proxy.tmaster.state.flow_count() == 1 # request from chain[0] (regular proxy doesn't store CONNECTs)
-
- def test_closing_connect_response(self):
- """
- https://github.com/mitmproxy/mitmproxy/issues/313
- """
- def handle_request(f):
- f.request.httpversion = (1, 0)
- del f.request.headers["Content-Length"]
- f.reply()
- _handle_request = self.chain[0].tmaster.handle_request
- self.chain[0].tmaster.handle_request = handle_request
- try:
- assert self.pathoc().request("get:/p/418").status_code == 418
- finally:
- self.chain[0].tmaster.handle_request = _handle_request
-
- def test_sni(self):
- p = self.pathoc(sni="foo.com")
- req = p.request("get:'/p/418:b\"content\"'")
- assert req.content == "content"
- assert req.status_code == 418
-
-class TestProxyChainingSSLReconnect(tservers.HTTPChainProxyTest):
- ssl = True
-
- def test_reconnect(self):
- """
- Tests proper functionality of ConnectionHandler.server_reconnect mock.
- If we have a disconnect on a secure connection that's transparently proxified to
- an upstream http proxy, we need to send the CONNECT request again.
- """
- def kill_requests(master, attr, exclude):
- k = [0] # variable scope workaround: put into array
- _func = getattr(master, attr)
- def handler(f):
- k[0] += 1
- if not (k[0] in exclude):
- f.client_conn.finish()
- f.error = Error("terminated")
- f.reply(KILL)
- return _func(f)
- setattr(master, attr, handler)
-
- kill_requests(self.proxy.tmaster, "handle_request",
- exclude=[
- # fail first request
- 2, # allow second request
- ])
-
- kill_requests(self.chain[0].tmaster, "handle_request",
- exclude=[
- 1, # CONNECT
- # fail first request
- 3, # reCONNECT
- 4, # request
- ])
-
- p = self.pathoc()
- req = p.request("get:'/p/418:b\"content\"'")
- assert self.chain[1].tmaster.state.flow_count() == 2 # CONNECT and request
- assert self.chain[0].tmaster.state.flow_count() == 4 # CONNECT, failing request,
- # reCONNECT, request
- assert self.proxy.tmaster.state.flow_count() == 2 # failing request, request
- # (doesn't store (repeated) CONNECTs from chain[0]
- # as it is a regular proxy)
- assert req.content == "content"
- assert req.status_code == 418
-
- assert not self.proxy.tmaster.state._flow_list[0].response # killed
- assert self.proxy.tmaster.state._flow_list[1].response
-
- assert self.chain[1].tmaster.state._flow_list[0].request.form_in == "authority"
- assert self.chain[1].tmaster.state._flow_list[1].request.form_in == "relative"
-
- assert self.chain[0].tmaster.state._flow_list[0].request.form_in == "authority"
- assert self.chain[0].tmaster.state._flow_list[1].request.form_in == "relative"
- assert self.chain[0].tmaster.state._flow_list[2].request.form_in == "authority"
- assert self.chain[0].tmaster.state._flow_list[3].request.form_in == "relative"
-
- assert self.proxy.tmaster.state._flow_list[0].request.form_in == "relative"
- assert self.proxy.tmaster.state._flow_list[1].request.form_in == "relative"
-
- req = p.request("get:'/p/418:b\"content2\"'")
-
- assert req.status_code == 502
- assert self.chain[1].tmaster.state.flow_count() == 3 # + new request
- assert self.chain[0].tmaster.state.flow_count() == 6 # + new request, repeated CONNECT from chain[1]
- # (both terminated)
- assert self.proxy.tmaster.state.flow_count() == 2 # nothing happened here
+ assert "Invalid HTTP request form" in r.content \ No newline at end of file
diff --git a/test/test_proxy.py b/test/test_proxy.py
index 2e206529..2f455992 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -23,25 +23,34 @@ class TestServerConnection:
self.d.shutdown()
def test_simple(self):
- sc = ServerConnection((self.d.IFACE, self.d.port), None)
+ sc = ServerConnection((self.d.IFACE, self.d.port))
sc.connect()
f = tutils.tflow()
f.server_conn = sc
f.request.path = "/p/200:da"
- sc.send(f.request._assemble())
+ sc.send(f.request.assemble())
assert http.read_response(sc.rfile, f.request.method, 1000)
assert self.d.last_log()
sc.finish()
def test_terminate_error(self):
- sc = ServerConnection((self.d.IFACE, self.d.port), None)
+ sc = ServerConnection((self.d.IFACE, self.d.port))
sc.connect()
sc.connection = mock.Mock()
sc.connection.recv = mock.Mock(return_value=False)
sc.connection.flush = mock.Mock(side_effect=tcp.NetLibDisconnect)
sc.finish()
+ def test_repr(self):
+ sc = tutils.tserver_conn()
+ assert "address:22" in repr(sc)
+ assert "ssl" not in repr(sc)
+ sc.ssl_established = True
+ assert "ssl" in repr(sc)
+ sc.sni = "foo"
+ assert "foo" in repr(sc)
+
class TestProcessProxyOptions:
def p(self, *args):
@@ -112,7 +121,7 @@ class TestProcessProxyOptions:
class TestProxyServer:
- @tutils.SkipWindows # binding to 0.0.0.0:1 works without special permissions on Windows
+ @tutils.SkipWindows # binding to 0.0.0.0:1 works without special permissions on Windows
def test_err(self):
parser = argparse.ArgumentParser()
cmdline.common_options(parser)
diff --git a/test/test_script.py b/test/test_script.py
index 7c421fde..aed7def1 100644
--- a/test/test_script.py
+++ b/test/test_script.py
@@ -99,7 +99,7 @@ class TestScript:
d = Dummy()
assert s.run(hook, d)[0]
d.reply()
- while (time.time() - t_start) < 5 and m.call_count <= 5:
+ while (time.time() - t_start) < 20 and m.call_count <= 5:
if m.call_count == 5:
return
time.sleep(0.001)
diff --git a/test/test_server.py b/test/test_server.py
index 6c0829f4..a6d591ed 100644
--- a/test/test_server.py
+++ b/test/test_server.py
@@ -1,10 +1,10 @@
import socket, time
-import mock
+from libmproxy.proxy.config import ProxyConfig
from netlib import tcp, http_auth, http
from libpathod import pathoc, pathod
+from netlib.certutils import SSLCert
import tutils, tservers
-from libmproxy import flow
-from libmproxy.protocol import KILL
+from libmproxy.protocol import KILL, Error
from libmproxy.protocol.http import CONTENT_MISSING
"""
@@ -21,8 +21,11 @@ class CommonMixin:
def test_replay(self):
assert self.pathod("304").status_code == 304
- assert len(self.master.state.view) == 1
- l = self.master.state.view[0]
+ if isinstance(self, tservers.HTTPUpstreamProxTest) and self.ssl:
+ assert len(self.master.state.view) == 2
+ else:
+ assert len(self.master.state.view) == 1
+ l = self.master.state.view[-1]
assert l.response.code == 304
l.request.path = "/p/305"
rt = self.master.replay_request(l, block=True)
@@ -31,18 +34,28 @@ class CommonMixin:
# Disconnect error
l.request.path = "/p/305:d0"
rt = self.master.replay_request(l, block=True)
- assert l.error
+ assert not rt
+ if isinstance(self, tservers.HTTPUpstreamProxTest):
+ assert l.response.code == 502
+ else:
+ assert l.error
# Port error
l.request.port = 1
- self.master.replay_request(l, block=True)
- assert l.error
+ # In upstream mode, we get a 502 response from the upstream proxy server.
+ # In upstream mode with ssl, the replay will fail as we cannot establish SSL with the upstream proxy.
+ rt = self.master.replay_request(l, block=True)
+ assert not rt
+ if isinstance(self, tservers.HTTPUpstreamProxTest) and not self.ssl:
+ assert l.response.code == 502
+ else:
+ assert l.error
def test_http(self):
f = self.pathod("304")
assert f.status_code == 304
- l = self.master.state.view[0]
+ l = self.master.state.view[-1] # In Upstream mode with SSL, we may already have a previous CONNECT request.
assert l.client_conn.address
assert "host" in l.request.headers
assert l.response.code == 304
@@ -55,6 +68,51 @@ class CommonMixin:
line = t.rfile.readline()
assert ("Bad Request" in line) or ("Bad Gateway" in line)
+ def test_sni(self):
+ if not self.ssl:
+ return
+
+ f = self.pathod("304", sni="testserver.com")
+ assert f.status_code == 304
+ log = self.server.last_log()
+ assert log["request"]["sni"] == "testserver.com"
+
+class TcpMixin:
+ def _ignore_on(self):
+ conf = ProxyConfig(ignore=[".+:%s" % self.server.port])
+ self.config.ignore.append(conf.ignore[0])
+
+ def _ignore_off(self):
+ self.config.ignore.pop()
+
+ def test_ignore(self):
+ spec = '304:h"Alternate-Protocol"="mitmproxy-will-remove-this"'
+ n = self.pathod(spec)
+ self._ignore_on()
+ i = self.pathod(spec)
+ i2 = self.pathod(spec)
+ self._ignore_off()
+
+ assert i.status_code == i2.status_code == n.status_code == 304
+ assert "Alternate-Protocol" in i.headers
+ assert "Alternate-Protocol" in i2.headers
+ assert "Alternate-Protocol" not in n.headers
+
+ # Test that we get the original SSL cert
+ if self.ssl:
+ i_cert = SSLCert(i.sslinfo.certchain[0])
+ i2_cert = SSLCert(i2.sslinfo.certchain[0])
+ n_cert = SSLCert(n.sslinfo.certchain[0])
+
+ assert i_cert == i2_cert
+ assert i_cert != n_cert
+
+ # Test Non-HTTP traffic
+ spec = "200:i0,@100:d0" # this results in just 100 random bytes
+ assert self.pathod(spec).status_code == 502 # mitmproxy responds with bad gateway
+ self._ignore_on()
+ tutils.raises("invalid server response", self.pathod, spec) # pathoc tries to parse answer as HTTP
+ self._ignore_off()
class AppMixin:
@@ -64,7 +122,6 @@ class AppMixin:
assert "mitmproxy" in ret.content
-
class TestHTTP(tservers.HTTPProxTest, CommonMixin, AppMixin):
def test_app_err(self):
p = self.pathoc()
@@ -175,7 +232,7 @@ class TestHTTPConnectSSLError(tservers.HTTPProxTest):
tutils.raises("502 - Bad Gateway", p.http_connect, dst)
-class TestHTTPS(tservers.HTTPProxTest, CommonMixin):
+class TestHTTPS(tservers.HTTPProxTest, CommonMixin, TcpMixin):
ssl = True
ssloptions = pathod.SSLOptions(request_client_cert=True)
clientcerts = True
@@ -184,12 +241,6 @@ class TestHTTPS(tservers.HTTPProxTest, CommonMixin):
assert f.status_code == 304
assert self.server.last_log()["request"]["clientcert"]["keyinfo"]
- def test_sni(self):
- f = self.pathod("304", sni="testserver.com")
- assert f.status_code == 304
- l = self.server.last_log()
- assert self.server.last_log()["request"]["sni"] == "testserver.com"
-
def test_error_post_connect(self):
p = self.pathoc()
assert p.request("get:/:i0,'invalid\r\n\r\n'").status_code == 400
@@ -217,21 +268,16 @@ class TestHTTPSNoCommonName(tservers.HTTPProxTest):
assert f.sslinfo.certchain[0].get_subject().CN == "127.0.0.1"
-class TestReverse(tservers.ReverseProxTest, CommonMixin):
+class TestReverse(tservers.ReverseProxTest, CommonMixin, TcpMixin):
reverse = True
-class TestTransparent(tservers.TransparentProxTest, CommonMixin):
+class TestTransparent(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = False
-class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin):
+class TestTransparentSSL(tservers.TransparentProxTest, CommonMixin, TcpMixin):
ssl = True
- def test_sni(self):
- f = self.pathod("304", sni="testserver.com")
- assert f.status_code == 304
- l = self.server.last_log()
- assert l["request"]["sni"] == "testserver.com"
def test_sslerr(self):
p = pathoc.Pathoc(("localhost", self.proxy.port))
@@ -312,7 +358,7 @@ class TestProxy(tservers.HTTPProxTest):
f = self.pathod("200:b@100")
assert f.status_code == 200
f = self.master.state.view[0]
- assert f.server_conn.peername == ("127.0.0.1", self.server.port)
+ assert f.server_conn.address == ("127.0.0.1", self.server.port)
class TestProxySSL(tservers.HTTPProxTest):
ssl=True
@@ -330,21 +376,19 @@ class MasterRedirectRequest(tservers.TestMaster):
def handle_request(self, f):
request = f.request
if request.path == "/p/201":
- url = request.get_url(False, f)
+ url = request.url
new = "http://127.0.0.1:%s/p/201" % self.redirect_port
- request.set_url(new, f)
- request.set_url(new, f)
+ request.url = new
f.live.change_server(("127.0.0.1", self.redirect_port), False)
- request.set_url(url, f)
+ request.url = url
tutils.raises("SSL handshake error", f.live.change_server, ("127.0.0.1", self.redirect_port), True)
- request.set_url(new, f)
- request.set_url(url, f)
- request.set_url(new, f)
+ request.url = new
tservers.TestMaster.handle_request(self, f)
def handle_response(self, f):
f.response.content = str(f.client_conn.address.port)
+ f.response.headers["server-conn-id"] = [str(f.server_conn.source_address.port)]
tservers.TestMaster.handle_response(self, f)
@@ -377,7 +421,8 @@ class TestRedirectRequest(tservers.HTTPProxTest):
assert self.server.last_log()
assert not self.server2.last_log()
- assert r3.content == r2.content == r1.content
+ assert r1.content == r2.content == r3.content
+ assert r1.headers.get_first("server-conn-id") == r3.headers.get_first("server-conn-id")
# Make sure that we actually use the same connection in this test case
class MasterStreamRequest(tservers.TestMaster):
@@ -502,6 +547,132 @@ class TestIncompleteResponse(tservers.HTTPProxTest):
class TestCertForward(tservers.HTTPProxTest):
certforward = True
ssl = True
+
def test_app_err(self):
tutils.raises("handshake error", self.pathod, "200:b@100")
+
+class TestUpstreamProxy(tservers.HTTPUpstreamProxTest, CommonMixin, AppMixin):
+ ssl = False
+
+ def test_order(self):
+ self.proxy.tmaster.replacehooks.add("~q", "foo", "bar") # replace in request
+ self.chain[0].tmaster.replacehooks.add("~q", "bar", "baz")
+ self.chain[1].tmaster.replacehooks.add("~q", "foo", "oh noes!")
+ self.chain[0].tmaster.replacehooks.add("~s", "baz", "ORLY") # replace in response
+
+ p = self.pathoc()
+ req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase)
+ assert req.content == "ORLY"
+ assert req.status_code == 418
+
+
+class TestUpstreamProxySSL(tservers.HTTPUpstreamProxTest, CommonMixin, TcpMixin):
+ ssl = True
+
+ def _ignore_on(self):
+ super(TestUpstreamProxySSL, self)._ignore_on()
+ conf = ProxyConfig(ignore=[".+:%s" % self.server.port])
+ for proxy in self.chain:
+ proxy.tmaster.server.config.ignore.append(conf.ignore[0])
+
+ def _ignore_off(self):
+ super(TestUpstreamProxySSL, self)._ignore_off()
+ for proxy in self.chain:
+ proxy.tmaster.server.config.ignore.pop()
+
+ def test_simple(self):
+ p = self.pathoc()
+ req = p.request("get:'/p/418:b\"content\"'")
+ assert req.content == "content"
+ assert req.status_code == 418
+
+ assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT from pathoc to chain[0],
+ # request from pathoc to chain[0]
+ assert self.chain[0].tmaster.state.flow_count() == 2 # CONNECT from proxy to chain[1],
+ # request from proxy to chain[1]
+ assert self.chain[1].tmaster.state.flow_count() == 1 # request from chain[0] (regular proxy doesn't store CONNECTs)
+
+ def test_closing_connect_response(self):
+ """
+ https://github.com/mitmproxy/mitmproxy/issues/313
+ """
+ def handle_request(f):
+ f.request.httpversion = (1, 0)
+ del f.request.headers["Content-Length"]
+ f.reply()
+ _handle_request = self.chain[0].tmaster.handle_request
+ self.chain[0].tmaster.handle_request = handle_request
+ try:
+ assert self.pathoc().request("get:/p/418").status_code == 418
+ finally:
+ self.chain[0].tmaster.handle_request = _handle_request
+
+
+class TestProxyChainingSSLReconnect(tservers.HTTPUpstreamProxTest):
+ ssl = True
+
+ def test_reconnect(self):
+ """
+ Tests proper functionality of ConnectionHandler.server_reconnect mock.
+ If we have a disconnect on a secure connection that's transparently proxified to
+ an upstream http proxy, we need to send the CONNECT request again.
+ """
+ def kill_requests(master, attr, exclude):
+ k = [0] # variable scope workaround: put into array
+ _func = getattr(master, attr)
+ def handler(f):
+ k[0] += 1
+ if not (k[0] in exclude):
+ f.client_conn.finish()
+ f.error = Error("terminated")
+ f.reply(KILL)
+ return _func(f)
+ setattr(master, attr, handler)
+
+ kill_requests(self.chain[1].tmaster, "handle_request",
+ exclude=[
+ # fail first request
+ 2, # allow second request
+ ])
+
+ kill_requests(self.chain[0].tmaster, "handle_request",
+ exclude=[
+ 1, # CONNECT
+ # fail first request
+ 3, # reCONNECT
+ 4, # request
+ ])
+
+ p = self.pathoc()
+ req = p.request("get:'/p/418:b\"content\"'")
+ assert self.proxy.tmaster.state.flow_count() == 2 # CONNECT and request
+ assert self.chain[0].tmaster.state.flow_count() == 4 # CONNECT, failing request,
+ # reCONNECT, request
+ assert self.chain[1].tmaster.state.flow_count() == 2 # failing request, request
+ # (doesn't store (repeated) CONNECTs from chain[0]
+ # as it is a regular proxy)
+ assert req.content == "content"
+ assert req.status_code == 418
+
+ assert not self.chain[1].tmaster.state._flow_list[0].response # killed
+ assert self.chain[1].tmaster.state._flow_list[1].response
+
+ assert self.proxy.tmaster.state._flow_list[0].request.form_in == "authority"
+ assert self.proxy.tmaster.state._flow_list[1].request.form_in == "relative"
+
+ assert self.chain[0].tmaster.state._flow_list[0].request.form_in == "authority"
+ assert self.chain[0].tmaster.state._flow_list[1].request.form_in == "relative"
+ assert self.chain[0].tmaster.state._flow_list[2].request.form_in == "authority"
+ assert self.chain[0].tmaster.state._flow_list[3].request.form_in == "relative"
+
+ assert self.chain[1].tmaster.state._flow_list[0].request.form_in == "relative"
+ assert self.chain[1].tmaster.state._flow_list[1].request.form_in == "relative"
+
+ req = p.request("get:'/p/418:b\"content2\"'")
+
+ assert req.status_code == 502
+ assert self.proxy.tmaster.state.flow_count() == 3 # + new request
+ assert self.chain[0].tmaster.state.flow_count() == 6 # + new request, repeated CONNECT from chain[1]
+ # (both terminated)
+ assert self.chain[1].tmaster.state.flow_count() == 2 # nothing happened here
diff --git a/test/tservers.py b/test/tservers.py
index 9f2abbe1..8a2e72a4 100644
--- a/test/tservers.py
+++ b/test/tservers.py
@@ -84,29 +84,19 @@ class ProxTestBase(object):
masterclass = TestMaster
externalapp = False
certforward = False
+
@classmethod
def setupAll(cls):
cls.server = libpathod.test.Daemon(ssl=cls.ssl, ssloptions=cls.ssloptions)
cls.server2 = libpathod.test.Daemon(ssl=cls.ssl, ssloptions=cls.ssloptions)
- pconf = cls.get_proxy_config()
- cls.confdir = os.path.join(tempfile.gettempdir(), "mitmproxy")
- cls.config = ProxyConfig(
- no_upstream_cert = cls.no_upstream_cert,
- confdir = cls.confdir,
- authenticator = cls.authenticator,
- certforward = cls.certforward,
- ssl_ports=([cls.server.port, cls.server2.port] if cls.ssl else []),
- **pconf
- )
+
+ cls.config = ProxyConfig(**cls.get_proxy_config())
+
tmaster = cls.masterclass(cls.config)
tmaster.start_app(APP_HOST, APP_PORT, cls.externalapp)
cls.proxy = ProxyThread(tmaster)
cls.proxy.start()
- @property
- def master(cls):
- return cls.proxy.tmaster
-
@classmethod
def teardownAll(cls):
shutil.rmtree(cls.confdir)
@@ -121,24 +111,20 @@ class ProxTestBase(object):
self.server2.clear_log()
@property
- def scheme(self):
- return "https" if self.ssl else "http"
-
- @property
- def proxies(self):
- """
- The URL base for the server instance.
- """
- return (
- (self.scheme, ("127.0.0.1", self.proxy.port))
- )
+ def master(self):
+ return self.proxy.tmaster
@classmethod
def get_proxy_config(cls):
- d = dict()
- if cls.clientcerts:
- d["clientcerts"] = tutils.test_data.path("data/clientcert")
- return d
+ cls.confdir = os.path.join(tempfile.gettempdir(), "mitmproxy")
+ return dict(
+ no_upstream_cert = cls.no_upstream_cert,
+ confdir = cls.confdir,
+ authenticator = cls.authenticator,
+ certforward = cls.certforward,
+ ssl_ports=([cls.server.port, cls.server2.port] if cls.ssl else []),
+ clientcerts = tutils.test_data.path("data/clientcert") if cls.clientcerts else None
+ )
class HTTPProxTest(ProxTestBase):
@@ -265,49 +251,50 @@ class ReverseProxTest(ProxTestBase):
class ChainProxTest(ProxTestBase):
"""
- Chain n instances of mitmproxy in a row - because we can.
+ Chain three instances of mitmproxy in a row to test upstream mode.
+ Proxy order is cls.proxy -> cls.chain[0] -> cls.chain[1]
+ cls.proxy and cls.chain[0] are in upstream mode,
+ cls.chain[1] is in regular mode.
"""
+ chain = None
n = 2
- chain_config = [lambda port, sslports: ProxyConfig(
- upstream_server= (False, False, "127.0.0.1", port),
- http_form_in = "absolute",
- http_form_out = "absolute",
- ssl_ports=sslports
- )] * n
+
@classmethod
def setupAll(cls):
- super(ChainProxTest, cls).setupAll()
cls.chain = []
- for i in range(cls.n):
- sslports = [cls.server.port, cls.server2.port]
- config = cls.chain_config[i](cls.proxy.port if i == 0 else cls.chain[-1].port,
- sslports)
+ super(ChainProxTest, cls).setupAll()
+ for _ in range(cls.n):
+ config = ProxyConfig(**cls.get_proxy_config())
tmaster = cls.masterclass(config)
- tmaster.start_app(APP_HOST, APP_PORT, cls.externalapp)
- cls.chain.append(ProxyThread(tmaster))
- cls.chain[-1].start()
+ proxy = ProxyThread(tmaster)
+ proxy.start()
+ cls.chain.insert(0, proxy)
+
+ # Patch the orginal proxy to upstream mode
+ cls.config = cls.proxy.tmaster.config = cls.proxy.tmaster.server.config = ProxyConfig(**cls.get_proxy_config())
+
@classmethod
def teardownAll(cls):
super(ChainProxTest, cls).teardownAll()
- for p in cls.chain:
- p.tmaster.shutdown()
+ for proxy in cls.chain:
+ proxy.shutdown()
def setUp(self):
super(ChainProxTest, self).setUp()
- for p in self.chain:
- p.tmaster.clear_log()
- p.tmaster.state.clear()
+ for proxy in self.chain:
+ proxy.tmaster.clear_log()
+ proxy.tmaster.state.clear()
+ @classmethod
+ def get_proxy_config(cls):
+ d = super(ChainProxTest, cls).get_proxy_config()
+ if cls.chain: # First proxy is in normal mode.
+ d.update(
+ mode="upstream",
+ upstream_server=(False, False, "127.0.0.1", cls.chain[0].port)
+ )
+ return d
-class HTTPChainProxyTest(ChainProxTest):
- def pathoc(self, sni=None):
- """
- Returns a connected Pathoc instance.
- """
- p = libpathod.pathoc.Pathoc(("localhost", self.chain[-1].port), ssl=self.ssl, sni=sni)
- if self.ssl:
- p.connect(("127.0.0.1", self.server.port))
- else:
- p.connect()
- return p
+class HTTPUpstreamProxTest(ChainProxTest, HTTPProxTest):
+ pass \ No newline at end of file