Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
_http3.py310 linesDownload Raw Back to http
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 
codekingpro/portable-devtools · Team Ai