improve curl/httpie export

This commit is contained in:
Maximilian Hils 2019-11-15 19:02:59 +01:00
parent 0873566ff0
commit 01ddda75e8
2 changed files with 51 additions and 42 deletions

View File

@ -1,47 +1,60 @@
import typing
import shlex
from mitmproxy import ctx
from mitmproxy import command
from mitmproxy import flow
from mitmproxy import exceptions
from mitmproxy.utils import strutils
from mitmproxy.net.http.http1 import assemble
import mitmproxy.types
import typing
import pyperclip
import mitmproxy.types
from mitmproxy import command
from mitmproxy import ctx, http
from mitmproxy import exceptions
from mitmproxy import flow
from mitmproxy.net.http.http1 import assemble
from mitmproxy.utils import strutils
def cleanup_request(f: flow.Flow):
def cleanup_request(f: flow.Flow) -> http.HTTPRequest:
if not hasattr(f, "request"):
raise exceptions.CommandError("Can't export flow with no request.")
request = f.request.copy() # type: ignore
assert isinstance(f, http.HTTPFlow)
request = f.request.copy()
request.decode(strict=False)
# a bit of clean-up
if request.method == 'GET' and request.headers.get("content-length", None) == "0":
request.headers.pop('content-length')
request.headers.pop(':authority', None)
# a bit of clean-up - these headers should be automatically set by curl/httpie
request.headers.pop('content-length')
if request.headers.get("host", "") == request.host:
request.headers.pop("host")
if request.headers.get(":authority", "") == request.host:
request.headers.pop(":authority")
return request
def request_content_for_console(request: http.HTTPRequest) -> str:
try:
text = request.get_text(strict=True)
except ValueError:
# shlex.quote doesn't support a bytes object
# see https://github.com/python/cpython/pull/10871
raise exceptions.CommandError("Request content must be valid unicode")
escape_control_chars = {chr(i): f"\\x{i:02x}" for i in range(32)}
return "".join(
escape_control_chars.get(x, x)
for x in text
)
def curl_command(f: flow.Flow) -> str:
request = cleanup_request(f)
args = ["curl"]
for k, v in request.headers.items(multi=True):
args += ["--compressed "] if k == 'accept-encoding' else []
args += ["-H", "%s:%s" % (k, v)]
if k.lower() == "accept-encoding":
args.append("--compressed")
else:
args += ["-H", f"{k}: {v}"]
if request.method != "GET":
args += ["-X", request.method]
args.append(request.url)
if request.content:
try:
content = strutils.always_str(request.content)
except UnicodeDecodeError:
# shlex.quote doesn't support a bytes object
# see https://github.com/python/cpython/pull/10871
raise exceptions.CommandError("Request content must be valid unicode")
args += ["--data-binary", content]
args += ["-d", request_content_for_console(request)]
return ' '.join(shlex.quote(arg) for arg in args)
@ -49,16 +62,10 @@ def httpie_command(f: flow.Flow) -> str:
request = cleanup_request(f)
args = ["http", request.method, request.url]
for k, v in request.headers.items(multi=True):
args.append("%s:%s" % (k, v))
args.append(f"{k}: {v}")
cmd = ' '.join(shlex.quote(arg) for arg in args)
if request.content:
try:
content = strutils.always_str(request.content)
except UnicodeDecodeError:
# shlex.quote doesn't support a bytes object
# see https://github.com/python/cpython/pull/10871
raise exceptions.CommandError("Request content must be valid unicode")
cmd += " <<< " + shlex.quote(content)
cmd += " <<< " + shlex.quote(request_content_for_console(request))
return cmd
@ -67,9 +74,9 @@ def raw(f: flow.Flow) -> bytes:
formats = dict(
curl = curl_command,
httpie = httpie_command,
raw = raw,
curl=curl_command,
httpie=httpie_command,
raw=raw,
)

View File

@ -42,22 +42,23 @@ def tcp_flow():
class TestExportCurlCommand:
def test_get(self, get_request):
result = """curl -H header:qvalue 'http://address:22/path?a=foo&a=bar&b=baz'"""
result = """curl -H 'header: qvalue' 'http://address:22/path?a=foo&a=bar&b=baz'"""
assert export.curl_command(get_request) == result
def test_post(self, post_request):
post_request.request.content = b'nobinarysupport'
result = "curl -H content-length:15 -X POST http://address:22/path --data-binary nobinarysupport"
result = "curl -X POST http://address:22/path -d nobinarysupport"
assert export.curl_command(post_request) == result
def test_fails_with_binary_data(self, post_request):
# shlex.quote doesn't support a bytes object
# see https://github.com/python/cpython/pull/10871
post_request.request.headers["Content-Type"] = "application/json; charset=utf-8"
with pytest.raises(exceptions.CommandError):
export.curl_command(post_request)
def test_patch(self, patch_request):
result = """curl -H header:qvalue -H content-length:7 -X PATCH 'http://address:22/path?query=param' --data-binary content"""
result = """curl -H 'header: qvalue' -X PATCH 'http://address:22/path?query=param' -d content"""
assert export.curl_command(patch_request) == result
def test_tcp(self, tcp_flow):
@ -73,28 +74,29 @@ class TestExportCurlCommand:
)
)
command = export.curl_command(request)
assert shlex.split(command)[-2] == '--data-binary'
assert shlex.split(command)[-2] == '-d'
assert shlex.split(command)[-1] == "'&#"
class TestExportHttpieCommand:
def test_get(self, get_request):
result = """http GET 'http://address:22/path?a=foo&a=bar&b=baz' header:qvalue"""
result = """http GET 'http://address:22/path?a=foo&a=bar&b=baz' 'header: qvalue'"""
assert export.httpie_command(get_request) == result
def test_post(self, post_request):
post_request.request.content = b'nobinarysupport'
result = "http POST http://address:22/path content-length:15 <<< nobinarysupport"
result = "http POST http://address:22/path <<< nobinarysupport"
assert export.httpie_command(post_request) == result
def test_fails_with_binary_data(self, post_request):
# shlex.quote doesn't support a bytes object
# see https://github.com/python/cpython/pull/10871
post_request.request.headers["Content-Type"] = "application/json; charset=utf-8"
with pytest.raises(exceptions.CommandError):
export.httpie_command(post_request)
def test_patch(self, patch_request):
result = """http PATCH 'http://address:22/path?query=param' header:qvalue content-length:7 <<< content"""
result = """http PATCH 'http://address:22/path?query=param' 'header: qvalue' <<< content"""
assert export.httpie_command(patch_request) == result
def test_tcp(self, tcp_flow):