diff --git a/mitmproxy/addons/maplocal.py b/mitmproxy/addons/maplocal.py index fc4353b30..c0881c8cd 100644 --- a/mitmproxy/addons/maplocal.py +++ b/mitmproxy/addons/maplocal.py @@ -133,11 +133,15 @@ class MapLocal: if mimetype: headers = {"Content-Type": mimetype} if local_file: - flow.response = http.HTTPResponse.make( - 200, - local_file.read_bytes(), - headers - ) + try: + flow.response = http.HTTPResponse.make( + 200, + local_file.read_bytes(), + headers + ) + except IOError as e: + ctx.log.warn(f"Could not read file: {e}") + continue # only set flow.response once, for the first matching rule return if any_spec_matches: diff --git a/test/mitmproxy/addons/test_maplocal.py b/test/mitmproxy/addons/test_maplocal.py index 5bfd95a44..439724584 100644 --- a/test/mitmproxy/addons/test_maplocal.py +++ b/test/mitmproxy/addons/test_maplocal.py @@ -24,8 +24,18 @@ from mitmproxy.test import tflow ("https://example.com/foo/bar.jpg", ":/foo/bar.jpg:/tmp", ["/tmp/index.html"]), ] + [ # URL decode and special characters - ("http://example.com/foo%20bar.jpg", ":example.com:/tmp", ["/tmp/foo bar.jpg", "/tmp/foo bar.jpg/index.html", "/tmp/foo_bar.jpg", "/tmp/foo_bar.jpg/index.html"]), - ("http://example.com/fóobår.jpg", ":example.com:/tmp", ["/tmp/fóobår.jpg", "/tmp/fóobår.jpg/index.html", "/tmp/f_ob_r.jpg", "/tmp/f_ob_r.jpg/index.html"]), + ("http://example.com/foo%20bar.jpg", ":example.com:/tmp", [ + "/tmp/foo bar.jpg", + "/tmp/foo bar.jpg/index.html", + "/tmp/foo_bar.jpg", + "/tmp/foo_bar.jpg/index.html" + ]), + ("http://example.com/fóobår.jpg", ":example.com:/tmp", [ + "/tmp/fóobår.jpg", + "/tmp/fóobår.jpg/index.html", + "/tmp/f_ob_r.jpg", + "/tmp/f_ob_r.jpg/index.html" + ]), ] + [ # index.html ("https://example.com/foo", ":example.com/foo:/tmp", ["/tmp/index.html"]), @@ -41,13 +51,23 @@ from mitmproxy.test import tflow ), ( "https://example/results?id=1&foo=2", ":example/(results\\?id=.+):/tmp", - ["/tmp/results?id=1&foo=2", "/tmp/results?id=1&foo=2/index.html", "/tmp/results_id=1_foo=2", "/tmp/results_id=1_foo=2/index.html"] + [ + "/tmp/results?id=1&foo=2", + "/tmp/results?id=1&foo=2/index.html", + "/tmp/results_id=1_foo=2", + "/tmp/results_id=1_foo=2/index.html" + ] ), ] + [ # test directory traversal detection ("https://example.com/../../../../../../etc/passwd", ":example.com:/tmp", []), # those get already sanitized to benign versions before they reach our detection: - ("https://example.com/C:\\foo.txt", ":example.com:/tmp", ["/tmp/C:/foo.txt", "/tmp/C:/foo.txt/index.html", "/tmp/C_/foo.txt", "/tmp/C_/foo.txt/index.html"]), + ("https://example.com/C:\\foo.txt", ":example.com:/tmp", [ + "/tmp/C:/foo.txt", + "/tmp/C:/foo.txt/index.html", + "/tmp/C_/foo.txt", + "/tmp/C_/foo.txt/index.html" + ]), ("https://example.com//etc/passwd", ":example.com:/tmp", ["/tmp/etc/passwd", "/tmp/etc/passwd/index.html"]), ] ) @@ -61,7 +81,17 @@ def test_file_candidates(url, spec, expected_candidates): class TestMapLocal: - def test_map_local(self, tmpdir): + + def test_configure(self, tmpdir): + ml = MapLocal() + with taddons.context(ml) as tctx: + tctx.configure(ml, map_local=["/foo/bar/" + str(tmpdir)]) + with pytest.raises(Exception, match="Invalid regular expression"): + tctx.configure(ml, map_local=["/foo/+/" + str(tmpdir)]) + with pytest.raises(Exception, match="Invalid file path"): + tctx.configure(ml, map_local=["/foo/.+/three"]) + + def test_simple(self, tmpdir): ml = MapLocal() with taddons.context(ml) as tctx: @@ -70,7 +100,7 @@ class TestMapLocal: tctx.configure( ml, map_local=[ - "://example.org/images:" + str(tmpdir) + "|//example.org/images|" + str(tmpdir) ] ) f = tflow.tflow() @@ -83,7 +113,7 @@ class TestMapLocal: tctx.configure( ml, map_local=[ - "://example.org:" + str(tmpdir) + "|//example.org|" + str(tmpdir) ] ) f = tflow.tflow() @@ -96,10 +126,59 @@ class TestMapLocal: tctx.configure( ml, map_local=[ - ":example.org/foo/foo/bar.jpg:" + str(tmpfile) + "|example.org/foo/foo/bar.jpg|" + str(tmpfile) ] ) f = tflow.tflow() f.request.url = b"https://example.org/foo/foo/bar.jpg" ml.request(f) assert f.response.content == b"foofoobar" + + @pytest.mark.asyncio + async def test_nonexistent_files(self, tmpdir, monkeypatch): + ml = MapLocal() + + with taddons.context(ml) as tctx: + tctx.configure( + ml, + map_local=[ + "|example.org/css|" + str(tmpdir) + ] + ) + f = tflow.tflow() + f.request.url = b"https://example.org/css/nonexistent" + ml.request(f) + assert f.response.status_code == 404 + assert await tctx.master.await_log("None of the local file candidates exist") + + tmpfile = tmpdir.join("foo.jpg") + tmpfile.write("foo") + tctx.configure( + ml, + map_local=[ + "|//example.org/images|" + str(tmpfile) + ] + ) + tmpfile.remove() + monkeypatch.setattr(Path, "is_file", lambda x: True) + f = tflow.tflow() + f.request.url = b"https://example.org/images/foo.jpg" + ml.request(f) + assert await tctx.master.await_log("could not read file") + + def test_has_reply(self, tmpdir): + ml = MapLocal() + with taddons.context(ml) as tctx: + tmpfile = tmpdir.join("foo.jpg") + tmpfile.write("foo") + tctx.configure( + ml, + map_local=[ + "|//example.org/images|" + str(tmpfile) + ] + ) + f = tflow.tflow() + f.request.url = b"https://example.org/images/foo.jpg" + f.kill() + ml.request(f) + assert not f.response diff --git a/test/mitmproxy/addons/test_mapremote.py b/test/mitmproxy/addons/test_mapremote.py index 3e06457cc..06879ffa0 100644 --- a/test/mitmproxy/addons/test_mapremote.py +++ b/test/mitmproxy/addons/test_mapremote.py @@ -11,13 +11,8 @@ class TestMapRemote: mr = mapremote.MapRemote() with taddons.context(mr) as tctx: tctx.configure(mr, map_remote=["one/two/three"]) - with pytest.raises(Exception, match="Cannot parse map_remote .* Invalid number"): - tctx.configure(mr, map_remote=["/"]) - with pytest.raises(Exception, match="Cannot parse map_remote .* Invalid filter"): - tctx.configure(mr, map_remote=["/~b/two/three"]) - with pytest.raises(Exception, match="Cannot parse map_remote .* Invalid regular expression"): + with pytest.raises(Exception, match="Invalid regular expression"): tctx.configure(mr, map_remote=["/foo/+/three"]) - tctx.configure(mr, map_remote=["/a/b/c/"]) def test_simple(self): mr = mapremote.MapRemote() diff --git a/test/mitmproxy/addons/test_modifybody.py b/test/mitmproxy/addons/test_modifybody.py index f78932e52..1b25361d2 100644 --- a/test/mitmproxy/addons/test_modifybody.py +++ b/test/mitmproxy/addons/test_modifybody.py @@ -12,7 +12,6 @@ class TestModifyBody: tctx.configure(mb, modify_body=["one/two/three"]) with pytest.raises(Exception, match="Cannot parse modify_body"): tctx.configure(mb, modify_body=["/"]) - tctx.configure(mb, modify_body=["/a/b/c/"]) def test_simple(self): mb = modifybody.ModifyBody() diff --git a/test/mitmproxy/addons/test_modifyheaders.py b/test/mitmproxy/addons/test_modifyheaders.py index f3ebd279f..c358c0d2a 100644 --- a/test/mitmproxy/addons/test_modifyheaders.py +++ b/test/mitmproxy/addons/test_modifyheaders.py @@ -22,15 +22,6 @@ def test_parse_modify_spec(): assert spec.subject == b"bar" assert spec.read_replacement() == b"voing" - with pytest.raises(ValueError, match="Invalid number of parameters"): - parse_modify_spec("/", False) - - with pytest.raises(ValueError, match="Invalid filter pattern"): - parse_modify_spec("/~b/one/two", False) - - with pytest.raises(ValueError, match="Invalid filter pattern"): - parse_modify_spec("/~b/one/two", False) - with pytest.raises(ValueError, match="Invalid regular expression"): parse_modify_spec("/[/two", True) diff --git a/test/mitmproxy/utils/test_spec.py b/test/mitmproxy/utils/test_spec.py new file mode 100644 index 000000000..63a063563 --- /dev/null +++ b/test/mitmproxy/utils/test_spec.py @@ -0,0 +1,20 @@ +import pytest +from mitmproxy.utils.spec import parse_spec + + +def test_parse_spec(): + flow_filter, subject, replacement = parse_spec("/foo/bar/voing") + assert flow_filter.pattern == "foo" + assert subject == "bar" + assert replacement == "voing" + + flow_filter, subject, replacement = parse_spec("/bar/voing") + assert flow_filter(1) is True + assert subject == "bar" + assert replacement == "voing" + + with pytest.raises(ValueError, match="Invalid number of parameters"): + parse_spec("/") + + with pytest.raises(ValueError, match="Invalid filter pattern"): + parse_spec("/~b/one/two")