aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--docs/install.rst10
-rw-r--r--mitmproxy/addons/core_option_validation.py6
-rw-r--r--mitmproxy/addons/proxyauth.py2
-rw-r--r--mitmproxy/addons/script.py5
-rw-r--r--mitmproxy/certs.py8
-rw-r--r--mitmproxy/io/compat.py2
-rw-r--r--mitmproxy/log.py5
-rw-r--r--mitmproxy/net/tcp.py2
-rw-r--r--mitmproxy/options.py7
-rw-r--r--mitmproxy/optmanager.py11
-rw-r--r--mitmproxy/proxy/protocol/http1.py5
-rw-r--r--mitmproxy/proxy/protocol/http2.py3
-rw-r--r--mitmproxy/proxy/protocol/http_replay.py3
-rw-r--r--mitmproxy/proxy/protocol/tls.py6
-rw-r--r--mitmproxy/proxy/root_context.py11
-rw-r--r--mitmproxy/tools/cmdline.py1
-rw-r--r--mitmproxy/tools/console/consoleaddons.py6
-rw-r--r--mitmproxy/tools/console/defaultkeys.py2
-rw-r--r--mitmproxy/tools/console/master.py2
-rw-r--r--mitmproxy/tools/main.py14
-rw-r--r--mitmproxy/tools/web/static/static.js1
-rw-r--r--mitmproxy/utils/arg_check.py148
-rw-r--r--mitmproxy/utils/human.py11
-rw-r--r--setup.py6
-rw-r--r--test/mitmproxy/addons/test_core_option_validation.py1
-rw-r--r--test/mitmproxy/addons/test_proxyauth.py421
-rw-r--r--test/mitmproxy/addons/test_script.py27
-rw-r--r--test/mitmproxy/data/addonscripts/load_error.py2
-rw-r--r--test/mitmproxy/proxy/protocol/test_http2.py3
-rw-r--r--test/mitmproxy/proxy/protocol/test_tls.py3
-rw-r--r--test/mitmproxy/test_log.py5
-rw-r--r--test/mitmproxy/test_optmanager.py6
-rw-r--r--test/mitmproxy/utils/test_arg_check.py36
-rw-r--r--test/mitmproxy/utils/test_human.py1
-rw-r--r--web/package.json6
-rw-r--r--web/src/js/__tests__/components/Header/FlowMenuSpec.js1
-rw-r--r--web/src/js/__tests__/components/Header/OptionMenuSpec.js1
-rw-r--r--web/src/js/__tests__/components/Header/__snapshots__/FileMenuSpec.js.snap8
-rw-r--r--web/src/js/app.jsx7
-rw-r--r--web/src/js/backends/static.js33
-rw-r--r--web/src/js/components/Footer.jsx10
-rw-r--r--web/src/js/components/Header.jsx5
-rw-r--r--web/src/js/components/Header/FileMenu.jsx4
-rw-r--r--web/src/js/components/Header/FlowMenu.jsx7
-rw-r--r--web/src/js/components/Header/OptionMenu.jsx8
-rw-r--r--web/src/js/components/common/HideInStatic.jsx5
-rw-r--r--web/src/templates/index.html1
-rw-r--r--web/yarn.lock24
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:
diff --git a/setup.py b/setup.py
index 433635e4..739d1538 100644
--- a/setup.py
+++ b/setup.py
@@ -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}) {
&nbsp;Save...
</a>
+ <HideInStatic>
<a href="#" onClick={e => { e.preventDefault(); openModal(); }}>
<i className="fa fa-fw fa-cog"></i>
&nbsp;Options
</a>
-
<Divider/>
<a href="http://mitm.it/" target="_blank">
<i className="fa fa-fw fa-external-link"></i>
&nbsp;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"