diff options
51 files changed, 706 insertions, 399 deletions
diff --git a/.travis.yml b/.travis.yml index 51c16aa5..63202320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ git: matrix: fast_finish: true include: - - python: 3.5 + - python: 3.6 env: TOXENV=lint - os: osx osx_image: xcode7.3 @@ -28,7 +28,7 @@ matrix: env: TOXENV=py36 - python: 3.6 env: TOXENV=individual_coverage - - python: 3.5 + - python: 3.6 env: TOXENV=docs - language: node_js node_js: "node" diff --git a/mitmproxy/addons/core_option_validation.py b/mitmproxy/addons/core_option_validation.py index baeee764..42da0b74 100644 --- a/mitmproxy/addons/core_option_validation.py +++ b/mitmproxy/addons/core_option_validation.py @@ -19,11 +19,9 @@ class CoreOptionValidation: "then the upstream certificate is not retrieved before generating " "the client certificate chain." ) - if "body_size_limit" in updated and opts.body_size_limit: + if "body_size_limit" in updated: try: - opts._processed["body_size_limit"] = human.parse_size( - opts.body_size_limit - ) + human.parse_size(opts.body_size_limit) except ValueError as e: raise exceptions.OptionsError( "Invalid body size limit specification: %s" % diff --git a/mitmproxy/addons/proxyauth.py b/mitmproxy/addons/proxyauth.py index 5f884b55..64233e88 100644 --- a/mitmproxy/addons/proxyauth.py +++ b/mitmproxy/addons/proxyauth.py @@ -61,7 +61,7 @@ class ProxyAuth: - True, if authentication is done as if mitmproxy is a proxy - False, if authentication is done as if mitmproxy is a HTTP server """ - return ctx.options.mode in ("regular", "upstream") + return ctx.options.mode == "regular" or ctx.options.mode.startswith("upstream:") def which_auth_header(self) -> str: if self.is_proxy_auth(): diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py index 58e8cdcd..2d030321 100644 --- a/mitmproxy/addons/script.py +++ b/mitmproxy/addons/script.py @@ -72,11 +72,12 @@ class Script: ctx.master.addons.remove(self.ns) self.ns = None with addonmanager.safecall(): - self.ns = load_script(self.fullpath) + ns = load_script(self.fullpath) + ctx.master.addons.register(ns) + self.ns = ns if self.ns: # We're already running, so we have to explicitly register and # configure the addon - ctx.master.addons.register(self.ns) ctx.master.addons.invoke_addon(self.ns, "running") ctx.master.addons.invoke_addon( self.ns, diff --git a/mitmproxy/certs.py b/mitmproxy/certs.py index 0e441efe..c5f930e1 100644 --- a/mitmproxy/certs.py +++ b/mitmproxy/certs.py @@ -480,10 +480,8 @@ class SSLCert(serializable.Serializable): except PyAsn1Error: continue for i in dec[0]: - if i[0] is None and isinstance(i[1], univ.OctetString) and not isinstance(i[1], char.IA5String): - # This would give back the IP address: b'.'.join([str(e).encode() for e in i[1].asNumbers()]) - continue - else: + if i[0].hasValue(): e = i[0].asOctets() - altnames.append(e) + altnames.append(e) + return altnames diff --git a/mitmproxy/log.py b/mitmproxy/log.py index 886b1449..3083a000 100644 --- a/mitmproxy/log.py +++ b/mitmproxy/log.py @@ -4,6 +4,11 @@ class LogEntry: self.msg = msg self.level = level + def __eq__(self, other): + if isinstance(other, LogEntry): + return self.__dict__ == other.__dict__ + return False + def __repr__(self): return "LogEntry({}, {})".format(self.msg, self.level) diff --git a/mitmproxy/master.py b/mitmproxy/master.py index 2bc78f4b..b41e2a8d 100644 --- a/mitmproxy/master.py +++ b/mitmproxy/master.py @@ -10,6 +10,7 @@ from mitmproxy import exceptions from mitmproxy import command from mitmproxy import http from mitmproxy import log +from mitmproxy.net import server_spec from mitmproxy.proxy.protocol import http_replay from mitmproxy.types import basethread @@ -32,16 +33,25 @@ class Master: """ The master handles mitmproxy's main event loop. """ - def __init__(self, opts, server): - self.options = opts or options.Options() + def __init__(self, opts): + self.options = opts or options.Options() # type: options.Options self.commands = command.CommandManager(self) self.addons = addonmanager.AddonManager(self) self.event_queue = queue.Queue() self.should_exit = threading.Event() - self.server = server + self._server = None self.first_tick = True - channel = controller.Channel(self.event_queue, self.should_exit) - server.set_channel(channel) + + @property + def server(self): + return self._server + + @server.setter + def server(self, server): + server.set_channel( + controller.Channel(self.event_queue, self.should_exit) + ) + self._server = server @contextlib.contextmanager def handlecontext(self): @@ -71,7 +81,8 @@ class Master: def start(self): self.should_exit.clear() - ServerThread(self.server).start() + if self.server: + ServerThread(self.server).start() def run(self): self.start() @@ -101,7 +112,8 @@ class Master: return changed def shutdown(self): - self.server.shutdown() + if self.server: + self.server.shutdown() self.should_exit.set() self.addons.trigger("done") @@ -110,10 +122,10 @@ class Master: Loads a flow """ if isinstance(f, http.HTTPFlow): - if self.server and self.options.mode.startswith("reverse:"): - f.request.host = self.server.config.upstream_server.address[0] - f.request.port = self.server.config.upstream_server.address[1] - f.request.scheme = self.server.config.upstream_server.scheme + if self.options.mode.startswith("reverse:"): + _, upstream_spec = server_spec.parse_with_mode(self.options.mode) + f.request.host, f.request.port = upstream_spec.address + f.request.scheme = upstream_spec.scheme f.reply = controller.DummyReply() for e, o in eventsequence.iterate(f): self.addons.handle_lifecycle(e, o) @@ -168,7 +180,7 @@ class Master: f.request.headers.insert(0, "host", host) rt = http_replay.RequestReplayThread( - self.server.config, + self.options, f, self.event_queue, self.should_exit diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 10aaee12..1ecdd6a6 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -173,7 +173,7 @@ class Options(optmanager.OptManager): ) self.add_option( "server", bool, True, - "Start a proxy server." + "Start a proxy server. Enabled by default." ) self.add_option( "server_replay_nopop", bool, False, @@ -406,8 +406,9 @@ class Options(optmanager.OptManager): ) self.add_option( "rawtcp", bool, False, - "Enable/disable experimental raw TCP support. " - "Disabled by default. " + "Enable/disable experimental raw TCP support. TCP connections starting with non-ascii " + "bytes are treated as if they would match tcp_hosts. The heuristic is very rough, use " + "with caution. Disabled by default. " ) self.add_option( @@ -516,24 +517,6 @@ class Options(optmanager.OptManager): "Limit which flows are displayed." ) - # Web options - self.add_option( - "web_open_browser", bool, True, - "Start a browser." - ) - self.add_option( - "web_debug", bool, False, - "Mitmweb debugging." - ) - self.add_option( - "web_port", int, 8081, - "Mitmweb port." - ) - self.add_option( - "web_iface", str, "127.0.0.1", - "Mitmweb interface." - ) - # Dump options self.add_option( "flow_detail", int, 1, diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py index 84c8d2ea..01d97af3 100644 --- a/mitmproxy/optmanager.py +++ b/mitmproxy/optmanager.py @@ -94,7 +94,6 @@ class OptManager: self.__dict__["_options"] = {} self.__dict__["changed"] = blinker.Signal() self.__dict__["errored"] = blinker.Signal() - self.__dict__["_processed"] = {} def add_option( self, @@ -151,13 +150,17 @@ class OptManager: self.changed.connect(_call, weak=False) def __eq__(self, other): - return self._options == other._options + if isinstance(other, OptManager): + return self._options == other._options + return False - def __copy__(self): + def __deepcopy__(self, memodict = None): o = OptManager() - o.__dict__["_options"] = copy.deepcopy(self._options) + o.__dict__["_options"] = copy.deepcopy(self._options, memodict) return o + __copy__ = __deepcopy__ + def __getattr__(self, attr): if attr in self._options: return self._options[attr].current() diff --git a/mitmproxy/proxy/config.py b/mitmproxy/proxy/config.py index b809d89a..9458cd42 100644 --- a/mitmproxy/proxy/config.py +++ b/mitmproxy/proxy/config.py @@ -1,6 +1,6 @@ import os import re -from typing import Any +import typing from OpenSSL import SSL, crypto @@ -42,10 +42,11 @@ class ProxyConfig: self.certstore = None # type: certs.CertStore self.client_certs = None # type: str self.openssl_verification_mode_server = None # type: int + self.upstream_server = None # type: typing.Optional[server_spec.ServerSpec] self.configure(options, set(options.keys())) options.changed.connect(self.configure) - def configure(self, options: moptions.Options, updated: Any) -> None: + def configure(self, options: moptions.Options, updated: typing.Any) -> None: if options.add_upstream_certs_to_client_chain and not options.ssl_insecure: raise exceptions.OptionsError( "The verify-upstream-cert requires certificate verification to be disabled. " @@ -58,8 +59,10 @@ class ProxyConfig: else: self.openssl_verification_mode_server = SSL.VERIFY_PEER - self.check_ignore = HostMatcher(options.ignore_hosts) - self.check_tcp = HostMatcher(options.tcp_hosts) + if "ignore_hosts" in updated: + self.check_ignore = HostMatcher(options.ignore_hosts) + if "tcp_hosts" in updated: + self.check_tcp = HostMatcher(options.tcp_hosts) self.openssl_method_client, self.openssl_options_client = \ tcp.sslversion_choices[options.ssl_version_client] diff --git a/mitmproxy/proxy/protocol/http1.py b/mitmproxy/proxy/protocol/http1.py index 84cd6324..91f1e9b7 100644 --- a/mitmproxy/proxy/protocol/http1.py +++ b/mitmproxy/proxy/protocol/http1.py @@ -1,6 +1,7 @@ from mitmproxy import http from mitmproxy.proxy.protocol import http as httpbase from mitmproxy.net.http import http1 +from mitmproxy.utils import human class Http1Layer(httpbase._HttpTransmissionLayer): @@ -19,7 +20,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer): return http1.read_body( self.client_conn.rfile, expected_size, - self.config.options._processed.get("body_size_limit") + human.parse_size(self.config.options.body_size_limit) ) def send_request_headers(self, request): @@ -45,7 +46,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer): return http1.read_body( self.server_conn.rfile, expected_size, - self.config.options._processed.get("body_size_limit") + human.parse_size(self.config.options.body_size_limit) ) def send_response_headers(self, response): diff --git a/mitmproxy/proxy/protocol/http2.py b/mitmproxy/proxy/protocol/http2.py index eab5292f..cf021291 100644 --- a/mitmproxy/proxy/protocol/http2.py +++ b/mitmproxy/proxy/protocol/http2.py @@ -17,6 +17,7 @@ import mitmproxy.net.http from mitmproxy.net import tcp from mitmproxy.types import basethread from mitmproxy.net.http import http2, headers +from mitmproxy.utils import human class SafeH2Connection(connection.H2Connection): @@ -183,7 +184,7 @@ class Http2Layer(base.Layer): return True def _handle_data_received(self, eid, event, source_conn): - bsl = self.config.options._processed.get("body_size_limit") + bsl = human.parse_size(self.config.options.body_size_limit) if bsl and self.streams[eid].queued_data_length > bsl: self.streams[eid].kill() self.connections[source_conn].safe_reset_stream( diff --git a/mitmproxy/proxy/protocol/http_replay.py b/mitmproxy/proxy/protocol/http_replay.py index 1aa91847..fd673a6f 100644 --- a/mitmproxy/proxy/protocol/http_replay.py +++ b/mitmproxy/proxy/protocol/http_replay.py @@ -1,11 +1,18 @@ +import queue +import threading +import typing + from mitmproxy import log from mitmproxy import controller from mitmproxy import exceptions from mitmproxy import http from mitmproxy import flow +from mitmproxy import options from mitmproxy import connections +from mitmproxy.net import server_spec from mitmproxy.net.http import http1 from mitmproxy.types import basethread +from mitmproxy.utils import human # TODO: Doesn't really belong into mitmproxy.proxy.protocol... @@ -14,12 +21,19 @@ from mitmproxy.types import basethread class RequestReplayThread(basethread.BaseThread): name = "RequestReplayThread" - def __init__(self, config, f, event_queue, should_exit): + def __init__( + self, + opts: options.Options, + f: http.HTTPFlow, + event_queue: typing.Optional[queue.Queue], + should_exit: threading.Event + ) -> None: """ event_queue can be a queue or None, if no scripthooks should be processed. """ - self.config, self.f = config, f + self.options = opts + self.f = f f.live = True if event_queue: self.channel = controller.Channel(event_queue, should_exit) @@ -31,7 +45,7 @@ class RequestReplayThread(basethread.BaseThread): def run(self): r = self.f.request - bsl = self.config.options._processed.get("body_size_limit") + bsl = human.parse_size(self.options.body_size_limit) first_line_format_backup = r.first_line_format server = None try: @@ -45,9 +59,9 @@ class RequestReplayThread(basethread.BaseThread): if not self.f.response: # In all modes, we directly connect to the server displayed - if self.config.options.mode.startswith("upstream:"): - server_address = self.config.upstream_server.address - server = connections.ServerConnection(server_address, (self.config.options.listen_host, 0)) + if self.options.mode.startswith("upstream:"): + server_address = server_spec.parse_with_mode(self.options.mode)[1].address + server = connections.ServerConnection(server_address, (self.options.listen_host, 0)) server.connect() if r.scheme == "https": connect_request = http.make_connect_request((r.data.host, r.port)) @@ -61,7 +75,7 @@ class RequestReplayThread(basethread.BaseThread): if resp.status_code != 200: raise exceptions.ReplayException("Upstream server refuses CONNECT request") server.establish_ssl( - self.config.client_certs, + self.options.client_certs, sni=self.f.server_conn.sni ) r.first_line_format = "relative" @@ -71,12 +85,12 @@ class RequestReplayThread(basethread.BaseThread): server_address = (r.host, r.port) server = connections.ServerConnection( server_address, - (self.config.options.listen_host, 0) + (self.options.listen_host, 0) ) server.connect() if r.scheme == "https": server.establish_ssl( - self.config.client_certs, + self.options.client_certs, sni=self.f.server_conn.sni ) r.first_line_format = "relative" diff --git a/mitmproxy/proxy/root_context.py b/mitmproxy/proxy/root_context.py index 3d21b13c..c0ec64c9 100644 --- a/mitmproxy/proxy/root_context.py +++ b/mitmproxy/proxy/root_context.py @@ -104,7 +104,16 @@ class RootContext: if alpn == b'http/1.1': return protocol.Http1Layer(top_layer, http.HTTPMode.transparent) - # 6. Assume HTTP1 by default + # 6. Check for raw tcp mode + is_ascii = ( + len(d) == 3 and + # expect A-Za-z + all(65 <= x <= 90 or 97 <= x <= 122 for x in d) + ) + if self.config.options.rawtcp and not is_ascii: + return protocol.RawTCPLayer(top_layer) + + # 7. Assume HTTP1 by default return protocol.Http1Layer(top_layer, http.HTTPMode.transparent) def log(self, msg, level, subs=()): diff --git a/mitmproxy/test/taddons.py b/mitmproxy/test/taddons.py index 49142871..6160746a 100644 --- a/mitmproxy/test/taddons.py +++ b/mitmproxy/test/taddons.py @@ -1,12 +1,11 @@ -import sys import contextlib +import sys import mitmproxy.master import mitmproxy.options -from mitmproxy import proxy from mitmproxy import addonmanager -from mitmproxy import eventsequence from mitmproxy import command +from mitmproxy import eventsequence from mitmproxy.addons import script @@ -59,10 +58,11 @@ class context: handlers can run as they would within mitmproxy. The context also provides a number of helper methods for common testing scenarios. """ - def __init__(self, master = None, options = None): + + def __init__(self, master=None, options=None): options = options or mitmproxy.options.Options() self.master = master or RecordingMaster( - options, proxy.DummyServer(options) + options ) self.options = self.master.options self.wrapped = None diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py index 68ddc2c8..93ce6f24 100644 --- a/mitmproxy/tools/cmdline.py +++ b/mitmproxy/tools/cmdline.py @@ -73,6 +73,7 @@ def common_options(parser, opts): opts.make_parser(group, "upstream_auth", metavar="USER:PASS") opts.make_parser(group, "proxyauth", metavar="SPEC") opts.make_parser(group, "rawtcp") + opts.make_parser(group, "http2") # Proxy SSL options group = parser.add_argument_group("SSL") diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py index 0b0993c8..853447cf 100644 --- a/mitmproxy/tools/console/consoleaddons.py +++ b/mitmproxy/tools/console/consoleaddons.py @@ -68,12 +68,14 @@ class ConsoleAddon: """ return ["single", "vertical", "horizontal"] - @command.command("intercept_toggle") + @command.command("console.intercept.toggle") def intercept_toggle(self) -> None: """ Toggles interception on/off leaving intercept filters intact. """ - ctx.options.intercept_active = not ctx.options.intercept_active + ctx.options.update( + intercept_active = not ctx.options.intercept_active + ) @command.command("console.layout.cycle") def layout_cycle(self) -> None: diff --git a/mitmproxy/tools/console/defaultkeys.py b/mitmproxy/tools/console/defaultkeys.py index 4634b1e2..8139569e 100644 --- a/mitmproxy/tools/console/defaultkeys.py +++ b/mitmproxy/tools/console/defaultkeys.py @@ -24,7 +24,7 @@ def map(km): km.add("ctrl f", "console.nav.pagedown", ["global"], "Page down") km.add("ctrl b", "console.nav.pageup", ["global"], "Page up") - km.add("I", "console.command intercept_toggle", ["global"], "Toggle intercept") + km.add("I", "console.intercept.toggle", ["global"], "Toggle intercept") km.add("i", "console.command set intercept=", ["global"], "Set intercept") km.add("W", "console.command set save_stream_file=", ["global"], "Stream to file") km.add("A", "flow.resume @all", ["flowlist", "flowview"], "Resume all intercepted flows") diff --git a/mitmproxy/tools/console/master.py b/mitmproxy/tools/console/master.py index 8c8cfe61..0d9dee9b 100644 --- a/mitmproxy/tools/console/master.py +++ b/mitmproxy/tools/console/master.py @@ -28,18 +28,11 @@ from mitmproxy.tools.console import window class ConsoleMaster(master.Master): - def __init__(self, options, server): - super().__init__(options, server) - - if not sys.stdout.isatty(): - print("Error: mitmproxy's console interface requires a tty. " - "Please run mitmproxy in an interactive shell environment.", file=sys.stderr) - sys.exit(1) + def __init__(self, opts): + super().__init__(opts) self.view = view.View() # type: view.View self.stream_path = None - # This line is just for type hinting - self.options = self.options # type: Options self.keymap = keymap.Keymap(self) defaultkeys.map(self.keymap) self.options.errored.connect(self.options_error) @@ -66,7 +59,7 @@ class ConsoleMaster(master.Master): self.window = None def __setattr__(self, name, value): - self.__dict__[name] = value + super().__setattr__(name, value) signals.update_settings.send(self) def options_error(self, opts, exc): @@ -160,10 +153,10 @@ class ConsoleMaster(master.Master): self.ui.start() os.unlink(name) - def set_palette(self, options, updated): + def set_palette(self, opts, updated): self.ui.register_palette( - palettes.palettes[options.console_palette].palette( - options.console_palette_transparent + palettes.palettes[opts.console_palette].palette( + opts.console_palette_transparent ) ) self.ui.clear() @@ -178,6 +171,11 @@ class ConsoleMaster(master.Master): self.loop.process_input([key]) def run(self): + if not sys.stdout.isatty(): + print("Error: mitmproxy's console interface requires a tty. " + "Please run mitmproxy in an interactive shell environment.", file=sys.stderr) + sys.exit(1) + self.ui = urwid.raw_display.Screen() self.ui.set_terminal_properties(256) self.set_palette(self.options, None) diff --git a/mitmproxy/tools/console/statusbar.py b/mitmproxy/tools/console/statusbar.py index 7628c475..5bfc611c 100644 --- a/mitmproxy/tools/console/statusbar.py +++ b/mitmproxy/tools/console/statusbar.py @@ -257,11 +257,11 @@ class StatusBar(urwid.WidgetWrap): ('heading', ("%s %s [%s/%s]" % (arrow, marked, offset, fc)).ljust(11)), ] - if self.master.server.bound: - host = self.master.server.address[0] - if host == "0.0.0.0": + if self.master.options.server: + host = self.master.options.listen_host + if host == "0.0.0.0" or host == "": host = "*" - boundaddr = "[%s:%s]" % (host, self.master.server.address[1]) + boundaddr = "[%s:%s]" % (host, self.master.options.listen_port) else: boundaddr = "" t.extend(self.get_status()) diff --git a/mitmproxy/tools/dump.py b/mitmproxy/tools/dump.py index 4d0ccf4b..af04f8a3 100644 --- a/mitmproxy/tools/dump.py +++ b/mitmproxy/tools/dump.py @@ -18,11 +18,10 @@ class DumpMaster(master.Master): def __init__( self, options: options.Options, - server, with_termlog=True, with_dumper=True, ) -> None: - master.Master.__init__(self, options, server) + super().__init__(options) self.errorcheck = ErrorCheck() if with_termlog: self.addons.add(termlog.TermLog(), termstatus.TermStatus()) diff --git a/mitmproxy/tools/main.py b/mitmproxy/tools/main.py index 1575de98..3735cbf4 100644 --- a/mitmproxy/tools/main.py +++ b/mitmproxy/tools/main.py @@ -1,23 +1,26 @@ from __future__ import print_function # this is here for the version check to work on Python 2. + import sys -# This must be at the very top, before importing anything else that might break! -# Keep all other imports below with the 'noqa' magic comment. if sys.version_info < (3, 5): + # This must be before any mitmproxy imports, as they already break! + # Keep all other imports below with the 'noqa' magic comment. print("#" * 49, file=sys.stderr) print("# mitmproxy only supports Python 3.5 and above! #", file=sys.stderr) print("#" * 49, file=sys.stderr) +import argparse # noqa import os # noqa import signal # noqa +import typing # noqa from mitmproxy.tools import cmdline # noqa -from mitmproxy import exceptions # noqa +from mitmproxy import exceptions, master # noqa from mitmproxy import options # noqa from mitmproxy import optmanager # noqa from mitmproxy import proxy # noqa -from mitmproxy import log # noqa -from mitmproxy.utils import debug # noqa +from mitmproxy import log # noqa +from mitmproxy.utils import debug, arg_check # noqa def assert_utf8_env(): @@ -53,7 +56,12 @@ def process_options(parser, opts, args): return proxy.config.ProxyConfig(opts) -def run(MasterKlass, args, extra=None): # pragma: no cover +def run( + master_cls: typing.Type[master.Master], + make_parser: typing.Callable[[options.Options], argparse.ArgumentParser], + arguments: typing.Sequence[str], + extra: typing.Callable[[typing.Any], dict] = None +): # pragma: no cover """ extra: Extra argument processing callable which returns a dict of options. @@ -61,12 +69,24 @@ def run(MasterKlass, args, extra=None): # pragma: no cover debug.register_info_dumpers() opts = options.Options() - parser = cmdline.mitmdump(opts) - args = parser.parse_args(args) - master = None + master = master_cls(opts) + + parser = make_parser(opts) + + # To make migration from 2.x to 3.0 bearable. + if "-R" in sys.argv and sys.argv[sys.argv.index("-R") + 1].startswith("http"): + print("-R is used for specifying replacements.\n" + "To use mitmproxy in reverse mode please use --mode reverse:SPEC instead") + + try: + args = parser.parse_args(arguments) + except SystemExit: + arg_check.check() + sys.exit(1) try: unknown = optmanager.load_paths(opts, args.conf) pconf = process_options(parser, opts, args) + server = None # type: typing.Any if pconf.options.server: try: server = proxy.server.ProxyServer(pconf) @@ -76,7 +96,7 @@ def run(MasterKlass, args, extra=None): # pragma: no cover else: server = proxy.server.DummyServer(pconf) - master = MasterKlass(opts, server) + master.server = server master.addons.trigger("configure", opts.keys()) master.addons.trigger("tick") remaining = opts.update_known(**unknown) @@ -114,7 +134,7 @@ def mitmproxy(args=None): # pragma: no cover assert_utf8_env() from mitmproxy.tools import console - run(console.master.ConsoleMaster, args) + run(console.master.ConsoleMaster, cmdline.mitmproxy, args) def mitmdump(args=None): # pragma: no cover @@ -124,16 +144,16 @@ def mitmdump(args=None): # pragma: no cover if args.filter_args: v = " ".join(args.filter_args) return dict( - view_filter = v, - save_stream_filter = v, + view_filter=v, + save_stream_filter=v, ) return {} - m = run(dump.DumpMaster, args, extra) + m = run(dump.DumpMaster, cmdline.mitmdump, args, extra) if m and m.errorcheck.has_errored: sys.exit(1) def mitmweb(args=None): # pragma: no cover from mitmproxy.tools import web - run(web.master.WebMaster, args) + run(web.master.WebMaster, cmdline.mitmweb, args) diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py index dc5b2627..b13aeff9 100644 --- a/mitmproxy/tools/web/master.py +++ b/mitmproxy/tools/web/master.py @@ -13,12 +13,12 @@ from mitmproxy.addons import termlog from mitmproxy.addons import view from mitmproxy.addons import termstatus from mitmproxy.options import Options # noqa -from mitmproxy.tools.web import app +from mitmproxy.tools.web import app, webaddons class WebMaster(master.Master): - def __init__(self, options, server, with_termlog=True): - super().__init__(options, server) + def __init__(self, options, with_termlog=True): + super().__init__(options) self.view = view.View() self.view.sig_view_add.connect(self._sig_view_add) self.view.sig_view_remove.connect(self._sig_view_remove) @@ -34,6 +34,7 @@ class WebMaster(master.Master): self.addons.add(*addons.default_addons()) self.addons.add( + webaddons.WebAddon(), intercept.Intercept(), readfile.ReadFile(), self.view, @@ -44,8 +45,6 @@ class WebMaster(master.Master): self.app = app.Application( self, self.options.web_debug ) - # This line is just for type hinting - self.options = self.options # type: Options def _sig_view_add(self, view, flow): app.ClientConnection.broadcast( diff --git a/mitmproxy/tools/web/webaddons.py b/mitmproxy/tools/web/webaddons.py new file mode 100644 index 00000000..6b52188c --- /dev/null +++ b/mitmproxy/tools/web/webaddons.py @@ -0,0 +1,18 @@ +class WebAddon: + def load(self, loader): + loader.add_option( + "web_open_browser", bool, True, + "Start a browser." + ) + loader.add_option( + "web_debug", bool, False, + "Enable mitmweb debugging." + ) + loader.add_option( + "web_port", int, 8081, + "Web UI port." + ) + loader.add_option( + "web_iface", str, "127.0.0.1", + "Web UI interface." + ) diff --git a/mitmproxy/utils/arg_check.py b/mitmproxy/utils/arg_check.py new file mode 100644 index 00000000..73f7047c --- /dev/null +++ b/mitmproxy/utils/arg_check.py @@ -0,0 +1,148 @@ +import sys + +DEPRECATED = """ +--cadir +-Z +--body-size-limit +--stream +--palette +--palette-transparent +--follow +--order +--no-mouse +--reverse +--socks +--http2-priority +--no-http2-priority +--no-websocket +--websocket +--spoof-source-address +--upstream-bind-address +--ciphers-client +--ciphers-server +--client-certs +--no-upstream-cert +--add-upstream-certs-to-client-chain +--upstream-trusted-cadir +--upstream-trusted-ca +--ssl-version-client +--ssl-version-server +--no-onboarding +--onboarding-host +--onboarding-port +--server-replay-use-header +--no-pop +--replay-ignore-content +--replay-ignore-payload-param +--replay-ignore-param +--replay-ignore-host +--replace-from-file +""" + +REPLACED = """ +-t +-u +--wfile +-a +--afile +-z +-b +--bind-address +--port +-I +--ignore +--tcp +--cert +--insecure +-c +--replace +-i +-f +--filter +""" + +REPLACEMENTS = { + "--stream": "stream_large_bodies", + "--palette": "console_palette", + "--palette-transparent": "console_palette_transparent:", + "--follow": "console_focus_follow", + "--order": "console_order", + "--no-mouse": "console_mouse", + "--reverse": "console_order_reversed", + "--no-http2-priority": "http2_priority", + "--no-websocket": "websocket", + "--no-upstream-cert": "upstream_cert", + "--upstream-trusted-cadir": "ssl_verify_upstream_trusted_cadir", + "--upstream-trusted-ca": "ssl_verify_upstream_trusted_ca", + "--no-onboarding": "onboarding", + "--no-pop": "server_replay_nopop", + "--replay-ignore-content": "server_replay_ignore_content", + "--replay-ignore-payload-param": "server_replay_ignore_payload_params", + "--replay-ignore-param": "server_replay_ignore_params", + "--replay-ignore-host": "server_replay_ignore_host", + "--replace-from-file": "replacements (use @ to specify path)", + "-t": "--stickycookie", + "-u": "--stickyauth", + "--wfile": "--save-stream-file", + "-a": "-w Prefix path with + to append.", + "--afile": "-w Prefix path with + to append.", + "-z": "--anticomp", + "-b": "--listen-host", + "--bind-address": "--listen-host", + "--port": "--listen-port", + "-I": "--ignore-hosts", + "--ignore": "--ignore-hosts", + "--tcp": "--tcp-hosts", + "--cert": "--certs", + "--insecure": "--ssl-insecure", + "-c": "-C", + "--replace": "--replacements", + "-i": "--intercept", + "-f": "--view-filter", + "--filter": "--view-filter" +} + + +def check(): + args = sys.argv[1:] + print() + if "-U" in args: + print("-U is deprecated, please use --mode upstream:SPEC instead") + + if "-T" in args: + print("-T is deprecated, please use --mode transparent instead") + + for option in ("-e", "--eventlog", "--norefresh"): + if option in args: + print("{} has been removed.".format(option)) + + for option in ("--nonanonymous", "--singleuser", "--htpasswd"): + if option in args: + print( + '{} is deprecated.\n' + 'Please use `--proxyauth SPEC` instead.\n' + 'SPEC Format: "username:pass", "any" to accept any user/pass combination,\n' + '"@path" to use an Apache htpasswd file, or\n' + '"ldap[s]:url_server_ldap:dn_auth:password:dn_subtree" ' + 'for LDAP authentication.'.format(option)) + + for option in REPLACED.splitlines(): + if option in args: + print( + "{} is deprecated.\n" + "Please use `{}` instead.".format( + option, + REPLACEMENTS.get(option) + ) + ) + + for option in DEPRECATED.splitlines(): + if option in args: + print( + "{} is deprecated.\n" + "Please use `--set {}=value` instead.\n" + "To show all options and their default values use --options".format( + option, + REPLACEMENTS.get(option, None) or option.lstrip("-").replace("-", "_") + ) + ) diff --git a/mitmproxy/utils/human.py b/mitmproxy/utils/human.py index d67fb310..e2e3142a 100644 --- a/mitmproxy/utils/human.py +++ b/mitmproxy/utils/human.py @@ -1,6 +1,8 @@ import datetime import ipaddress import time +import functools +import typing SIZE_TABLE = [ ("b", 1024 ** 0), @@ -25,7 +27,14 @@ def pretty_size(size): return "%s%s" % (size, SIZE_TABLE[0][0]) -def parse_size(s): +@functools.lru_cache() +def parse_size(s: typing.Optional[str]) -> typing.Optional[int]: + """ + Parse a size with an optional k/m/... suffix. + Invalid values raise a ValueError. For added convenience, passing `None` returns `None`. + """ + if s is None: + return None try: return int(s) except ValueError: @@ -71,9 +71,9 @@ setup( "hyperframe>=5.0, <6", "jsbeautifier>=1.6.3, <1.7", "kaitaistruct>=0.7, <0.8", - "ldap3>=2.2.0, <2.3", + "ldap3>=2.2.0, <2.4", "passlib>=1.6.5, <1.8", - "pyasn1>=0.1.9, <0.3", + "pyasn1>=0.3.1, <0.4", "pyOpenSSL>=17.2,<17.3", "pyparsing>=2.1.3, <2.3", "pyperclip>=1.5.22, <1.6", @@ -88,7 +88,7 @@ setup( "pydivert>=2.0.3, <2.1", ], 'dev': [ - "flake8>=3.2.1, <3.4", + "flake8>=3.2.1, <3.5", "Flask>=0.10.1, <0.13", "mypy>=0.521,<0.522", "pytest-cov>=2.2.1, <3", diff --git a/test/filename_matching.py b/test/filename_matching.py index 51cedf03..e74848d4 100644 --- a/test/filename_matching.py +++ b/test/filename_matching.py @@ -22,7 +22,7 @@ def check_src_files_have_test(): def check_test_files_have_src(): unknown_test_files = [] - excluded = ['test/mitmproxy/data/', 'test/mitmproxy/net/data/', '/tservers.py'] + excluded = ['test/mitmproxy/data/', 'test/mitmproxy/net/data/', '/tservers.py', '/conftest.py'] test_files = glob.glob('test/mitmproxy/**/*.py', recursive=True) + glob.glob('test/pathod/**/*.py', recursive=True) test_files = [f for f in test_files if os.path.basename(f) != '__init__.py'] test_files = [f for f in test_files if not any(os.path.normpath(p) in f for p in excluded)] diff --git a/test/mitmproxy/addons/test_core_option_validation.py b/test/mitmproxy/addons/test_core_option_validation.py index 6d6d5ba4..cd5d4dfa 100644 --- a/test/mitmproxy/addons/test_core_option_validation.py +++ b/test/mitmproxy/addons/test_core_option_validation.py @@ -11,7 +11,6 @@ def test_simple(): with pytest.raises(exceptions.OptionsError): tctx.configure(sa, body_size_limit = "invalid") tctx.configure(sa, body_size_limit = "1m") - assert tctx.master.options._processed["body_size_limit"] with pytest.raises(exceptions.OptionsError, match="mutually exclusive"): tctx.configure( diff --git a/test/mitmproxy/addons/test_proxyauth.py b/test/mitmproxy/addons/test_proxyauth.py index 40044bf0..1d05e137 100644 --- a/test/mitmproxy/addons/test_proxyauth.py +++ b/test/mitmproxy/addons/test_proxyauth.py @@ -10,197 +10,242 @@ from mitmproxy.test import tflow from mitmproxy.test import tutils -def test_parse_http_basic_auth(): - assert proxyauth.parse_http_basic_auth( - proxyauth.mkauth("test", "test") - ) == ("basic", "test", "test") - with pytest.raises(ValueError): - proxyauth.parse_http_basic_auth("") - with pytest.raises(ValueError): - proxyauth.parse_http_basic_auth("foo bar") - with pytest.raises(ValueError): - proxyauth.parse_http_basic_auth("basic abc") - with pytest.raises(ValueError): - v = "basic " + binascii.b2a_base64(b"foo").decode("ascii") - proxyauth.parse_http_basic_auth(v) - - -def test_configure(): - up = proxyauth.ProxyAuth() - with taddons.context() as ctx: - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="foo") - - ctx.configure(up, proxyauth="foo:bar") - assert up.singleuser == ["foo", "bar"] - - ctx.configure(up, proxyauth=None) - assert up.singleuser is None - - ctx.configure(up, proxyauth="any") - assert up.nonanonymous - ctx.configure(up, proxyauth=None) - assert not up.nonanonymous - - with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"): - with mock.patch('ldap3.Connection', return_value="test"): - ctx.configure(up, proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com") - assert up.ldapserver - ctx.configure(up, proxyauth="ldaps:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com") - assert up.ldapserver - - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="ldap:test:test:test") - - with pytest.raises(IndexError): - ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person") - - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="ldapssssssss:fake_server:dn:password:tree") - - with pytest.raises(exceptions.OptionsError): +class TestMkauth: + def test_mkauth_scheme(self): + assert proxyauth.mkauth('username', 'password') == 'basic dXNlcm5hbWU6cGFzc3dvcmQ=\n' + + @pytest.mark.parametrize('scheme, expected', [ + ('', ' dXNlcm5hbWU6cGFzc3dvcmQ=\n'), + ('basic', 'basic dXNlcm5hbWU6cGFzc3dvcmQ=\n'), + ('foobar', 'foobar dXNlcm5hbWU6cGFzc3dvcmQ=\n'), + ]) + def test_mkauth(self, scheme, expected): + assert proxyauth.mkauth('username', 'password', scheme) == expected + + +class TestParseHttpBasicAuth: + @pytest.mark.parametrize('input', [ + '', + 'foo bar', + 'basic abc', + 'basic ' + binascii.b2a_base64(b"foo").decode("ascii"), + ]) + def test_parse_http_basic_auth_error(self, input): + with pytest.raises(ValueError): + proxyauth.parse_http_basic_auth(input) + + def test_parse_http_basic_auth(self): + input = proxyauth.mkauth("test", "test") + assert proxyauth.parse_http_basic_auth(input) == ("basic", "test", "test") + + +class TestProxyAuth: + @pytest.mark.parametrize('mode, expected', [ + ('', False), + ('foobar', False), + ('regular', True), + ('upstream:', True), + ('upstream:foobar', True), + ]) + def test_is_proxy_auth(self, mode, expected): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.options.mode = mode + assert up.is_proxy_auth() is expected + + @pytest.mark.parametrize('is_proxy_auth, expected', [ + (True, 'Proxy-Authorization'), + (False, 'Authorization'), + ]) + def test_which_auth_header(self, is_proxy_auth, expected): + up = proxyauth.ProxyAuth() + with mock.patch('mitmproxy.addons.proxyauth.ProxyAuth.is_proxy_auth', return_value=is_proxy_auth): + assert up.which_auth_header() == expected + + @pytest.mark.parametrize('is_proxy_auth, expected_status_code, expected_header', [ + (True, 407, 'Proxy-Authenticate'), + (False, 401, 'WWW-Authenticate'), + ]) + def test_auth_required_response(self, is_proxy_auth, expected_status_code, expected_header): + up = proxyauth.ProxyAuth() + with mock.patch('mitmproxy.addons.proxyauth.ProxyAuth.is_proxy_auth', return_value=is_proxy_auth): + resp = up.auth_required_response() + assert resp.status_code == expected_status_code + assert expected_header in resp.headers.keys() + + def test_check(self): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, proxyauth="any", mode="regular") + f = tflow.tflow() + assert not up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + + f.request.headers["Proxy-Authorization"] = "invalid" + assert not up.check(f) + + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test", scheme="unknown" + ) + assert not up.check(f) + + ctx.configure(up, proxyauth="test:test") + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + ctx.configure(up, proxyauth="test:foo") + assert not up.check(f) + ctx.configure( up, - proxyauth= "@" + tutils.test_data.path("mitmproxy/net/data/server.crt") + proxyauth="@" + tutils.test_data.path( + "mitmproxy/net/data/htpasswd" + ) ) - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="@nonexistent") + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "foo" + ) + assert not up.check(f) + + with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"): + with mock.patch('ldap3.Connection', search="test"): + with mock.patch('ldap3.Connection.search', return_value="test"): + ctx.configure( + up, + proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com" + ) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + assert up.check(f) + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "", "" + ) + assert not up.check(f) + + def test_authenticate(self): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, proxyauth="any", mode="regular") + + f = tflow.tflow() + assert not f.response + up.authenticate(f) + assert f.response.status_code == 407 + + f = tflow.tflow() + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" + ) + up.authenticate(f) + assert not f.response + assert not f.request.headers.get("Proxy-Authorization") + + f = tflow.tflow() + ctx.configure(up, mode="reverse") + assert not f.response + up.authenticate(f) + assert f.response.status_code == 401 + + f = tflow.tflow() + f.request.headers["Authorization"] = proxyauth.mkauth( + "test", "test" + ) + up.authenticate(f) + assert not f.response + assert not f.request.headers.get("Authorization") + + def test_configure(self): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="foo") + + ctx.configure(up, proxyauth="foo:bar") + assert up.singleuser == ["foo", "bar"] + + ctx.configure(up, proxyauth=None) + assert up.singleuser is None + + ctx.configure(up, proxyauth="any") + assert up.nonanonymous + ctx.configure(up, proxyauth=None) + assert not up.nonanonymous + + with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"): + with mock.patch('ldap3.Connection', return_value="test"): + ctx.configure(up, proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com") + assert up.ldapserver + ctx.configure(up, proxyauth="ldaps:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com") + assert up.ldapserver + + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="ldap:test:test:test") + + with pytest.raises(IndexError): + ctx.configure(up, proxyauth="ldap:fake_serveruid=?dc=example,dc=com:person") + + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="ldapssssssss:fake_server:dn:password:tree") + + with pytest.raises(exceptions.OptionsError): + ctx.configure( + up, + proxyauth= "@" + tutils.test_data.path("mitmproxy/net/data/server.crt") + ) + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="@nonexistent") - ctx.configure( - up, - proxyauth= "@" + tutils.test_data.path( - "mitmproxy/net/data/htpasswd" + ctx.configure( + up, + proxyauth= "@" + tutils.test_data.path( + "mitmproxy/net/data/htpasswd" + ) ) - ) - assert up.htpasswd - assert up.htpasswd.check_password("test", "test") - assert not up.htpasswd.check_password("test", "foo") - ctx.configure(up, proxyauth=None) - assert not up.htpasswd - - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="any", mode="transparent") - with pytest.raises(exceptions.OptionsError): - ctx.configure(up, proxyauth="any", mode="socks5") - - -def test_check(monkeypatch): - up = proxyauth.ProxyAuth() - with taddons.context() as ctx: - ctx.configure(up, proxyauth="any", mode="regular") - f = tflow.tflow() - assert not up.check(f) - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - assert up.check(f) - - f.request.headers["Proxy-Authorization"] = "invalid" - assert not up.check(f) - - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test", scheme="unknown" - ) - assert not up.check(f) - - ctx.configure(up, proxyauth="test:test") - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - assert up.check(f) - ctx.configure(up, proxyauth="test:foo") - assert not up.check(f) - - ctx.configure( - up, - proxyauth="@" + tutils.test_data.path( - "mitmproxy/net/data/htpasswd" + assert up.htpasswd + assert up.htpasswd.check_password("test", "test") + assert not up.htpasswd.check_password("test", "foo") + ctx.configure(up, proxyauth=None) + assert not up.htpasswd + + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="any", mode="transparent") + with pytest.raises(exceptions.OptionsError): + ctx.configure(up, proxyauth="any", mode="socks5") + + def test_handlers(self): + up = proxyauth.ProxyAuth() + with taddons.context() as ctx: + ctx.configure(up, proxyauth="any", mode="regular") + + f = tflow.tflow() + assert not f.response + up.requestheaders(f) + assert f.response.status_code == 407 + + f = tflow.tflow() + f.request.method = "CONNECT" + assert not f.response + up.http_connect(f) + assert f.response.status_code == 407 + + f = tflow.tflow() + f.request.method = "CONNECT" + f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( + "test", "test" ) - ) - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - assert up.check(f) - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "foo" - ) - assert not up.check(f) - - with mock.patch('ldap3.Server', return_value="ldap://fake_server:389 - cleartext"): - with mock.patch('ldap3.Connection', search="test"): - with mock.patch('ldap3.Connection.search', return_value="test"): - ctx.configure( - up, - proxyauth="ldap:localhost:cn=default,dc=cdhdt,dc=com:password:ou=application,dc=cdhdt,dc=com" - ) - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - assert up.check(f) - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "", "" - ) - assert not up.check(f) - - -def test_authenticate(): - up = proxyauth.ProxyAuth() - with taddons.context() as ctx: - ctx.configure(up, proxyauth="any", mode="regular") - - f = tflow.tflow() - assert not f.response - up.authenticate(f) - assert f.response.status_code == 407 - - f = tflow.tflow() - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - up.authenticate(f) - assert not f.response - assert not f.request.headers.get("Proxy-Authorization") - - f = tflow.tflow() - ctx.configure(up, mode="reverse") - assert not f.response - up.authenticate(f) - assert f.response.status_code == 401 - - f = tflow.tflow() - f.request.headers["Authorization"] = proxyauth.mkauth( - "test", "test" - ) - up.authenticate(f) - assert not f.response - assert not f.request.headers.get("Authorization") - - -def test_handlers(): - up = proxyauth.ProxyAuth() - with taddons.context() as ctx: - ctx.configure(up, proxyauth="any", mode="regular") - - f = tflow.tflow() - assert not f.response - up.requestheaders(f) - assert f.response.status_code == 407 - - f = tflow.tflow() - f.request.method = "CONNECT" - assert not f.response - up.http_connect(f) - assert f.response.status_code == 407 - - f = tflow.tflow() - f.request.method = "CONNECT" - f.request.headers["Proxy-Authorization"] = proxyauth.mkauth( - "test", "test" - ) - up.http_connect(f) - assert not f.response - - f2 = tflow.tflow(client_conn=f.client_conn) - up.requestheaders(f2) - assert not f2.response - assert f2.metadata["proxyauth"] == ('test', 'test') + up.http_connect(f) + assert not f.response + + f2 = tflow.tflow(client_conn=f.client_conn) + up.requestheaders(f2) + assert not f2.response + assert f2.metadata["proxyauth"] == ('test', 'test') diff --git a/test/mitmproxy/addons/test_script.py b/test/mitmproxy/addons/test_script.py index 64fd9505..aa7ca68e 100644 --- a/test/mitmproxy/addons/test_script.py +++ b/test/mitmproxy/addons/test_script.py @@ -1,15 +1,16 @@ -import traceback -import sys import os +import sys +import traceback +from unittest import mock + import pytest -from unittest import mock -from mitmproxy.test import tflow -from mitmproxy.test import tutils -from mitmproxy.test import taddons from mitmproxy import addonmanager from mitmproxy import exceptions from mitmproxy.addons import script +from mitmproxy.test import taddons +from mitmproxy.test import tflow +from mitmproxy.test import tutils def test_load_script(): @@ -216,6 +217,20 @@ class TestScriptLoader: assert not tctx.options.scripts assert not sl.addons + def test_load_err(self): + sc = script.ScriptLoader() + with taddons.context() as tctx: + tctx.configure(sc, scripts=[ + tutils.test_data.path("mitmproxy/data/addonscripts/load_error.py") + ]) + try: + tctx.invoke(sc, "tick") + except ValueError: + pass # this is expected and normally guarded. + # on the next tick we should not fail however. + tctx.invoke(sc, "tick") + assert len(tctx.master.addons) == 0 + def test_order(self): rec = tutils.test_data.path("mitmproxy/data/addonscripts/recorder") sc = script.ScriptLoader() diff --git a/test/mitmproxy/addons/test_termstatus.py b/test/mitmproxy/addons/test_termstatus.py index 2debaff5..5f960a1c 100644 --- a/test/mitmproxy/addons/test_termstatus.py +++ b/test/mitmproxy/addons/test_termstatus.py @@ -1,3 +1,4 @@ +from mitmproxy import proxy from mitmproxy.addons import termstatus from mitmproxy.test import taddons @@ -5,6 +6,7 @@ from mitmproxy.test import taddons def test_configure(): ts = termstatus.TermStatus() with taddons.context() as ctx: + ctx.master.server = proxy.DummyServer() ctx.configure(ts, server=False) ts.running() assert not ctx.master.logs diff --git a/test/mitmproxy/data/addonscripts/load_error.py b/test/mitmproxy/data/addonscripts/load_error.py new file mode 100644 index 00000000..4c05e9ed --- /dev/null +++ b/test/mitmproxy/data/addonscripts/load_error.py @@ -0,0 +1,2 @@ +def load(_): + raise ValueError() diff --git a/test/mitmproxy/proxy/protocol/test_http2.py b/test/mitmproxy/proxy/protocol/test_http2.py index 583e6e27..4f161ef5 100644 --- a/test/mitmproxy/proxy/protocol/test_http2.py +++ b/test/mitmproxy/proxy/protocol/test_http2.py @@ -8,7 +8,6 @@ import pytest import h2 from mitmproxy import options -from mitmproxy.proxy.config import ProxyConfig import mitmproxy.net from ...net import tservers as net_tservers @@ -89,10 +88,8 @@ class _Http2TestBase: @classmethod def setup_class(cls): - opts = cls.get_options() - cls.config = ProxyConfig(opts) - - tmaster = tservers.TestMaster(opts, cls.config) + cls.options = cls.get_options() + tmaster = tservers.TestMaster(cls.options) cls.proxy = tservers.ProxyThread(tmaster) cls.proxy.start() @@ -319,7 +316,7 @@ class TestRequestWithPriority(_Http2Test): (False, (None, None, None), (None, None, None)), ]) def test_request_with_priority(self, http2_priority_enabled, priority, expected_priority): - self.config.options.http2_priority = http2_priority_enabled + self.options.http2_priority = http2_priority_enabled h2_conn = self.setup_connection() @@ -397,7 +394,7 @@ class TestPriority(_Http2Test): (False, (True, 42424242, 42), []), ]) def test_priority(self, prioritize_before, http2_priority_enabled, priority, expected_priority): - self.config.options.http2_priority = http2_priority_enabled + self.options.http2_priority = http2_priority_enabled self.__class__.priority_data = [] h2_conn = self.setup_connection() @@ -508,8 +505,7 @@ class TestBodySizeLimit(_Http2Test): return True def test_body_size_limit(self): - self.config.options.body_size_limit = "20" - self.config.options._processed["body_size_limit"] = 20 + self.options.body_size_limit = "20" h2_conn = self.setup_connection() diff --git a/test/mitmproxy/proxy/protocol/test_websocket.py b/test/mitmproxy/proxy/protocol/test_websocket.py index 58857f92..460d85f8 100644 --- a/test/mitmproxy/proxy/protocol/test_websocket.py +++ b/test/mitmproxy/proxy/protocol/test_websocket.py @@ -7,7 +7,6 @@ from mitmproxy import options from mitmproxy import exceptions from mitmproxy.http import HTTPFlow from mitmproxy.websocket import WebSocketFlow -from mitmproxy.proxy.config import ProxyConfig from mitmproxy.net import tcp from mitmproxy.net import http @@ -49,10 +48,8 @@ class _WebSocketTestBase: @classmethod def setup_class(cls): - opts = cls.get_options() - cls.config = ProxyConfig(opts) - - tmaster = tservers.TestMaster(opts, cls.config) + cls.options = cls.get_options() + tmaster = tservers.TestMaster(cls.options) cls.proxy = tservers.ProxyThread(tmaster) cls.proxy.start() diff --git a/test/mitmproxy/proxy/test_server.py b/test/mitmproxy/proxy/test_server.py index 4cae756a..562f822c 100644 --- a/test/mitmproxy/proxy/test_server.py +++ b/test/mitmproxy/proxy/test_server.py @@ -117,13 +117,12 @@ class TcpMixin: def _ignore_on(self): assert not hasattr(self, "_ignore_backup") - self._ignore_backup = self.config.check_ignore - self.config.check_ignore = HostMatcher( - [".+:%s" % self.server.port] + self.config.check_ignore.patterns) + self._ignore_backup = self.options.ignore_hosts + self.options.ignore_hosts = [".+:%s" % self.server.port] + self.options.ignore_hosts def _ignore_off(self): assert hasattr(self, "_ignore_backup") - self.config.check_ignore = self._ignore_backup + self.options.ignore_hosts = self._ignore_backup del self._ignore_backup def test_ignore(self): @@ -163,13 +162,12 @@ class TcpMixin: def _tcpproxy_on(self): assert not hasattr(self, "_tcpproxy_backup") - self._tcpproxy_backup = self.config.check_tcp - self.config.check_tcp = HostMatcher( - [".+:%s" % self.server.port] + self.config.check_tcp.patterns) + self._tcpproxy_backup = self.options.tcp_hosts + self.options.tcp_hosts = [".+:%s" % self.server.port] + self.options.tcp_hosts def _tcpproxy_off(self): assert hasattr(self, "_tcpproxy_backup") - self.config.check_tcp = self._tcpproxy_backup + self.options.tcp_hosts = self._tcpproxy_backup del self._tcpproxy_backup def test_tcp(self): @@ -194,7 +192,8 @@ class TcpMixin: i2_cert = certs.SSLCert(i2.sslinfo.certchain[0]) n_cert = certs.SSLCert(n.sslinfo.certchain[0]) - assert i_cert == i2_cert == n_cert + assert i_cert == i2_cert + assert i_cert != n_cert # Make sure that TCP messages are in the event log. # Re-enable and fix this when we start keeping TCPFlows in the state. @@ -353,22 +352,22 @@ class TestHTTPS(tservers.HTTPProxyTest, CommonMixin, TcpMixin): def test_clientcert_file(self): try: - self.config.client_certs = os.path.join( + self.options.client_certs = os.path.join( tutils.test_data.path("mitmproxy/data/clientcert"), "client.pem") f = self.pathod("304") assert f.status_code == 304 assert self.server.last_log()["request"]["clientcert"]["keyinfo"] finally: - self.config.client_certs = None + self.options.client_certs = None def test_clientcert_dir(self): try: - self.config.client_certs = tutils.test_data.path("mitmproxy/data/clientcert") + self.options.client_certs = tutils.test_data.path("mitmproxy/data/clientcert") f = self.pathod("304") assert f.status_code == 304 assert self.server.last_log()["request"]["clientcert"]["keyinfo"] finally: - self.config.client_certs = None + self.options.client_certs = None def test_error_post_connect(self): p = self.pathoc() @@ -412,7 +411,7 @@ class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest): return p.request("get:/p/242") def test_verification_w_cadir(self): - self.config.options.update( + self.options.update( ssl_insecure=False, ssl_verify_upstream_trusted_cadir=tutils.test_data.path( "mitmproxy/data/servercert/" @@ -422,7 +421,7 @@ class TestHTTPSUpstreamServerVerificationWTrustedCert(tservers.HTTPProxyTest): assert self._request().status_code == 242 def test_verification_w_pemfile(self): - self.config.options.update( + self.options.update( ssl_insecure=False, ssl_verify_upstream_trusted_cadir=None, ssl_verify_upstream_trusted_ca=tutils.test_data.path( @@ -458,7 +457,7 @@ class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest): return opts def test_no_verification_w_bad_cert(self): - self.config.options.ssl_insecure = True + self.options.ssl_insecure = True r = self._request() assert r.status_code == 242 @@ -466,7 +465,7 @@ class TestHTTPSUpstreamServerVerificationWBadCert(tservers.HTTPProxyTest): # We only test for a single invalid cert here. # Actual testing of different root-causes (invalid hostname, expired, ...) # is done in mitmproxy.net. - self.config.options.ssl_insecure = False + self.options.ssl_insecure = False r = self._request() assert r.status_code == 502 assert b"Certificate Verification Error" in r.raw_content @@ -493,7 +492,7 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin): reverse = True def test_host_header(self): - self.config.options.keep_host_header = True + self.options.keep_host_header = True p = self.pathoc() with p.connect(): resp = p.request("get:/p/200:h'Host'='example.com'") @@ -503,7 +502,7 @@ class TestReverse(tservers.ReverseProxyTest, CommonMixin, TcpMixin): assert req.host_header == "example.com" def test_overridden_host_header(self): - self.config.options.keep_host_header = False # default value + self.options.keep_host_header = False # default value p = self.pathoc() with p.connect(): resp = p.request("get:/p/200:h'Host'='example.com'") diff --git a/test/mitmproxy/test_addonmanager.py b/test/mitmproxy/test_addonmanager.py index fc048571..accf48e0 100644 --- a/test/mitmproxy/test_addonmanager.py +++ b/test/mitmproxy/test_addonmanager.py @@ -6,7 +6,6 @@ from mitmproxy import exceptions from mitmproxy import options from mitmproxy import command from mitmproxy import master -from mitmproxy import proxy from mitmproxy.test import taddons from mitmproxy.test import tflow @@ -51,7 +50,7 @@ def test_command(): def test_halt(): o = options.Options() - m = master.Master(o, proxy.DummyServer(o)) + m = master.Master(o) a = addonmanager.AddonManager(m) halt = THalt() end = TAddon("end") @@ -68,7 +67,7 @@ def test_halt(): def test_lifecycle(): o = options.Options() - m = master.Master(o, proxy.DummyServer(o)) + m = master.Master(o) a = addonmanager.AddonManager(m) a.add(TAddon("one")) @@ -141,7 +140,7 @@ def test_simple(): def test_load_option(): o = options.Options() - m = master.Master(o, proxy.DummyServer(o)) + m = master.Master(o) a = addonmanager.AddonManager(m) a.add(AOption()) assert "custom_option" in m.options._options @@ -149,7 +148,7 @@ def test_load_option(): def test_nesting(): o = options.Options() - m = master.Master(o, proxy.DummyServer(o)) + m = master.Master(o) a = addonmanager.AddonManager(m) a.add( diff --git a/test/mitmproxy/test_controller.py b/test/mitmproxy/test_controller.py index 2e13d298..e840380a 100644 --- a/test/mitmproxy/test_controller.py +++ b/test/mitmproxy/test_controller.py @@ -30,7 +30,8 @@ class TestMaster: assert ctx.master.should_exit.is_set() def test_server_simple(self): - m = master.Master(None, proxy.DummyServer(None)) + m = master.Master(None) + m.server = proxy.DummyServer() m.start() m.shutdown() m.start() diff --git a/test/mitmproxy/test_flow.py b/test/mitmproxy/test_flow.py index 0b04c4c1..7f9d577b 100644 --- a/test/mitmproxy/test_flow.py +++ b/test/mitmproxy/test_flow.py @@ -6,13 +6,11 @@ from mitmproxy.test import tflow, tutils import mitmproxy.io from mitmproxy import flowfilter from mitmproxy import options -from mitmproxy.proxy import config from mitmproxy.io import tnetstring from mitmproxy.exceptions import FlowReadException, ReplayException, ControlException from mitmproxy import flow from mitmproxy import http from mitmproxy.net import http as net_http -from mitmproxy.proxy.server import DummyServer from mitmproxy import master from . import tservers @@ -93,8 +91,7 @@ class TestFlowMaster: opts = options.Options( mode="reverse:https://use-this-domain" ) - conf = config.ProxyConfig(opts) - fm = master.Master(opts, DummyServer(conf)) + fm = master.Master(opts) fm.addons.add(s) f = tflow.tflow(resp=True) fm.load_flow(f) @@ -102,8 +99,7 @@ class TestFlowMaster: def test_replay(self): opts = options.Options() - conf = config.ProxyConfig(opts) - fm = master.Master(opts, DummyServer(conf)) + fm = master.Master(opts) f = tflow.tflow(resp=True) f.request.content = None with pytest.raises(ReplayException, match="missing"): @@ -131,7 +127,7 @@ class TestFlowMaster: def test_all(self): s = tservers.TestState() - fm = master.Master(None, DummyServer()) + fm = master.Master(None) fm.addons.add(s) f = tflow.tflow(req=None) fm.addons.handle_lifecycle("clientconnect", f.client_conn) diff --git a/test/mitmproxy/test_log.py b/test/mitmproxy/test_log.py index cde679ed..349e3ac8 100644 --- a/test/mitmproxy/test_log.py +++ b/test/mitmproxy/test_log.py @@ -4,3 +4,8 @@ from mitmproxy import log def test_logentry(): e = log.LogEntry("foo", "info") assert repr(e) == "LogEntry(foo, info)" + + f = log.LogEntry("foo", "warning") + assert e == e + assert e != f + assert e != 42 diff --git a/test/mitmproxy/test_optmanager.py b/test/mitmproxy/test_optmanager.py index fe72e6bb..d9b93227 100644 --- a/test/mitmproxy/test_optmanager.py +++ b/test/mitmproxy/test_optmanager.py @@ -73,6 +73,11 @@ def test_required_int(): o.parse_setval("required_int", None) +def test_deepcopy(): + o = TD() + copy.deepcopy(o) + + def test_options(): o = TO() assert o.keys() == {"bool", "one", "two", "required_int"} @@ -244,6 +249,7 @@ def test_serialize(): o2 = TD2() optmanager.load(o2, data) assert o2 == o + assert not o == 42 t = """ unknown: foo diff --git a/test/mitmproxy/tools/console/conftest.py b/test/mitmproxy/tools/console/conftest.py deleted file mode 100644 index afd94c6a..00000000 --- a/test/mitmproxy/tools/console/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -from unittest import mock - -import pytest - - -@pytest.fixture(scope="module", autouse=True) -def definitely_atty(): - with mock.patch("sys.stdout.isatty", lambda: True): - yield diff --git a/test/mitmproxy/tools/console/test_master.py b/test/mitmproxy/tools/console/test_master.py index ef357c76..3aa0dc54 100644 --- a/test/mitmproxy/tools/console/test_master.py +++ b/test/mitmproxy/tools/console/test_master.py @@ -1,7 +1,6 @@ import urwid from mitmproxy import options -from mitmproxy import proxy from mitmproxy.test import tflow from mitmproxy.test import tutils from mitmproxy.tools import console @@ -30,7 +29,7 @@ class TestMaster(tservers.MasterTest): if "verbosity" not in opts: opts["verbosity"] = 'warn' o = options.Options(**opts) - m = console.master.ConsoleMaster(o, proxy.DummyServer()) + m = console.master.ConsoleMaster(o) m.addons.trigger("configure", o.keys()) return m diff --git a/test/mitmproxy/tools/console/test_statusbar.py b/test/mitmproxy/tools/console/test_statusbar.py index 55a3c4a0..2f7825c9 100644 --- a/test/mitmproxy/tools/console/test_statusbar.py +++ b/test/mitmproxy/tools/console/test_statusbar.py @@ -1,4 +1,4 @@ -from mitmproxy import options, proxy +from mitmproxy import options from mitmproxy.tools.console import statusbar, master @@ -26,7 +26,7 @@ def test_statusbar(monkeypatch): scripts=["nonexistent"], save_stream_file="foo", ) - m = master.ConsoleMaster(o, proxy.DummyServer()) + m = master.ConsoleMaster(o) monkeypatch.setattr(m.addons.get("clientplayback"), "count", lambda: 42) monkeypatch.setattr(m.addons.get("serverplayback"), "count", lambda: 42) diff --git a/test/mitmproxy/tools/test_cmdline.py b/test/mitmproxy/tools/test_cmdline.py index 65cfeb07..e247dc1d 100644 --- a/test/mitmproxy/tools/test_cmdline.py +++ b/test/mitmproxy/tools/test_cmdline.py @@ -1,7 +1,8 @@ import argparse -from mitmproxy.tools import cmdline -from mitmproxy.tools import main + from mitmproxy import options +from mitmproxy.tools import cmdline, web, dump, console +from mitmproxy.tools import main def test_common(): @@ -14,17 +15,20 @@ def test_common(): def test_mitmproxy(): opts = options.Options() + console.master.ConsoleMaster(opts) ap = cmdline.mitmproxy(opts) assert ap def test_mitmdump(): opts = options.Options() + dump.DumpMaster(opts) ap = cmdline.mitmdump(opts) assert ap def test_mitmweb(): opts = options.Options() + web.master.WebMaster(opts) ap = cmdline.mitmweb(opts) assert ap diff --git a/test/mitmproxy/tools/test_dump.py b/test/mitmproxy/tools/test_dump.py index 597333af..952c3f4f 100644 --- a/test/mitmproxy/tools/test_dump.py +++ b/test/mitmproxy/tools/test_dump.py @@ -1,7 +1,6 @@ import pytest from unittest import mock -from mitmproxy import proxy from mitmproxy import log from mitmproxy import controller from mitmproxy import options @@ -13,7 +12,7 @@ from .. import tservers class TestDumpMaster(tservers.MasterTest): def mkmaster(self, flt, **opts): o = options.Options(view_filter=flt, verbosity='error', flow_detail=0, **opts) - m = dump.DumpMaster(o, proxy.DummyServer(), with_termlog=False, with_dumper=False) + m = dump.DumpMaster(o, with_termlog=False, with_dumper=False) return m def test_has_error(self): @@ -27,12 +26,12 @@ class TestDumpMaster(tservers.MasterTest): def test_addons_termlog(self, termlog): with mock.patch('sys.stdout'): o = options.Options() - m = dump.DumpMaster(o, proxy.DummyServer(), with_termlog=termlog) + m = dump.DumpMaster(o, with_termlog=termlog) assert (m.addons.get('termlog') is not None) == termlog @pytest.mark.parametrize("dumper", [False, True]) def test_addons_dumper(self, dumper): with mock.patch('sys.stdout'): o = options.Options() - m = dump.DumpMaster(o, proxy.DummyServer(), with_dumper=dumper) + m = dump.DumpMaster(o, with_dumper=dumper) assert (m.addons.get('dumper') is not None) == dumper diff --git a/test/mitmproxy/tools/web/test_app.py b/test/mitmproxy/tools/web/test_app.py index 4d290284..091ef5e8 100644 --- a/test/mitmproxy/tools/web/test_app.py +++ b/test/mitmproxy/tools/web/test_app.py @@ -7,7 +7,6 @@ from tornado import httpclient from tornado import websocket from mitmproxy import exceptions -from mitmproxy import proxy from mitmproxy import options from mitmproxy.test import tflow from mitmproxy.tools.web import app @@ -21,7 +20,7 @@ def json(resp: httpclient.HTTPResponse): class TestApp(tornado.testing.AsyncHTTPTestCase): def get_app(self): o = options.Options(http2=False) - m = webmaster.WebMaster(o, proxy.DummyServer(), with_termlog=False) + m = webmaster.WebMaster(o, with_termlog=False) f = tflow.tflow(resp=True) f.id = "42" m.view.add([f]) @@ -323,5 +322,5 @@ class TestApp(tornado.testing.AsyncHTTPTestCase): web_root = os.path.join(here, os.pardir, os.pardir, os.pardir, os.pardir, 'web') tflow_path = os.path.join(web_root, 'src/js/__tests__/ducks/_tflow.js') content = """export default function(){{\n return {tflow_json}\n}}""".format(tflow_json=tflow_json) - with open(tflow_path, 'w') as f: + with open(tflow_path, 'w', newline="\n") as f: f.write(content) diff --git a/test/mitmproxy/tools/web/test_master.py b/test/mitmproxy/tools/web/test_master.py index 27f99a18..2bceb5ca 100644 --- a/test/mitmproxy/tools/web/test_master.py +++ b/test/mitmproxy/tools/web/test_master.py @@ -1,7 +1,5 @@ from mitmproxy.tools.web import master -from mitmproxy import proxy from mitmproxy import options -from mitmproxy.proxy.config import ProxyConfig from ... import tservers @@ -9,8 +7,7 @@ from ... import tservers class TestWebMaster(tservers.MasterTest): def mkmaster(self, **opts): o = options.Options(**opts) - c = ProxyConfig(o) - return master.WebMaster(o, proxy.DummyServer(c)) + return master.WebMaster(o) def test_basic(self): m = self.mkmaster() diff --git a/test/mitmproxy/tservers.py b/test/mitmproxy/tservers.py index 9faaf20e..dd5bb327 100644 --- a/test/mitmproxy/tservers.py +++ b/test/mitmproxy/tservers.py @@ -69,9 +69,10 @@ class TestState: class TestMaster(taddons.RecordingMaster): - def __init__(self, opts, config): - s = ProxyServer(config) - super().__init__(opts, s) + def __init__(self, opts): + super().__init__(opts) + config = ProxyConfig(opts) + self.server = ProxyServer(config) def clear_addons(self, addons): self.addons.clear() @@ -129,9 +130,8 @@ class ProxyTestBase: ssl=cls.ssl, ssloptions=cls.ssloptions) - opts = cls.get_options() - cls.config = ProxyConfig(opts) - tmaster = cls.masterclass(opts, cls.config) + cls.options = cls.get_options() + tmaster = cls.masterclass(cls.options) cls.proxy = ProxyThread(tmaster) cls.proxy.start() @@ -338,19 +338,16 @@ class ChainProxyTest(ProxyTestBase): @classmethod def setup_class(cls): + # We need to initialize the chain first so that the normal server gets a correct config. cls.chain = [] - super().setup_class() for _ in range(cls.n): opts = cls.get_options() - config = ProxyConfig(opts) - tmaster = cls.masterclass(opts, config) + tmaster = cls.masterclass(opts) proxy = ProxyThread(tmaster) proxy.start() cls.chain.insert(0, proxy) - # Patch the orginal proxy to upstream mode - opts = cls.get_options() - cls.config = cls.proxy.tmaster.config = cls.proxy.tmaster.server.config = ProxyConfig(opts) + super().setup_class() @classmethod def teardown_class(cls): diff --git a/test/mitmproxy/utils/test_arg_check.py b/test/mitmproxy/utils/test_arg_check.py new file mode 100644 index 00000000..72913955 --- /dev/null +++ b/test/mitmproxy/utils/test_arg_check.py @@ -0,0 +1,36 @@ +import io +import contextlib +from unittest import mock + +import pytest + +from mitmproxy.utils import arg_check + + +@pytest.mark.parametrize('arg, output', [ + (["-T"], "-T is deprecated, please use --mode transparent instead"), + (["-U"], "-U is deprecated, please use --mode upstream:SPEC instead"), + (["--cadir"], "--cadir is deprecated.\n" + "Please use `--set cadir=value` instead.\n" + "To show all options and their default values use --options"), + (["--palette"], "--palette is deprecated.\n" + "Please use `--set console_palette=value` instead.\n" + "To show all options and their default values use --options"), + (["--wfile"], "--wfile is deprecated.\n" + "Please use `--save-stream-file` instead."), + (["--eventlog"], "--eventlog has been removed."), + (["--nonanonymous"], '--nonanonymous is deprecated.\n' + 'Please use `--proxyauth SPEC` instead.\n' + 'SPEC Format: "username:pass", "any" to accept any user/pass combination,\n' + '"@path" to use an Apache htpasswd file, or\n' + '"ldap[s]:url_server_ldap:dn_auth:password:dn_subtree" ' + 'for LDAP authentication.') + +]) +def test_check_args(arg, output): + f = io.StringIO() + with contextlib.redirect_stdout(f): + with mock.patch('sys.argv') as m: + m.__getitem__.return_value = arg + arg_check.check() + assert f.getvalue().strip() == output diff --git a/test/mitmproxy/utils/test_human.py b/test/mitmproxy/utils/test_human.py index 76dc2f88..e8ffaad4 100644 --- a/test/mitmproxy/utils/test_human.py +++ b/test/mitmproxy/utils/test_human.py @@ -22,6 +22,7 @@ def test_parse_size(): human.parse_size("1f") with pytest.raises(ValueError): human.parse_size("ak") + assert human.parse_size(None) is None def test_pretty_size(): |