diff options
Diffstat (limited to 'mitmproxy/protocol/http2.py')
-rw-r--r-- | mitmproxy/protocol/http2.py | 101 |
1 files changed, 56 insertions, 45 deletions
diff --git a/mitmproxy/protocol/http2.py b/mitmproxy/protocol/http2.py index 30763c66..39512c8f 100644 --- a/mitmproxy/protocol/http2.py +++ b/mitmproxy/protocol/http2.py @@ -1,28 +1,27 @@ -from __future__ import (absolute_import, print_function, division) +from __future__ import absolute_import, print_function, division import threading import time -from six.moves import queue - import traceback + +import h2.exceptions +import hyperframe import six -from h2.connection import H2Connection -from h2.exceptions import StreamClosedError +from h2 import connection from h2 import events -from hyperframe.frame import PriorityFrame - -from netlib.tcp import ssl_read_select -from netlib.exceptions import HttpException -from netlib.http import Headers -from netlib.utils import http2_read_raw_frame, parse_url +from six.moves import queue -from .base import Layer -from .http import _HttpTransmissionLayer, HttpLayer -from ..exceptions import ProtocolException, Http2ProtocolException -from ..models import HTTPRequest, HTTPResponse +import netlib.exceptions +from mitmproxy import exceptions +from mitmproxy import models +from mitmproxy.protocol import base +from mitmproxy.protocol import http +import netlib.http +from netlib import tcp +from netlib.http import http2 -class SafeH2Connection(H2Connection): +class SafeH2Connection(connection.H2Connection): def __init__(self, conn, *args, **kwargs): super(SafeH2Connection, self).__init__(*args, **kwargs) @@ -45,7 +44,7 @@ class SafeH2Connection(H2Connection): with self.lock: try: self.reset_stream(stream_id, error_code) - except StreamClosedError: # pragma: no cover + except h2.exceptions.StreamClosedError: # pragma: no cover # stream is already closed - good pass self.conn.send(self.data_to_send()) @@ -58,7 +57,7 @@ class SafeH2Connection(H2Connection): def safe_send_headers(self, is_zombie, stream_id, headers): with self.lock: if is_zombie(): # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") self.send_headers(stream_id, headers.fields) self.conn.send(self.data_to_send()) @@ -69,7 +68,7 @@ class SafeH2Connection(H2Connection): self.lock.acquire() if is_zombie(): # pragma: no cover self.lock.release() - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") max_outbound_frame_size = self.max_outbound_frame_size frame_chunk = chunk[position:position + max_outbound_frame_size] if self.local_flow_control_window(stream_id) < len(frame_chunk): @@ -82,12 +81,12 @@ class SafeH2Connection(H2Connection): position += max_outbound_frame_size with self.lock: if is_zombie(): # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") self.end_stream(stream_id) self.conn.send(self.data_to_send()) -class Http2Layer(Layer): +class Http2Layer(base.Layer): def __init__(self, ctx, mode): super(Http2Layer, self).__init__(ctx) @@ -107,13 +106,13 @@ class Http2Layer(Layer): self.active_conns.append(self.server_conn.connection) def connect(self): # pragma: no cover - raise Http2ProtocolException("HTTP2 layer should already have a connection.") + raise exceptions.Http2ProtocolException("HTTP2 layer should already have a connection.") def set_server(self): # pragma: no cover - raise Http2ProtocolException("Cannot change server for HTTP2 connections.") + raise exceptions.Http2ProtocolException("Cannot change server for HTTP2 connections.") def disconnect(self): # pragma: no cover - raise Http2ProtocolException("Cannot dis- or reconnect in HTTP2 connections.") + raise exceptions.Http2ProtocolException("Cannot dis- or reconnect in HTTP2 connections.") def next_layer(self): # pragma: no cover # WebSockets over HTTP/2? @@ -134,19 +133,19 @@ class Http2Layer(Layer): eid = event.stream_id if isinstance(event, events.RequestReceived): - headers = Headers([[k, v] for k, v in event.headers]) + headers = netlib.http.Headers([[k, v] for k, v in event.headers]) self.streams[eid] = Http2SingleStreamLayer(self, eid, headers) self.streams[eid].timestamp_start = time.time() self.streams[eid].start() elif isinstance(event, events.ResponseReceived): - headers = Headers([[k, v] for k, v in event.headers]) + headers = netlib.http.Headers([[k, v] for k, v in event.headers]) self.streams[eid].queued_data_length = 0 self.streams[eid].timestamp_start = time.time() self.streams[eid].response_headers = headers self.streams[eid].response_arrived.set() elif isinstance(event, events.DataReceived): if self.config.body_size_limit and self.streams[eid].queued_data_length > self.config.body_size_limit: - raise HttpException("HTTP body too large. Limit is {}.".format(self.config.body_size_limit)) + raise netlib.exceptions.HttpException("HTTP body too large. Limit is {}.".format(self.config.body_size_limit)) self.streams[eid].data_queue.put(event.data) self.streams[eid].queued_data_length += len(event.data) source_conn.h2.safe_increment_flow_control(event.stream_id, event.flow_controlled_length) @@ -177,7 +176,7 @@ class Http2Layer(Layer): self.client_conn.h2.push_stream(parent_eid, event.pushed_stream_id, event.headers) self.client_conn.send(self.client_conn.h2.data_to_send()) - headers = Headers([[str(k), str(v)] for k, v in event.headers]) + headers = netlib.http.Headers([[str(k), str(v)] for k, v in event.headers]) headers['x-mitmproxy-pushed'] = 'true' self.streams[event.pushed_stream_id] = Http2SingleStreamLayer(self, event.pushed_stream_id, headers) self.streams[event.pushed_stream_id].timestamp_start = time.time() @@ -196,7 +195,7 @@ class Http2Layer(Layer): depends_on = self.streams[depends_on].server_stream_id # weight is between 1 and 256 (inclusive), but represented as uint8 (0 to 255) - frame = PriorityFrame(stream_id, depends_on, event.weight - 1, event.exclusive) + frame = hyperframe.frame.PriorityFrame(stream_id, depends_on, event.weight - 1, event.exclusive) self.server_conn.send(frame.serialize()) elif isinstance(event, events.TrailersReceived): raise NotImplementedError() @@ -225,7 +224,7 @@ class Http2Layer(Layer): self.client_conn.send(self.client_conn.h2.data_to_send()) while True: - r = ssl_read_select(self.active_conns, 1) + r = tcp.ssl_read_select(self.active_conns, 1) for conn in r: source_conn = self.client_conn if conn == self.client_conn.connection else self.server_conn other_conn = self.server_conn if conn == self.client_conn.connection else self.client_conn @@ -233,7 +232,7 @@ class Http2Layer(Layer): with source_conn.h2.lock: try: - raw_frame = b''.join(http2_read_raw_frame(source_conn.rfile)) + raw_frame = b''.join(http2.framereader.http2_read_raw_frame(source_conn.rfile)) except: # read frame failed: connection closed self._kill_all_streams() @@ -251,7 +250,7 @@ class Http2Layer(Layer): self._cleanup_streams() -class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): +class Http2SingleStreamLayer(http._HttpTransmissionLayer, threading.Thread): def __init__(self, ctx, stream_id, request_headers): super(Http2SingleStreamLayer, self).__init__(ctx, name="Thread-Http2SingleStreamLayer-{}".format(stream_id)) @@ -306,6 +305,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 @@ -316,7 +318,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): else: # pragma: no cover first_line_format = "absolute" # FIXME: verify if path or :host contains what we need - scheme, host, port, _ = parse_url(path) + scheme, host, port, _ = netlib.http.url.parse(path) if authority: host, _, port = authority.partition(':') @@ -332,7 +334,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): data.append(self.request_data_queue.get()) data = b"".join(data) - return HTTPRequest( + return models.HTTPRequest( first_line_format, method, scheme, @@ -357,15 +359,20 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): with self.server_conn.h2.lock: # We must not assign a stream id if we are already a zombie. if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") 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, @@ -373,18 +380,20 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): message.body ) if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def read_response_headers(self): self.response_arrived.wait() status_code = int(self.response_headers.get(':status', 502)) + headers = self.response_headers.copy() + headers.clear(":status") - return HTTPResponse( + return models.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, @@ -401,16 +410,18 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): yield self.response_data_queue.get() return if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.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") + raise exceptions.Http2ProtocolException("Zombie Stream") def send_response_body(self, _response, chunks): self.client_conn.h2.safe_send_body( @@ -419,7 +430,7 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): chunks ) if self.zombie: # pragma: no cover - raise Http2ProtocolException("Zombie Stream") + raise exceptions.Http2ProtocolException("Zombie Stream") def check_close_connection(self, flow): # This layer only handles a single stream. @@ -434,11 +445,11 @@ class Http2SingleStreamLayer(_HttpTransmissionLayer, threading.Thread): self() def __call__(self): - layer = HttpLayer(self, self.mode) + layer = http.HttpLayer(self, self.mode) try: layer() - except ProtocolException as e: + except exceptions.ProtocolException as e: self.log(repr(e), "info") self.log(traceback.format_exc(), "debug") |