codekingpro/portable-devtools
114k
1import time2from abc import abstractmethod3from typing import assert_never4 5from aioquic.h3.connection import ErrorCode as H3ErrorCode6from aioquic.h3.connection import FrameUnexpected as H3FrameUnexpected7from aioquic.h3.events import DataReceived8from aioquic.h3.events import HeadersReceived9from aioquic.h3.events import PushPromiseReceived10 11from . import ErrorCode12from . import RequestData13from . import RequestEndOfMessage14from . import RequestHeaders15from . import RequestProtocolError16from . import RequestTrailers17from . import ResponseData18from . import ResponseEndOfMessage19from . import ResponseHeaders20from . import ResponseProtocolError21from . import ResponseTrailers22from ._base import format_error23from ._base import HttpConnection24from ._base import HttpEvent25from ._base import ReceiveHttp26from ._http2 import format_h2_request_headers27from ._http2 import format_h2_response_headers28from ._http2 import parse_h2_request_headers29from ._http2 import parse_h2_response_headers30from ._http_h3 import LayeredH3Connection31from ._http_h3 import StreamClosed32from ._http_h3 import TrailersReceived33from mitmproxy import connection34from mitmproxy import http35from mitmproxy import version36from mitmproxy.proxy import commands37from mitmproxy.proxy import context38from mitmproxy.proxy import events39from mitmproxy.proxy import layer40from mitmproxy.proxy.layers.quic import error_code_to_str41from mitmproxy.proxy.layers.quic import QuicConnectionClosed42from mitmproxy.proxy.layers.quic import QuicStreamEvent43from mitmproxy.proxy.utils import expect44 45 46class Http3Connection(HttpConnection):47 h3_conn: LayeredH3Connection48 49 ReceiveData: type[RequestData | ResponseData]50 ReceiveEndOfMessage: type[RequestEndOfMessage | ResponseEndOfMessage]51 ReceiveProtocolError: type[RequestProtocolError | ResponseProtocolError]52 ReceiveTrailers: type[RequestTrailers | ResponseTrailers]53 54 def __init__(self, context: context.Context, conn: connection.Connection):55 super().__init__(context, conn)56 self.h3_conn = LayeredH3Connection(57 self.conn, is_client=self.conn is self.context.server58 )59 60 def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:61 if isinstance(event, events.Start):62 yield from self.h3_conn.transmit()63 64 # send mitmproxy HTTP events over the H3 connection65 elif isinstance(event, HttpEvent):66 try:67 if isinstance(event, (RequestData, ResponseData)):68 self.h3_conn.send_data(event.stream_id, event.data)69 elif isinstance(event, (RequestHeaders, ResponseHeaders)):70 headers = yield from (71 format_h2_request_headers(self.context, event)72 if isinstance(event, RequestHeaders)73 else format_h2_response_headers(self.context, event)74 )75 self.h3_conn.send_headers(76 event.stream_id, headers, end_stream=event.end_stream77 )78 elif isinstance(event, (RequestTrailers, ResponseTrailers)):79 self.h3_conn.send_trailers(80 event.stream_id, [*event.trailers.fields]81 )82 elif isinstance(event, (RequestEndOfMessage, ResponseEndOfMessage)):83 self.h3_conn.end_stream(event.stream_id)84 elif isinstance(event, (RequestProtocolError, ResponseProtocolError)):85 status = event.code.http_status_code()86 if (87 isinstance(event, ResponseProtocolError)88 and not self.h3_conn.has_sent_headers(event.stream_id)89 and status is not None90 ):91 self.h3_conn.send_headers(92 event.stream_id,93 [94 (b":status", b"%d" % status),95 (b"server", version.MITMPROXY.encode()),96 (b"content-type", b"text/html"),97 ],98 )99 self.h3_conn.send_data(100 event.stream_id,101 format_error(status, event.message),102 end_stream=True,103 )104 else:105 match event.code:106 case ErrorCode.CANCEL | ErrorCode.CLIENT_DISCONNECTED:107 error_code = H3ErrorCode.H3_REQUEST_CANCELLED108 case ErrorCode.KILL:109 error_code = H3ErrorCode.H3_INTERNAL_ERROR110 case ErrorCode.HTTP_1_1_REQUIRED:111 error_code = H3ErrorCode.H3_VERSION_FALLBACK112 case ErrorCode.PASSTHROUGH_CLOSE:113 # FIXME: This probably shouldn't be a protocol error, but an EOM event.114 error_code = H3ErrorCode.H3_REQUEST_CANCELLED115 case (116 ErrorCode.GENERIC_CLIENT_ERROR117 | ErrorCode.GENERIC_SERVER_ERROR118 | ErrorCode.REQUEST_TOO_LARGE119 | ErrorCode.RESPONSE_TOO_LARGE120 | ErrorCode.CONNECT_FAILED121 | ErrorCode.DESTINATION_UNKNOWN122 | ErrorCode.REQUEST_VALIDATION_FAILED123 | ErrorCode.RESPONSE_VALIDATION_FAILED124 ):125 error_code = H3ErrorCode.H3_INTERNAL_ERROR126 case other: # pragma: no cover127 assert_never(other)128 self.h3_conn.close_stream(event.stream_id, error_code.value)129 else: # pragma: no cover130 raise AssertionError(f"Unexpected event: {event!r}")131 132 except H3FrameUnexpected as e:133 # Http2Connection also ignores HttpEvents that violate the current stream state134 yield commands.Log(f"Received {event!r} unexpectedly: {e}")135 136 else:137 # transmit buffered data138 yield from self.h3_conn.transmit()139 140 # forward stream messages from the QUIC layer to the H3 connection141 elif isinstance(event, QuicStreamEvent):142 h3_events = self.h3_conn.handle_stream_event(event)143 for h3_event in h3_events:144 if isinstance(h3_event, StreamClosed):145 err_str = error_code_to_str(h3_event.error_code)146 match h3_event.error_code:147 case H3ErrorCode.H3_REQUEST_CANCELLED:148 err_code = ErrorCode.CANCEL149 case H3ErrorCode.H3_VERSION_FALLBACK:150 err_code = ErrorCode.HTTP_1_1_REQUIRED151 case _:152 err_code = self.ReceiveProtocolError.code153 yield ReceiveHttp(154 self.ReceiveProtocolError(155 h3_event.stream_id,156 f"stream closed by client ({err_str})",157 code=err_code,158 )159 )160 elif isinstance(h3_event, DataReceived):161 if h3_event.data:162 yield ReceiveHttp(163 self.ReceiveData(h3_event.stream_id, h3_event.data)164 )165 if h3_event.stream_ended:166 yield ReceiveHttp(self.ReceiveEndOfMessage(h3_event.stream_id))167 elif isinstance(h3_event, HeadersReceived):168 try:169 receive_event = self.parse_headers(h3_event)170 except ValueError as e:171 self.h3_conn.close_connection(172 error_code=H3ErrorCode.H3_GENERAL_PROTOCOL_ERROR,173 reason_phrase=f"Invalid HTTP/3 request headers: {e}",174 )175 else:176 yield ReceiveHttp(receive_event)177 if h3_event.stream_ended:178 yield ReceiveHttp(179 self.ReceiveEndOfMessage(h3_event.stream_id)180 )181 elif isinstance(h3_event, TrailersReceived):182 yield ReceiveHttp(183 self.ReceiveTrailers(184 h3_event.stream_id, http.Headers(h3_event.trailers)185 )186 )187 if h3_event.stream_ended:188 yield ReceiveHttp(self.ReceiveEndOfMessage(h3_event.stream_id))189 elif isinstance(h3_event, PushPromiseReceived): # pragma: no cover190 self.h3_conn.close_connection(191 error_code=H3ErrorCode.H3_GENERAL_PROTOCOL_ERROR,192 reason_phrase=f"Received HTTP/3 push promise, even though we signalled no support.",193 )194 else: # pragma: no cover195 raise AssertionError(f"Unexpected event: {event!r}")196 yield from self.h3_conn.transmit()197 198 # report a protocol error for all remaining open streams when a connection is closed199 elif isinstance(event, QuicConnectionClosed):200 self._handle_event = self.done # type: ignore201 self.h3_conn.handle_connection_closed(event)202 msg = event.reason_phrase or error_code_to_str(event.error_code)203 for stream_id in self.h3_conn.get_open_stream_ids():204 yield ReceiveHttp(205 self.ReceiveProtocolError(206 stream_id, msg, self.ReceiveProtocolError.code207 )208 )209 210 else: # pragma: no cover211 raise AssertionError(f"Unexpected event: {event!r}")212 213 @expect(HttpEvent, QuicStreamEvent, QuicConnectionClosed)214 def done(self, _) -> layer.CommandGenerator[None]:215 yield from ()216 217 @abstractmethod218 def parse_headers(self, event: HeadersReceived) -> RequestHeaders | ResponseHeaders:219 pass # pragma: no cover220 221 222class Http3Server(Http3Connection):223 ReceiveData = RequestData224 ReceiveEndOfMessage = RequestEndOfMessage225 ReceiveProtocolError = RequestProtocolError226 ReceiveTrailers = RequestTrailers227 228 def __init__(self, context: context.Context):229 super().__init__(context, context.client)230 231 def parse_headers(self, event: HeadersReceived) -> RequestHeaders | ResponseHeaders:232 # same as HTTP/2233 (234 host,235 port,236 method,237 scheme,238 authority,239 path,240 headers,241 ) = parse_h2_request_headers(event.headers)242 request = http.Request(243 host=host,244 port=port,245 method=method,246 scheme=scheme,247 authority=authority,248 path=path,249 http_version=b"HTTP/3",250 headers=headers,251 content=None,252 trailers=None,253 timestamp_start=time.time(),254 timestamp_end=None,255 )256 return RequestHeaders(event.stream_id, request, end_stream=event.stream_ended)257 258 259class Http3Client(Http3Connection):260 ReceiveData = ResponseData261 ReceiveEndOfMessage = ResponseEndOfMessage262 ReceiveProtocolError = ResponseProtocolError263 ReceiveTrailers = ResponseTrailers264 265 our_stream_id: dict[int, int]266 their_stream_id: dict[int, int]267 268 def __init__(self, context: context.Context):269 super().__init__(context, context.server)270 self.our_stream_id = {}271 self.their_stream_id = {}272 273 def _handle_event(self, event: events.Event) -> layer.CommandGenerator[None]:274 # QUIC and HTTP/3 would actually allow for direct stream ID mapping, but since we want275 # to support H2<->H3, we need to translate IDs.276 # NOTE: We always create bidirectional streams, as we can't safely infer unidirectionality.277 if isinstance(event, HttpEvent):278 ours = self.our_stream_id.get(event.stream_id, None)279 if ours is None:280 ours = self.h3_conn.get_next_available_stream_id()281 self.our_stream_id[event.stream_id] = ours282 self.their_stream_id[ours] = event.stream_id283 event.stream_id = ours284 285 for cmd in super()._handle_event(event):286 if isinstance(cmd, ReceiveHttp):287 cmd.event.stream_id = self.their_stream_id[cmd.event.stream_id]288 yield cmd289 290 def parse_headers(self, event: HeadersReceived) -> RequestHeaders | ResponseHeaders:291 # same as HTTP/2292 status_code, headers = parse_h2_response_headers(event.headers)293 response = http.Response(294 http_version=b"HTTP/3",295 status_code=status_code,296 reason=b"",297 headers=headers,298 content=None,299 trailers=None,300 timestamp_start=time.time(),301 timestamp_end=None,302 )303 return ResponseHeaders(event.stream_id, response, event.stream_ended)304 305 306__all__ = [307 "Http3Client",308 "Http3Server",309]310 