diff options
49 files changed, 630 insertions, 276 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/docs/install.rst b/docs/install.rst index 7753dc44..6f166c8a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -85,8 +85,8 @@ libraries. This was tested on a fully patched installation of Ubuntu 16.04. .. code:: bash - sudo apt-get install python3-dev python3-pip libffi-dev libssl-dev - sudo pip3 install mitmproxy # or pip3 install --user mitmproxy + sudo apt-get install python3-pip + sudo pip3 install mitmproxy On older Ubuntu versions, e.g., **12.04** and **14.04**, you may need to install a newer version of Python. mitmproxy requires Python 3.5 or higher. Please take @@ -104,8 +104,8 @@ libraries. This was tested on a fully patched installation of Fedora 24. .. code:: bash - sudo dnf install make gcc redhat-rpm-config python3-devel python3-pip libffi-devel openssl-devel - sudo pip3 install mitmproxy # or pip3 install --user mitmproxy + sudo dnf install python3-pip + sudo pip3 install mitmproxy Make sure to have an up-to-date version of pip by running ``pip3 install -U pip``. @@ -121,7 +121,7 @@ You can check you Python version by running ``python3 --version``. .. code:: bash - sudo zypper install python3-pip python3-devel libffi-devel openssl-devel gcc-c++ + sudo zypper install python3-pip sudo pip3 install mitmproxy 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/io/compat.py b/mitmproxy/io/compat.py index 99da496d..da9d2a44 100644 --- a/mitmproxy/io/compat.py +++ b/mitmproxy/io/compat.py @@ -58,7 +58,7 @@ def convert_017_018(data): # convert_unicode needs to be called for every dual release and the first py3-only release data = convert_unicode(data) - data["server_conn"]["ip_address"] = data["server_conn"].pop("peer_address") + data["server_conn"]["ip_address"] = data["server_conn"].pop("peer_address", None) data["marked"] = False data["version"] = (0, 18) return data 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/net/tcp.py b/mitmproxy/net/tcp.py index 0c2f0e28..e109236e 100644 --- a/mitmproxy/net/tcp.py +++ b/mitmproxy/net/tcp.py @@ -855,6 +855,8 @@ class TCPServer: if self.address[0] == 'localhost': raise socket.error("Binding to 'localhost' is prohibited. Please use '::1' or '127.0.0.1' directly.") + self.socket = None + try: # First try to bind an IPv6 socket, with possible IPv4 if the OS supports it. # This allows us to accept connections for ::1 and 127.0.0.1 on the same socket. diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 87318919..b008e588 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -150,7 +150,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, @@ -383,8 +383,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( 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/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 915c71c4..fd673a6f 100644 --- a/mitmproxy/proxy/protocol/http_replay.py +++ b/mitmproxy/proxy/protocol/http_replay.py @@ -12,6 +12,7 @@ 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... @@ -44,7 +45,7 @@ class RequestReplayThread(basethread.BaseThread): def run(self): r = self.f.request - bsl = self.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: diff --git a/mitmproxy/proxy/protocol/tls.py b/mitmproxy/proxy/protocol/tls.py index b7bc6b1c..10eea4ae 100644 --- a/mitmproxy/proxy/protocol/tls.py +++ b/mitmproxy/proxy/protocol/tls.py @@ -292,7 +292,7 @@ class TlsClientHello: if self._client_hello.extensions: for extension in self._client_hello.extensions.extensions: if extension.type == 0x10: - return list(extension.body.alpn_protocols) + return list(x.name for x in extension.body.alpn_protocols) return [] @classmethod @@ -519,8 +519,8 @@ class TlsLayer(base.Layer): # We only support http/1.1 and h2. # If the server only supports spdy (next to http/1.1), it may select that # and mitmproxy would enter TCP passthrough mode, which we want to avoid. - alpn = [x.name for x in self._client_hello.alpn_protocols if - not (x.name.startswith(b"h2-") or x.name.startswith(b"spdy"))] + alpn = [x for x in self._client_hello.alpn_protocols if + not (x.startswith(b"h2-") or x.startswith(b"spdy"))] if alpn and b"h2" in alpn and not self.config.options.http2: alpn.remove(b"h2") 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/tools/cmdline.py b/mitmproxy/tools/cmdline.py index f919486b..d611a8d2 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 87542fd4..49934e4d 100644 --- a/mitmproxy/tools/console/consoleaddons.py +++ b/mitmproxy/tools/console/consoleaddons.py @@ -116,12 +116,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 ef3ab0b3..0d9dee9b 100644 --- a/mitmproxy/tools/console/master.py +++ b/mitmproxy/tools/console/master.py @@ -59,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): diff --git a/mitmproxy/tools/main.py b/mitmproxy/tools/main.py index d8d16ea4..3735cbf4 100644 --- a/mitmproxy/tools/main.py +++ b/mitmproxy/tools/main.py @@ -20,7 +20,7 @@ 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.utils import debug, arg_check # noqa def assert_utf8_env(): @@ -72,7 +72,17 @@ def run( master = master_cls(opts) parser = make_parser(opts) - args = parser.parse_args(arguments) + + # 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) diff --git a/mitmproxy/tools/web/static/static.js b/mitmproxy/tools/web/static/static.js new file mode 100644 index 00000000..4ffedbcf --- /dev/null +++ b/mitmproxy/tools/web/static/static.js @@ -0,0 +1 @@ +MITMWEB_STATIC = false;
\ No newline at end of file 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/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/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 5e6fa701..4f161ef5 100644 --- a/test/mitmproxy/proxy/protocol/test_http2.py +++ b/test/mitmproxy/proxy/protocol/test_http2.py @@ -507,9 +507,6 @@ class TestBodySizeLimit(_Http2Test): def test_body_size_limit(self): self.options.body_size_limit = "20" - # FIXME: This should not be required? - self.options._processed["body_size_limit"] = 20 - h2_conn = self.setup_connection() self._send_request( diff --git a/test/mitmproxy/proxy/protocol/test_tls.py b/test/mitmproxy/proxy/protocol/test_tls.py index 980ba7bd..e17ee46f 100644 --- a/test/mitmproxy/proxy/protocol/test_tls.py +++ b/test/mitmproxy/proxy/protocol/test_tls.py @@ -23,5 +23,4 @@ class TestClientHello: ) c = TlsClientHello(data) assert c.sni == 'example.com' - assert c.alpn_protocols[0].name == b'h2' - assert c.alpn_protocols[1].name == b'http/1.1' + assert c.alpn_protocols == [b'h2', b'http/1.1'] 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/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(): diff --git a/web/package.json b/web/package.json index a42abf89..d9224530 100644 --- a/web/package.json +++ b/web/package.json @@ -28,11 +28,11 @@ "lodash": "^4.17.4", "mock-xmlhttprequest": "^1.1.0", "prop-types": "^15.5.10", - "react": "^15.5.4", + "react": "16.0.0-beta.3", "react-codemirror": "^1.0.0", - "react-dom": "^15.4.2", + "react-dom": "16.0.0-beta.3", "react-redux": "^5.0.5", - "react-test-renderer": "^15.5.4", + "react-test-renderer": "16.0.0-beta.3", "redux": "^3.6.0", "redux-logger": "^3.0.6", "redux-mock-store": "^1.2.3", diff --git a/web/src/js/__tests__/components/Header/FlowMenuSpec.js b/web/src/js/__tests__/components/Header/FlowMenuSpec.js index 1278d8ee..65fde213 100644 --- a/web/src/js/__tests__/components/Header/FlowMenuSpec.js +++ b/web/src/js/__tests__/components/Header/FlowMenuSpec.js @@ -7,7 +7,6 @@ import { TFlow, TStore }from '../../ducks/tutils' import { MessageUtils } from "../../../flow/utils" import { Provider } from 'react-redux' - describe('FlowMenu Component', () => { let actions = { resumeFlow: jest.fn(), diff --git a/web/src/js/__tests__/components/Header/OptionMenuSpec.js b/web/src/js/__tests__/components/Header/OptionMenuSpec.js index b84fce6e..980285ef 100644 --- a/web/src/js/__tests__/components/Header/OptionMenuSpec.js +++ b/web/src/js/__tests__/components/Header/OptionMenuSpec.js @@ -4,7 +4,6 @@ import { Provider } from 'react-redux' import OptionMenu from '../../../components/Header/OptionMenu' import { TStore } from '../../ducks/tutils' - describe('OptionMenu Component', () => { it('should render correctly', () => { let store = TStore(), diff --git a/web/src/js/__tests__/components/Header/__snapshots__/FileMenuSpec.js.snap b/web/src/js/__tests__/components/Header/__snapshots__/FileMenuSpec.js.snap index 15c1afbc..ef935914 100644 --- a/web/src/js/__tests__/components/Header/__snapshots__/FileMenuSpec.js.snap +++ b/web/src/js/__tests__/components/Header/__snapshots__/FileMenuSpec.js.snap @@ -72,17 +72,9 @@ exports[`FileMenu Component should render correctly 1`] = ` /> Options </a> - - </li> - <li> - <hr className="divider" /> - - </li> - <li> - <a href="http://mitm.it/" target="_blank" diff --git a/web/src/js/app.jsx b/web/src/js/app.jsx index 76720124..ee660fd6 100644 --- a/web/src/js/app.jsx +++ b/web/src/js/app.jsx @@ -9,6 +9,7 @@ import rootReducer from './ducks/index' import { add as addLog } from './ducks/eventLog' import useUrlState from './urlState' import WebSocketBackend from './backends/websocket' +import StaticBackend from './backends/static' import { logger } from 'redux-logger' @@ -25,7 +26,11 @@ const store = createStore( ) useUrlState(store) -window.backend = new WebSocketBackend(store) +if (MITMWEB_STATIC) { + window.backend = new StaticBackend(store) +} else { + window.backend = new WebSocketBackend(store) +} window.addEventListener('error', msg => { store.dispatch(addLog(msg)) diff --git a/web/src/js/backends/static.js b/web/src/js/backends/static.js new file mode 100644 index 00000000..6657fecf --- /dev/null +++ b/web/src/js/backends/static.js @@ -0,0 +1,33 @@ +/* + * This backend uses the REST API only to host static instances, + * without any Websocket connection. + */ +import { fetchApi } from "../utils" + +export default class StaticBackend { + constructor(store) { + this.store = store + this.onOpen() + } + + onOpen() { + this.fetchData("settings") + this.fetchData("flows") + this.fetchData("events") + this.fetchData("options") + } + + fetchData(resource) { + fetchApi(`/${resource}`) + .then(res => res.json()) + .then(json => { + this.receive(resource, json) + }) + } + + receive(resource, data) { + let type = `${resource}_RECEIVE`.toUpperCase() + this.store.dispatch({ type, cmd: "receive", resource, data }) + } + +} diff --git a/web/src/js/components/Footer.jsx b/web/src/js/components/Footer.jsx index 08d15496..db9afe6f 100644 --- a/web/src/js/components/Footer.jsx +++ b/web/src/js/components/Footer.jsx @@ -2,6 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { formatSize } from '../utils.js' +import HideInStatic from '../components/common/HideInStatic' Footer.propTypes = { settings: PropTypes.object.isRequired, @@ -49,11 +50,14 @@ function Footer({ settings }) { <span className="label label-success">stream: {formatSize(stream_large_bodies)}</span> )} <div className="pull-right"> - {server && ( + <HideInStatic> + { + server && ( <span className="label label-primary" title="HTTP Proxy Server Address"> {listen_host||"*"}:{listen_port} - </span> - )} + </span>) + } + </HideInStatic> <span className="label label-info" title="Mitmproxy Version"> v{version} </span> diff --git a/web/src/js/components/Header.jsx b/web/src/js/components/Header.jsx index ebe7453c..9b7354eb 100644 --- a/web/src/js/components/Header.jsx +++ b/web/src/js/components/Header.jsx @@ -8,6 +8,7 @@ import FileMenu from './Header/FileMenu' import FlowMenu from './Header/FlowMenu' import {setActiveMenu} from '../ducks/ui/header' import ConnectionIndicator from "./Header/ConnectionIndicator" +import HideInStatic from './common/HideInStatic' class Header extends Component { static entries = [MainMenu, OptionMenu] @@ -40,7 +41,9 @@ class Header extends Component { {Entry.title} </a> ))} - <ConnectionIndicator/> + <HideInStatic> + <ConnectionIndicator/> + </HideInStatic> </nav> <div> <Active/> diff --git a/web/src/js/components/Header/FileMenu.jsx b/web/src/js/components/Header/FileMenu.jsx index 62f721cf..5cb8e507 100644 --- a/web/src/js/components/Header/FileMenu.jsx +++ b/web/src/js/components/Header/FileMenu.jsx @@ -5,6 +5,7 @@ import FileChooser from '../common/FileChooser' import Dropdown, {Divider} from '../common/Dropdown' import * as flowsActions from '../../ducks/flows' import * as modalActions from '../../ducks/ui/modal' +import HideInStatic from "../common/HideInStatic"; FileMenu.propTypes = { clearFlows: PropTypes.func.isRequired, @@ -36,17 +37,18 @@ export function FileMenu ({clearFlows, loadFlows, saveFlows, openModal}) { Save... </a> + <HideInStatic> <a href="#" onClick={e => { e.preventDefault(); openModal(); }}> <i className="fa fa-fw fa-cog"></i> Options </a> - <Divider/> <a href="http://mitm.it/" target="_blank"> <i className="fa fa-fw fa-external-link"></i> Install Certificates... </a> + </HideInStatic> </Dropdown> ) } diff --git a/web/src/js/components/Header/FlowMenu.jsx b/web/src/js/components/Header/FlowMenu.jsx index 8f104213..70c8bfcf 100644 --- a/web/src/js/components/Header/FlowMenu.jsx +++ b/web/src/js/components/Header/FlowMenu.jsx @@ -4,6 +4,7 @@ import { connect } from "react-redux" import Button from "../common/Button" import { MessageUtils } from "../../flow/utils.js" import * as flowsActions from "../../ducks/flows" +import HideInStatic from "../common/HideInStatic"; FlowMenu.title = 'Flow' @@ -22,6 +23,7 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow return <div/> return ( <div> + <HideInStatic> <div className="menu-group"> <div className="menu-content"> <Button title="[r]eplay flow" icon="fa-repeat text-primary" @@ -43,6 +45,8 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow </div> <div className="menu-legend">Flow Modification</div> </div> + </HideInStatic> + <div className="menu-group"> <div className="menu-content"> <Button title="download" icon="fa-download" @@ -52,6 +56,8 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow </div> <div className="menu-legend">Export</div> </div> + + <HideInStatic> <div className="menu-group"> <div className="menu-content"> <Button disabled={!flow || !flow.intercepted} title="[a]ccept intercepted flow" @@ -65,6 +71,7 @@ export function FlowMenu({ flow, resumeFlow, killFlow, replayFlow, duplicateFlow </div> <div className="menu-legend">Interception</div> </div> + </HideInStatic> </div> diff --git a/web/src/js/components/Header/OptionMenu.jsx b/web/src/js/components/Header/OptionMenu.jsx index b33d578d..c41c9d99 100644 --- a/web/src/js/components/Header/OptionMenu.jsx +++ b/web/src/js/components/Header/OptionMenu.jsx @@ -3,12 +3,14 @@ import PropTypes from 'prop-types' import { connect } from "react-redux" import { SettingsToggle, EventlogToggle } from "./MenuToggle" import DocsLink from "../common/DocsLink" +import HideInStatic from "../common/HideInStatic"; OptionMenu.title = 'Options' export default function OptionMenu() { return ( <div> + <HideInStatic> <div className="menu-group"> <div className="menu-content"> <SettingsToggle setting="http2">HTTP/2.0</SettingsToggle> @@ -17,6 +19,7 @@ export default function OptionMenu() { </div> <div className="menu-legend">Protocol Support</div> </div> + <div className="menu-group"> <div className="menu-content"> <SettingsToggle setting="anticache"> @@ -29,12 +32,17 @@ export default function OptionMenu() { </div> <div className="menu-legend">HTTP Options</div> </div> + </HideInStatic> + <div className="menu-group"> <div className="menu-content"> + <HideInStatic> <SettingsToggle setting="showhost"> Use Host Header <i className="fa fa-question-circle" title="Use the Host header to construct URLs for display."></i> </SettingsToggle> + </HideInStatic> + <EventlogToggle/> </div> <div className="menu-legend">View Options</div> diff --git a/web/src/js/components/common/HideInStatic.jsx b/web/src/js/components/common/HideInStatic.jsx new file mode 100644 index 00000000..c5f3bf47 --- /dev/null +++ b/web/src/js/components/common/HideInStatic.jsx @@ -0,0 +1,5 @@ +import React from 'react' + +export default function HideInStatic({ children }) { + return global.MITMWEB_STATIC ? null : [children] +} diff --git a/web/src/templates/index.html b/web/src/templates/index.html index db9d2ecb..d2d01776 100644 --- a/web/src/templates/index.html +++ b/web/src/templates/index.html @@ -7,6 +7,7 @@ <link rel="stylesheet" href="/static/vendor.css"/> <link rel="stylesheet" href="/static/app.css"/> <link rel="icon" href="/static/images/favicon.ico" type="image/x-icon"/> + <script src="/static/static.js"></script> <script src="/static/vendor.js"></script> <script src="/static/app.js"></script> </head> diff --git a/web/yarn.lock b/web/yarn.lock index f841e38b..6efd8885 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -4309,7 +4309,7 @@ promise@^7.1.1: dependencies:
asap "~2.0.3"
-prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@~15.5.7:
+prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6:
version "15.5.10"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
dependencies:
@@ -4404,14 +4404,14 @@ react-codemirror@^1.0.0: lodash.isequal "^4.5.0"
prop-types "^15.5.4"
-react-dom@^15.4.2:
- version "15.5.4"
- resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.5.4.tgz#ba0c28786fd52ed7e4f2135fe0288d462aef93da"
+react-dom@16.0.0-beta.3:
+ version "16.0.0-beta.3"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.0-beta.3.tgz#6b662e8db127d14565b98799c13532044c7768b9"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.1.0"
object-assign "^4.1.0"
- prop-types "~15.5.7"
+ prop-types "^15.5.6"
react-redux@^5.0.5:
version "5.0.5"
@@ -4425,21 +4425,21 @@ react-redux@^5.0.5: loose-envify "^1.1.0"
prop-types "^15.5.10"
-react-test-renderer@^15.5.4:
- version "15.5.4"
- resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc"
+react-test-renderer@16.0.0-beta.3:
+ version "16.0.0-beta.3"
+ resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.0.0-beta.3.tgz#334a97818c0fd841bb377da34bc2e5a0284772fb"
dependencies:
fbjs "^0.8.9"
object-assign "^4.1.0"
-react@^15.5.4:
- version "15.5.4"
- resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047"
+react@16.0.0-beta.3:
+ version "16.0.0-beta.3"
+ resolved "https://registry.yarnpkg.com/react/-/react-16.0.0-beta.3.tgz#f3974ce09dfef8e7debaba87c063a35aa09878a4"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.1.0"
object-assign "^4.1.0"
- prop-types "^15.5.7"
+ prop-types "^15.5.6"
read-only-stream@^2.0.0:
version "2.0.0"
|