Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
connection.py1100 linesDownload Raw Back to urllib3
1from __future__ import annotations2 3import datetime4import http.client5import logging6import os7import re8import socket9import sys10import threading11import typing12import warnings13from http.client import HTTPConnection as _HTTPConnection14from http.client import HTTPException as HTTPException  # noqa: F40115from http.client import ResponseNotReady16from socket import timeout as SocketTimeout17 18if typing.TYPE_CHECKING:19    from .response import HTTPResponse20    from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT21    from .util.ssltransport import SSLTransport22 23from ._collections import HTTPHeaderDict24from .http2 import probe as http2_probe25from .util.response import assert_header_parsing26from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout27from .util.util import to_str28from .util.wait import wait_for_read29 30try:  # Compiled with SSL?31    import ssl32 33    BaseSSLError = ssl.SSLError34except (ImportError, AttributeError):35    ssl = None  # type: ignore[assignment]36 37    class BaseSSLError(BaseException):  # type: ignore[no-redef]38        pass39 40 41from ._base_connection import _TYPE_BODY42from ._base_connection import ProxyConfig as ProxyConfig43from ._base_connection import _ResponseOptions as _ResponseOptions44from ._version import __version__45from .exceptions import (46    ConnectTimeoutError,47    HeaderParsingError,48    NameResolutionError,49    NewConnectionError,50    ProxyError,51    SystemTimeWarning,52)53from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_54from .util.request import body_to_chunks55from .util.ssl_ import assert_fingerprint as _assert_fingerprint56from .util.ssl_ import (57    create_urllib3_context,58    is_ipaddress,59    resolve_cert_reqs,60    resolve_ssl_version,61    ssl_wrap_socket,62)63from .util.ssl_match_hostname import CertificateError, match_hostname64from .util.url import Url65 66# Not a no-op, we're adding this to the namespace so it can be imported.67ConnectionError = ConnectionError68BrokenPipeError = BrokenPipeError69 70 71log = logging.getLogger(__name__)72 73port_by_scheme = {"http": 80, "https": 443}74 75# When it comes time to update this value as a part of regular maintenance76# (ie test_recent_date is failing) update it to ~6 months before the current date.77RECENT_DATE = datetime.date(2025, 1, 1)78 79_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")80 81 82class HTTPConnection(_HTTPConnection):83    """84    Based on :class:`http.client.HTTPConnection` but provides an extra constructor85    backwards-compatibility layer between older and newer Pythons.86 87    Additional keyword parameters are used to configure attributes of the connection.88    Accepted parameters include:89 90    - ``source_address``: Set the source address for the current connection.91    - ``socket_options``: Set specific options on the underlying socket. If not specified, then92      defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling93      Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy.94 95      For example, if you wish to enable TCP Keep Alive in addition to the defaults,96      you might pass:97 98      .. code-block:: python99 100         HTTPConnection.default_socket_options + [101             (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),102         ]103 104      Or you may want to disable the defaults by passing an empty list (e.g., ``[]``).105    """106 107    default_port: typing.ClassVar[int] = port_by_scheme["http"]  # type: ignore[misc]108 109    #: Disable Nagle's algorithm by default.110    #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]``111    default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [112        (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)113    ]114 115    #: Whether this connection verifies the host's certificate.116    is_verified: bool = False117 118    #: Whether this proxy connection verified the proxy host's certificate.119    # If no proxy is currently connected to the value will be ``None``.120    proxy_is_verified: bool | None = None121 122    blocksize: int123    source_address: tuple[str, int] | None124    socket_options: connection._TYPE_SOCKET_OPTIONS | None125 126    _has_connected_to_proxy: bool127    _response_options: _ResponseOptions | None128    _tunnel_host: str | None129    _tunnel_port: int | None130    _tunnel_scheme: str | None131 132    def __init__(133        self,134        host: str,135        port: int | None = None,136        *,137        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,138        source_address: tuple[str, int] | None = None,139        blocksize: int = 16384,140        socket_options: None | (141            connection._TYPE_SOCKET_OPTIONS142        ) = default_socket_options,143        proxy: Url | None = None,144        proxy_config: ProxyConfig | None = None,145    ) -> None:146        super().__init__(147            host=host,148            port=port,149            timeout=Timeout.resolve_default_timeout(timeout),150            source_address=source_address,151            blocksize=blocksize,152        )153        self.socket_options = socket_options154        self.proxy = proxy155        self.proxy_config = proxy_config156 157        self._has_connected_to_proxy = False158        self._response_options = None159        self._tunnel_host: str | None = None160        self._tunnel_port: int | None = None161        self._tunnel_scheme: str | None = None162 163    def __str__(self) -> str:164        return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})"165 166    def __repr__(self) -> str:167        return f"<{self} at {id(self):#x}>"168 169    @property170    def host(self) -> str:171        """172        Getter method to remove any trailing dots that indicate the hostname is an FQDN.173 174        In general, SSL certificates don't include the trailing dot indicating a175        fully-qualified domain name, and thus, they don't validate properly when176        checked against a domain name that includes the dot. In addition, some177        servers may not expect to receive the trailing dot when provided.178 179        However, the hostname with trailing dot is critical to DNS resolution; doing a180        lookup with the trailing dot will properly only resolve the appropriate FQDN,181        whereas a lookup without a trailing dot will search the system's search domain182        list. Thus, it's important to keep the original host around for use only in183        those cases where it's appropriate (i.e., when doing DNS lookup to establish the184        actual TCP connection across which we're going to send HTTP requests).185        """186        return self._dns_host.rstrip(".")187 188    @host.setter189    def host(self, value: str) -> None:190        """191        Setter for the `host` property.192 193        We assume that only urllib3 uses the _dns_host attribute; httplib itself194        only uses `host`, and it seems reasonable that other libraries follow suit.195        """196        self._dns_host = value197 198    def _new_conn(self) -> socket.socket:199        """Establish a socket connection and set nodelay settings on it.200 201        :return: New socket connection.202        """203        try:204            sock = connection.create_connection(205                (self._dns_host, self.port),206                self.timeout,207                source_address=self.source_address,208                socket_options=self.socket_options,209            )210        except socket.gaierror as e:211            raise NameResolutionError(self.host, self, e) from e212        except SocketTimeout as e:213            raise ConnectTimeoutError(214                self,215                f"Connection to {self.host} timed out. (connect timeout={self.timeout})",216            ) from e217 218        except OSError as e:219            raise NewConnectionError(220                self, f"Failed to establish a new connection: {e}"221            ) from e222 223        sys.audit("http.client.connect", self, self.host, self.port)224 225        return sock226 227    def set_tunnel(228        self,229        host: str,230        port: int | None = None,231        headers: typing.Mapping[str, str] | None = None,232        scheme: str = "http",233    ) -> None:234        if scheme not in ("http", "https"):235            raise ValueError(236                f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'"237            )238        super().set_tunnel(host, port=port, headers=headers)239        self._tunnel_scheme = scheme240 241    if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)):242        # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3.243        # When using connection_from_host, host will come without brackets.244        def _wrap_ipv6(self, ip: bytes) -> bytes:245            if b":" in ip and ip[0] != b"["[0]:246                return b"[" + ip + b"]"247            return ip248 249        if sys.version_info < (3, 11, 9):250            # `_tunnel` copied from 3.11.13 backporting251            # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261252            # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1253            def _tunnel(self) -> None:254                _MAXLINE = http.client._MAXLINE  # type: ignore[attr-defined]255                connect = b"CONNECT %s:%d HTTP/1.0\r\n" % (  # type: ignore[str-format]256                    self._wrap_ipv6(self._tunnel_host.encode("ascii")),  # type: ignore[union-attr]257                    self._tunnel_port,258                )259                headers = [connect]260                for header, value in self._tunnel_headers.items():  # type: ignore[attr-defined]261                    headers.append(f"{header}: {value}\r\n".encode("latin-1"))262                headers.append(b"\r\n")263                # Making a single send() call instead of one per line encourages264                # the host OS to use a more optimal packet size instead of265                # potentially emitting a series of small packets.266                self.send(b"".join(headers))267                del headers268 269                response = self.response_class(self.sock, method=self._method)  # type: ignore[attr-defined]270                try:271                    (version, code, message) = response._read_status()  # type: ignore[attr-defined]272 273                    if code != http.HTTPStatus.OK:274                        self.close()275                        raise OSError(276                            f"Tunnel connection failed: {code} {message.strip()}"277                        )278                    while True:279                        line = response.fp.readline(_MAXLINE + 1)280                        if len(line) > _MAXLINE:281                            raise http.client.LineTooLong("header line")282                        if not line:283                            # for sites which EOF without sending a trailer284                            break285                        if line in (b"\r\n", b"\n", b""):286                            break287 288                        if self.debuglevel > 0:289                            print("header:", line.decode())290                finally:291                    response.close()292 293        elif (3, 12) <= sys.version_info < (3, 12, 3):294            # `_tunnel` copied from 3.12.11 backporting295            # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8296            def _tunnel(self) -> None:  # noqa: F811297                connect = b"CONNECT %s:%d HTTP/1.1\r\n" % (  # type: ignore[str-format]298                    self._wrap_ipv6(self._tunnel_host.encode("idna")),  # type: ignore[union-attr]299                    self._tunnel_port,300                )301                headers = [connect]302                for header, value in self._tunnel_headers.items():  # type: ignore[attr-defined]303                    headers.append(f"{header}: {value}\r\n".encode("latin-1"))304                headers.append(b"\r\n")305                # Making a single send() call instead of one per line encourages306                # the host OS to use a more optimal packet size instead of307                # potentially emitting a series of small packets.308                self.send(b"".join(headers))309                del headers310 311                response = self.response_class(self.sock, method=self._method)  # type: ignore[attr-defined]312                try:313                    (version, code, message) = response._read_status()  # type: ignore[attr-defined]314 315                    self._raw_proxy_headers = http.client._read_headers(response.fp)  # type: ignore[attr-defined]316 317                    if self.debuglevel > 0:318                        for header in self._raw_proxy_headers:319                            print("header:", header.decode())320 321                    if code != http.HTTPStatus.OK:322                        self.close()323                        raise OSError(324                            f"Tunnel connection failed: {code} {message.strip()}"325                        )326 327                finally:328                    response.close()329 330    def connect(self) -> None:331        self.sock = self._new_conn()332        if self._tunnel_host:333            # If we're tunneling it means we're connected to our proxy.334            self._has_connected_to_proxy = True335 336            # TODO: Fix tunnel so it doesn't depend on self.sock state.337            self._tunnel()338 339        # If there's a proxy to be connected to we are fully connected.340        # This is set twice (once above and here) due to forwarding proxies341        # not using tunnelling.342        self._has_connected_to_proxy = bool(self.proxy)343 344        if self._has_connected_to_proxy:345            self.proxy_is_verified = False346 347    @property348    def is_closed(self) -> bool:349        return self.sock is None350 351    @property352    def is_connected(self) -> bool:353        if self.sock is None:354            return False355        return not wait_for_read(self.sock, timeout=0.0)356 357    @property358    def has_connected_to_proxy(self) -> bool:359        return self._has_connected_to_proxy360 361    @property362    def proxy_is_forwarding(self) -> bool:363        """364        Return True if a forwarding proxy is configured, else return False365        """366        return bool(self.proxy) and self._tunnel_host is None367 368    @property369    def proxy_is_tunneling(self) -> bool:370        """371        Return True if a tunneling proxy is configured, else return False372        """373        return self._tunnel_host is not None374 375    def close(self) -> None:376        try:377            super().close()378        finally:379            # Reset all stateful properties so connection380            # can be re-used without leaking prior configs.381            self.sock = None382            self.is_verified = False383            self.proxy_is_verified = None384            self._has_connected_to_proxy = False385            self._response_options = None386            self._tunnel_host = None387            self._tunnel_port = None388            self._tunnel_scheme = None389 390    def putrequest(391        self,392        method: str,393        url: str,394        skip_host: bool = False,395        skip_accept_encoding: bool = False,396    ) -> None:397        """"""398        # Empty docstring because the indentation of CPython's implementation399        # is broken but we don't want this method in our documentation.400        match = _CONTAINS_CONTROL_CHAR_RE.search(method)401        if match:402            raise ValueError(403                f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})"404            )405 406        return super().putrequest(407            method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding408        )409 410    def putheader(self, header: str, *values: str) -> None:  # type: ignore[override]411        """"""412        if not any(isinstance(v, str) and v == SKIP_HEADER for v in values):413            super().putheader(header, *values)414        elif to_str(header.lower()) not in SKIPPABLE_HEADERS:415            skippable_headers = "', '".join(416                [str.title(header) for header in sorted(SKIPPABLE_HEADERS)]417            )418            raise ValueError(419                f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'"420            )421 422    # `request` method's signature intentionally violates LSP.423    # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental.424    def request(  # type: ignore[override]425        self,426        method: str,427        url: str,428        body: _TYPE_BODY | None = None,429        headers: typing.Mapping[str, str] | None = None,430        *,431        chunked: bool = False,432        preload_content: bool = True,433        decode_content: bool = True,434        enforce_content_length: bool = True,435    ) -> None:436        # Update the inner socket's timeout value to send the request.437        # This only triggers if the connection is re-used.438        if self.sock is not None:439            self.sock.settimeout(self.timeout)440 441        # Store these values to be fed into the HTTPResponse442        # object later. TODO: Remove this in favor of a real443        # HTTP lifecycle mechanism.444 445        # We have to store these before we call .request()446        # because sometimes we can still salvage a response447        # off the wire even if we aren't able to completely448        # send the request body.449        self._response_options = _ResponseOptions(450            request_method=method,451            request_url=url,452            preload_content=preload_content,453            decode_content=decode_content,454            enforce_content_length=enforce_content_length,455        )456 457        if headers is None:458            headers = {}459        header_keys = frozenset(to_str(k.lower()) for k in headers)460        skip_accept_encoding = "accept-encoding" in header_keys461        skip_host = "host" in header_keys462        self.putrequest(463            method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host464        )465 466        # Transform the body into an iterable of sendall()-able chunks467        # and detect if an explicit Content-Length is doable.468        chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize)469        chunks = chunks_and_cl.chunks470        content_length = chunks_and_cl.content_length471 472        # When chunked is explicit set to 'True' we respect that.473        if chunked:474            if "transfer-encoding" not in header_keys:475                self.putheader("Transfer-Encoding", "chunked")476        else:477            # Detect whether a framing mechanism is already in use. If so478            # we respect that value, otherwise we pick chunked vs content-length479            # depending on the type of 'body'.480            if "content-length" in header_keys:481                chunked = False482            elif "transfer-encoding" in header_keys:483                chunked = True484 485            # Otherwise we go off the recommendation of 'body_to_chunks()'.486            else:487                chunked = False488                if content_length is None:489                    if chunks is not None:490                        chunked = True491                        self.putheader("Transfer-Encoding", "chunked")492                else:493                    self.putheader("Content-Length", str(content_length))494 495        # Now that framing headers are out of the way we send all the other headers.496        if "user-agent" not in header_keys:497            self.putheader("User-Agent", _get_default_user_agent())498        for header, value in headers.items():499            self.putheader(header, value)500        self.endheaders()501 502        # If we're given a body we start sending that in chunks.503        if chunks is not None:504            for chunk in chunks:505                # Sending empty chunks isn't allowed for TE: chunked506                # as it indicates the end of the body.507                if not chunk:508                    continue509                if isinstance(chunk, str):510                    chunk = chunk.encode("utf-8")511                if chunked:512                    self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk))513                else:514                    self.send(chunk)515 516        # Regardless of whether we have a body or not, if we're in517        # chunked mode we want to send an explicit empty chunk.518        if chunked:519            self.send(b"0\r\n\r\n")520 521    def request_chunked(522        self,523        method: str,524        url: str,525        body: _TYPE_BODY | None = None,526        headers: typing.Mapping[str, str] | None = None,527    ) -> None:528        """529        Alternative to the common request method, which sends the530        body with chunked encoding and not as one block531        """532        warnings.warn(533            "HTTPConnection.request_chunked() is deprecated and will be removed "534            "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).",535            category=FutureWarning,536            stacklevel=2,537        )538        self.request(method, url, body=body, headers=headers, chunked=True)539 540    def getresponse(  # type: ignore[override]541        self,542    ) -> HTTPResponse:543        """544        Get the response from the server.545 546        If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable.547 548        If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed.549        """550        # Raise the same error as http.client.HTTPConnection551        if self._response_options is None:552            raise ResponseNotReady()553 554        # Reset this attribute for being used again.555        resp_options = self._response_options556        self._response_options = None557 558        # Since the connection's timeout value may have been updated559        # we need to set the timeout on the socket.560        self.sock.settimeout(self.timeout)561 562        # This is needed here to avoid circular import errors563        from .response import HTTPResponse564 565        # Save a reference to the shutdown function before ownership is passed566        # to httplib_response567        # TODO should we implement it everywhere?568        _shutdown = getattr(self.sock, "shutdown", None)569 570        # Get the response from http.client.HTTPConnection571        httplib_response = super().getresponse()572 573        try:574            assert_header_parsing(httplib_response.msg)575        except (HeaderParsingError, TypeError) as hpe:576            log.warning(577                "Failed to parse headers (url=%s): %s",578                _url_from_connection(self, resp_options.request_url),579                hpe,580                exc_info=True,581            )582 583        headers = HTTPHeaderDict(httplib_response.msg.items())584 585        response = HTTPResponse(586            body=httplib_response,587            headers=headers,588            status=httplib_response.status,589            version=httplib_response.version,590            version_string=getattr(self, "_http_vsn_str", "HTTP/?"),591            reason=httplib_response.reason,592            preload_content=resp_options.preload_content,593            decode_content=resp_options.decode_content,594            original_response=httplib_response,595            enforce_content_length=resp_options.enforce_content_length,596            request_method=resp_options.request_method,597            request_url=resp_options.request_url,598            sock_shutdown=_shutdown,599        )600        return response601 602 603class HTTPSConnection(HTTPConnection):604    """605    Many of the parameters to this constructor are passed to the underlying SSL606    socket by means of :py:func:`urllib3.util.ssl_wrap_socket`.607    """608 609    default_port = port_by_scheme["https"]  # type: ignore[misc]610 611    cert_reqs: int | str | None = None612    ca_certs: str | None = None613    ca_cert_dir: str | None = None614    ca_cert_data: None | str | bytes = None615    ssl_version: int | str | None = None616    ssl_minimum_version: int | None = None617    ssl_maximum_version: int | None = None618    assert_fingerprint: str | None = None619    _connect_callback: typing.Callable[..., None] | None = None620 621    def __init__(622        self,623        host: str,624        port: int | None = None,625        *,626        timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT,627        source_address: tuple[str, int] | None = None,628        blocksize: int = 16384,629        socket_options: None | (630            connection._TYPE_SOCKET_OPTIONS631        ) = HTTPConnection.default_socket_options,632        proxy: Url | None = None,633        proxy_config: ProxyConfig | None = None,634        cert_reqs: int | str | None = None,635        assert_hostname: None | str | typing.Literal[False] = None,636        assert_fingerprint: str | None = None,637        server_hostname: str | None = None,638        ssl_context: ssl.SSLContext | None = None,639        ca_certs: str | None = None,640        ca_cert_dir: str | None = None,641        ca_cert_data: None | str | bytes = None,642        ssl_minimum_version: int | None = None,643        ssl_maximum_version: int | None = None,644        ssl_version: int | str | None = None,  # Deprecated645        cert_file: str | None = None,646        key_file: str | None = None,647        key_password: str | None = None,648    ) -> None:649        super().__init__(650            host,651            port=port,652            timeout=timeout,653            source_address=source_address,654            blocksize=blocksize,655            socket_options=socket_options,656            proxy=proxy,657            proxy_config=proxy_config,658        )659 660        self.key_file = key_file661        self.cert_file = cert_file662        self.key_password = key_password663        self.ssl_context = ssl_context664        self.server_hostname = server_hostname665        self.assert_hostname = assert_hostname666        self.assert_fingerprint = assert_fingerprint667        self.ssl_version = ssl_version668        self.ssl_minimum_version = ssl_minimum_version669        self.ssl_maximum_version = ssl_maximum_version670        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)671        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)672        self.ca_cert_data = ca_cert_data673 674        # cert_reqs depends on ssl_context so calculate last.675        if cert_reqs is None:676            if self.ssl_context is not None:677                cert_reqs = self.ssl_context.verify_mode678            else:679                cert_reqs = resolve_cert_reqs(None)680        self.cert_reqs = cert_reqs681        self._connect_callback = None682 683    def set_cert(684        self,685        key_file: str | None = None,686        cert_file: str | None = None,687        cert_reqs: int | str | None = None,688        key_password: str | None = None,689        ca_certs: str | None = None,690        assert_hostname: None | str | typing.Literal[False] = None,691        assert_fingerprint: str | None = None,692        ca_cert_dir: str | None = None,693        ca_cert_data: None | str | bytes = None,694    ) -> None:695        """696        This method should only be called once, before the connection is used.697        """698        warnings.warn(699            "HTTPSConnection.set_cert() is deprecated and will be removed "700            "in urllib3 v3.0. Instead provide the parameters to the "701            "HTTPSConnection constructor.",702            category=FutureWarning,703            stacklevel=2,704        )705 706        # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also707        # have an SSLContext object in which case we'll use its verify_mode.708        if cert_reqs is None:709            if self.ssl_context is not None:710                cert_reqs = self.ssl_context.verify_mode711            else:712                cert_reqs = resolve_cert_reqs(None)713 714        self.key_file = key_file715        self.cert_file = cert_file716        self.cert_reqs = cert_reqs717        self.key_password = key_password718        self.assert_hostname = assert_hostname719        self.assert_fingerprint = assert_fingerprint720        self.ca_certs = ca_certs and os.path.expanduser(ca_certs)721        self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)722        self.ca_cert_data = ca_cert_data723 724    def connect(self) -> None:725        # Today we don't need to be doing this step before the /actual/ socket726        # connection, however in the future we'll need to decide whether to727        # create a new socket or re-use an existing "shared" socket as a part728        # of the HTTP/2 handshake dance.729        if self._tunnel_host is not None and self._tunnel_port is not None:730            probe_http2_host = self._tunnel_host731            probe_http2_port = self._tunnel_port732        else:733            probe_http2_host = self.host734            probe_http2_port = self.port735 736        # Check if the target origin supports HTTP/2.737        # If the value comes back as 'None' it means that the current thread738        # is probing for HTTP/2 support. Otherwise, we're waiting for another739        # probe to complete, or we get a value right away.740        target_supports_http2: bool | None741        if "h2" in ssl_.ALPN_PROTOCOLS:742            target_supports_http2 = http2_probe.acquire_and_get(743                host=probe_http2_host, port=probe_http2_port744            )745        else:746            # If HTTP/2 isn't going to be offered it doesn't matter if747            # the target supports HTTP/2. Don't want to make a probe.748            target_supports_http2 = False749 750        if self._connect_callback is not None:751            self._connect_callback(752                "before connect",753                thread_id=threading.get_ident(),754                target_supports_http2=target_supports_http2,755            )756 757        try:758            sock: socket.socket | ssl.SSLSocket759            self.sock = sock = self._new_conn()760            server_hostname: str = self.host761            tls_in_tls = False762 763            # Do we need to establish a tunnel?764            if self.proxy_is_tunneling:765                # We're tunneling to an HTTPS origin so need to do TLS-in-TLS.766                if self._tunnel_scheme == "https":767                    # _connect_tls_proxy will verify and assign proxy_is_verified768                    self.sock = sock = self._connect_tls_proxy(self.host, sock)769                    tls_in_tls = True770                elif self._tunnel_scheme == "http":771                    self.proxy_is_verified = False772 773                # If we're tunneling it means we're connected to our proxy.774                self._has_connected_to_proxy = True775 776                self._tunnel()777                # Override the host with the one we're requesting data from.778                server_hostname = typing.cast(str, self._tunnel_host)779 780            if self.server_hostname is not None:781                server_hostname = self.server_hostname782 783            is_time_off = datetime.date.today() < RECENT_DATE784            if is_time_off:785                warnings.warn(786                    (787                        f"System time is way off (before {RECENT_DATE}). This will probably "788                        "lead to SSL verification errors"789                    ),790                    SystemTimeWarning,791                )792 793            # Remove trailing '.' from fqdn hostnames to allow certificate validation794            server_hostname_rm_dot = server_hostname.rstrip(".")795 796            sock_and_verified = _ssl_wrap_socket_and_match_hostname(797                sock=sock,798                cert_reqs=self.cert_reqs,799                ssl_version=self.ssl_version,800                ssl_minimum_version=self.ssl_minimum_version,801                ssl_maximum_version=self.ssl_maximum_version,802                ca_certs=self.ca_certs,803                ca_cert_dir=self.ca_cert_dir,804                ca_cert_data=self.ca_cert_data,805                cert_file=self.cert_file,806                key_file=self.key_file,807                key_password=self.key_password,808                server_hostname=server_hostname_rm_dot,809                ssl_context=self.ssl_context,810                tls_in_tls=tls_in_tls,811                assert_hostname=self.assert_hostname,812                assert_fingerprint=self.assert_fingerprint,813            )814            self.sock = sock_and_verified.socket815 816        # If an error occurs during connection/handshake we may need to release817        # our lock so another connection can probe the origin.818        except BaseException:819            if self._connect_callback is not None:820                self._connect_callback(821                    "after connect failure",822                    thread_id=threading.get_ident(),823                    target_supports_http2=target_supports_http2,824                )825 826            if target_supports_http2 is None:827                http2_probe.set_and_release(828                    host=probe_http2_host, port=probe_http2_port, supports_http2=None829                )830            raise831 832        # If this connection doesn't know if the origin supports HTTP/2833        # we report back to the HTTP/2 probe our result.834        if target_supports_http2 is None:835            supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2"836            http2_probe.set_and_release(837                host=probe_http2_host,838                port=probe_http2_port,839                supports_http2=supports_http2,840            )841 842        # Forwarding proxies can never have a verified target since843        # the proxy is the one doing the verification. Should instead844        # use a CONNECT tunnel in order to verify the target.845        # See: https://github.com/urllib3/urllib3/issues/3267.846        if self.proxy_is_forwarding:847            self.is_verified = False848        else:849            self.is_verified = sock_and_verified.is_verified850 851        # If there's a proxy to be connected to we are fully connected.852        # This is set twice (once above and here) due to forwarding proxies853        # not using tunnelling.854        self._has_connected_to_proxy = bool(self.proxy)855 856        # Set `self.proxy_is_verified` unless it's already set while857        # establishing a tunnel.858        if self._has_connected_to_proxy and self.proxy_is_verified is None:859            self.proxy_is_verified = sock_and_verified.is_verified860 861    def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket:862        """863        Establish a TLS connection to the proxy using the provided SSL context.864        """865        # `_connect_tls_proxy` is called when self._tunnel_host is truthy.866        proxy_config = typing.cast(ProxyConfig, self.proxy_config)867        ssl_context = proxy_config.ssl_context868        sock_and_verified = _ssl_wrap_socket_and_match_hostname(869            sock,870            cert_reqs=self.cert_reqs,871            ssl_version=self.ssl_version,872            ssl_minimum_version=self.ssl_minimum_version,873            ssl_maximum_version=self.ssl_maximum_version,874            ca_certs=self.ca_certs,875            ca_cert_dir=self.ca_cert_dir,876            ca_cert_data=self.ca_cert_data,877            server_hostname=hostname,878            ssl_context=ssl_context,879            assert_hostname=proxy_config.assert_hostname,880            assert_fingerprint=proxy_config.assert_fingerprint,881            # Features that aren't implemented for proxies yet:882            cert_file=None,883            key_file=None,884            key_password=None,885            tls_in_tls=False,886        )887        self.proxy_is_verified = sock_and_verified.is_verified888        return sock_and_verified.socket  # type: ignore[return-value]889 890 891class _WrappedAndVerifiedSocket(typing.NamedTuple):892    """893    Wrapped socket and whether the connection is894    verified after the TLS handshake895    """896 897    socket: ssl.SSLSocket | SSLTransport898    is_verified: bool899 900 901def _ssl_wrap_socket_and_match_hostname(902    sock: socket.socket,903    *,904    cert_reqs: None | str | int,905    ssl_version: None | str | int,906    ssl_minimum_version: int | None,907    ssl_maximum_version: int | None,908    cert_file: str | None,909    key_file: str | None,910    key_password: str | None,911    ca_certs: str | None,912    ca_cert_dir: str | None,913    ca_cert_data: None | str | bytes,914    assert_hostname: None | str | typing.Literal[False],915    assert_fingerprint: str | None,916    server_hostname: str | None,917    ssl_context: ssl.SSLContext | None,918    tls_in_tls: bool = False,919) -> _WrappedAndVerifiedSocket:920    """Logic for constructing an SSLContext from all TLS parameters, passing921    that down into ssl_wrap_socket, and then doing certificate verification922    either via hostname or fingerprint. This function exists to guarantee923    that both proxies and targets have the same behavior when connecting via TLS.924    """925    default_ssl_context = False926    if ssl_context is None:927        default_ssl_context = True928        context = create_urllib3_context(929            ssl_version=resolve_ssl_version(ssl_version),930            ssl_minimum_version=ssl_minimum_version,931            ssl_maximum_version=ssl_maximum_version,932            cert_reqs=resolve_cert_reqs(cert_reqs),933        )934    else:935        context = ssl_context936 937    context.verify_mode = resolve_cert_reqs(cert_reqs)938 939    # In some cases, we want to verify hostnames ourselves940    if (941        # `ssl` can't verify fingerprints or alternate hostnames942        assert_fingerprint943        or assert_hostname944        # assert_hostname can be set to False to disable hostname checking945        or assert_hostname is False946        # We still support OpenSSL 1.0.2, which prevents us from verifying947        # hostnames easily: https://github.com/pyca/pyopenssl/pull/933948        or ssl_.IS_PYOPENSSL949        or not ssl_.HAS_NEVER_CHECK_COMMON_NAME950    ):951        context.check_hostname = False952 953    # Try to load OS default certs if none are given. We need to do the hasattr() check954    # for custom pyOpenSSL SSLContext objects because they don't support955    # load_default_certs().956    if (957        not ca_certs958        and not ca_cert_dir959        and not ca_cert_data960        and default_ssl_context961        and hasattr(context, "load_default_certs")962    ):963        context.load_default_certs()964 965    # Ensure that IPv6 addresses are in the proper format and don't have a966    # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses967    # and interprets them as DNS hostnames.968    if server_hostname is not None:969        normalized = server_hostname.strip("[]")970        if "%" in normalized:971            normalized = normalized[: normalized.rfind("%")]972        if is_ipaddress(normalized):973            server_hostname = normalized974 975    ssl_sock = ssl_wrap_socket(976        sock=sock,977        keyfile=key_file,978        certfile=cert_file,979        key_password=key_password,980        ca_certs=ca_certs,981        ca_cert_dir=ca_cert_dir,982        ca_cert_data=ca_cert_data,983        server_hostname=server_hostname,984        ssl_context=context,985        tls_in_tls=tls_in_tls,986    )987 988    try:989        if assert_fingerprint:990            _assert_fingerprint(991                ssl_sock.getpeercert(binary_form=True), assert_fingerprint992            )993        elif (994            context.verify_mode != ssl.CERT_NONE995            and not context.check_hostname996            and assert_hostname is not False997        ):998            cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert()  # type: ignore[assignment]999 1000            # Need to signal to our match_hostname whether to use 'commonName' or not.1001            # If we're using our own constructed SSLContext we explicitly set 'False'1002            # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name.1003            if default_ssl_context:1004                hostname_checks_common_name = False1005            else:1006                hostname_checks_common_name = (1007                    getattr(context, "hostname_checks_common_name", False) or False1008                )1009 1010            _match_hostname(1011                cert,1012                assert_hostname or server_hostname,  # type: ignore[arg-type]1013                hostname_checks_common_name,1014            )1015 1016        return _WrappedAndVerifiedSocket(1017            socket=ssl_sock,1018            is_verified=context.verify_mode == ssl.CERT_REQUIRED1019            or bool(assert_fingerprint),1020        )1021    except BaseException:1022        ssl_sock.close()1023        raise1024 1025 1026def _match_hostname(1027    cert: _TYPE_PEER_CERT_RET_DICT | None,1028    asserted_hostname: str,1029    hostname_checks_common_name: bool = False,1030) -> None:1031    # Our upstream implementation of ssl.match_hostname()1032    # only applies this normalization to IP addresses so it doesn't1033    # match DNS SANs so we do the same thing!1034    stripped_hostname = asserted_hostname.strip("[]")1035    if is_ipaddress(stripped_hostname):1036        asserted_hostname = stripped_hostname1037 1038    try:1039        match_hostname(cert, asserted_hostname, hostname_checks_common_name)1040    except CertificateError as e:1041        log.warning(1042            "Certificate did not match expected hostname: %s. Certificate: %s",1043            asserted_hostname,1044            cert,1045        )1046        # Add cert to exception and reraise so client code can inspect1047        # the cert when catching the exception, if they want to1048        e._peer_cert = cert  # type: ignore[attr-defined]1049        raise1050 1051 1052def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError:1053    # Look for the phrase 'wrong version number', if found1054    # then we should warn the user that we're very sure that1055    # this proxy is HTTP-only and they have a configuration issue.1056    error_normalized = " ".join(re.split("[^a-z]", str(err).lower()))1057    is_likely_http_proxy = (1058        "wrong version number" in error_normalized1059        or "unknown protocol" in error_normalized1060        or "record layer failure" in error_normalized1061    )1062    http_proxy_warning = (1063        ". Your proxy appears to only use HTTP and not HTTPS, "1064        "try changing your proxy URL to be HTTP. See: "1065        "https://urllib3.readthedocs.io/en/latest/advanced-usage.html"1066        "#https-proxy-error-http-proxy"1067    )1068    new_err = ProxyError(1069        f"Unable to connect to proxy"1070        f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}",1071        err,1072    )1073    new_err.__cause__ = err1074    return new_err1075 1076 1077def _get_default_user_agent() -> str:1078    return f"python-urllib3/{__version__}"1079 1080 1081class DummyConnection:1082    """Used to detect a failed ConnectionCls import."""1083 1084 1085if not ssl:1086    HTTPSConnection = DummyConnection  # type: ignore[misc, assignment] # noqa: F8111087 1088 1089VerifiedHTTPSConnection = HTTPSConnection1090 1091 1092def _url_from_connection(1093    conn: HTTPConnection | HTTPSConnection, path: str | None = None1094) -> str:1095    """Returns the URL from a given connection. This is mainly used for testing and logging."""1096 1097    scheme = "https" if isinstance(conn, HTTPSConnection) else "http"1098 1099    return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url1100 
codekingpro/portable-devtools · Team Ai