Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
httpsession.py510 linesDownload Raw Back to botocore
1import logging2import os3import os.path4import socket5import sys6import warnings7from base64 import b64encode8 9from urllib3 import PoolManager, Timeout, proxy_from_url10from urllib3.exceptions import (11    ConnectTimeoutError as URLLib3ConnectTimeoutError,12)13from urllib3.exceptions import (14    LocationParseError,15    NewConnectionError,16    ProtocolError,17    ProxyError,18)19from urllib3.exceptions import ReadTimeoutError as URLLib3ReadTimeoutError20from urllib3.exceptions import SSLError as URLLib3SSLError21from urllib3.util.retry import Retry22from urllib3.util.ssl_ import (23    OP_NO_COMPRESSION,24    PROTOCOL_TLS,25    OP_NO_SSLv2,26    OP_NO_SSLv3,27    is_ipaddress,28    ssl,29)30from urllib3.util.url import parse_url31 32try:33    from urllib3.util.ssl_ import OP_NO_TICKET, PROTOCOL_TLS_CLIENT34except ImportError:35    # Fallback directly to ssl for version of urllib3 before 1.26.36    # They are available in the standard library starting in Python 3.6.37    from ssl import OP_NO_TICKET, PROTOCOL_TLS_CLIENT38 39try:40    # pyopenssl will be removed in urllib3 2.0, we'll fall back to ssl_ at that point.41    # This can be removed once our urllib3 floor is raised to >= 2.0.42    with warnings.catch_warnings():43        warnings.simplefilter("ignore", category=DeprecationWarning)44        # Always import the original SSLContext, even if it has been patched45        from urllib3.contrib.pyopenssl import (46            orig_util_SSLContext as SSLContext,47        )48except ImportError:49    from urllib3.util.ssl_ import SSLContext50 51try:52    from urllib3.util.ssl_ import DEFAULT_CIPHERS53except ImportError:54    # Defer to system configuration starting with55    # urllib3 2.0. This will choose the ciphers provided by56    # Openssl 1.1.1+ or secure system defaults.57    DEFAULT_CIPHERS = None58 59import botocore.awsrequest60from botocore.compat import (61    IPV6_ADDRZ_RE,62    ensure_bytes,63    filter_ssl_warnings,64    unquote,65    urlparse,66)67from botocore.exceptions import (68    ConnectionClosedError,69    ConnectTimeoutError,70    EndpointConnectionError,71    HTTPClientError,72    InvalidProxiesConfigError,73    ProxyConnectionError,74    ReadTimeoutError,75    SSLError,76)77 78filter_ssl_warnings()79logger = logging.getLogger(__name__)80DEFAULT_TIMEOUT = 6081MAX_POOL_CONNECTIONS = 1082DEFAULT_CA_BUNDLE = os.path.join(os.path.dirname(__file__), 'cacert.pem')83 84try:85    from certifi import where86except ImportError:87 88    def where():89        return DEFAULT_CA_BUNDLE90 91 92def get_cert_path(verify):93    if verify is not True:94        return verify95 96    cert_path = where()97    logger.debug(f"Certificate path: {cert_path}")98 99    return cert_path100 101 102def create_urllib3_context(103    ssl_version=None, cert_reqs=None, options=None, ciphers=None104):105    """This function is a vendored version of the same function in urllib3106 107    We vendor this function to ensure that the SSL contexts we construct108    always use the std lib SSLContext instead of pyopenssl.109    """110    # PROTOCOL_TLS is deprecated in Python 3.10111    if not ssl_version or ssl_version == PROTOCOL_TLS:112        ssl_version = PROTOCOL_TLS_CLIENT113 114    context = SSLContext(ssl_version)115 116    if ciphers:117        context.set_ciphers(ciphers)118    elif DEFAULT_CIPHERS:119        context.set_ciphers(DEFAULT_CIPHERS)120 121    # Setting the default here, as we may have no ssl module on import122    cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs123 124    if options is None:125        options = 0126        # SSLv2 is easily broken and is considered harmful and dangerous127        options |= OP_NO_SSLv2128        # SSLv3 has several problems and is now dangerous129        options |= OP_NO_SSLv3130        # Disable compression to prevent CRIME attacks for OpenSSL 1.0+131        # (issue urllib3#309)132        options |= OP_NO_COMPRESSION133        # TLSv1.2 only. Unless set explicitly, do not request tickets.134        # This may save some bandwidth on wire, and although the ticket is encrypted,135        # there is a risk associated with it being on wire,136        # if the server is not rotating its ticketing keys properly.137        options |= OP_NO_TICKET138 139    context.options |= options140 141    # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is142    # necessary for conditional client cert authentication with TLS 1.3.143    # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older144    # versions of Python.  We only enable on Python 3.7.4+ or if certificate145    # verification is enabled to work around Python issue #37428146    # See: https://bugs.python.org/issue37428147    if (148        cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)149    ) and getattr(context, "post_handshake_auth", None) is not None:150        context.post_handshake_auth = True151 152    def disable_check_hostname():153        if (154            getattr(context, "check_hostname", None) is not None155        ):  # Platform-specific: Python 3.2156            # We do our own verification, including fingerprints and alternative157            # hostnames. So disable it here158            context.check_hostname = False159 160    # The order of the below lines setting verify_mode and check_hostname161    # matter due to safe-guards SSLContext has to prevent an SSLContext with162    # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more163    # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used164    # or not so we don't know the initial state of the freshly created SSLContext.165    if cert_reqs == ssl.CERT_REQUIRED:166        context.verify_mode = cert_reqs167        disable_check_hostname()168    else:169        disable_check_hostname()170        context.verify_mode = cert_reqs171 172    # Enable logging of TLS session keys via defacto standard environment variable173    # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values.174    if hasattr(context, "keylog_filename"):175        sslkeylogfile = os.environ.get("SSLKEYLOGFILE")176        if sslkeylogfile and not sys.flags.ignore_environment:177            context.keylog_filename = sslkeylogfile178 179    return context180 181 182def ensure_boolean(val):183    """Ensures a boolean value if a string or boolean is provided184 185    For strings, the value for True/False is case insensitive186    """187    if isinstance(val, bool):188        return val189    else:190        return val.lower() == 'true'191 192 193def mask_proxy_url(proxy_url):194    """195    Mask proxy url credentials.196 197    :type proxy_url: str198    :param proxy_url: The proxy url, i.e. https://username:password@proxy.com199 200    :return: Masked proxy url, i.e. https://***:***@proxy.com201    """202    mask = '*' * 3203    parsed_url = urlparse(proxy_url)204    if parsed_url.username:205        proxy_url = proxy_url.replace(parsed_url.username, mask, 1)206    if parsed_url.password:207        proxy_url = proxy_url.replace(parsed_url.password, mask, 1)208    return proxy_url209 210 211def _is_ipaddress(host):212    """Wrap urllib3's is_ipaddress to support bracketed IPv6 addresses."""213    return is_ipaddress(host) or bool(IPV6_ADDRZ_RE.match(host))214 215 216class ProxyConfiguration:217    """Represents a proxy configuration dictionary and additional settings.218 219    This class represents a proxy configuration dictionary and provides utility220    functions to retrieve well structured proxy urls and proxy headers from the221    proxy configuration dictionary.222    """223 224    def __init__(self, proxies=None, proxies_settings=None):225        if proxies is None:226            proxies = {}227        if proxies_settings is None:228            proxies_settings = {}229 230        self._proxies = proxies231        self._proxies_settings = proxies_settings232 233    def proxy_url_for(self, url):234        """Retrieves the corresponding proxy url for a given url."""235        parsed_url = urlparse(url)236        proxy = self._proxies.get(parsed_url.scheme)237        if proxy:238            proxy = self._fix_proxy_url(proxy)239        return proxy240 241    def proxy_headers_for(self, proxy_url):242        """Retrieves the corresponding proxy headers for a given proxy url."""243        headers = {}244        username, password = self._get_auth_from_url(proxy_url)245        if username and password:246            basic_auth = self._construct_basic_auth(username, password)247            headers['Proxy-Authorization'] = basic_auth248        return headers249 250    @property251    def settings(self):252        return self._proxies_settings253 254    def _fix_proxy_url(self, proxy_url):255        if proxy_url.startswith('http:') or proxy_url.startswith('https:'):256            return proxy_url257        elif proxy_url.startswith('//'):258            return 'http:' + proxy_url259        else:260            return 'http://' + proxy_url261 262    def _construct_basic_auth(self, username, password):263        auth_str = f'{username}:{password}'264        encoded_str = b64encode(auth_str.encode('ascii')).strip().decode()265        return f'Basic {encoded_str}'266 267    def _get_auth_from_url(self, url):268        parsed_url = urlparse(url)269        try:270            return unquote(parsed_url.username), unquote(parsed_url.password)271        except (AttributeError, TypeError):272            return None, None273 274 275class URLLib3Session:276    """A basic HTTP client that supports connection pooling and proxies.277 278    This class is inspired by requests.adapters.HTTPAdapter, but has been279    boiled down to meet the use cases needed by botocore. For the most part280    this classes matches the functionality of HTTPAdapter in requests v2.7.0281    (the same as our vendored version). The only major difference of note is282    that we currently do not support sending chunked requests. While requests283    v2.7.0 implemented this themselves, later version urllib3 support this284    directly via a flag to urlopen so enabling it if needed should be trivial.285    """286 287    def __init__(288        self,289        verify=True,290        proxies=None,291        timeout=None,292        max_pool_connections=MAX_POOL_CONNECTIONS,293        socket_options=None,294        client_cert=None,295        proxies_config=None,296    ):297        self._verify = verify298        self._proxy_config = ProxyConfiguration(299            proxies=proxies, proxies_settings=proxies_config300        )301        self._pool_classes_by_scheme = {302            'http': botocore.awsrequest.AWSHTTPConnectionPool,303            'https': botocore.awsrequest.AWSHTTPSConnectionPool,304        }305        if timeout is None:306            timeout = DEFAULT_TIMEOUT307        if not isinstance(timeout, (int, float)):308            timeout = Timeout(connect=timeout[0], read=timeout[1])309 310        self._cert_file = None311        self._key_file = None312        if isinstance(client_cert, str):313            self._cert_file = client_cert314        elif isinstance(client_cert, tuple):315            self._cert_file, self._key_file = client_cert316 317        self._timeout = timeout318        self._max_pool_connections = max_pool_connections319        self._socket_options = socket_options320        if socket_options is None:321            self._socket_options = []322        self._proxy_managers = {}323        self._manager = PoolManager(**self._get_pool_manager_kwargs())324        self._manager.pool_classes_by_scheme = self._pool_classes_by_scheme325 326    def _proxies_kwargs(self, **kwargs):327        proxies_settings = self._proxy_config.settings328        proxies_kwargs = {329            'use_forwarding_for_https': proxies_settings.get(330                'proxy_use_forwarding_for_https'331            ),332            **kwargs,333        }334        return {k: v for k, v in proxies_kwargs.items() if v is not None}335 336    def _get_pool_manager_kwargs(self, **extra_kwargs):337        pool_manager_kwargs = {338            'timeout': self._timeout,339            'maxsize': self._max_pool_connections,340            'ssl_context': self._get_ssl_context(),341            'socket_options': self._socket_options,342            'cert_file': self._cert_file,343            'key_file': self._key_file,344        }345        pool_manager_kwargs.update(**extra_kwargs)346        return pool_manager_kwargs347 348    def _get_ssl_context(self):349        return create_urllib3_context()350 351    def _get_proxy_manager(self, proxy_url):352        if proxy_url not in self._proxy_managers:353            proxy_headers = self._proxy_config.proxy_headers_for(proxy_url)354            proxy_ssl_context = self._setup_proxy_ssl_context(proxy_url)355            proxy_manager_kwargs = self._get_pool_manager_kwargs(356                proxy_headers=proxy_headers357            )358            proxy_manager_kwargs.update(359                self._proxies_kwargs(proxy_ssl_context=proxy_ssl_context)360            )361            proxy_manager = proxy_from_url(proxy_url, **proxy_manager_kwargs)362            proxy_manager.pool_classes_by_scheme = self._pool_classes_by_scheme363            self._proxy_managers[proxy_url] = proxy_manager364 365        return self._proxy_managers[proxy_url]366 367    def _path_url(self, url):368        parsed_url = urlparse(url)369        path = parsed_url.path370        if not path:371            path = '/'372        if parsed_url.query:373            path = path + '?' + parsed_url.query374        return path375 376    def _setup_ssl_cert(self, conn, url, verify):377        if url.lower().startswith('https') and verify:378            conn.cert_reqs = 'CERT_REQUIRED'379            conn.ca_certs = get_cert_path(verify)380        else:381            conn.cert_reqs = 'CERT_NONE'382            conn.ca_certs = None383 384    def _setup_proxy_ssl_context(self, proxy_url):385        proxies_settings = self._proxy_config.settings386        proxy_ca_bundle = proxies_settings.get('proxy_ca_bundle')387        proxy_cert = proxies_settings.get('proxy_client_cert')388        if proxy_ca_bundle is None and proxy_cert is None:389            return None390 391        context = self._get_ssl_context()392        try:393            url = parse_url(proxy_url)394            # urllib3 disables this by default but we need it for proper395            # proxy tls negotiation when proxy_url is not an IP Address396            if not _is_ipaddress(url.host):397                context.check_hostname = True398            if proxy_ca_bundle is not None:399                context.load_verify_locations(cafile=proxy_ca_bundle)400 401            if isinstance(proxy_cert, tuple):402                context.load_cert_chain(proxy_cert[0], keyfile=proxy_cert[1])403            elif isinstance(proxy_cert, str):404                context.load_cert_chain(proxy_cert)405 406            return context407        except (OSError, URLLib3SSLError, LocationParseError) as e:408            raise InvalidProxiesConfigError(error=e)409 410    def _get_connection_manager(self, url, proxy_url=None):411        if proxy_url:412            manager = self._get_proxy_manager(proxy_url)413        else:414            manager = self._manager415        return manager416 417    def _get_request_target(self, url, proxy_url):418        has_proxy = proxy_url is not None419 420        if not has_proxy:421            return self._path_url(url)422 423        # HTTP proxies expect the request_target to be the absolute url to know424        # which host to establish a connection to. urllib3 also supports425        # forwarding for HTTPS through the 'use_forwarding_for_https' parameter.426        proxy_scheme = urlparse(proxy_url).scheme427        using_https_forwarding_proxy = (428            proxy_scheme == 'https'429            and self._proxies_kwargs().get('use_forwarding_for_https', False)430        )431 432        if using_https_forwarding_proxy or url.startswith('http:'):433            return url434        else:435            return self._path_url(url)436 437    def _chunked(self, headers):438        transfer_encoding = headers.get('Transfer-Encoding', b'')439        transfer_encoding = ensure_bytes(transfer_encoding)440        return transfer_encoding.lower() == b'chunked'441 442    def close(self):443        self._manager.clear()444        for manager in self._proxy_managers.values():445            manager.clear()446 447    def send(self, request):448        try:449            proxy_url = self._proxy_config.proxy_url_for(request.url)450            manager = self._get_connection_manager(request.url, proxy_url)451            conn = manager.connection_from_url(request.url)452            self._setup_ssl_cert(conn, request.url, self._verify)453            if ensure_boolean(454                os.environ.get('BOTO_EXPERIMENTAL__ADD_PROXY_HOST_HEADER', '')455            ):456                # This is currently an "experimental" feature which provides457                # no guarantees of backwards compatibility. It may be subject458                # to change or removal in any patch version. Anyone opting in459                # to this feature should strictly pin botocore.460                host = urlparse(request.url).hostname461                conn.proxy_headers['host'] = host462 463            request_target = self._get_request_target(request.url, proxy_url)464            urllib_response = conn.urlopen(465                method=request.method,466                url=request_target,467                body=request.body,468                headers=request.headers,469                retries=Retry(False),470                assert_same_host=False,471                preload_content=False,472                decode_content=False,473                chunked=self._chunked(request.headers),474            )475 476            http_response = botocore.awsrequest.AWSResponse(477                request.url,478                urllib_response.status,479                urllib_response.headers,480                urllib_response,481            )482 483            if not request.stream_output:484                # Cause the raw stream to be exhausted immediately. We do it485                # this way instead of using preload_content because486                # preload_content will never buffer chunked responses487                http_response.content488 489            return http_response490        except URLLib3SSLError as e:491            raise SSLError(endpoint_url=request.url, error=e)492        except (NewConnectionError, socket.gaierror) as e:493            raise EndpointConnectionError(endpoint_url=request.url, error=e)494        except ProxyError as e:495            raise ProxyConnectionError(496                proxy_url=mask_proxy_url(proxy_url), error=e497            )498        except URLLib3ConnectTimeoutError as e:499            raise ConnectTimeoutError(endpoint_url=request.url, error=e)500        except URLLib3ReadTimeoutError as e:501            raise ReadTimeoutError(endpoint_url=request.url, error=e)502        except ProtocolError as e:503            raise ConnectionClosedError(504                error=e, request=request, endpoint_url=request.url505            )506        except Exception as e:507            message = 'Exception received when sending urllib3 HTTP request'508            logger.debug(message, exc_info=True)509            raise HTTPClientError(error=e)510 
codekingpro/portable-devtools · Team Ai