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