aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/test/tservers.py
diff options
context:
space:
mode:
Diffstat (limited to 'mitmproxy/test/tservers.py')
-rw-r--r--mitmproxy/test/tservers.py329
1 files changed, 329 insertions, 0 deletions
diff --git a/mitmproxy/test/tservers.py b/mitmproxy/test/tservers.py
new file mode 100644
index 00000000..dbc9f7d0
--- /dev/null
+++ b/mitmproxy/test/tservers.py
@@ -0,0 +1,329 @@
+import os.path
+import threading
+import tempfile
+import flask
+import mock
+
+from libmproxy.proxy.config import ProxyConfig
+from libmproxy.proxy.server import ProxyServer
+import libpathod.test
+import libpathod.pathoc
+from libmproxy import flow, controller
+from libmproxy.cmdline import APP_HOST, APP_PORT
+
+testapp = flask.Flask(__name__)
+
+
+@testapp.route("/")
+def hello():
+ return "testapp"
+
+
+@testapp.route("/error")
+def error():
+ raise ValueError("An exception...")
+
+
+def errapp(environ, start_response):
+ raise ValueError("errapp")
+
+
+class TestMaster(flow.FlowMaster):
+
+ def __init__(self, config):
+ config.port = 0
+ s = ProxyServer(config)
+ state = flow.State()
+ flow.FlowMaster.__init__(self, s, state)
+ self.apps.add(testapp, "testapp", 80)
+ self.apps.add(errapp, "errapp", 80)
+ self.clear_log()
+
+ def handle_request(self, f):
+ flow.FlowMaster.handle_request(self, f)
+ f.reply()
+
+ def handle_response(self, f):
+ flow.FlowMaster.handle_response(self, f)
+ f.reply()
+
+ def clear_log(self):
+ self.log = []
+
+ def handle_log(self, l):
+ self.log.append(l.msg)
+ l.reply()
+
+
+class ProxyThread(threading.Thread):
+
+ def __init__(self, tmaster):
+ threading.Thread.__init__(self)
+ self.tmaster = tmaster
+ self.name = "ProxyThread (%s:%s)" % (
+ tmaster.server.address.host, tmaster.server.address.port)
+ controller.should_exit = False
+
+ @property
+ def port(self):
+ return self.tmaster.server.address.port
+
+ @property
+ def log(self):
+ return self.tmaster.log
+
+ def run(self):
+ self.tmaster.run()
+
+ def shutdown(self):
+ self.tmaster.shutdown()
+
+
+class ProxTestBase(object):
+ # Test Configuration
+ ssl = None
+ ssloptions = False
+ no_upstream_cert = False
+ authenticator = None
+ masterclass = TestMaster
+
+ @classmethod
+ def setup_class(cls):
+ cls.server = libpathod.test.Daemon(
+ ssl=cls.ssl,
+ ssloptions=cls.ssloptions)
+ cls.server2 = libpathod.test.Daemon(
+ ssl=cls.ssl,
+ ssloptions=cls.ssloptions)
+
+ cls.config = ProxyConfig(**cls.get_proxy_config())
+
+ tmaster = cls.masterclass(cls.config)
+ tmaster.start_app(APP_HOST, APP_PORT)
+ cls.proxy = ProxyThread(tmaster)
+ cls.proxy.start()
+
+ @classmethod
+ def teardown_class(cls):
+ # perf: we want to run tests in parallell
+ # should this ever cause an error, travis should catch it.
+ # shutil.rmtree(cls.cadir)
+ cls.proxy.shutdown()
+ cls.server.shutdown()
+ cls.server2.shutdown()
+
+ def setup(self):
+ self.master.clear_log()
+ self.master.state.clear()
+ self.server.clear_log()
+ self.server2.clear_log()
+
+ @property
+ def master(self):
+ return self.proxy.tmaster
+
+ @classmethod
+ def get_proxy_config(cls):
+ cls.cadir = os.path.join(tempfile.gettempdir(), "mitmproxy")
+ return dict(
+ no_upstream_cert = cls.no_upstream_cert,
+ cadir = cls.cadir,
+ authenticator = cls.authenticator,
+ )
+
+
+class HTTPProxTest(ProxTestBase):
+
+ def pathoc_raw(self):
+ return libpathod.pathoc.Pathoc(("127.0.0.1", self.proxy.port), fp=None)
+
+ def pathoc(self, sni=None):
+ """
+ Returns a connected Pathoc instance.
+ """
+ p = libpathod.pathoc.Pathoc(
+ ("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
+ )
+ if self.ssl:
+ p.connect(("127.0.0.1", self.server.port))
+ else:
+ p.connect()
+ return p
+
+ def pathod(self, spec, sni=None):
+ """
+ Constructs a pathod GET request, with the appropriate base and proxy.
+ """
+ p = self.pathoc(sni=sni)
+ spec = spec.encode("string_escape")
+ if self.ssl:
+ q = "get:'/p/%s'" % spec
+ else:
+ q = "get:'%s/p/%s'" % (self.server.urlbase, spec)
+ return p.request(q)
+
+ def app(self, page):
+ if self.ssl:
+ p = libpathod.pathoc.Pathoc(
+ ("127.0.0.1", self.proxy.port), True, fp=None
+ )
+ p.connect((APP_HOST, APP_PORT))
+ return p.request("get:'%s'" % page)
+ else:
+ p = self.pathoc()
+ return p.request("get:'http://%s%s'" % (APP_HOST, page))
+
+
+class TResolver:
+
+ def __init__(self, port):
+ self.port = port
+
+ def original_addr(self, sock):
+ return ("127.0.0.1", self.port)
+
+
+class TransparentProxTest(ProxTestBase):
+ ssl = None
+ resolver = TResolver
+
+ @classmethod
+ def setup_class(cls):
+ super(TransparentProxTest, cls).setup_class()
+
+ cls._resolver = mock.patch(
+ "libmproxy.platform.resolver",
+ new=lambda: cls.resolver(cls.server.port)
+ )
+ cls._resolver.start()
+
+ @classmethod
+ def teardown_class(cls):
+ cls._resolver.stop()
+ super(TransparentProxTest, cls).teardown_class()
+
+ @classmethod
+ def get_proxy_config(cls):
+ d = ProxTestBase.get_proxy_config()
+ d["mode"] = "transparent"
+ return d
+
+ def pathod(self, spec, sni=None):
+ """
+ Constructs a pathod GET request, with the appropriate base and proxy.
+ """
+ if self.ssl:
+ p = self.pathoc(sni=sni)
+ q = "get:'/p/%s'" % spec
+ else:
+ p = self.pathoc()
+ q = "get:'/p/%s'" % spec
+ return p.request(q)
+
+ def pathoc(self, sni=None):
+ """
+ Returns a connected Pathoc instance.
+ """
+ p = libpathod.pathoc.Pathoc(
+ ("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
+ )
+ p.connect()
+ return p
+
+
+class ReverseProxTest(ProxTestBase):
+ ssl = None
+
+ @classmethod
+ def get_proxy_config(cls):
+ d = ProxTestBase.get_proxy_config()
+ d["upstream_server"] = (
+ "https" if cls.ssl else "http",
+ ("127.0.0.1", cls.server.port)
+ )
+ d["mode"] = "reverse"
+ return d
+
+ def pathoc(self, sni=None):
+ """
+ Returns a connected Pathoc instance.
+ """
+ p = libpathod.pathoc.Pathoc(
+ ("localhost", self.proxy.port), ssl=self.ssl, sni=sni, fp=None
+ )
+ p.connect()
+ return p
+
+ def pathod(self, spec, sni=None):
+ """
+ Constructs a pathod GET request, with the appropriate base and proxy.
+ """
+ if self.ssl:
+ p = self.pathoc(sni=sni)
+ q = "get:'/p/%s'" % spec
+ else:
+ p = self.pathoc()
+ q = "get:'/p/%s'" % spec
+ return p.request(q)
+
+
+class SocksModeTest(HTTPProxTest):
+
+ @classmethod
+ def get_proxy_config(cls):
+ d = ProxTestBase.get_proxy_config()
+ d["mode"] = "socks5"
+ return d
+
+
+class ChainProxTest(ProxTestBase):
+
+ """
+ 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
+
+ @classmethod
+ def setup_class(cls):
+ cls.chain = []
+ super(ChainProxTest, cls).setup_class()
+ for _ in range(cls.n):
+ config = ProxyConfig(**cls.get_proxy_config())
+ tmaster = cls.masterclass(config)
+ 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 teardown_class(cls):
+ super(ChainProxTest, cls).teardown_class()
+ for proxy in cls.chain:
+ proxy.shutdown()
+
+ def setup(self):
+ super(ChainProxTest, self).setup()
+ 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=("http", ("127.0.0.1", cls.chain[0].port))
+ )
+ return d
+
+
+class HTTPUpstreamProxTest(ChainProxTest, HTTPProxTest):
+ pass