diff options
-rw-r--r-- | mitmproxy/addons/__init__.py | 2 | ||||
-rw-r--r-- | mitmproxy/addons/browser.py | 74 | ||||
-rw-r--r-- | mitmproxy/command.py | 9 | ||||
-rw-r--r-- | mitmproxy/flow.py | 6 | ||||
-rw-r--r-- | mitmproxy/tools/console/consoleaddons.py | 4 | ||||
-rw-r--r-- | mitmproxy/tools/console/defaultkeys.py | 3 | ||||
-rw-r--r-- | pathod/pathoc.py | 1 | ||||
-rw-r--r-- | setup.py | 4 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_browser.py | 31 | ||||
-rw-r--r-- | test/mitmproxy/test_command.py | 7 | ||||
-rw-r--r-- | test/mitmproxy/test_http.py | 9 |
11 files changed, 143 insertions, 7 deletions
diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py index 62135765..8f84c20d 100644 --- a/mitmproxy/addons/__init__.py +++ b/mitmproxy/addons/__init__.py @@ -1,6 +1,7 @@ from mitmproxy.addons import allowremote from mitmproxy.addons import anticache from mitmproxy.addons import anticomp +from mitmproxy.addons import browser from mitmproxy.addons import check_ca from mitmproxy.addons import clientplayback from mitmproxy.addons import core_option_validation @@ -25,6 +26,7 @@ def default_addons(): return [ core.Core(), core_option_validation.CoreOptionValidation(), + browser.Browser(), allowremote.AllowRemote(), anticache.AntiCache(), anticomp.AntiComp(), diff --git a/mitmproxy/addons/browser.py b/mitmproxy/addons/browser.py new file mode 100644 index 00000000..247c356b --- /dev/null +++ b/mitmproxy/addons/browser.py @@ -0,0 +1,74 @@ +import shutil +import subprocess +import tempfile +import typing + +from mitmproxy import command +from mitmproxy import ctx + + +def get_chrome_executable() -> typing.Optional[str]: + for browser in ( + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + # https://stackoverflow.com/questions/40674914/google-chrome-path-in-windows-10 + r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe", + r"C:\Program Files (x86)\Google\Application\chrome.exe", + # Linux binary names from Python's webbrowser module. + "google-chrome", + "chrome", + "chromium", + "chromium-browser", + ): + if shutil.which(browser): + return browser + return None + + +class Browser: + browser = None + tdir = None + + @command.command("browser.start") + def start(self) -> None: + """ + Start an isolated instance of Chrome that points to the currently + running proxy. + """ + if self.browser: + if self.browser.poll() is None: + ctx.log.alert("Browser already running") + return + else: + self.done() + + cmd = get_chrome_executable() + if not cmd: + ctx.log.alert("Your platform is not supported yet - please submit a patch.") + return + + self.tdir = tempfile.TemporaryDirectory() + self.browser = subprocess.Popen( + [ + cmd, + "--user-data-dir=%s" % str(self.tdir.name), + "--proxy-server=%s:%s" % ( + ctx.options.listen_host or "127.0.0.1", + ctx.options.listen_port + ), + "--disable-fre", + "--no-default-browser-check", + "--no-first-run", + "--disable-extensions", + + "about:blank", + ], + stdout = subprocess.DEVNULL, + stderr = subprocess.DEVNULL, + ) + + def done(self): + if self.browser: + self.browser.kill() + self.tdir.cleanup() + self.browser = None + self.tdir = None diff --git a/mitmproxy/command.py b/mitmproxy/command.py index c9776bc3..eae3d80c 100644 --- a/mitmproxy/command.py +++ b/mitmproxy/command.py @@ -190,10 +190,19 @@ def parsearg(manager: CommandManager, spec: str, argtype: type) -> typing.Any: raise exceptions.CommandError("Unsupported argument type: %s" % argtype) +def verify_arg_signature(f: typing.Callable, args: list, kwargs: dict) -> None: + sig = inspect.signature(f) + try: + sig.bind(*args, **kwargs) + except TypeError as v: + raise exceptions.CommandError("Argument mismatch: %s" % v.args[0]) + + def command(path): def decorator(function): @functools.wraps(function) def wrapper(*args, **kwargs): + verify_arg_signature(function, args, kwargs) return function(*args, **kwargs) wrapper.__dict__["command_path"] = path return wrapper diff --git a/mitmproxy/flow.py b/mitmproxy/flow.py index dc778404..111566b8 100644 --- a/mitmproxy/flow.py +++ b/mitmproxy/flow.py @@ -179,5 +179,7 @@ class Flow(stateobject.StateObject): if not self.intercepted: return self.intercepted = False - self.reply.ack() - self.reply.commit() + # If a flow is intercepted and then duplicated, the duplicated one is not taken. + if self.reply.state == "taken": + self.reply.ack() + self.reply.commit() diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py index 49934e4d..1bda219f 100644 --- a/mitmproxy/tools/console/consoleaddons.py +++ b/mitmproxy/tools/console/consoleaddons.py @@ -329,9 +329,9 @@ class ConsoleAddon: correct viewier, and fall back to the programs in $PAGER or $EDITOR if necessary. """ - fpart = getattr(f, part) + fpart = getattr(f, part, None) if not fpart: - raise exceptions.CommandError("Could not view part %s." % part) + raise exceptions.CommandError("Part must be either request or response, not %s." % part) t = fpart.headers.get("content-type") content = fpart.get_content(strict=False) if not content: diff --git a/mitmproxy/tools/console/defaultkeys.py b/mitmproxy/tools/console/defaultkeys.py index 8139569e..8c28524a 100644 --- a/mitmproxy/tools/console/defaultkeys.py +++ b/mitmproxy/tools/console/defaultkeys.py @@ -2,7 +2,8 @@ def map(km): km.add(":", "console.command ", ["global"], "Command prompt") km.add("?", "console.view.help", ["global"], "View help") - km.add("C", "console.view.commands", ["global"], "View commands") + km.add("B", "browser.start", ["global"], "View commands") + km.add("C", "console.view.commands", ["global"], "Start an attached browser") km.add("K", "console.view.keybindings", ["global"], "View key bindings") km.add("O", "console.view.options", ["global"], "View options") km.add("E", "console.view.eventlog", ["global"], "View event log") diff --git a/pathod/pathoc.py b/pathod/pathoc.py index e1052750..20a915c0 100644 --- a/pathod/pathoc.py +++ b/pathod/pathoc.py @@ -244,6 +244,7 @@ class Pathoc(tcp.TCPClient): port=connect_to[1], path=None, http_version='HTTP/1.1', + headers=[(b"Host", connect_to[0].encode("idna"))], content=b'', ) self.wfile.write(net_http.http1.assemble_request(req)) @@ -70,8 +70,8 @@ setup( "kaitaistruct>=0.7, <0.8", "ldap3>=2.4,<2.5", "passlib>=1.6.5, <1.8", - "pyasn1>=0.3.1, <0.4", - "pyOpenSSL>=17.2,<17.4", + "pyasn1>=0.3.1,<0.5", + "pyOpenSSL>=17.2,<17.6", "pyparsing>=2.1.3, <2.3", "pyperclip>=1.5.22, <1.7", "requests>=2.9.1, <3", 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/test_command.py b/test/mitmproxy/test_command.py index 87432163..43b97742 100644 --- a/test/mitmproxy/test_command.py +++ b/test/mitmproxy/test_command.py @@ -163,3 +163,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], {})
\ No newline at end of file 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" |