From 9ea68ebd284ce13d765519a20dd7cfe998c0ae1c Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 31 May 2016 14:34:09 +1200 Subject: [PATCH 1/2] Improve handling of pseudo-headers - The canonical source for :method, :scheme and :path are the .method, .scheme and .path attributes on the request object. - These pseudo-headers are stripped after reading the request, and re-inserted just before sending. - The :authority header remains, and should be handled analagously to the Host header in HTTP1 with respect to display and user interaction. --- mitmproxy/protocol/http2.py | 10 +++++++++- netlib/http/http2/connections.py | 14 ++++++++------ netlib/multidict.py | 8 ++++++++ test/netlib/http/http2/test_connections.py | 5 ++++- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index 30763c664..bdc851283 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -306,6 +306,9 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): method = self.request_headers.get(':method', 'GET') scheme = self.request_headers.get(':scheme', 'https') path = self.request_headers.get(':path', '/') + self.request_headers.clear(":method") + self.request_headers.clear(":scheme") + self.request_headers.clear(":path") host = None port = None @@ -362,10 +365,15 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): self.server_stream_id = self.server_conn.h2.get_next_available_stream_id() self.server_to_client_stream_ids[self.server_stream_id] = self.client_stream_id + headers = message.headers.copy() + headers.insert(0, ":path", message.path) + headers.insert(0, ":method", message.method) + headers.insert(0, ":scheme", message.scheme) + self.server_conn.h2.safe_send_headers( self.is_zombie, self.server_stream_id, - message.headers + headers ) self.server_conn.h2.safe_send_body( self.is_zombie, diff --git a/netlib/http/http2/connections.py b/netlib/http/http2/connections.py index b988d6eff..6b91f2ff6 100644 --- a/netlib/http/http2/connections.py +++ b/netlib/http/http2/connections.py @@ -98,6 +98,11 @@ class HTTP2Protocol(object): method = headers.get(':method', 'GET') scheme = headers.get(':scheme', 'https') path = headers.get(':path', '/') + + headers.clear(":method") + headers.clear(":scheme") + headers.clear(":path") + host = None port = None @@ -202,12 +207,9 @@ class HTTP2Protocol(object): if ':authority' not in headers: headers.insert(0, b':authority', authority.encode('ascii')) - if ':scheme' not in headers: - headers.insert(0, b':scheme', request.scheme.encode('ascii')) - if ':path' not in headers: - headers.insert(0, b':path', request.path.encode('ascii')) - if ':method' not in headers: - headers.insert(0, b':method', request.method.encode('ascii')) + headers.insert(0, b':scheme', request.scheme.encode('ascii')) + headers.insert(0, b':path', request.path.encode('ascii')) + headers.insert(0, b':method', request.method.encode('ascii')) if hasattr(request, 'stream_id'): stream_id = request.stream_id diff --git a/netlib/multidict.py b/netlib/multidict.py index 98fde7e33..f8876cbd5 100644 --- a/netlib/multidict.py +++ b/netlib/multidict.py @@ -171,6 +171,14 @@ class _MultiDict(MutableMapping, Serializable): else: return super(_MultiDict, self).items() + def clear(self, key): + """ + Removes all items with the specified key, and does not raise an + exception if the key does not exist. + """ + if key in self: + del self[key] + def to_dict(self): """ Get the MultiDict as a plain Python dict. diff --git a/test/netlib/http/http2/test_connections.py b/test/netlib/http/http2/test_connections.py index ff462ba64..69667d1cb 100644 --- a/test/netlib/http/http2/test_connections.py +++ b/test/netlib/http/http2/test_connections.py @@ -312,7 +312,10 @@ class TestReadRequest(tservers.ServerTestBase): req = protocol.read_request(NotImplemented) assert req.stream_id - assert req.headers.fields == ((b':method', b'GET'), (b':path', b'/'), (b':scheme', b'https')) + assert req.headers.fields == () + assert req.method == "GET" + assert req.path == "/" + assert req.scheme == "https" assert req.content == b'foobar' From 4de4223b2ddb4417be0d6a2fa0556d531a494091 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Tue, 31 May 2016 15:12:20 +1200 Subject: [PATCH 2/2] Extend pseudo-header treatment to :status on responses --- mitmproxy/protocol/http2.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index bdc851283..b41016768 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -387,12 +387,14 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): self.response_arrived.wait() status_code = int(self.response_headers.get(':status', 502)) + headers = self.response_headers.copy() + headers.clear(":status") return HTTPResponse( http_version=b"HTTP/2.0", status_code=status_code, reason='', - headers=self.response_headers, + headers=headers, content=None, timestamp_start=self.timestamp_start, timestamp_end=self.timestamp_end, @@ -412,10 +414,12 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): raise Http2ProtocolException("Zombie Stream") def send_response_headers(self, response): + headers = response.headers.copy() + headers.insert(0, ":status", str(response.status_code)) self.client_conn.h2.safe_send_headers( self.is_zombie, self.client_stream_id, - response.headers + headers ) if self.zombie: # pragma: no cover raise Http2ProtocolException("Zombie Stream")