Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_http2.py715 linesDownload Raw Back to http
1import collections2import time3from collections.abc import Sequence4from enum import Enum5from logging import DEBUG6from logging import ERROR7from typing import Any8from typing import assert_never9from typing import ClassVar10 11import h2.config12import h2.connection13import h2.errors14import h2.events15import h2.exceptions16import h2.settings17import h2.stream18import h2.utilities19 20from ...commands import CloseConnection21from ...commands import Log22from ...commands import RequestWakeup23from ...commands import SendData24from ...context import Context25from ...events import ConnectionClosed26from ...events import DataReceived27from ...events import Event28from ...events import Start29from ...events import Wakeup30from ...layer import CommandGenerator31from ...utils import expect32from . import ErrorCode33from . import RequestData34from . import RequestEndOfMessage35from . import RequestHeaders36from . import RequestProtocolError37from . import RequestTrailers38from . import ResponseData39from . import ResponseEndOfMessage40from . import ResponseHeaders41from . import ResponseProtocolError42from . import ResponseTrailers43from ._base import format_error44from ._base import HttpConnection45from ._base import HttpEvent46from ._base import ReceiveHttp47from ._http_h2 import BufferedH2Connection48from ._http_h2 import H2ConnectionLogger49from mitmproxy import http50from mitmproxy import version51from mitmproxy.connection import Connection52from mitmproxy.net.http import status_codes53from mitmproxy.net.http import url54from mitmproxy.utils import human55 56 57class StreamState(Enum):58    EXPECTING_HEADERS = 159    HEADERS_RECEIVED = 260 61 62CATCH_HYPER_H2_ERRORS = (ValueError, IndexError)63 64 65class Http2Connection(HttpConnection):66    h2_conf: ClassVar[h2.config.H2Configuration]67    h2_conf_defaults: dict[str, Any] = dict(68        header_encoding=False,69        validate_outbound_headers=False,70        # validate_inbound_headers is controlled by the validate_inbound_headers option.71        normalize_inbound_headers=False,  # changing this to True is required to pass h2spec72        normalize_outbound_headers=False,73    )74    h2_conn: BufferedH2Connection75    streams: dict[int, StreamState]76    """keep track of all active stream ids to send protocol errors on teardown"""77 78    ReceiveProtocolError: type[RequestProtocolError | ResponseProtocolError]79    ReceiveData: type[RequestData | ResponseData]80    ReceiveTrailers: type[RequestTrailers | ResponseTrailers]81    ReceiveEndOfMessage: type[RequestEndOfMessage | ResponseEndOfMessage]82 83    def __init__(self, context: Context, conn: Connection):84        super().__init__(context, conn)85        if self.debug:86            self.h2_conf.logger = H2ConnectionLogger(87                self.context.client.peername, self.__class__.__name__88            )89        self.h2_conf.validate_inbound_headers = (90            self.context.options.validate_inbound_headers91        )92        self.h2_conn = BufferedH2Connection(self.h2_conf)93        self.streams = {}94 95    def is_closed(self, stream_id: int) -> bool:96        """Check if a non-idle stream is closed"""97        stream = self.h2_conn.streams.get(stream_id, None)98        if (99            stream is not None100            and stream.state_machine.state is not h2.stream.StreamState.CLOSED101            and self.h2_conn.state_machine.state102            is not h2.connection.ConnectionState.CLOSED103        ):104            return False105        else:106            return True107 108    def is_open_for_us(self, stream_id: int) -> bool:109        """Check if we can write to a non-idle stream."""110        stream = self.h2_conn.streams.get(stream_id, None)111        if (112            stream is not None113            and stream.state_machine.state114            is not h2.stream.StreamState.HALF_CLOSED_LOCAL115            and stream.state_machine.state is not h2.stream.StreamState.CLOSED116            and self.h2_conn.state_machine.state117            is not h2.connection.ConnectionState.CLOSED118        ):119            return True120        else:121            return False122 123    def _handle_event(self, event: Event) -> CommandGenerator[None]:124        if isinstance(event, Start):125            self.h2_conn.initiate_connection()126            yield SendData(self.conn, self.h2_conn.data_to_send())127 128        elif isinstance(event, HttpEvent):129            if isinstance(event, (RequestData, ResponseData)):130                if self.is_open_for_us(event.stream_id):131                    self.h2_conn.send_data(event.stream_id, event.data)132            elif isinstance(event, (RequestTrailers, ResponseTrailers)):133                if self.is_open_for_us(event.stream_id):134                    trailers = [*event.trailers.fields]135                    self.h2_conn.send_trailers(event.stream_id, trailers)136            elif isinstance(event, (RequestEndOfMessage, ResponseEndOfMessage)):137                if self.is_open_for_us(event.stream_id):138                    self.h2_conn.end_stream(event.stream_id)139            elif isinstance(event, (RequestProtocolError, ResponseProtocolError)):140                if not self.is_closed(event.stream_id):141                    stream: h2.stream.H2Stream = self.h2_conn.streams[event.stream_id]142                    status = event.code.http_status_code()143                    if (144                        isinstance(event, ResponseProtocolError)145                        and self.is_open_for_us(event.stream_id)146                        and not stream.state_machine.headers_sent147                        and status is not None148                    ):149                        self.h2_conn.send_headers(150                            event.stream_id,151                            [152                                (b":status", b"%d" % status),153                                (b"server", version.MITMPROXY.encode()),154                                (b"content-type", b"text/html"),155                            ],156                        )157                        self.h2_conn.send_data(158                            event.stream_id,159                            format_error(status, event.message),160                            end_stream=True,161                        )162                    else:163                        match event.code:164                            case ErrorCode.CANCEL | ErrorCode.CLIENT_DISCONNECTED:165                                error_code = h2.errors.ErrorCodes.CANCEL166                            case ErrorCode.KILL:167                                # XXX: Debateable whether this is the best error code.168                                error_code = h2.errors.ErrorCodes.INTERNAL_ERROR169                            case ErrorCode.HTTP_1_1_REQUIRED:170                                error_code = h2.errors.ErrorCodes.HTTP_1_1_REQUIRED171                            case ErrorCode.PASSTHROUGH_CLOSE:172                                # FIXME: This probably shouldn't be a protocol error, but an EOM event.173                                error_code = h2.errors.ErrorCodes.CANCEL174                            case (175                                ErrorCode.GENERIC_CLIENT_ERROR176                                | ErrorCode.GENERIC_SERVER_ERROR177                                | ErrorCode.REQUEST_TOO_LARGE178                                | ErrorCode.RESPONSE_TOO_LARGE179                                | ErrorCode.CONNECT_FAILED180                                | ErrorCode.DESTINATION_UNKNOWN181                                | ErrorCode.REQUEST_VALIDATION_FAILED182                                | ErrorCode.RESPONSE_VALIDATION_FAILED183                            ):184                                error_code = h2.errors.ErrorCodes.INTERNAL_ERROR185                            case other:  # pragma: no cover186                                assert_never(other)187                        self.h2_conn.reset_stream(event.stream_id, error_code.value)188            else:189                raise AssertionError(f"Unexpected event: {event}")190            data_to_send = self.h2_conn.data_to_send()191            if data_to_send:192                yield SendData(self.conn, data_to_send)193 194        elif isinstance(event, DataReceived):195            try:196                try:197                    events = self.h2_conn.receive_data(event.data)198                except CATCH_HYPER_H2_ERRORS as e:  # pragma: no cover199                    # this should never raise a ValueError, but we triggered one while fuzzing:200                    # https://github.com/python-hyper/hyper-h2/issues/1231201                    # this stays here as defense-in-depth.202                    raise h2.exceptions.ProtocolError(203                        f"uncaught hyper-h2 error: {e}"204                    ) from e205            except h2.exceptions.ProtocolError as e:206                events = [e]207 208            for h2_event in events:209                if self.debug:210                    yield Log(f"{self.debug}[h2] {h2_event}", DEBUG)211                if (yield from self.handle_h2_event(h2_event)):212                    if self.debug:213                        yield Log(f"{self.debug}[h2] done", DEBUG)214                    return215 216            data_to_send = self.h2_conn.data_to_send()217            if data_to_send:218                yield SendData(self.conn, data_to_send)219 220        elif isinstance(event, ConnectionClosed):221            yield from self.close_connection("peer closed connection")222        else:223            raise AssertionError(f"Unexpected event: {event!r}")224 225    def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:226        """returns true if further processing should be stopped."""227        if isinstance(event, h2.events.DataReceived):228            state = self.streams.get(event.stream_id, None)229            if state is StreamState.HEADERS_RECEIVED:230                is_empty_eos_data_frame = event.stream_ended and not event.data231                if not is_empty_eos_data_frame:232                    yield ReceiveHttp(self.ReceiveData(event.stream_id, event.data))233            elif state is StreamState.EXPECTING_HEADERS:234                yield from self.protocol_error(235                    f"Received HTTP/2 data frame, expected headers."236                )237                return True238            self.h2_conn.acknowledge_received_data(239                event.flow_controlled_length, event.stream_id240            )241        elif isinstance(event, h2.events.TrailersReceived):242            trailers = http.Headers(event.headers)243            yield ReceiveHttp(self.ReceiveTrailers(event.stream_id, trailers))244        elif isinstance(event, h2.events.StreamEnded):245            state = self.streams.get(event.stream_id, None)246            if state is StreamState.HEADERS_RECEIVED:247                yield ReceiveHttp(self.ReceiveEndOfMessage(event.stream_id))248            elif state is StreamState.EXPECTING_HEADERS:249                raise AssertionError("unreachable")250            if self.is_closed(event.stream_id):251                self.streams.pop(event.stream_id, None)252        elif isinstance(event, h2.events.StreamReset):253            if event.stream_id in self.streams:254                try:255                    err_str = h2.errors.ErrorCodes(event.error_code).name256                except ValueError:257                    err_str = str(event.error_code)258                match event.error_code:259                    case h2.errors.ErrorCodes.CANCEL:260                        err_code = ErrorCode.CANCEL261                    case h2.errors.ErrorCodes.HTTP_1_1_REQUIRED:262                        err_code = ErrorCode.HTTP_1_1_REQUIRED263                    case _:264                        err_code = self.ReceiveProtocolError.code265                yield ReceiveHttp(266                    self.ReceiveProtocolError(267                        event.stream_id,268                        f"stream reset by client ({err_str})",269                        code=err_code,270                    )271                )272                self.streams.pop(event.stream_id)273            else:274                pass  # We don't track priority frames which could be followed by a stream reset here.275        elif isinstance(event, h2.exceptions.ProtocolError):276            yield from self.protocol_error(f"HTTP/2 protocol error: {event}")277            return True278        elif isinstance(event, h2.events.ConnectionTerminated):279            yield from self.close_connection(f"HTTP/2 connection closed: {event!r}")280            return True281            # The implementation above isn't really ideal, we should probably only terminate streams > last_stream_id?282            # We currently lack a mechanism to signal that connections are still active but cannot be reused.283            # for stream_id in self.streams:284            #    if stream_id > event.last_stream_id:285            #        yield ReceiveHttp(self.ReceiveProtocolError(stream_id, f"HTTP/2 connection closed: {event!r}"))286            #        self.streams.pop(stream_id)287        elif isinstance(event, h2.events.RemoteSettingsChanged):288            pass289        elif isinstance(event, h2.events.SettingsAcknowledged):290            pass291        elif isinstance(event, h2.events.PriorityUpdated):292            pass293        elif isinstance(event, h2.events.PingReceived):294            pass295        elif isinstance(event, h2.events.PingAckReceived):296            pass297        elif isinstance(event, h2.events.PushedStreamReceived):298            yield Log(299                "Received HTTP/2 push promise, even though we signalled no support.",300                ERROR,301            )302        elif isinstance(event, h2.events.UnknownFrameReceived):303            # https://http2.github.io/http2-spec/#rfc.section.4.1304            # Implementations MUST ignore and discard any frame that has a type that is unknown.305            yield Log(f"Ignoring unknown HTTP/2 frame type: {event.frame.type}")306        elif isinstance(event, h2.events.AlternativeServiceAvailable):307            yield Log(308                "Received HTTP/2 Alt-Svc frame, which will not be forwarded.", DEBUG309            )310        else:311            raise AssertionError(f"Unexpected event: {event!r}")312        return False313 314    def protocol_error(315        self,316        message: str,317        error_code: int = h2.errors.ErrorCodes.PROTOCOL_ERROR,318    ) -> CommandGenerator[None]:319        yield Log(f"{human.format_address(self.conn.peername)}: {message}")320        self.h2_conn.close_connection(error_code, message.encode())321        yield SendData(self.conn, self.h2_conn.data_to_send())322        yield from self.close_connection(message)323 324    def close_connection(self, msg: str) -> CommandGenerator[None]:325        yield CloseConnection(self.conn)326        for stream_id in self.streams:327            yield ReceiveHttp(328                self.ReceiveProtocolError(329                    stream_id, msg, self.ReceiveProtocolError.code330                )331            )332        self.streams.clear()333        self._handle_event = self.done  # type: ignore334 335    @expect(DataReceived, HttpEvent, ConnectionClosed, Wakeup)336    def done(self, _) -> CommandGenerator[None]:337        yield from ()338 339 340def normalize_h1_headers(341    headers: list[tuple[bytes, bytes]], is_client: bool342) -> list[tuple[bytes, bytes]]:343    # HTTP/1 servers commonly send capitalized headers (Content-Length vs content-length),344    # which isn't valid HTTP/2. As such we normalize.345    # Make sure that this is not just an iterator but an iterable,346    # otherwise hyper-h2 will silently drop headers.347    return list(348        h2.utilities.normalize_outbound_headers(349            headers,350            h2.utilities.HeaderValidationFlags(is_client, False, not is_client, False),351        )352    )353 354 355def normalize_h2_headers(headers: list[tuple[bytes, bytes]]) -> CommandGenerator[None]:356    for i in range(len(headers)):357        if not headers[i][0].islower():358            yield Log(359                f"Lowercased {repr(headers[i][0]).lstrip('b')} header as uppercase is not allowed with HTTP/2."360            )361            headers[i] = (headers[i][0].lower(), headers[i][1])362 363 364def format_h2_request_headers(365    context: Context,366    event: RequestHeaders,367) -> CommandGenerator[list[tuple[bytes, bytes]]]:368    pseudo_headers = [369        (b":method", event.request.data.method),370        (b":scheme", event.request.data.scheme),371        (b":path", event.request.data.path),372    ]373    if event.request.authority:374        pseudo_headers.append((b":authority", event.request.data.authority))375 376    if event.request.is_http2 or event.request.is_http3:377        hdrs = list(event.request.headers.fields)378        if context.options.normalize_outbound_headers:379            yield from normalize_h2_headers(hdrs)380    else:381        headers = event.request.headers382        if not event.request.authority and "host" in headers:383            headers = headers.copy()384            pseudo_headers.append((b":authority", headers.pop(b"host")))385        hdrs = normalize_h1_headers(list(headers.fields), True)386 387    return pseudo_headers + hdrs388 389 390def format_h2_response_headers(391    context: Context,392    event: ResponseHeaders,393) -> CommandGenerator[list[tuple[bytes, bytes]]]:394    headers = [395        (b":status", b"%d" % event.response.status_code),396        *event.response.headers.fields,397    ]398    if event.response.is_http2 or event.response.is_http3:399        if context.options.normalize_outbound_headers:400            yield from normalize_h2_headers(headers)401    else:402        headers = normalize_h1_headers(headers, False)403    return headers404 405 406class Http2Server(Http2Connection):407    h2_conf = h2.config.H2Configuration(408        **Http2Connection.h2_conf_defaults,409        client_side=False,410    )411 412    ReceiveProtocolError = RequestProtocolError413    ReceiveData = RequestData414    ReceiveTrailers = RequestTrailers415    ReceiveEndOfMessage = RequestEndOfMessage416 417    def __init__(self, context: Context):418        super().__init__(context, context.client)419 420    def _handle_event(self, event: Event) -> CommandGenerator[None]:421        if isinstance(event, ResponseHeaders):422            if self.is_open_for_us(event.stream_id):423                self.h2_conn.send_headers(424                    event.stream_id,425                    headers=(426                        yield from format_h2_response_headers(self.context, event)427                    ),428                    end_stream=event.end_stream,429                )430                yield SendData(self.conn, self.h2_conn.data_to_send())431        else:432            yield from super()._handle_event(event)433 434    def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:435        if isinstance(event, h2.events.RequestReceived):436            try:437                (438                    host,439                    port,440                    method,441                    scheme,442                    authority,443                    path,444                    headers,445                ) = parse_h2_request_headers(event.headers)446            except ValueError as e:447                yield from self.protocol_error(f"Invalid HTTP/2 request headers: {e}")448                return True449            request = http.Request(450                host=host,451                port=port,452                method=method,453                scheme=scheme,454                authority=authority,455                path=path,456                http_version=b"HTTP/2.0",457                headers=headers,458                content=None,459                trailers=None,460                timestamp_start=time.time(),461                timestamp_end=None,462            )463            self.streams[event.stream_id] = StreamState.HEADERS_RECEIVED464            yield ReceiveHttp(465                RequestHeaders(466                    event.stream_id, request, end_stream=bool(event.stream_ended)467                )468            )469            return False470        else:471            return (yield from super().handle_h2_event(event))472 473 474class Http2Client(Http2Connection):475    h2_conf = h2.config.H2Configuration(476        **Http2Connection.h2_conf_defaults,477        client_side=True,478    )479 480    ReceiveProtocolError = ResponseProtocolError481    ReceiveData = ResponseData482    ReceiveTrailers = ResponseTrailers483    ReceiveEndOfMessage = ResponseEndOfMessage484 485    our_stream_id: dict[int, int]486    their_stream_id: dict[int, int]487    stream_queue: collections.defaultdict[int, list[Event]]488    """Queue of streams that we haven't sent yet because we have reached MAX_CONCURRENT_STREAMS"""489    provisional_max_concurrency: int | None = 10490    """A provisional currency limit before we get the server's first settings frame."""491    last_activity: float492    """Timestamp of when we've last seen network activity on this connection."""493 494    def __init__(self, context: Context):495        super().__init__(context, context.server)496        # Disable HTTP/2 push for now to keep things simple.497        # don't send here, that is done as part of initiate_connection().498        self.h2_conn.local_settings.enable_push = 0499        # hyper-h2 pitfall: we need to acknowledge here, otherwise its sends out the old settings.500        self.h2_conn.local_settings.acknowledge()501        self.our_stream_id = {}502        self.their_stream_id = {}503        self.stream_queue = collections.defaultdict(list)504 505    def _handle_event(self, event: Event) -> CommandGenerator[None]:506        # We can't reuse stream ids from the client because they may arrived reordered here507        # and HTTP/2 forbids opening a stream on a lower id than what was previously sent (see test_stream_concurrency).508        # To mitigate this, we transparently map the outside's stream id to our stream id.509        if isinstance(event, HttpEvent):510            ours = self.our_stream_id.get(event.stream_id, None)511            if ours is None:512                no_free_streams = self.h2_conn.open_outbound_streams >= (513                    self.provisional_max_concurrency514                    or self.h2_conn.remote_settings.max_concurrent_streams515                )516                if no_free_streams:517                    self.stream_queue[event.stream_id].append(event)518                    return519                ours = self.h2_conn.get_next_available_stream_id()520                self.our_stream_id[event.stream_id] = ours521                self.their_stream_id[ours] = event.stream_id522            event.stream_id = ours523 524        for cmd in self._handle_event2(event):525            if isinstance(cmd, ReceiveHttp):526                cmd.event.stream_id = self.their_stream_id[cmd.event.stream_id]527            yield cmd528 529        can_resume_queue = self.stream_queue and self.h2_conn.open_outbound_streams < (530            self.provisional_max_concurrency531            or self.h2_conn.remote_settings.max_concurrent_streams532        )533        if can_resume_queue:534            # popitem would be LIFO, but we want FIFO.535            events = self.stream_queue.pop(next(iter(self.stream_queue)))536            for event in events:537                yield from self._handle_event(event)538 539    def _handle_event2(self, event: Event) -> CommandGenerator[None]:540        if isinstance(event, Wakeup):541            send_ping_now = (542                # add one second to avoid unnecessary roundtrip, we don't need to be super correct here.543                time.time() - self.last_activity + 1544                > self.context.options.http2_ping_keepalive545            )546            if send_ping_now:547                # PING frames MUST contain 8 octets of opaque data in the payload.548                # A sender can include any value it chooses and use those octets in any fashion.549                self.last_activity = time.time()550                self.h2_conn.ping(b"0" * 8)551                data = self.h2_conn.data_to_send()552                if data is not None:553                    yield Log(554                        f"Send HTTP/2 keep-alive PING to {human.format_address(self.conn.peername)}",555                        DEBUG,556                    )557                    yield SendData(self.conn, data)558            time_until_next_ping = self.context.options.http2_ping_keepalive - (559                time.time() - self.last_activity560            )561            yield RequestWakeup(time_until_next_ping)562            return563 564        self.last_activity = time.time()565        if isinstance(event, Start):566            if self.context.options.http2_ping_keepalive > 0:567                yield RequestWakeup(self.context.options.http2_ping_keepalive)568            yield from super()._handle_event(event)569        elif isinstance(event, RequestHeaders):570            self.h2_conn.send_headers(571                event.stream_id,572                headers=(yield from format_h2_request_headers(self.context, event)),573                end_stream=event.end_stream,574            )575            self.streams[event.stream_id] = StreamState.EXPECTING_HEADERS576            yield SendData(self.conn, self.h2_conn.data_to_send())577        else:578            yield from super()._handle_event(event)579 580    def handle_h2_event(self, event: h2.events.Event) -> CommandGenerator[bool]:581        if isinstance(event, h2.events.ResponseReceived):582            if (583                self.streams.get(event.stream_id, None)584                is not StreamState.EXPECTING_HEADERS585            ):586                yield from self.protocol_error(f"Received unexpected HTTP/2 response.")587                return True588 589            try:590                status_code, headers = parse_h2_response_headers(event.headers)591            except ValueError as e:592                yield from self.protocol_error(f"Invalid HTTP/2 response headers: {e}")593                return True594 595            response = http.Response(596                http_version=b"HTTP/2.0",597                status_code=status_code,598                reason=b"",599                headers=headers,600                content=None,601                trailers=None,602                timestamp_start=time.time(),603                timestamp_end=None,604            )605            self.streams[event.stream_id] = StreamState.HEADERS_RECEIVED606            yield ReceiveHttp(607                ResponseHeaders(event.stream_id, response, bool(event.stream_ended))608            )609            return False610        elif isinstance(event, h2.events.InformationalResponseReceived):611            # We violate the spec here ("A proxy MUST forward 1xx responses", RFC 7231),612            # but that's probably fine:613            # - 100 Continue is sent by mitmproxy to clients (irrespective of what the server does).614            # - 101 Switching Protocols is not allowed for HTTP/2.615            # - 102 Processing is WebDAV only and also ignorable.616            # - 103 Early Hints is not mission-critical.617            headers = http.Headers(event.headers)618            status: str | int = "<unknown status>"619            try:620                status = int(headers[":status"])621                reason = status_codes.RESPONSES.get(status, "")622            except (KeyError, ValueError):623                reason = ""624            yield Log(f"Swallowing HTTP/2 informational response: {status} {reason}")625            return False626        elif isinstance(event, h2.events.RequestReceived):627            yield from self.protocol_error(628                f"HTTP/2 protocol error: received request from server"629            )630            return True631        elif isinstance(event, h2.events.RemoteSettingsChanged):632            # We have received at least one settings from now,633            # which means we can rely on the max concurrency in remote_settings634            self.provisional_max_concurrency = None635            return (yield from super().handle_h2_event(event))636        else:637            return (yield from super().handle_h2_event(event))638 639 640def split_pseudo_headers(641    h2_headers: Sequence[tuple[bytes, bytes]],642) -> tuple[dict[bytes, bytes], http.Headers]:643    pseudo_headers: dict[bytes, bytes] = {}644    i = 0645    for header, value in h2_headers:646        if header.startswith(b":"):647            if header in pseudo_headers:648                raise ValueError(f"Duplicate HTTP/2 pseudo header: {header!r}")649            pseudo_headers[header] = value650            i += 1651        else:652            # Pseudo-headers must be at the start, we are done here.653            break654 655    headers = http.Headers(h2_headers[i:])656 657    return pseudo_headers, headers658 659 660def parse_h2_request_headers(661    h2_headers: Sequence[tuple[bytes, bytes]],662) -> tuple[str, int, bytes, bytes, bytes, bytes, http.Headers]:663    """Split HTTP/2 pseudo-headers from the actual headers and parse them."""664    pseudo_headers, headers = split_pseudo_headers(h2_headers)665 666    try:667        method: bytes = pseudo_headers.pop(b":method")668        scheme: bytes = pseudo_headers.pop(669            b":scheme"670        )  # this raises for HTTP/2 CONNECT requests671        path: bytes = pseudo_headers.pop(b":path")672        authority: bytes = pseudo_headers.pop(b":authority", b"")673    except KeyError as e:674        raise ValueError(f"Required pseudo header is missing: {e}")675 676    if pseudo_headers:677        raise ValueError(f"Unknown pseudo headers: {pseudo_headers}")678 679    if authority:680        host, port = url.parse_authority(authority, check=True)681        if port is None:682            port = 80 if scheme == b"http" else 443683    else:684        host = ""685        port = 0686 687    return host, port, method, scheme, authority, path, headers688 689 690def parse_h2_response_headers(691    h2_headers: Sequence[tuple[bytes, bytes]],692) -> tuple[int, http.Headers]:693    """Split HTTP/2 pseudo-headers from the actual headers and parse them."""694    pseudo_headers, headers = split_pseudo_headers(h2_headers)695 696    try:697        status_code: int = int(pseudo_headers.pop(b":status"))698    except KeyError as e:699        raise ValueError(f"Required pseudo header is missing: {e}")700 701    if pseudo_headers:702        raise ValueError(f"Unknown pseudo headers: {pseudo_headers}")703 704    return status_code, headers705 706 707__all__ = [708    "format_h2_request_headers",709    "format_h2_response_headers",710    "parse_h2_request_headers",711    "parse_h2_response_headers",712    "Http2Client",713    "Http2Server",714]715 
codekingpro/portable-devtools · Team Ai