is_http10 and is_http11 properties to requests

This commit is contained in:
Blake Burkhart 2020-10-13 09:06:06 -05:00
parent b17857c7ae
commit 21330f511e
8 changed files with 37 additions and 12 deletions

View File

@ -16,7 +16,7 @@ Unreleased: mitmproxy next
* Fix file unlinking before external viewer finishes loading (@wchasekelley)
* Add --cert-passphrase command line argument (@mirosyn)
* Add interactive tutorials to the documentation (@mplattner)
* Add support for sending (but not parsing) HTTP Trailers to the HTTP/1 protocol (@bburky)
* Add support for sending (but not parsing) HTTP Trailers to the HTTP/1.1 protocol (@bburky)
* --- TODO: add new PRs above this line ---

View File

@ -16,7 +16,7 @@ menu:
HTTP/1.0 and HTTP/1.1 support in mitmproxy is based on our custom HTTP stack,
which takes care of all semantics and on-the-wire parsing/serialization tasks.
mitmproxy currently does not parsing HTTP trailers - but if you want to send
mitmproxy currently does not support parsing HTTP trailers - but if you want to send
us a PR, we promise to take look!
## HTTP/2

View File

@ -16,9 +16,17 @@ def request(flow: http.HTTPFlow):
print("HTTP Trailers detected! Request contains:", flow.request.trailers)
if flow.request.path == "/inject_trailers":
if not flow.request.is_http2:
# HTTP 1.0 requires transfer-encoding: chunked to send trailers
if flow.request.is_http10:
# HTTP/1.0 doesn't support trailers
return
elif flow.request.is_http11:
if not flow.request.content:
# Avoid sending a body on GET requests or a 0 byte chunked body with trailers.
# Otherwise some servers return 400 Bad Request.
return
# HTTP 1.1 requires transfer-encoding: chunked to send trailers
flow.request.headers["transfer-encoding"] = "chunked"
# HTTP 2+ supports trailers on all requests/responses
flow.request.headers["trailer"] = "x-my-injected-trailer-header"
flow.request.trailers = Headers([
@ -32,8 +40,11 @@ def response(flow: http.HTTPFlow):
print("HTTP Trailers detected! Response contains:", flow.response.trailers)
if flow.request.path == "/inject_trailers":
if not flow.response.is_http2:
# HTTP 1.0 requires transfer-encoding: chunked to send trailers
if flow.request.is_http10:
return
elif flow.request.is_http11:
if not flow.response.content:
return
flow.response.headers["transfer-encoding"] = "chunked"
flow.response.headers["trailer"] = "x-my-injected-trailer-header"

View File

@ -35,12 +35,12 @@ def assemble_body(headers, body_chunks, trailers):
if chunk:
yield b"%x\r\n%s\r\n" % (len(chunk), chunk)
if trailers:
yield b"0\r\n%s\r\n" % (trailers,)
yield b"0\r\n%s\r\n" % trailers
else:
yield b"0\r\n\r\n"
else:
if trailers:
raise exceptions.HttpException("Sending HTTP/1 trailer headers requires transfer-encoding: chunked")
raise exceptions.HttpException("Sending HTTP/1.1 trailer headers requires transfer-encoding: chunked")
for chunk in body_chunks:
yield chunk

View File

@ -70,6 +70,14 @@ class Message(serializable.Serializable):
def http_version(self, http_version: Union[str, bytes]) -> None:
self.data.http_version = strutils.always_bytes(http_version, "utf-8", "surrogateescape")
@property
def is_http10(self) -> bool:
return self.data.http_version == b"HTTP/1.0"
@property
def is_http11(self) -> bool:
return self.data.http_version == b"HTTP/1.1"
@property
def is_http2(self) -> bool:
return self.data.http_version == b"HTTP/2.0"

View File

@ -23,7 +23,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer):
def read_request_trailers(self, request):
if "Trailer" in request.headers:
# TODO: not implemented yet
self.log("HTTP/1 request trailer headers are not implemented yet!", "warn")
self.log("HTTP/1.1 request trailer headers are not implemented yet!", "warn")
return None
def send_request_headers(self, request):
@ -37,7 +37,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer):
self.server_conn.wfile.flush()
def send_request_trailers(self, request):
# HTTP/1 request trailer headers are sent in the body
# HTTP/1.1 request trailer headers are sent in the body
pass
def send_request(self, request):
@ -59,7 +59,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer):
# Trailers should actually be parsed unconditionally, the "Trailer" header is optional
if "Trailer" in response.headers:
# TODO: not implemented yet
self.log("HTTP/1 trailer headers are not implemented yet!", "warn")
self.log("HTTP/1.1 trailer headers are not implemented yet!", "warn")
return None
def send_response_headers(self, response):
@ -73,7 +73,7 @@ class Http1Layer(httpbase._HttpTransmissionLayer):
self.client_conn.wfile.flush()
def send_response_trailers(self, response):
# HTTP/1 response trailer headers are sent in the body
# HTTP/1.1 response trailer headers are sent in the body
pass
def check_close_connection(self, flow):

View File

@ -64,6 +64,9 @@ def test_assemble_body():
c = list(assemble_body(Headers(transfer_encoding="chunked"), [b"123456789a"], Headers(trailer="trailer")))
assert c == [b"a\r\n123456789a\r\n", b"0\r\ntrailer: trailer\r\n\r\n"]
with pytest.raises(exceptions.HttpException):
list(assemble_body(Headers(), [b"body"], Headers(trailer="trailer")))
def test_assemble_request_line():
assert _assemble_request_line(treq().data) == b"GET /path HTTP/1.1"

View File

@ -99,6 +99,9 @@ class TestMessage:
def test_http_version(self):
_test_decoded_attr(tutils.tresp(), "http_version")
assert tutils.tresp(http_version=b"HTTP/1.0").is_http10
assert tutils.tresp(http_version=b"HTTP/1.1").is_http11
assert tutils.tresp(http_version=b"HTTP/2.0").is_http2
class TestMessageContentEncoding: