diff options
-rw-r--r-- | docs/features/replacements.rst | 11 | ||||
-rw-r--r-- | mitmproxy/addons/__init__.py | 1 | ||||
-rw-r--r-- | mitmproxy/addons/replace.py | 40 | ||||
-rw-r--r-- | mitmproxy/certs.py | 3 | ||||
-rw-r--r-- | mitmproxy/flowfilter.py | 8 | ||||
-rw-r--r-- | mitmproxy/net/http/message.py | 6 | ||||
-rw-r--r-- | mitmproxy/options.py | 9 | ||||
-rw-r--r-- | mitmproxy/tools/cmdline.py | 1 | ||||
-rw-r--r-- | mitmproxy/tools/console/grideditor/editors.py | 6 | ||||
-rw-r--r-- | mitmproxy/types/multidict.py | 3 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_replace.py | 69 | ||||
-rw-r--r-- | test/mitmproxy/net/http/test_message.py | 6 | ||||
-rw-r--r-- | test/mitmproxy/test_certs.py | 1 | ||||
-rw-r--r-- | test/mitmproxy/types/test_multidict.py | 5 | ||||
-rw-r--r-- | web/src/js/filt/filt.peg | 2 |
15 files changed, 75 insertions, 96 deletions
diff --git a/docs/features/replacements.rst b/docs/features/replacements.rst index 215f0ddb..39dccca2 100644 --- a/docs/features/replacements.rst +++ b/docs/features/replacements.rst @@ -48,14 +48,14 @@ In practice, it's pretty common for the replacement literal to be long and complex. For instance, it might be an XSS exploit that weighs in at hundreds or thousands of characters. To cope with this, there's a variation of the replacement hook specifier that lets you load the replacement text from a file. -So, you might start **mitmdump** as follows: +To specify a file as replacement, prefix the file path with ``@``. +You might start **mitmdump** as follows: ->>> mitmdump --replace-from-file :~q:foo:~/xss-exploit +>>> mitmdump --replacements :~q:foo:@~/xss-exploit This will load the replacement text from the file ``~/xss-exploit``. -Both the ``--replace`` and ``--replace-from-file`` flags can be passed multiple -times. +The ``--replacements`` flag can be passed multiple times. Interactively @@ -66,7 +66,6 @@ replacement hooks using a built-in editor. The context-sensitive help (:kbd:`?`) complete usage information. ================== ======================= -command-line ``--replace``, - ``--replace-from-file`` +command-line ``--replacements`` mitmproxy shortcut :kbd:`O` then :kbd:`R` ================== ======================= diff --git a/mitmproxy/addons/__init__.py b/mitmproxy/addons/__init__.py index 80e3b2cb..7a45106c 100644 --- a/mitmproxy/addons/__init__.py +++ b/mitmproxy/addons/__init__.py @@ -30,7 +30,6 @@ def default_addons(): onboarding.Onboarding(), proxyauth.ProxyAuth(), replace.Replace(), - replace.ReplaceFile(), script.ScriptLoader(), serverplayback.ServerPlayback(), setheaders.SetHeaders(), diff --git a/mitmproxy/addons/replace.py b/mitmproxy/addons/replace.py index 0d0c3aa5..d6c11ca4 100644 --- a/mitmproxy/addons/replace.py +++ b/mitmproxy/addons/replace.py @@ -1,3 +1,4 @@ +import os import re from mitmproxy import exceptions @@ -42,7 +43,7 @@ def parse_hook(s): return patt, a, b -class _ReplaceBase: +class Replace: def __init__(self): self.lst = [] @@ -51,12 +52,12 @@ class _ReplaceBase: .replacements is a list of tuples (fpat, rex, s): fpatt: a string specifying a filter pattern. - rex: a regular expression, as bytes. - s: the replacement string, as bytes + rex: a regular expression, as string. + s: the replacement string """ - if self.optionName in updated: + if "replacements" in updated: lst = [] - for rep in getattr(options, self.optionName): + for rep in options.replacements: fpatt, rex, s = parse_hook(rep) flt = flowfilter.parse(fpatt) @@ -65,11 +66,16 @@ class _ReplaceBase: "Invalid filter pattern: %s" % fpatt ) try: + # We should ideally escape here before trying to compile re.compile(rex) except re.error as e: raise exceptions.OptionsError( "Invalid regular expression: %s - %s" % (rex, str(e)) ) + if s.startswith("@") and not os.path.isfile(s[1:]): + raise exceptions.OptionsError( + "Invalid file path: {}".format(s[1:]) + ) lst.append((rex, s, flt)) self.lst = lst @@ -89,21 +95,13 @@ class _ReplaceBase: if not flow.reply.has_message: self.execute(flow) - -class Replace(_ReplaceBase): - optionName = "replacements" - def replace(self, obj, rex, s): + if s.startswith("@"): + s = os.path.expanduser(s[1:]) + try: + with open(s, "rb") as f: + s = f.read() + except IOError: + ctx.log.warn("Could not read replacement file: %s" % s) + return obj.replace(rex, s, flags=re.DOTALL) - - -class ReplaceFile(_ReplaceBase): - optionName = "replacement_files" - - def replace(self, obj, rex, s): - try: - v = open(s, "rb").read() - except IOError as e: - ctx.log.warn("Could not read replacement file: %s" % s) - return - obj.replace(rex, v, flags=re.DOTALL) diff --git a/mitmproxy/certs.py b/mitmproxy/certs.py index 6485eed7..618b34de 100644 --- a/mitmproxy/certs.py +++ b/mitmproxy/certs.py @@ -384,9 +384,6 @@ class SSLCert(serializable.Serializable): def __eq__(self, other): return self.digest("sha256") == other.digest("sha256") - def __ne__(self, other): - return not self.__eq__(other) - def get_state(self): return self.to_pem() diff --git a/mitmproxy/flowfilter.py b/mitmproxy/flowfilter.py index 7c4f95f7..2c7fc52f 100644 --- a/mitmproxy/flowfilter.py +++ b/mitmproxy/flowfilter.py @@ -319,10 +319,14 @@ class FDomain(_Rex): code = "d" help = "Domain" flags = re.IGNORECASE + is_binary = False @only(http.HTTPFlow) def __call__(self, f): - return bool(self.re.search(f.request.data.host)) + return bool( + self.re.search(f.request.host) or + self.re.search(f.request.pretty_host) + ) class FUrl(_Rex): @@ -339,7 +343,7 @@ class FUrl(_Rex): @only(http.HTTPFlow) def __call__(self, f): - return self.re.search(f.request.url) + return self.re.search(f.request.pretty_url) class FSrc(_Rex): diff --git a/mitmproxy/net/http/message.py b/mitmproxy/net/http/message.py index c0a78ea9..506674d6 100644 --- a/mitmproxy/net/http/message.py +++ b/mitmproxy/net/http/message.py @@ -13,9 +13,6 @@ class MessageData(serializable.Serializable): return self.__dict__ == other.__dict__ return False - def __ne__(self, other): - return not self.__eq__(other) - def set_state(self, state): for k, v in state.items(): if k == "headers": @@ -39,9 +36,6 @@ class Message(serializable.Serializable): return self.data == other.data return False - def __ne__(self, other): - return not self.__eq__(other) - def get_state(self): return self.data.get_state() diff --git a/mitmproxy/options.py b/mitmproxy/options.py index 6dd8616b..036b3d29 100644 --- a/mitmproxy/options.py +++ b/mitmproxy/options.py @@ -78,7 +78,7 @@ class Options(optmanager.OptManager): "Kill extra requests during replay." ) self.add_option( - "keepserving", bool, True, + "keepserving", bool, False, "Continue serving after client playback or file read." ) self.add_option( @@ -121,13 +121,6 @@ class Options(optmanager.OptManager): """ ) self.add_option( - "replacement_files", Sequence[str], [], - """ - Replacement pattern, where the replacement clause is a path to a - file. - """ - ) - self.add_option( "server_replay_use_headers", Sequence[str], [], "Request headers to be considered during replay." ) diff --git a/mitmproxy/tools/cmdline.py b/mitmproxy/tools/cmdline.py index c5d2bbd7..da091c12 100644 --- a/mitmproxy/tools/cmdline.py +++ b/mitmproxy/tools/cmdline.py @@ -93,7 +93,6 @@ def common_options(parser, opts): # Replacements group = parser.add_argument_group("Replacements") opts.make_parser(group, "replacements", metavar="PATTERN", short="R") - opts.make_parser(group, "replacement_files", metavar="PATTERN") # Set headers group = parser.add_argument_group("Set Headers") diff --git a/mitmproxy/tools/console/grideditor/editors.py b/mitmproxy/tools/console/grideditor/editors.py index 2d24cf86..0d9929ae 100644 --- a/mitmproxy/tools/console/grideditor/editors.py +++ b/mitmproxy/tools/console/grideditor/editors.py @@ -1,5 +1,8 @@ +import os import re + import urwid + from mitmproxy import exceptions from mitmproxy import flowfilter from mitmproxy.addons import script @@ -87,6 +90,9 @@ class ReplaceEditor(base.GridEditor): re.compile(val) except re.error: return "Invalid regular expression." + elif col == 2: + if val.startswith("@") and not os.path.isfile(os.path.expanduser(val[1:])): + return "Invalid file path" return False diff --git a/mitmproxy/types/multidict.py b/mitmproxy/types/multidict.py index 36a1d6cd..c4f42580 100644 --- a/mitmproxy/types/multidict.py +++ b/mitmproxy/types/multidict.py @@ -67,9 +67,6 @@ class _MultiDict(MutableMapping, metaclass=ABCMeta): return self.fields == other.fields return False - def __ne__(self, other): - return not self.__eq__(other) - def get_all(self, key): """ Return the list of all values for a given key. diff --git a/test/mitmproxy/addons/test_replace.py b/test/mitmproxy/addons/test_replace.py index 2311641a..7d590b35 100644 --- a/test/mitmproxy/addons/test_replace.py +++ b/test/mitmproxy/addons/test_replace.py @@ -1,6 +1,5 @@ import pytest -from .. import tservers from mitmproxy.addons import replace from mitmproxy.test import taddons from mitmproxy.test import tflow @@ -32,7 +31,7 @@ class TestReplace: with taddons.context() as tctx: tctx.configure( r, - replacements = [ + replacements=[ "/~q/foo/bar", "/~s/foo/bar", ] @@ -47,53 +46,57 @@ class TestReplace: r.response(f) assert f.response.content == b"bar" - -class TestUpstreamProxy(tservers.HTTPUpstreamProxyTest): - ssl = False - def test_order(self): - sa = replace.Replace() - self.proxy.tmaster.addons.add(sa) - - self.proxy.tmaster.options.replacements = [ - "/~q/foo/bar", - "/~q/bar/baz", - "/~q/foo/oh noes!", - "/~s/baz/ORLY" - ] - p = self.pathoc() - with p.connect(): - req = p.request("get:'%s/p/418:b\"foo\"'" % self.server.urlbase) - assert req.content == b"ORLY" - assert req.status_code == 418 + r = replace.Replace() + with taddons.context() as tctx: + tctx.configure( + r, + replacements=[ + "/foo/bar", + "/bar/baz", + "/foo/oh noes!", + "/bar/oh noes!", + ] + ) + f = tflow.tflow() + f.request.content = b"foo" + r.request(f) + assert f.request.content == b"baz" class TestReplaceFile: def test_simple(self, tmpdir): - r = replace.ReplaceFile() - rp = tmpdir.join("replacement") - rp.write("bar") + r = replace.Replace() with taddons.context() as tctx: + tmpfile = tmpdir.join("replacement") + tmpfile.write("bar") tctx.configure( r, - replacement_files = [ - "/~q/foo/" + str(rp), - "/~s/foo/" + str(rp), - "/~b nonexistent/nonexistent/nonexistent", - ] + replacements=["/~q/foo/@" + str(tmpfile)] ) f = tflow.tflow() f.request.content = b"foo" r.request(f) assert f.request.content == b"bar" - f = tflow.tflow(resp=True) - f.response.content = b"foo" - r.response(f) - assert f.response.content == b"bar" + def test_nonexistent(self, tmpdir): + r = replace.Replace() + with taddons.context() as tctx: + with pytest.raises(Exception, match="Invalid file path"): + tctx.configure( + r, + replacements=["/~q/foo/@nonexistent"] + ) + tmpfile = tmpdir.join("replacement") + tmpfile.write("bar") + tctx.configure( + r, + replacements=["/~q/foo/@" + str(tmpfile)] + ) + tmpfile.remove() f = tflow.tflow() - f.request.content = b"nonexistent" + f.request.content = b"foo" assert not tctx.master.event_log r.request(f) assert tctx.master.event_log diff --git a/test/mitmproxy/net/http/test_message.py b/test/mitmproxy/net/http/test_message.py index 034bd600..b75bc7c2 100644 --- a/test/mitmproxy/net/http/test_message.py +++ b/test/mitmproxy/net/http/test_message.py @@ -38,14 +38,12 @@ def _test_decoded_attr(message, attr): class TestMessageData: - def test_eq_ne(self): + def test_eq(self): data = tutils.tresp(timestamp_start=42, timestamp_end=42).data same = tutils.tresp(timestamp_start=42, timestamp_end=42).data assert data == same - assert not data != same other = tutils.tresp(content=b"foo").data - assert not data == other assert data != other assert data != 0 @@ -61,10 +59,8 @@ class TestMessage: resp = tutils.tresp(timestamp_start=42, timestamp_end=42) same = tutils.tresp(timestamp_start=42, timestamp_end=42) assert resp == same - assert not resp != same other = tutils.tresp(timestamp_start=0, timestamp_end=0) - assert not resp == other assert resp != other assert resp != 0 diff --git a/test/mitmproxy/test_certs.py b/test/mitmproxy/test_certs.py index 2d12c370..88c49561 100644 --- a/test/mitmproxy/test_certs.py +++ b/test/mitmproxy/test_certs.py @@ -160,7 +160,6 @@ class TestSSLCert: assert c2.to_pem() assert c2.has_expired is not None - assert not c1 == c2 assert c1 != c2 def test_err_broken_sans(self): diff --git a/test/mitmproxy/types/test_multidict.py b/test/mitmproxy/types/test_multidict.py index 7d38f6ba..c76cd753 100644 --- a/test/mitmproxy/types/test_multidict.py +++ b/test/mitmproxy/types/test_multidict.py @@ -93,11 +93,6 @@ class TestMultiDict: md1.fields = md1.fields[1:] + md1.fields[:1] assert not (md1 == md2) - def test_ne(self): - assert not TMultiDict() != TMultiDict() - assert TMultiDict() != self._multi() - assert TMultiDict() != 42 - def test_hash(self): """ If a class defines mutable objects and implements an __eq__() method, diff --git a/web/src/js/filt/filt.peg b/web/src/js/filt/filt.peg index b2576661..12959474 100644 --- a/web/src/js/filt/filt.peg +++ b/web/src/js/filt/filt.peg @@ -96,7 +96,7 @@ function responseBody(regex){ function domain(regex){ regex = new RegExp(regex, "i"); function domainFilter(flow){ - return flow.request && regex.test(flow.request.host); + return flow.request && (regex.test(flow.request.host) || regex.test(flow.request.pretty_host)); } domainFilter.desc = "domain matches " + regex; return domainFilter; |