diff options
-rw-r--r-- | mitmproxy/addons/export.py | 26 | ||||
-rw-r--r-- | test/mitmproxy/addons/test_export.py | 30 |
2 files changed, 52 insertions, 4 deletions
diff --git a/mitmproxy/addons/export.py b/mitmproxy/addons/export.py index 4bb44548..f3300079 100644 --- a/mitmproxy/addons/export.py +++ b/mitmproxy/addons/export.py @@ -11,9 +11,13 @@ import mitmproxy.types import pyperclip -def curl_command(f: flow.Flow) -> str: +def raise_if_missing_request(f: flow.Flow) -> None: if not hasattr(f, "request"): raise exceptions.CommandError("Can't export flow with no request.") + + +def curl_command(f: flow.Flow) -> str: + raise_if_missing_request(f) data = "curl " request = f.request.copy() # type: ignore request.decode(strict=False) @@ -30,14 +34,30 @@ def curl_command(f: flow.Flow) -> str: return data +def httpie_command(f: flow.Flow) -> str: + raise_if_missing_request(f) + request = f.request.copy() # type: ignore + data = "http %s " % request.method + request.decode(strict=False) + data += "%s" % request.url + for k, v in request.headers.items(multi=True): + data += " '%s:%s'" % (k, v) + if request.content: + data += " <<< '%s'" % strutils.bytes_to_escaped_str( + request.content, + escape_single_quotes=True + ) + return data + + def raw(f: flow.Flow) -> bytes: - if not hasattr(f, "request"): - raise exceptions.CommandError("Can't export flow with no request.") + raise_if_missing_request(f) return assemble.assemble_request(f.request) # type: ignore formats = dict( curl = curl_command, + httpie = httpie_command, raw = raw, ) diff --git a/test/mitmproxy/addons/test_export.py b/test/mitmproxy/addons/test_export.py index 07227a7a..b625df56 100644 --- a/test/mitmproxy/addons/test_export.py +++ b/test/mitmproxy/addons/test_export.py @@ -65,6 +65,26 @@ class TestExportCurlCommand: export.curl_command(tcp_flow) +class TestExportHttpieCommand: + def test_get(self, get_request): + result = """http GET http://address:22/path?a=foo&a=bar&b=baz 'header:qvalue' 'content-length:0'""" + assert export.httpie_command(get_request) == result + + def test_post(self, post_request): + result = "http POST http://address:22/path 'content-length:256' <<< '{}'".format( + str(bytes(range(256)))[2:-1] + ) + assert export.httpie_command(post_request) == result + + def test_patch(self, patch_request): + result = """http PATCH http://address:22/path?query=param 'header:qvalue' 'content-length:7' <<< 'content'""" + assert export.httpie_command(patch_request) == result + + def test_tcp(self, tcp_flow): + with pytest.raises(exceptions.CommandError): + export.httpie_command(tcp_flow) + + class TestRaw: def test_get(self, get_request): assert b"header: qvalue" in export.raw(get_request) @@ -83,7 +103,7 @@ def test_export(tmpdir): f = str(tmpdir.join("path")) e = export.Export() with taddons.context(): - assert e.formats() == ["curl", "raw"] + assert e.formats() == ["curl", "httpie", "raw"] with pytest.raises(exceptions.CommandError): e.file("nonexistent", tflow.tflow(resp=True), f) @@ -95,6 +115,10 @@ def test_export(tmpdir): assert qr(f) os.unlink(f) + e.file("httpie", tflow.tflow(resp=True), f) + assert qr(f) + os.unlink(f) + @pytest.mark.parametrize("exception, log_message", [ (PermissionError, "Permission denied"), @@ -126,6 +150,10 @@ def test_clip(tmpdir): assert pc.called with mock.patch('pyperclip.copy') as pc: + e.clip("httpie", tflow.tflow(resp=True)) + assert pc.called + + with mock.patch('pyperclip.copy') as pc: log_message = "Pyperclip could not find a " \ "copy/paste mechanism for your system." pc.side_effect = pyperclip.PyperclipException(log_message) |