diff --git a/.github/.trivyignore b/.github/.trivyignore index fd69f0b5d6..e9de2222cc 100644 --- a/.github/.trivyignore +++ b/.github/.trivyignore @@ -1 +1,16 @@ -# Empty for now +# ============================= +# Accepted Risk Vulnerabilities +# ============================= + +# Accepting risk due to Python 3.8 support. +CVE-2025-50181 # Requires misconfiguration of urllib3, which agent does not do without intervention +CVE-2025-66418 # Malicious servers could cause high resource consumption +CVE-2025-66471 # Malicious servers could cause high resource consumption +CVE-2026-21441 # Improper Handling of Highly Compressed Data (Data Amplification) + +# ======================= +# Ignored Vulnerabilities +# ======================= + +# Not relevant, only affects Pyodide +CVE-2025-50182 diff --git a/.github/containers/Dockerfile b/.github/containers/Dockerfile index b3016548d7..3f370a4a45 100644 --- a/.github/containers/Dockerfile +++ b/.github/containers/Dockerfile @@ -115,7 +115,7 @@ RUN mv "${HOME}/.local/bin/python3.11" "${HOME}/.local/bin/pypy3.11" && \ mv "${HOME}/.local/bin/python3.10" "${HOME}/.local/bin/pypy3.10" # Install CPython versions -RUN uv python install -f cp3.14 cp3.14t cp3.13 cp3.12 cp3.11 cp3.10 cp3.9 +RUN uv python install -f cp3.14 cp3.14t cp3.13 cp3.12 cp3.11 cp3.10 cp3.9 cp3.8 # Set default Python version to CPython 3.13 RUN uv python install -f --default cp3.13 diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 6375db76a2..2e7094fe09 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] env: ASV_FACTOR: "1.1" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0570d3dda6..9f3e4f0a4f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,6 +29,8 @@ jobs: matrix: include: # Linux glibc + - wheel: cp38-manylinux + os: ubuntu-24.04 - wheel: cp39-manylinux os: ubuntu-24.04 - wheel: cp310-manylinux @@ -39,13 +41,11 @@ jobs: os: ubuntu-24.04 - wheel: cp313-manylinux os: ubuntu-24.04 - - wheel: cp313t-manylinux - os: ubuntu-24.04 - wheel: cp314-manylinux os: ubuntu-24.04 - - wheel: cp314t-manylinux - os: ubuntu-24.04 # Linux musllibc + - wheel: cp38-musllinux + os: ubuntu-24.04 - wheel: cp39-musllinux os: ubuntu-24.04 - wheel: cp310-musllinux @@ -56,12 +56,8 @@ jobs: os: ubuntu-24.04 - wheel: cp313-musllinux os: ubuntu-24.04 - - wheel: cp313t-musllinux - os: ubuntu-24.04 - wheel: cp314-musllinux os: ubuntu-24.04 - - wheel: cp314t-musllinux - os: ubuntu-24.04 # Windows # Windows wheels won't but published until the full release announcement. # - wheel: cp313-win @@ -93,7 +89,6 @@ jobs: CIBW_ARCHS_MACOS: native CIBW_ARCHS_WINDOWS: AMD64 ARM64 CIBW_ENVIRONMENT_LINUX: "LD_LIBRARY_PATH=/opt/rh/devtoolset-8/root/usr/lib64:/opt/rh/devtoolset-8/root/usr/lib:/opt/rh/devtoolset-8/root/usr/lib64/dyninst:/opt/rh/devtoolset-8/root/usr/lib/dyninst:/usr/local/lib64:/usr/local/lib" - CIBW_ENABLE: cpython-freethreading CIBW_TEST_REQUIRES: pytest CIBW_TEST_COMMAND_LINUX: "export PYTHONPATH={project}/tests; pytest {project}/tests/agent_unittests -vx" CIBW_TEST_COMMAND_MACOS: "export PYTHONPATH={project}/tests; pytest {project}/tests/agent_unittests -vx" diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 312328b613..2aceaea9fa 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -37,7 +37,7 @@ Distributed under the following license(s): ## [urllib3](https://pypi.org/project/urllib3) -Copyright (c) 2008-2020 Andrey Petrov and contributors. +Copyright (c) 2008-2019 Andrey Petrov and contributors (see CONTRIBUTORS.txt) Distributed under the following license(s): @@ -46,7 +46,7 @@ Distributed under the following license(s): ## [wrapt](https://pypi.org/project/wrapt) -Copyright (c) 2013-2025, Graham Dumpleton +Copyright (c) 2013-2019, Graham Dumpleton All rights reserved. Distributed under the following license(s): diff --git a/asv.conf.json b/asv.conf.json index ca6902e314..203d52c887 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -6,7 +6,7 @@ "repo": ".", "environment_type": "virtualenv", "install_timeout": 120, - "pythons": ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"], + "pythons": ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"], "benchmark_dir": "tests/agent_benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results", diff --git a/newrelic/admin/__init__.py b/newrelic/admin/__init__.py index fe6433a23e..61f3995a3f 100644 --- a/newrelic/admin/__init__.py +++ b/newrelic/admin/__init__.py @@ -120,7 +120,19 @@ def load_internal_plugins(): def load_external_plugins(): - from importlib.metadata import entry_points + try: + # importlib.metadata was introduced into the standard library starting in Python 3.8. + from importlib.metadata import entry_points + except ImportError: + try: + # importlib_metadata is a backport library installable from PyPI. + from importlib_metadata import entry_points + except ImportError: + try: + # Fallback to pkg_resources, which is available in older versions of setuptools. + from pkg_resources import iter_entry_points as entry_points + except ImportError: + return group = "newrelic.admin" diff --git a/newrelic/common/agent_http.py b/newrelic/common/agent_http.py index 4bca7437cd..7f054cb3c7 100644 --- a/newrelic/common/agent_http.py +++ b/newrelic/common/agent_http.py @@ -354,7 +354,9 @@ def _connection(self): return self._connection_attr retries = urllib3.Retry(total=False, connect=None, read=None, redirect=0, status=None) - self._connection_attr = self.CONNECTION_CLS(self._host, self._port, retries=retries, **self._connection_kwargs) + self._connection_attr = self.CONNECTION_CLS( + self._host, self._port, strict=True, retries=retries, **self._connection_kwargs + ) return self._connection_attr def close_connection(self): diff --git a/newrelic/common/object_wrapper.py b/newrelic/common/object_wrapper.py index e535559109..be8c351f4e 100644 --- a/newrelic/common/object_wrapper.py +++ b/newrelic/common/object_wrapper.py @@ -21,16 +21,11 @@ import inspect -from newrelic.packages.wrapt import ( # noqa: F401 - BaseObjectProxy, - apply_patch, - resolve_path, - wrap_object, - wrap_object_attribute, -) from newrelic.packages.wrapt import BoundFunctionWrapper as _BoundFunctionWrapper from newrelic.packages.wrapt import CallableObjectProxy as _CallableObjectProxy from newrelic.packages.wrapt import FunctionWrapper as _FunctionWrapper +from newrelic.packages.wrapt import ObjectProxy as _ObjectProxy +from newrelic.packages.wrapt import apply_patch, resolve_path, wrap_object, wrap_object_attribute # noqa: F401 # We previously had our own pure Python implementation of the generic # object wrapper but we now defer to using the wrapt module as its C @@ -49,7 +44,7 @@ # ObjectProxy or FunctionWrapper should be used going forward. -class ObjectProxy(BaseObjectProxy): +class ObjectProxy(_ObjectProxy): """ This class provides method overrides for all object wrappers used by the agent. These methods allow attributes to be defined with the special prefix diff --git a/newrelic/common/package_version_utils.py b/newrelic/common/package_version_utils.py index 38c85057d0..da40f0dffa 100644 --- a/newrelic/common/package_version_utils.py +++ b/newrelic/common/package_version_utils.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import importlib.metadata as importlib_metadata import sys import warnings from functools import lru_cache @@ -96,17 +95,37 @@ def _get_package_version(name): except Exception: pass - try: - # In Python 3.10+ packages_distribution can be checked for as well. - if hasattr(importlib_metadata, "packages_distributions"): - distributions = importlib_metadata.packages_distributions() - distribution_name = distributions.get(name, name) - distribution_name = distribution_name[0] if isinstance(distribution_name, list) else distribution_name - else: - distribution_name = name - - version = importlib_metadata.version(distribution_name) - if version not in NULL_VERSIONS: - return version - except Exception: - pass + importlib_metadata = None + # importlib.metadata was introduced into the standard library starting in Python 3.8. + importlib_metadata = getattr(sys.modules.get("importlib", None), "metadata", None) + if importlib_metadata is None: + # importlib_metadata is a backport library installable from PyPI. + try: + import importlib_metadata + except ImportError: + pass + + if importlib_metadata is not None: + try: + # In Python 3.10+ packages_distribution can be checked for as well. + if hasattr(importlib_metadata, "packages_distributions"): + distributions = importlib_metadata.packages_distributions() + distribution_name = distributions.get(name, name) + distribution_name = distribution_name[0] if isinstance(distribution_name, list) else distribution_name + else: + distribution_name = name + + version = importlib_metadata.version(distribution_name) + if version not in NULL_VERSIONS: + return version + except Exception: + pass + + # Fallback to pkg_resources, which is available in older versions of setuptools. + if "pkg_resources" in sys.modules: + try: + version = sys.modules["pkg_resources"].get_distribution(name).version + if version not in NULL_VERSIONS: + return version + except Exception: + pass diff --git a/newrelic/config.py b/newrelic/config.py index 51e477416d..3960e4e1ea 100644 --- a/newrelic/config.py +++ b/newrelic/config.py @@ -4226,7 +4226,19 @@ def _process_module_builtin_defaults(): def _process_module_entry_points(): - from importlib.metadata import entry_points + try: + # importlib.metadata was introduced into the standard library starting in Python 3.8. + from importlib.metadata import entry_points + except ImportError: + try: + # importlib_metadata is a backport library installable from PyPI. + from importlib_metadata import entry_points + except ImportError: + try: + # Fallback to pkg_resources, which is available in older versions of setuptools. + from pkg_resources import iter_entry_points as entry_points + except ImportError: + return group = "newrelic.hooks" @@ -4294,7 +4306,19 @@ def _setup_instrumentation(): def _setup_extensions(): - from importlib.metadata import entry_points + try: + # importlib.metadata was introduced into the standard library starting in Python 3.8. + from importlib.metadata import entry_points + except ImportError: + try: + # importlib_metadata is a backport library installable from PyPI. + from importlib_metadata import entry_points + except ImportError: + try: + # Fallback to pkg_resources, which is available in older versions of setuptools. + from pkg_resources import iter_entry_points as entry_points + except ImportError: + return group = "newrelic.extension" diff --git a/newrelic/core/_thread_utilization.c b/newrelic/core/_thread_utilization.c index 18f081a5be..d1d7bfacc6 100644 --- a/newrelic/core/_thread_utilization.c +++ b/newrelic/core/_thread_utilization.c @@ -202,10 +202,13 @@ static PyObject *NRUtilization_new(PyTypeObject *type, return NULL; /* - * Using a mutex to ensure this is compatible with free threaded Python interpreters. - * In the past, this relied on the GIL for thread safety with weakrefs but that was - * not reliable enough anyway. + * XXX Using a mutex for now just in case the calls to get + * the current thread are causing release of GIL in a + * multithreaded context. May explain why having issues with + * object referred to by weakrefs being corrupted. The GIL + * should technically be enough to protect us here. */ + self->thread_mutex = PyThread_allocate_lock(); self->set_of_all_threads = PyDict_New(); @@ -452,10 +455,6 @@ moduleinit(void) PyModule_AddObject(module, "ThreadUtilization", (PyObject *)&NRUtilization_Type); -#ifdef Py_GIL_DISABLED - PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); -#endif - return module; } diff --git a/newrelic/core/config.py b/newrelic/core/config.py index c6b2d4233e..8cfdeda0ae 100644 --- a/newrelic/core/config.py +++ b/newrelic/core/config.py @@ -1119,7 +1119,8 @@ def _flatten(settings, o, name=None): for key, value in vars(o).items(): # Remove any leading underscores on keys accessed through # properties for reporting. - key = key.removeprefix("_") + if key.startswith("_"): + key = key[1:] if name: key = f"{name}.{key}" diff --git a/newrelic/core/environment.py b/newrelic/core/environment.py index 11203df657..7d3a04f1b6 100644 --- a/newrelic/core/environment.py +++ b/newrelic/core/environment.py @@ -248,7 +248,13 @@ def _get_stdlib_builtin_module_names(): # Since sys.stdlib_module_names is not available in versions of python below 3.10, # use isort's hardcoded stdlibs instead. python_version = sys.version_info[0:2] - if python_version < (3, 10): + if python_version < (3,): + stdlibs = isort_stdlibs.py27.stdlib + elif (3, 7) <= python_version < (3, 8): + stdlibs = isort_stdlibs.py37.stdlib + elif python_version < (3, 9): + stdlibs = isort_stdlibs.py38.stdlib + elif python_version < (3, 10): stdlibs = isort_stdlibs.py39.stdlib elif python_version >= (3, 10): stdlibs = sys.stdlib_module_names diff --git a/newrelic/hooks/database_dbapi2.py b/newrelic/hooks/database_dbapi2.py index 1e38880113..b100fa58de 100644 --- a/newrelic/hooks/database_dbapi2.py +++ b/newrelic/hooks/database_dbapi2.py @@ -89,9 +89,6 @@ def callproc(self, procname, parameters=DEFAULT): else: return self.__wrapped__.callproc(procname) - def __iter__(self): - return iter(self.__wrapped__) - class ConnectionWrapper(ObjectProxy): __cursor_wrapper__ = CursorWrapper diff --git a/newrelic/hooks/logger_structlog.py b/newrelic/hooks/logger_structlog.py index 66d7102505..8d9ba3cc5d 100644 --- a/newrelic/hooks/logger_structlog.py +++ b/newrelic/hooks/logger_structlog.py @@ -22,7 +22,7 @@ from newrelic.hooks.logger_logging import add_nr_linking_metadata -@functools.cache +@functools.lru_cache(maxsize=None) def normalize_level_name(method_name): # Look up level number for method name, using result to look up level name for that level number. # Convert result to upper case, and default to UNKNOWN in case of errors or missing values. diff --git a/newrelic/packages/asgiref_compatibility.py b/newrelic/packages/asgiref_compatibility.py index 444fa52582..f5b029b1da 100644 --- a/newrelic/packages/asgiref_compatibility.py +++ b/newrelic/packages/asgiref_compatibility.py @@ -30,16 +30,6 @@ import inspect -# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for -# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. -# The latter is replaced with the inspect.markcoroutinefunction decorator. -# Until 3.12 is the minimum supported Python version, provide a shim. - -if hasattr(inspect, "iscoroutinefunction"): - iscoroutinefunction = inspect.iscoroutinefunction -else: - iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] - def is_double_callable(application): """ Tests to see if an application is a legacy-style (double-callable) application. @@ -56,10 +46,10 @@ def is_double_callable(application): if hasattr(application, "__call__"): # We only check to see if its __call__ is a coroutine function - # if it's not, it still might be a coroutine function itself. - if iscoroutinefunction(application.__call__): + if asyncio.iscoroutinefunction(application.__call__): return False # Non-classes we just check directly - return not iscoroutinefunction(application) + return not asyncio.iscoroutinefunction(application) def double_to_single_callable(application): diff --git a/newrelic/packages/requirements.txt b/newrelic/packages/requirements.txt index 9a251fb557..5d8c59db2e 100644 --- a/newrelic/packages/requirements.txt +++ b/newrelic/packages/requirements.txt @@ -3,6 +3,6 @@ # This file is used by dependabot to keep track of and recommend updates # to the New Relic Python Agent's dependencies in newrelic/packages/. opentelemetry_proto==1.32.1 -urllib3==2.6.3 -wrapt==2.0.1 -asgiref==3.11.0 # We only vendor asgiref.compatibility.py +urllib3==1.26.19 +wrapt==1.16.0 +asgiref==3.6.0 # We only vendor asgiref.compatibility.py diff --git a/newrelic/packages/urllib3/LICENSE.txt b/newrelic/packages/urllib3/LICENSE.txt index e6183d0276..429a1767e4 100644 --- a/newrelic/packages/urllib3/LICENSE.txt +++ b/newrelic/packages/urllib3/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2008-2020 Andrey Petrov and contributors. +Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/newrelic/packages/urllib3/__init__.py b/newrelic/packages/urllib3/__init__.py index 3fe782c8a4..c6fa38212f 100644 --- a/newrelic/packages/urllib3/__init__.py +++ b/newrelic/packages/urllib3/__init__.py @@ -1,49 +1,40 @@ """ Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more """ - -from __future__ import annotations +from __future__ import absolute_import # Set default logging handler to avoid "No handler found" warnings. import logging -import sys -import typing import warnings from logging import NullHandler from . import exceptions -from ._base_connection import _TYPE_BODY -from ._collections import HTTPHeaderDict from ._version import __version__ from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url -from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .filepost import encode_multipart_formdata from .poolmanager import PoolManager, ProxyManager, proxy_from_url -from .response import BaseHTTPResponse, HTTPResponse +from .response import HTTPResponse from .util.request import make_headers from .util.retry import Retry from .util.timeout import Timeout +from .util.url import get_host -# Ensure that Python is compiled with OpenSSL 1.1.1+ -# If the 'ssl' module isn't available at all that's -# fine, we only care if the module is available. +# === NOTE TO REPACKAGERS AND VENDORS === +# Please delete this block, this logic is only +# for urllib3 being distributed via PyPI. +# See: https://github.com/urllib3/urllib3/issues/2680 try: - import ssl + import urllib3_secure_extra # type: ignore # noqa: F401 except ImportError: pass else: - if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: - warnings.warn( - "urllib3 v2 only supports OpenSSL 1.1.1+, currently " - f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " - "See: https://github.com/urllib3/urllib3/issues/3020", - exceptions.NotOpenSSLWarning, - ) - elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: - raise ImportError( - "urllib3 v2 only supports OpenSSL 1.1.1+, currently " - f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " - "See: https://github.com/urllib3/urllib3/issues/2168" - ) + warnings.warn( + "'urllib3[secure]' extra is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, + ) __author__ = "Andrey Petrov (andrey.petrov@shazow.net)" __license__ = "MIT" @@ -51,7 +42,6 @@ __all__ = ( "HTTPConnectionPool", - "HTTPHeaderDict", "HTTPSConnectionPool", "PoolManager", "ProxyManager", @@ -62,18 +52,15 @@ "connection_from_url", "disable_warnings", "encode_multipart_formdata", + "get_host", "make_headers", "proxy_from_url", - "request", - "BaseHTTPResponse", ) logging.getLogger(__name__).addHandler(NullHandler()) -def add_stderr_logger( - level: int = logging.DEBUG, -) -> logging.StreamHandler[typing.TextIO]: +def add_stderr_logger(level=logging.DEBUG): """ Helper for quickly adding a StreamHandler to the logger. Useful for debugging. @@ -100,112 +87,16 @@ def add_stderr_logger( # mechanisms to silence them. # SecurityWarning's always go off by default. warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# SubjectAltNameWarning's should go off once per host +warnings.simplefilter("default", exceptions.SubjectAltNameWarning, append=True) # InsecurePlatformWarning's don't vary between requests, so we keep it default. warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) +# SNIMissingWarnings should go off only once. +warnings.simplefilter("default", exceptions.SNIMissingWarning, append=True) -def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: +def disable_warnings(category=exceptions.HTTPWarning): """ Helper for quickly disabling all urllib3 warnings. """ warnings.simplefilter("ignore", category) - - -_DEFAULT_POOL = PoolManager() - - -def request( - method: str, - url: str, - *, - body: _TYPE_BODY | None = None, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - preload_content: bool | None = True, - decode_content: bool | None = True, - redirect: bool | None = True, - retries: Retry | bool | int | None = None, - timeout: Timeout | float | int | None = 3, - json: typing.Any | None = None, -) -> BaseHTTPResponse: - """ - A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. - Therefore, its side effects could be shared across dependencies relying on it. - To avoid side effects create a new ``PoolManager`` instance and use it instead. - The method does not accept low-level ``**urlopen_kw`` keyword arguments. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param fields: - Data to encode and send in the request body. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. - - :param bool preload_content: - If True, the response's body will be preloaded into memory. - - :param bool decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param redirect: - If True, automatically handle redirects (status codes 301, 302, - 303, 307, 308). Each redirect counts as a retry. Disabling retries - will disable redirect, too. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param json: - Data to encode and send as JSON with UTF-encoded in the request body. - The ``"Content-Type"`` header will be set to ``"application/json"`` - unless specified otherwise. - """ - - return _DEFAULT_POOL.request( - method, - url, - body=body, - fields=fields, - headers=headers, - preload_content=preload_content, - decode_content=decode_content, - redirect=redirect, - retries=retries, - timeout=timeout, - json=json, - ) - - -if sys.platform == "emscripten": - from .contrib.emscripten import inject_into_urllib3 # noqa: 401 - - inject_into_urllib3() diff --git a/newrelic/packages/urllib3/_base_connection.py b/newrelic/packages/urllib3/_base_connection.py deleted file mode 100644 index dc0f318c0b..0000000000 --- a/newrelic/packages/urllib3/_base_connection.py +++ /dev/null @@ -1,165 +0,0 @@ -from __future__ import annotations - -import typing - -from .util.connection import _TYPE_SOCKET_OPTIONS -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT -from .util.url import Url - -_TYPE_BODY = typing.Union[bytes, typing.IO[typing.Any], typing.Iterable[bytes], str] - - -class ProxyConfig(typing.NamedTuple): - ssl_context: ssl.SSLContext | None - use_forwarding_for_https: bool - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None - - -class _ResponseOptions(typing.NamedTuple): - # TODO: Remove this in favor of a better - # HTTP request/response lifecycle tracking. - request_method: str - request_url: str - preload_content: bool - decode_content: bool - enforce_content_length: bool - - -if typing.TYPE_CHECKING: - import ssl - from typing import Protocol - - from .response import BaseHTTPResponse - - class BaseHTTPConnection(Protocol): - default_port: typing.ClassVar[int] - default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] - - host: str - port: int - timeout: None | ( - float - ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. - blocksize: int - source_address: tuple[str, int] | None - socket_options: _TYPE_SOCKET_OPTIONS | None - - proxy: Url | None - proxy_config: ProxyConfig | None - - is_verified: bool - proxy_is_verified: bool | None - - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 8192, - socket_options: _TYPE_SOCKET_OPTIONS | None = ..., - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - ) -> None: ... - - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: ... - - def connect(self) -> None: ... - - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - # We know *at least* botocore is depending on the order of the - # first 3 parameters so to be safe we only mark the later ones - # as keyword-only to ensure we have space to extend. - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: ... - - def getresponse(self) -> BaseHTTPResponse: ... - - def close(self) -> None: ... - - @property - def is_closed(self) -> bool: - """Whether the connection either is brand new or has been previously closed. - If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` - properties must be False. - """ - - @property - def is_connected(self) -> bool: - """Whether the connection is actively connected to any origin (proxy or target)""" - - @property - def has_connected_to_proxy(self) -> bool: - """Whether the connection has successfully connected to its proxy. - This returns False if no proxy is in use. Used to determine whether - errors are coming from the proxy layer or from tunnelling to the target origin. - """ - - class BaseHTTPSConnection(BaseHTTPConnection, Protocol): - default_port: typing.ClassVar[int] - default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] - - # Certificate verification methods - cert_reqs: int | str | None - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None - ssl_context: ssl.SSLContext | None - - # Trusted CAs - ca_certs: str | None - ca_cert_dir: str | None - ca_cert_data: None | str | bytes - - # TLS version - ssl_minimum_version: int | None - ssl_maximum_version: int | None - ssl_version: int | str | None # Deprecated - - # Client certificates - cert_file: str | None - key_file: str | None - key_password: str | None - - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: _TYPE_SOCKET_OPTIONS | None = ..., - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: ... diff --git a/newrelic/packages/urllib3/_collections.py b/newrelic/packages/urllib3/_collections.py index 0378aab1b1..bceb8451f0 100644 --- a/newrelic/packages/urllib3/_collections.py +++ b/newrelic/packages/urllib3/_collections.py @@ -1,66 +1,34 @@ -from __future__ import annotations +from __future__ import absolute_import + +try: + from collections.abc import Mapping, MutableMapping +except ImportError: + from collections import Mapping, MutableMapping +try: + from threading import RLock +except ImportError: # Platform-specific: No threads available + + class RLock: + def __enter__(self): + pass -import typing -from collections import OrderedDict -from enum import Enum, auto -from threading import RLock + def __exit__(self, exc_type, exc_value, traceback): + pass -if typing.TYPE_CHECKING: - # We can only import Protocol if TYPE_CHECKING because it's a development - # dependency, and is not available at runtime. - from typing import Protocol - from typing_extensions import Self +from collections import OrderedDict - class HasGettableStringKeys(Protocol): - def keys(self) -> typing.Iterator[str]: ... +from .exceptions import InvalidHeader +from .packages import six +from .packages.six import iterkeys, itervalues - def __getitem__(self, key: str) -> str: ... +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] -__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] +_Null = object() -# Key type -_KT = typing.TypeVar("_KT") -# Value type -_VT = typing.TypeVar("_VT") -# Default type -_DT = typing.TypeVar("_DT") - -ValidHTTPHeaderSource = typing.Union[ - "HTTPHeaderDict", - typing.Mapping[str, str], - typing.Iterable[tuple[str, str]], - "HasGettableStringKeys", -] - - -class _Sentinel(Enum): - not_passed = auto() - - -def ensure_can_construct_http_header_dict( - potential: object, -) -> ValidHTTPHeaderSource | None: - if isinstance(potential, HTTPHeaderDict): - return potential - elif isinstance(potential, typing.Mapping): - # Full runtime checking of the contents of a Mapping is expensive, so for the - # purposes of typechecking, we assume that any Mapping is the right shape. - return typing.cast(typing.Mapping[str, str], potential) - elif isinstance(potential, typing.Iterable): - # Similarly to Mapping, full runtime checking of the contents of an Iterable is - # expensive, so for the purposes of typechecking, we assume that any Iterable - # is the right shape. - return typing.cast(typing.Iterable[tuple[str, str]], potential) - elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): - return typing.cast("HasGettableStringKeys", potential) - else: - return None - - -class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): +class RecentlyUsedContainer(MutableMapping): """ Provides a thread-safe dict-like container which maintains up to ``maxsize`` keys while throwing away the least-recently-used keys beyond @@ -74,134 +42,69 @@ class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, ``dispose_func(value)`` is called. Callback which will get called """ - _container: typing.OrderedDict[_KT, _VT] - _maxsize: int - dispose_func: typing.Callable[[_VT], None] | None - lock: RLock - - def __init__( - self, - maxsize: int = 10, - dispose_func: typing.Callable[[_VT], None] | None = None, - ) -> None: - super().__init__() + ContainerCls = OrderedDict + + def __init__(self, maxsize=10, dispose_func=None): self._maxsize = maxsize self.dispose_func = dispose_func - self._container = OrderedDict() + + self._container = self.ContainerCls() self.lock = RLock() - def __getitem__(self, key: _KT) -> _VT: + def __getitem__(self, key): # Re-insert the item, moving it to the end of the eviction line. with self.lock: item = self._container.pop(key) self._container[key] = item return item - def __setitem__(self, key: _KT, value: _VT) -> None: - evicted_item = None + def __setitem__(self, key, value): + evicted_value = _Null with self.lock: # Possibly evict the existing value of 'key' - try: - # If the key exists, we'll overwrite it, which won't change the - # size of the pool. Because accessing a key should move it to - # the end of the eviction line, we pop it out first. - evicted_item = key, self._container.pop(key) - self._container[key] = value - except KeyError: - # When the key does not exist, we insert the value first so that - # evicting works in all cases, including when self._maxsize is 0 - self._container[key] = value - if len(self._container) > self._maxsize: - # If we didn't evict an existing value, and we've hit our maximum - # size, then we have to evict the least recently used item from - # the beginning of the container. - evicted_item = self._container.popitem(last=False) - - # After releasing the lock on the pool, dispose of any evicted value. - if evicted_item is not None and self.dispose_func: - _, evicted_value = evicted_item + evicted_value = self._container.get(key, _Null) + self._container[key] = value + + # If we didn't evict an existing value, we might have to evict the + # least recently used item from the beginning of the container. + if len(self._container) > self._maxsize: + _key, evicted_value = self._container.popitem(last=False) + + if self.dispose_func and evicted_value is not _Null: self.dispose_func(evicted_value) - def __delitem__(self, key: _KT) -> None: + def __delitem__(self, key): with self.lock: value = self._container.pop(key) if self.dispose_func: self.dispose_func(value) - def __len__(self) -> int: + def __len__(self): with self.lock: return len(self._container) - def __iter__(self) -> typing.NoReturn: + def __iter__(self): raise NotImplementedError( "Iteration over this class is unlikely to be threadsafe." ) - def clear(self) -> None: + def clear(self): with self.lock: # Copy pointers to all values, then wipe the mapping - values = list(self._container.values()) + values = list(itervalues(self._container)) self._container.clear() if self.dispose_func: for value in values: self.dispose_func(value) - def keys(self) -> set[_KT]: # type: ignore[override] + def keys(self): with self.lock: - return set(self._container.keys()) - + return list(iterkeys(self._container)) -class HTTPHeaderDictItemView(set[tuple[str, str]]): - """ - HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of - address. - - If we directly try to get an item with a particular name, we will get a string - back that is the concatenated version of all the values: - - >>> d['X-Header-Name'] - 'Value1, Value2, Value3' - - However, if we iterate over an HTTPHeaderDict's items, we will optionally combine - these values based on whether combine=True was called when building up the dictionary - - >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) - >>> d.add("A", "2", combine=True) - >>> d.add("B", "bar") - >>> list(d.items()) - [ - ('A', '1, 2'), - ('B', 'foo'), - ('B', 'bar'), - ] - - This class conforms to the interface required by the MutableMapping ABC while - also giving us the nonstandard iteration behavior we want; items with duplicate - keys, ordered by time of first insertion. - """ - - _headers: HTTPHeaderDict - - def __init__(self, headers: HTTPHeaderDict) -> None: - self._headers = headers - - def __len__(self) -> int: - return len(list(self._headers.iteritems())) - - def __iter__(self) -> typing.Iterator[tuple[str, str]]: - return self._headers.iteritems() - def __contains__(self, item: object) -> bool: - if isinstance(item, tuple) and len(item) == 2: - passed_key, passed_val = item - if isinstance(passed_key, str) and isinstance(passed_val, str): - return self._headers._has_value_for_header(passed_key, passed_val) - return False - - -class HTTPHeaderDict(typing.MutableMapping[str, str]): +class HTTPHeaderDict(MutableMapping): """ :param headers: An iterable of field-value pairs. Must not contain multiple field names @@ -235,11 +138,9 @@ class HTTPHeaderDict(typing.MutableMapping[str, str]): '7' """ - _container: typing.MutableMapping[str, list[str]] - - def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): - super().__init__() - self._container = {} # 'dict' is insert-ordered + def __init__(self, headers=None, **kwargs): + super(HTTPHeaderDict, self).__init__() + self._container = OrderedDict() if headers is not None: if isinstance(headers, HTTPHeaderDict): self._copy_from(headers) @@ -248,156 +149,126 @@ def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): if kwargs: self.extend(kwargs) - def __setitem__(self, key: str, val: str) -> None: - # avoid a bytes/str comparison by decoding before httplib - if isinstance(key, bytes): - key = key.decode("latin-1") + def __setitem__(self, key, val): self._container[key.lower()] = [key, val] + return self._container[key.lower()] - def __getitem__(self, key: str) -> str: - if isinstance(key, bytes): - key = key.decode("latin-1") + def __getitem__(self, key): val = self._container[key.lower()] return ", ".join(val[1:]) - def __delitem__(self, key: str) -> None: - if isinstance(key, bytes): - key = key.decode("latin-1") + def __delitem__(self, key): del self._container[key.lower()] - def __contains__(self, key: object) -> bool: - if isinstance(key, bytes): - key = key.decode("latin-1") - if isinstance(key, str): - return key.lower() in self._container - return False - - def setdefault(self, key: str, default: str = "") -> str: - return super().setdefault(key, default) + def __contains__(self, key): + return key.lower() in self._container - def __eq__(self, other: object) -> bool: - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: + def __eq__(self, other): + if not isinstance(other, Mapping) and not hasattr(other, "keys"): return False - else: - other_as_http_header_dict = type(self)(maybe_constructable) - - return {k.lower(): v for k, v in self.itermerged()} == { - k.lower(): v for k, v in other_as_http_header_dict.itermerged() - } + if not isinstance(other, type(self)): + other = type(self)(other) + return dict((k.lower(), v) for k, v in self.itermerged()) == dict( + (k.lower(), v) for k, v in other.itermerged() + ) - def __ne__(self, other: object) -> bool: + def __ne__(self, other): return not self.__eq__(other) - def __len__(self) -> int: + if six.PY2: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + def __len__(self): return len(self._container) - def __iter__(self) -> typing.Iterator[str]: + def __iter__(self): # Only provide the originally cased names for vals in self._container.values(): yield vals[0] - def discard(self, key: str) -> None: + def pop(self, key, default=__marker): + """D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + """ + # Using the MutableMapping function directly fails due to the private marker. + # Using ordinary dict.pop would expose the internal structures. + # So let's reinvent the wheel. + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def discard(self, key): try: del self[key] except KeyError: pass - def add(self, key: str, val: str, *, combine: bool = False) -> None: + def add(self, key, val): """Adds a (name, value) pair, doesn't overwrite the value if it already exists. - If this is called with combine=True, instead of adding a new header value - as a distinct item during iteration, this will instead append the value to - any existing header value with a comma. If no existing header value exists - for the key, then the value will simply be added, ignoring the combine parameter. - >>> headers = HTTPHeaderDict(foo='bar') >>> headers.add('Foo', 'baz') >>> headers['foo'] 'bar, baz' - >>> list(headers.items()) - [('foo', 'bar'), ('foo', 'baz')] - >>> headers.add('foo', 'quz', combine=True) - >>> list(headers.items()) - [('foo', 'bar, baz, quz')] """ - # avoid a bytes/str comparison by decoding before httplib - if isinstance(key, bytes): - key = key.decode("latin-1") key_lower = key.lower() new_vals = [key, val] # Keep the common case aka no item present as fast as possible vals = self._container.setdefault(key_lower, new_vals) if new_vals is not vals: - # if there are values here, then there is at least the initial - # key/value pair - assert len(vals) >= 2 - if combine: - vals[-1] = vals[-1] + ", " + val - else: - vals.append(val) + vals.append(val) - def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + def extend(self, *args, **kwargs): """Generic import function for any type of header-like object. Adapted version of MutableMapping.update in order to insert items with self.add instead of self.__setitem__ """ if len(args) > 1: raise TypeError( - f"extend() takes at most 1 positional arguments ({len(args)} given)" + "extend() takes at most 1 positional " + "arguments ({0} given)".format(len(args)) ) other = args[0] if len(args) >= 1 else () if isinstance(other, HTTPHeaderDict): for key, val in other.iteritems(): self.add(key, val) - elif isinstance(other, typing.Mapping): - for key, val in other.items(): - self.add(key, val) - elif isinstance(other, typing.Iterable): - other = typing.cast(typing.Iterable[tuple[str, str]], other) - for key, value in other: - self.add(key, value) - elif hasattr(other, "keys") and hasattr(other, "__getitem__"): - # THIS IS NOT A TYPESAFE BRANCH - # In this branch, the object has a `keys` attr but is not a Mapping or any of - # the other types indicated in the method signature. We do some stuff with - # it as though it partially implements the Mapping interface, but we're not - # doing that stuff safely AT ALL. + elif isinstance(other, Mapping): + for key in other: + self.add(key, other[key]) + elif hasattr(other, "keys"): for key in other.keys(): self.add(key, other[key]) + else: + for key, value in other: + self.add(key, value) for key, value in kwargs.items(): self.add(key, value) - @typing.overload - def getlist(self, key: str) -> list[str]: ... - - @typing.overload - def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... - - def getlist( - self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed - ) -> list[str] | _DT: + def getlist(self, key, default=__marker): """Returns a list of all the values for the named field. Returns an empty list if the key doesn't exist.""" - if isinstance(key, bytes): - key = key.decode("latin-1") try: vals = self._container[key.lower()] except KeyError: - if default is _Sentinel.not_passed: - # _DT is unbound; empty list is instance of List[str] + if default is self.__marker: return [] - # _DT is bound; default is instance of _DT return default else: - # _DT may or may not be bound; vals[1:] is instance of List[str], which - # meets our external interface requirement of `Union[List[str], _DT]`. return vals[1:] - def _prepare_for_method_change(self) -> Self: + def _prepare_for_method_change(self): """ Remove content-specific header fields before changing the request method to GET or HEAD according to RFC 9110, Section 15.4. @@ -423,65 +294,62 @@ def _prepare_for_method_change(self) -> Self: # Backwards compatibility for http.cookiejar get_all = getlist - def __repr__(self) -> str: - return f"{type(self).__name__}({dict(self.itermerged())})" + def __repr__(self): + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - def _copy_from(self, other: HTTPHeaderDict) -> None: + def _copy_from(self, other): for key in other: val = other.getlist(key) - self._container[key.lower()] = [key, *val] + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + self._container[key.lower()] = [key] + val - def copy(self) -> Self: + def copy(self): clone = type(self)() clone._copy_from(self) return clone - def iteritems(self) -> typing.Iterator[tuple[str, str]]: + def iteritems(self): """Iterate over all header lines, including duplicate ones.""" for key in self: vals = self._container[key.lower()] for val in vals[1:]: yield vals[0], val - def itermerged(self) -> typing.Iterator[tuple[str, str]]: + def itermerged(self): """Iterate over all headers, merging duplicate ones together.""" for key in self: val = self._container[key.lower()] yield val[0], ", ".join(val[1:]) - def items(self) -> HTTPHeaderDictItemView: # type: ignore[override] - return HTTPHeaderDictItemView(self) - - def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: - if header_name in self: - return potential_value in self._container[header_name.lower()][1:] - return False - - def __ior__(self, other: object) -> HTTPHeaderDict: - # Supports extending a header dict in-place using operator |= - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - self.extend(maybe_constructable) - return self - - def __or__(self, other: object) -> Self: - # Supports merging header dicts using operator | - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - result = self.copy() - result.extend(maybe_constructable) - return result - - def __ror__(self, other: object) -> Self: - # Supports merging header dicts using operator | when other is on left side - # combining items with add instead of __setitem__ - maybe_constructable = ensure_can_construct_http_header_dict(other) - if maybe_constructable is None: - return NotImplemented - result = type(self)(maybe_constructable) - result.extend(self) - return result + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message): # Python 2 + """Read headers from a Python 2 httplib message object.""" + # python2.7 does not expose a proper API for exporting multiheaders + # efficiently. This function re-reads raw lines from the message + # object and extracts the multiheaders properly. + obs_fold_continued_leaders = (" ", "\t") + headers = [] + + for line in message.headers: + if line.startswith(obs_fold_continued_leaders): + if not headers: + # We received a header line that starts with OWS as described + # in RFC-7230 S3.2.4. This indicates a multiline header, but + # there exists no previous header to which we can attach it. + raise InvalidHeader( + "Header continuation with no previous header: %s" % line + ) + else: + key, value = headers[-1] + headers[-1] = (key, value + " " + line.strip()) + continue + + key, value = line.split(":", 1) + headers.append((key, value.strip())) + + return cls(headers) diff --git a/newrelic/packages/urllib3/_version.py b/newrelic/packages/urllib3/_version.py index 268d3b984d..c40db86d0a 100644 --- a/newrelic/packages/urllib3/_version.py +++ b/newrelic/packages/urllib3/_version.py @@ -1,34 +1,2 @@ -# file generated by setuptools-scm -# don't change, don't track in version control - -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", - "__commit_id__", - "commit_id", -] - -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple - from typing import Union - - VERSION_TUPLE = Tuple[Union[int, str], ...] - COMMIT_ID = Union[str, None] -else: - VERSION_TUPLE = object - COMMIT_ID = object - -version: str -__version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE -commit_id: COMMIT_ID -__commit_id__: COMMIT_ID - -__version__ = version = '2.6.3' -__version_tuple__ = version_tuple = (2, 6, 3) - -__commit_id__ = commit_id = None +# This file is protected via CODEOWNERS +__version__ = "1.26.19" diff --git a/newrelic/packages/urllib3/connection.py b/newrelic/packages/urllib3/connection.py index 2ceeb0a548..de35b63d67 100644 --- a/newrelic/packages/urllib3/connection.py +++ b/newrelic/packages/urllib3/connection.py @@ -1,59 +1,59 @@ -from __future__ import annotations +from __future__ import absolute_import import datetime -import http.client import logging import os import re import socket -import sys -import threading -import typing import warnings -from http.client import HTTPConnection as _HTTPConnection -from http.client import HTTPException as HTTPException # noqa: F401 -from http.client import ResponseNotReady +from socket import error as SocketError from socket import timeout as SocketTimeout -if typing.TYPE_CHECKING: - from .response import HTTPResponse - from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT - from .util.ssltransport import SSLTransport - -from ._collections import HTTPHeaderDict -from .http2 import probe as http2_probe -from .util.response import assert_header_parsing -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout -from .util.util import to_str -from .util.wait import wait_for_read +from .packages import six +from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection +from .packages.six.moves.http_client import HTTPException # noqa: F401 +from .util.proxy import create_proxy_ssl_context try: # Compiled with SSL? import ssl BaseSSLError = ssl.SSLError -except (ImportError, AttributeError): - ssl = None # type: ignore[assignment] +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None + + class BaseSSLError(BaseException): + pass + + +try: + # Python 3: not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: + # Python 2 + class ConnectionError(Exception): + pass + + +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + BrokenPipeError = BrokenPipeError +except NameError: # Python 2: - class BaseSSLError(BaseException): # type: ignore[no-redef] + class BrokenPipeError(Exception): pass -from ._base_connection import _TYPE_BODY -from ._base_connection import ProxyConfig as ProxyConfig -from ._base_connection import _ResponseOptions as _ResponseOptions +from ._collections import HTTPHeaderDict # noqa (historical, removed in v2) from ._version import __version__ from .exceptions import ( ConnectTimeoutError, - HeaderParsingError, - NameResolutionError, NewConnectionError, - ProxyError, + SubjectAltNameWarning, SystemTimeWarning, ) -from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ -from .util.request import body_to_chunks -from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection from .util.ssl_ import ( + assert_fingerprint, create_urllib3_context, is_ipaddress, resolve_cert_reqs, @@ -61,12 +61,6 @@ class BaseSSLError(BaseException): # type: ignore[no-redef] ssl_wrap_socket, ) from .util.ssl_match_hostname import CertificateError, match_hostname -from .util.url import Url - -# Not a no-op, we're adding this to the namespace so it can be imported. -ConnectionError = ConnectionError -BrokenPipeError = BrokenPipeError - log = logging.getLogger(__name__) @@ -74,12 +68,12 @@ class BaseSSLError(BaseException): # type: ignore[no-redef] # When it comes time to update this value as a part of regular maintenance # (ie test_recent_date is failing) update it to ~6 months before the current date. -RECENT_DATE = datetime.date(2025, 1, 1) +RECENT_DATE = datetime.date(2024, 1, 1) _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") -class HTTPConnection(_HTTPConnection): +class HTTPConnection(_HTTPConnection, object): """ Based on :class:`http.client.HTTPConnection` but provides an extra constructor backwards-compatibility layer between older and newer Pythons. @@ -87,6 +81,7 @@ class HTTPConnection(_HTTPConnection): Additional keyword parameters are used to configure attributes of the connection. Accepted parameters include: + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` - ``source_address``: Set the source address for the current connection. - ``socket_options``: Set specific options on the underlying socket. If not specified, then defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling @@ -104,70 +99,38 @@ class HTTPConnection(_HTTPConnection): Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ - default_port: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + default_port = port_by_scheme["http"] #: Disable Nagle's algorithm by default. #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` - default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - ] + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] #: Whether this connection verifies the host's certificate. - is_verified: bool = False + is_verified = False - #: Whether this proxy connection verified the proxy host's certificate. - # If no proxy is currently connected to the value will be ``None``. - proxy_is_verified: bool | None = None + #: Whether this proxy connection (if used) verifies the proxy host's + #: certificate. + proxy_is_verified = None - blocksize: int - source_address: tuple[str, int] | None - socket_options: connection._TYPE_SOCKET_OPTIONS | None + def __init__(self, *args, **kw): + if not six.PY2: + kw.pop("strict", None) - _has_connected_to_proxy: bool - _response_options: _ResponseOptions | None - _tunnel_host: str | None - _tunnel_port: int | None - _tunnel_scheme: str | None + # Pre-set source_address. + self.source_address = kw.get("source_address") - def __init__( - self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: None | ( - connection._TYPE_SOCKET_OPTIONS - ) = default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - ) -> None: - super().__init__( - host=host, - port=port, - timeout=Timeout.resolve_default_timeout(timeout), - source_address=source_address, - blocksize=blocksize, - ) - self.socket_options = socket_options - self.proxy = proxy - self.proxy_config = proxy_config + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop("socket_options", self.default_socket_options) - self._has_connected_to_proxy = False - self._response_options = None - self._tunnel_host: str | None = None - self._tunnel_port: int | None = None - self._tunnel_scheme: str | None = None + # Proxy options provided by the user. + self.proxy = kw.pop("proxy", None) + self.proxy_config = kw.pop("proxy_config", None) - def __str__(self) -> str: - return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" - - def __repr__(self) -> str: - return f"<{self} at {id(self):#x}>" + _HTTPConnection.__init__(self, *args, **kw) @property - def host(self) -> str: + def host(self): """ Getter method to remove any trailing dots that indicate the hostname is an FQDN. @@ -186,7 +149,7 @@ def host(self) -> str: return self._dns_host.rstrip(".") @host.setter - def host(self, value: str) -> None: + def host(self, value): """ Setter for the `host` property. @@ -195,409 +158,129 @@ def host(self, value: str) -> None: """ self._dns_host = value - def _new_conn(self) -> socket.socket: + def _new_conn(self): """Establish a socket connection and set nodelay settings on it. :return: New socket connection. """ + extra_kw = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + try: - sock = connection.create_connection( - (self._dns_host, self.port), - self.timeout, - source_address=self.source_address, - socket_options=self.socket_options, + conn = connection.create_connection( + (self._dns_host, self.port), self.timeout, **extra_kw ) - except socket.gaierror as e: - raise NameResolutionError(self.host, self, e) from e - except SocketTimeout as e: + + except SocketTimeout: raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) - except OSError as e: + except SocketError as e: raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e - - sys.audit("http.client.connect", self, self.host, self.port) + self, "Failed to establish a new connection: %s" % e + ) - return sock + return conn - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - if scheme not in ("http", "https"): - raise ValueError( - f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" - ) - super().set_tunnel(host, port=port, headers=headers) - self._tunnel_scheme = scheme - - if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)): - # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3. - # When using connection_from_host, host will come without brackets. - def _wrap_ipv6(self, ip: bytes) -> bytes: - if b":" in ip and ip[0] != b"["[0]: - return b"[" + ip + b"]" - return ip - - if sys.version_info < (3, 11, 9): - # `_tunnel` copied from 3.11.13 backporting - # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261 - # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1 - def _tunnel(self) -> None: - _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] - connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] - self._wrap_ipv6(self._tunnel_host.encode("ascii")), # type: ignore[union-attr] - self._tunnel_port, - ) - headers = [connect] - for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] - headers.append(f"{header}: {value}\r\n".encode("latin-1")) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of - # potentially emitting a series of small packets. - self.send(b"".join(headers)) - del headers - - response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] - try: - (version, code, message) = response._read_status() # type: ignore[attr-defined] - - if code != http.HTTPStatus.OK: - self.close() - raise OSError( - f"Tunnel connection failed: {code} {message.strip()}" - ) - while True: - line = response.fp.readline(_MAXLINE + 1) - if len(line) > _MAXLINE: - raise http.client.LineTooLong("header line") - if not line: - # for sites which EOF without sending a trailer - break - if line in (b"\r\n", b"\n", b""): - break - - if self.debuglevel > 0: - print("header:", line.decode()) - finally: - response.close() - - elif (3, 12) <= sys.version_info < (3, 12, 3): - # `_tunnel` copied from 3.12.11 backporting - # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8 - def _tunnel(self) -> None: # noqa: F811 - connect = b"CONNECT %s:%d HTTP/1.1\r\n" % ( # type: ignore[str-format] - self._wrap_ipv6(self._tunnel_host.encode("idna")), # type: ignore[union-attr] - self._tunnel_port, - ) - headers = [connect] - for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] - headers.append(f"{header}: {value}\r\n".encode("latin-1")) - headers.append(b"\r\n") - # Making a single send() call instead of one per line encourages - # the host OS to use a more optimal packet size instead of - # potentially emitting a series of small packets. - self.send(b"".join(headers)) - del headers - - response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] - try: - (version, code, message) = response._read_status() # type: ignore[attr-defined] - - self._raw_proxy_headers = http.client._read_headers(response.fp) # type: ignore[attr-defined] - - if self.debuglevel > 0: - for header in self._raw_proxy_headers: - print("header:", header.decode()) - - if code != http.HTTPStatus.OK: - self.close() - raise OSError( - f"Tunnel connection failed: {code} {message.strip()}" - ) - - finally: - response.close() - - def connect(self) -> None: - self.sock = self._new_conn() - if self._tunnel_host: - # If we're tunneling it means we're connected to our proxy. - self._has_connected_to_proxy = True + def _is_using_tunnel(self): + # Google App Engine's httplib does not define _tunnel_host + return getattr(self, "_tunnel_host", None) + def _prepare_conn(self, conn): + self.sock = conn + if self._is_using_tunnel(): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 - # If there's a proxy to be connected to we are fully connected. - # This is set twice (once above and here) due to forwarding proxies - # not using tunnelling. - self._has_connected_to_proxy = bool(self.proxy) - - if self._has_connected_to_proxy: - self.proxy_is_verified = False - - @property - def is_closed(self) -> bool: - return self.sock is None - - @property - def is_connected(self) -> bool: - if self.sock is None: - return False - return not wait_for_read(self.sock, timeout=0.0) - - @property - def has_connected_to_proxy(self) -> bool: - return self._has_connected_to_proxy - - @property - def proxy_is_forwarding(self) -> bool: - """ - Return True if a forwarding proxy is configured, else return False - """ - return bool(self.proxy) and self._tunnel_host is None - - @property - def proxy_is_tunneling(self) -> bool: - """ - Return True if a tunneling proxy is configured, else return False - """ - return self._tunnel_host is not None + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) - def close(self) -> None: - try: - super().close() - finally: - # Reset all stateful properties so connection - # can be re-used without leaking prior configs. - self.sock = None - self.is_verified = False - self.proxy_is_verified = None - self._has_connected_to_proxy = False - self._response_options = None - self._tunnel_host = None - self._tunnel_port = None - self._tunnel_scheme = None - - def putrequest( - self, - method: str, - url: str, - skip_host: bool = False, - skip_accept_encoding: bool = False, - ) -> None: - """""" + def putrequest(self, method, url, *args, **kwargs): + """ """ # Empty docstring because the indentation of CPython's implementation # is broken but we don't want this method in our documentation. match = _CONTAINS_CONTROL_CHAR_RE.search(method) if match: raise ValueError( - f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + "Method cannot contain non-token characters %r (found at least %r)" + % (method, match.group()) ) - return super().putrequest( - method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding - ) + return _HTTPConnection.putrequest(self, method, url, *args, **kwargs) - def putheader(self, header: str, *values: str) -> None: # type: ignore[override] - """""" + def putheader(self, header, *values): + """ """ if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): - super().putheader(header, *values) - elif to_str(header.lower()) not in SKIPPABLE_HEADERS: - skippable_headers = "', '".join( - [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] - ) + _HTTPConnection.putheader(self, header, *values) + elif six.ensure_str(header.lower()) not in SKIPPABLE_HEADERS: raise ValueError( - f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + "urllib3.util.SKIP_HEADER only supports '%s'" + % ("', '".join(map(str.title, sorted(SKIPPABLE_HEADERS))),) ) - # `request` method's signature intentionally violates LSP. - # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. - def request( # type: ignore[override] - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: + def request(self, method, url, body=None, headers=None): # Update the inner socket's timeout value to send the request. # This only triggers if the connection is re-used. - if self.sock is not None: + if getattr(self, "sock", None) is not None: self.sock.settimeout(self.timeout) - # Store these values to be fed into the HTTPResponse - # object later. TODO: Remove this in favor of a real - # HTTP lifecycle mechanism. - - # We have to store these before we call .request() - # because sometimes we can still salvage a response - # off the wire even if we aren't able to completely - # send the request body. - self._response_options = _ResponseOptions( - request_method=method, - request_url=url, - preload_content=preload_content, - decode_content=decode_content, - enforce_content_length=enforce_content_length, - ) - if headers is None: headers = {} - header_keys = frozenset(to_str(k.lower()) for k in headers) + else: + # Avoid modifying the headers passed into .request() + headers = headers.copy() + if "user-agent" not in (six.ensure_str(k.lower()) for k in headers): + headers["User-Agent"] = _get_default_user_agent() + super(HTTPConnection, self).request(method, url, body=body, headers=headers) + + def request_chunked(self, method, url, body=None, headers=None): + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + headers = headers or {} + header_keys = set([six.ensure_str(k.lower()) for k in headers]) skip_accept_encoding = "accept-encoding" in header_keys skip_host = "host" in header_keys self.putrequest( method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host ) - - # Transform the body into an iterable of sendall()-able chunks - # and detect if an explicit Content-Length is doable. - chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) - chunks = chunks_and_cl.chunks - content_length = chunks_and_cl.content_length - - # When chunked is explicit set to 'True' we respect that. - if chunked: - if "transfer-encoding" not in header_keys: - self.putheader("Transfer-Encoding", "chunked") - else: - # Detect whether a framing mechanism is already in use. If so - # we respect that value, otherwise we pick chunked vs content-length - # depending on the type of 'body'. - if "content-length" in header_keys: - chunked = False - elif "transfer-encoding" in header_keys: - chunked = True - - # Otherwise we go off the recommendation of 'body_to_chunks()'. - else: - chunked = False - if content_length is None: - if chunks is not None: - chunked = True - self.putheader("Transfer-Encoding", "chunked") - else: - self.putheader("Content-Length", str(content_length)) - - # Now that framing headers are out of the way we send all the other headers. if "user-agent" not in header_keys: self.putheader("User-Agent", _get_default_user_agent()) for header, value in headers.items(): self.putheader(header, value) + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") self.endheaders() - # If we're given a body we start sending that in chunks. - if chunks is not None: - for chunk in chunks: - # Sending empty chunks isn't allowed for TE: chunked - # as it indicates the end of the body. + if body is not None: + stringish_types = six.string_types + (bytes,) + if isinstance(body, stringish_types): + body = (body,) + for chunk in body: if not chunk: continue - if isinstance(chunk, str): - chunk = chunk.encode("utf-8") - if chunked: - self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) - else: - self.send(chunk) - - # Regardless of whether we have a body or not, if we're in - # chunked mode we want to send an explicit empty chunk. - if chunked: - self.send(b"0\r\n\r\n") - - def request_chunked( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - ) -> None: - """ - Alternative to the common request method, which sends the - body with chunked encoding and not as one block - """ - warnings.warn( - "HTTPConnection.request_chunked() is deprecated and will be removed " - "in urllib3 v2.1.0. Instead use HTTPConnection.request(..., chunked=True).", - category=DeprecationWarning, - stacklevel=2, - ) - self.request(method, url, body=body, headers=headers, chunked=True) - - def getresponse( # type: ignore[override] - self, - ) -> HTTPResponse: - """ - Get the response from the server. - - If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. - - 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. - """ - # Raise the same error as http.client.HTTPConnection - if self._response_options is None: - raise ResponseNotReady() - - # Reset this attribute for being used again. - resp_options = self._response_options - self._response_options = None + if not isinstance(chunk, bytes): + chunk = chunk.encode("utf8") + len_str = hex(len(chunk))[2:] + to_send = bytearray(len_str.encode()) + to_send += b"\r\n" + to_send += chunk + to_send += b"\r\n" + self.send(to_send) - # Since the connection's timeout value may have been updated - # we need to set the timeout on the socket. - self.sock.settimeout(self.timeout) - - # This is needed here to avoid circular import errors - from .response import HTTPResponse - - # Save a reference to the shutdown function before ownership is passed - # to httplib_response - # TODO should we implement it everywhere? - _shutdown = getattr(self.sock, "shutdown", None) - - # Get the response from http.client.HTTPConnection - httplib_response = super().getresponse() - - try: - assert_header_parsing(httplib_response.msg) - except (HeaderParsingError, TypeError) as hpe: - log.warning( - "Failed to parse headers (url=%s): %s", - _url_from_connection(self, resp_options.request_url), - hpe, - exc_info=True, - ) - - headers = HTTPHeaderDict(httplib_response.msg.items()) - - response = HTTPResponse( - body=httplib_response, - headers=headers, - status=httplib_response.status, - version=httplib_response.version, - version_string=getattr(self, "_http_vsn_str", "HTTP/?"), - reason=httplib_response.reason, - preload_content=resp_options.preload_content, - decode_content=resp_options.decode_content, - original_response=httplib_response, - enforce_content_length=resp_options.enforce_content_length, - request_method=resp_options.request_method, - request_url=resp_options.request_url, - sock_shutdown=_shutdown, - ) - return response + # After the if clause, to always have a closed body + self.send(b"0\r\n\r\n") class HTTPSConnection(HTTPConnection): @@ -606,103 +289,57 @@ class HTTPSConnection(HTTPConnection): socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. """ - default_port = port_by_scheme["https"] # type: ignore[misc] + default_port = port_by_scheme["https"] - cert_reqs: int | str | None = None - ca_certs: str | None = None - ca_cert_dir: str | None = None - ca_cert_data: None | str | bytes = None - ssl_version: int | str | None = None - ssl_minimum_version: int | None = None - ssl_maximum_version: int | None = None - assert_fingerprint: str | None = None - _connect_callback: typing.Callable[..., None] | None = None + cert_reqs = None + ca_certs = None + ca_cert_dir = None + ca_cert_data = None + ssl_version = None + assert_fingerprint = None + tls_in_tls_required = False def __init__( self, - host: str, - port: int | None = None, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: None | ( - connection._TYPE_SOCKET_OPTIONS - ) = HTTPConnection.default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: - super().__init__( - host, - port=port, - timeout=timeout, - source_address=source_address, - blocksize=blocksize, - socket_options=socket_options, - proxy=proxy, - proxy_config=proxy_config, - ) + host, + port=None, + key_file=None, + cert_file=None, + key_password=None, + strict=None, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + ssl_context=None, + server_hostname=None, + **kw + ): + + HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file self.key_password = key_password self.ssl_context = ssl_context self.server_hostname = server_hostname - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - self.ca_cert_data = ca_cert_data - # cert_reqs depends on ssl_context so calculate last. - if cert_reqs is None: - if self.ssl_context is not None: - cert_reqs = self.ssl_context.verify_mode - else: - cert_reqs = resolve_cert_reqs(None) - self.cert_reqs = cert_reqs - self._connect_callback = None + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = "https" def set_cert( self, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ) -> None: + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + ca_cert_data=None, + ): """ This method should only be called once, before the connection is used. """ - warnings.warn( - "HTTPSConnection.set_cert() is deprecated and will be removed " - "in urllib3 v2.1.0. Instead provide the parameters to the " - "HTTPSConnection constructor.", - category=DeprecationWarning, - stacklevel=2, - ) - # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also # have an SSLContext object in which case we'll use its verify_mode. if cert_reqs is None: @@ -721,322 +358,191 @@ def set_cert( self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) self.ca_cert_data = ca_cert_data - def connect(self) -> None: - # Today we don't need to be doing this step before the /actual/ socket - # connection, however in the future we'll need to decide whether to - # create a new socket or re-use an existing "shared" socket as a part - # of the HTTP/2 handshake dance. - if self._tunnel_host is not None and self._tunnel_port is not None: - probe_http2_host = self._tunnel_host - probe_http2_port = self._tunnel_port - else: - probe_http2_host = self.host - probe_http2_port = self.port - - # Check if the target origin supports HTTP/2. - # If the value comes back as 'None' it means that the current thread - # is probing for HTTP/2 support. Otherwise, we're waiting for another - # probe to complete, or we get a value right away. - target_supports_http2: bool | None - if "h2" in ssl_.ALPN_PROTOCOLS: - target_supports_http2 = http2_probe.acquire_and_get( - host=probe_http2_host, port=probe_http2_port - ) - else: - # If HTTP/2 isn't going to be offered it doesn't matter if - # the target supports HTTP/2. Don't want to make a probe. - target_supports_http2 = False - - if self._connect_callback is not None: - self._connect_callback( - "before connect", - thread_id=threading.get_ident(), - target_supports_http2=target_supports_http2, - ) + def connect(self): + # Add certificate verification + self.sock = conn = self._new_conn() + hostname = self.host + tls_in_tls = False - try: - sock: socket.socket | ssl.SSLSocket - self.sock = sock = self._new_conn() - server_hostname: str = self.host - tls_in_tls = False - - # Do we need to establish a tunnel? - if self.proxy_is_tunneling: - # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. - if self._tunnel_scheme == "https": - # _connect_tls_proxy will verify and assign proxy_is_verified - self.sock = sock = self._connect_tls_proxy(self.host, sock) - tls_in_tls = True - elif self._tunnel_scheme == "http": - self.proxy_is_verified = False - - # If we're tunneling it means we're connected to our proxy. - self._has_connected_to_proxy = True - - self._tunnel() - # Override the host with the one we're requesting data from. - server_hostname = typing.cast(str, self._tunnel_host) - - if self.server_hostname is not None: - server_hostname = self.server_hostname - - is_time_off = datetime.date.today() < RECENT_DATE - if is_time_off: - warnings.warn( - ( - f"System time is way off (before {RECENT_DATE}). This will probably " - "lead to SSL verification errors" - ), - SystemTimeWarning, - ) + if self._is_using_tunnel(): + if self.tls_in_tls_required: + self.sock = conn = self._connect_tls_proxy(hostname, conn) + tls_in_tls = True - # Remove trailing '.' from fqdn hostnames to allow certificate validation - server_hostname_rm_dot = server_hostname.rstrip(".") - - sock_and_verified = _ssl_wrap_socket_and_match_hostname( - sock=sock, - cert_reqs=self.cert_reqs, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - ca_cert_data=self.ca_cert_data, - cert_file=self.cert_file, - key_file=self.key_file, - key_password=self.key_password, - server_hostname=server_hostname_rm_dot, - ssl_context=self.ssl_context, - tls_in_tls=tls_in_tls, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint, + # Calls self._set_hostport(), so self.host is + # self._tunnel_host below. + self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 + + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + + server_hostname = hostname + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + "System time is way off (before {0}). This will probably " + "lead to SSL verification errors" + ).format(RECENT_DATE), + SystemTimeWarning, ) - self.sock = sock_and_verified.socket - - # If an error occurs during connection/handshake we may need to release - # our lock so another connection can probe the origin. - except BaseException: - if self._connect_callback is not None: - self._connect_callback( - "after connect failure", - thread_id=threading.get_ident(), - target_supports_http2=target_supports_http2, - ) - if target_supports_http2 is None: - http2_probe.set_and_release( - host=probe_http2_host, port=probe_http2_port, supports_http2=None - ) - raise - - # If this connection doesn't know if the origin supports HTTP/2 - # we report back to the HTTP/2 probe our result. - if target_supports_http2 is None: - supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" - http2_probe.set_and_release( - host=probe_http2_host, - port=probe_http2_port, - supports_http2=supports_http2, + # Wrap socket using verification with the root certs in + # trusted_root_certs + default_ssl_context = False + if self.ssl_context is None: + default_ssl_context = True + self.ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(self.ssl_version), + cert_reqs=resolve_cert_reqs(self.cert_reqs), ) - # Forwarding proxies can never have a verified target since - # the proxy is the one doing the verification. Should instead - # use a CONNECT tunnel in order to verify the target. - # See: https://github.com/urllib3/urllib3/issues/3267. - if self.proxy_is_forwarding: - self.is_verified = False - else: - self.is_verified = sock_and_verified.is_verified + context = self.ssl_context + context.verify_mode = resolve_cert_reqs(self.cert_reqs) + + # Try to load OS default certs if none are given. + # Works well on Windows (requires Python3.4+) + if ( + not self.ca_certs + and not self.ca_cert_dir + and not self.ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() - # If there's a proxy to be connected to we are fully connected. - # This is set twice (once above and here) due to forwarding proxies - # not using tunnelling. - self._has_connected_to_proxy = bool(self.proxy) + self.sock = ssl_wrap_socket( + sock=conn, + keyfile=self.key_file, + certfile=self.cert_file, + key_password=self.key_password, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) - # Set `self.proxy_is_verified` unless it's already set while - # establishing a tunnel. - if self._has_connected_to_proxy and self.proxy_is_verified is None: - self.proxy_is_verified = sock_and_verified.is_verified + # If we're using all defaults and the connection + # is TLSv1 or TLSv1.1 we throw a DeprecationWarning + # for the host. + if ( + default_ssl_context + and self.ssl_version is None + and hasattr(self.sock, "version") + and self.sock.version() in {"TLSv1", "TLSv1.1"} + ): # Defensive: + warnings.warn( + "Negotiating TLSv1/TLSv1.1 by default is deprecated " + "and will be disabled in urllib3 v2.0.0. Connecting to " + "'%s' with '%s' can be enabled by explicitly opting-in " + "with 'ssl_version'" % (self.host, self.sock.version()), + DeprecationWarning, + ) - def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + if self.assert_fingerprint: + assert_fingerprint( + self.sock.getpeercert(binary_form=True), self.assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not getattr(context, "check_hostname", False) + and self.assert_hostname is not False + ): + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = self.sock.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, + ) + _match_hostname(cert, self.assert_hostname or server_hostname) + + self.is_verified = ( + context.verify_mode == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None + ) + + def _connect_tls_proxy(self, hostname, conn): """ Establish a TLS connection to the proxy using the provided SSL context. """ - # `_connect_tls_proxy` is called when self._tunnel_host is truthy. - proxy_config = typing.cast(ProxyConfig, self.proxy_config) + proxy_config = self.proxy_config ssl_context = proxy_config.ssl_context - sock_and_verified = _ssl_wrap_socket_and_match_hostname( - sock, - cert_reqs=self.cert_reqs, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, + if ssl_context: + # If the user provided a proxy context, we assume CA and client + # certificates have already been set + return ssl_wrap_socket( + sock=conn, + server_hostname=hostname, + ssl_context=ssl_context, + ) + + ssl_context = create_proxy_ssl_context( + self.ssl_version, + self.cert_reqs, + self.ca_certs, + self.ca_cert_dir, + self.ca_cert_data, + ) + + # If no cert was provided, use only the default options for server + # certificate validation + socket = ssl_wrap_socket( + sock=conn, ca_certs=self.ca_certs, ca_cert_dir=self.ca_cert_dir, ca_cert_data=self.ca_cert_data, server_hostname=hostname, ssl_context=ssl_context, - assert_hostname=proxy_config.assert_hostname, - assert_fingerprint=proxy_config.assert_fingerprint, - # Features that aren't implemented for proxies yet: - cert_file=None, - key_file=None, - key_password=None, - tls_in_tls=False, ) - self.proxy_is_verified = sock_and_verified.is_verified - return sock_and_verified.socket # type: ignore[return-value] - -class _WrappedAndVerifiedSocket(typing.NamedTuple): - """ - Wrapped socket and whether the connection is - verified after the TLS handshake - """ - - socket: ssl.SSLSocket | SSLTransport - is_verified: bool - - -def _ssl_wrap_socket_and_match_hostname( - sock: socket.socket, - *, - cert_reqs: None | str | int, - ssl_version: None | str | int, - ssl_minimum_version: int | None, - ssl_maximum_version: int | None, - cert_file: str | None, - key_file: str | None, - key_password: str | None, - ca_certs: str | None, - ca_cert_dir: str | None, - ca_cert_data: None | str | bytes, - assert_hostname: None | str | typing.Literal[False], - assert_fingerprint: str | None, - server_hostname: str | None, - ssl_context: ssl.SSLContext | None, - tls_in_tls: bool = False, -) -> _WrappedAndVerifiedSocket: - """Logic for constructing an SSLContext from all TLS parameters, passing - that down into ssl_wrap_socket, and then doing certificate verification - either via hostname or fingerprint. This function exists to guarantee - that both proxies and targets have the same behavior when connecting via TLS. - """ - default_ssl_context = False - if ssl_context is None: - default_ssl_context = True - context = create_urllib3_context( - ssl_version=resolve_ssl_version(ssl_version), - ssl_minimum_version=ssl_minimum_version, - ssl_maximum_version=ssl_maximum_version, - cert_reqs=resolve_cert_reqs(cert_reqs), - ) - else: - context = ssl_context - - context.verify_mode = resolve_cert_reqs(cert_reqs) - - # In some cases, we want to verify hostnames ourselves - if ( - # `ssl` can't verify fingerprints or alternate hostnames - assert_fingerprint - or assert_hostname - # assert_hostname can be set to False to disable hostname checking - or assert_hostname is False - # We still support OpenSSL 1.0.2, which prevents us from verifying - # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 - or ssl_.IS_PYOPENSSL - or not ssl_.HAS_NEVER_CHECK_COMMON_NAME - ): - context.check_hostname = False - - # Try to load OS default certs if none are given. We need to do the hasattr() check - # for custom pyOpenSSL SSLContext objects because they don't support - # load_default_certs(). - if ( - not ca_certs - and not ca_cert_dir - and not ca_cert_data - and default_ssl_context - and hasattr(context, "load_default_certs") - ): - context.load_default_certs() - - # Ensure that IPv6 addresses are in the proper format and don't have a - # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses - # and interprets them as DNS hostnames. - if server_hostname is not None: - normalized = server_hostname.strip("[]") - if "%" in normalized: - normalized = normalized[: normalized.rfind("%")] - if is_ipaddress(normalized): - server_hostname = normalized - - ssl_sock = ssl_wrap_socket( - sock=sock, - keyfile=key_file, - certfile=cert_file, - key_password=key_password, - ca_certs=ca_certs, - ca_cert_dir=ca_cert_dir, - ca_cert_data=ca_cert_data, - server_hostname=server_hostname, - ssl_context=context, - tls_in_tls=tls_in_tls, - ) - - try: - if assert_fingerprint: - _assert_fingerprint( - ssl_sock.getpeercert(binary_form=True), assert_fingerprint - ) - elif ( - context.verify_mode != ssl.CERT_NONE - and not context.check_hostname - and assert_hostname is not False + if ssl_context.verify_mode != ssl.CERT_NONE and not getattr( + ssl_context, "check_hostname", False ): - cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] - - # Need to signal to our match_hostname whether to use 'commonName' or not. - # If we're using our own constructed SSLContext we explicitly set 'False' - # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. - if default_ssl_context: - hostname_checks_common_name = False - else: - hostname_checks_common_name = ( - getattr(context, "hostname_checks_common_name", False) or False + # While urllib3 attempts to always turn off hostname matching from + # the TLS library, this cannot always be done. So we check whether + # the TLS Library still thinks it's matching hostnames. + cert = socket.getpeercert() + if not cert.get("subjectAltName", ()): + warnings.warn( + ( + "Certificate for {0} has no `subjectAltName`, falling back to check for a " + "`commonName` for now. This feature is being removed by major browsers and " + "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 " + "for details.)".format(hostname) + ), + SubjectAltNameWarning, ) + _match_hostname(cert, hostname) - _match_hostname( - cert, - assert_hostname or server_hostname, # type: ignore[arg-type] - hostname_checks_common_name, - ) - - return _WrappedAndVerifiedSocket( - socket=ssl_sock, - is_verified=context.verify_mode == ssl.CERT_REQUIRED - or bool(assert_fingerprint), - ) - except BaseException: - ssl_sock.close() - raise + self.proxy_is_verified = ssl_context.verify_mode == ssl.CERT_REQUIRED + return socket -def _match_hostname( - cert: _TYPE_PEER_CERT_RET_DICT | None, - asserted_hostname: str, - hostname_checks_common_name: bool = False, -) -> None: +def _match_hostname(cert, asserted_hostname): # Our upstream implementation of ssl.match_hostname() # only applies this normalization to IP addresses so it doesn't # match DNS SANs so we do the same thing! - stripped_hostname = asserted_hostname.strip("[]") + stripped_hostname = asserted_hostname.strip("u[]") if is_ipaddress(stripped_hostname): asserted_hostname = stripped_hostname try: - match_hostname(cert, asserted_hostname, hostname_checks_common_name) + match_hostname(cert, asserted_hostname) except CertificateError as e: log.warning( "Certificate did not match expected hostname: %s. Certificate: %s", @@ -1045,55 +551,22 @@ def _match_hostname( ) # Add cert to exception and reraise so client code can inspect # the cert when catching the exception, if they want to - e._peer_cert = cert # type: ignore[attr-defined] + e._peer_cert = cert raise -def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: - # Look for the phrase 'wrong version number', if found - # then we should warn the user that we're very sure that - # this proxy is HTTP-only and they have a configuration issue. - error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) - is_likely_http_proxy = ( - "wrong version number" in error_normalized - or "unknown protocol" in error_normalized - or "record layer failure" in error_normalized - ) - http_proxy_warning = ( - ". Your proxy appears to only use HTTP and not HTTPS, " - "try changing your proxy URL to be HTTP. See: " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" - "#https-proxy-error-http-proxy" - ) - new_err = ProxyError( - f"Unable to connect to proxy" - f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", - err, - ) - new_err.__cause__ = err - return new_err - - -def _get_default_user_agent() -> str: - return f"python-urllib3/{__version__}" - - -class DummyConnection: - """Used to detect a failed ConnectionCls import.""" +def _get_default_user_agent(): + return "python-urllib3/%s" % __version__ -if not ssl: - HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 - +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" -VerifiedHTTPSConnection = HTTPSConnection + pass -def _url_from_connection( - conn: HTTPConnection | HTTPSConnection, path: str | None = None -) -> str: - """Returns the URL from a given connection. This is mainly used for testing and logging.""" +if not ssl: + HTTPSConnection = DummyConnection # noqa: F811 - scheme = "https" if isinstance(conn, HTTPSConnection) else "http" - return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url +VerifiedHTTPSConnection = HTTPSConnection diff --git a/newrelic/packages/urllib3/connectionpool.py b/newrelic/packages/urllib3/connectionpool.py index 3a0685b4cd..402bf670da 100644 --- a/newrelic/packages/urllib3/connectionpool.py +++ b/newrelic/packages/urllib3/connectionpool.py @@ -1,18 +1,15 @@ -from __future__ import annotations +from __future__ import absolute_import import errno import logging -import queue +import re +import socket import sys -import typing import warnings -import weakref +from socket import error as SocketError from socket import timeout as SocketTimeout -from types import TracebackType -from ._base_connection import _TYPE_BODY from ._collections import HTTPHeaderDict -from ._request_methods import RequestMethods from .connection import ( BaseSSLError, BrokenPipeError, @@ -20,14 +17,13 @@ HTTPConnection, HTTPException, HTTPSConnection, - ProxyConfig, - _wrap_proxy_error, + VerifiedHTTPSConnection, + port_by_scheme, ) -from .connection import port_by_scheme as port_by_scheme from .exceptions import ( ClosedPoolError, EmptyPoolError, - FullPoolError, + HeaderParsingError, HostChangedError, InsecureRequestWarning, LocationValueError, @@ -39,32 +35,38 @@ SSLError, TimeoutError, ) -from .response import BaseHTTPResponse +from .packages import six +from .packages.six.moves import queue +from .request import RequestMethods +from .response import HTTPResponse from .util.connection import is_connection_dropped from .util.proxy import connection_requires_http_tunnel -from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.queue import LifoQueue +from .util.request import set_file_position +from .util.response import assert_header_parsing from .util.retry import Retry from .util.ssl_match_hostname import CertificateError -from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.timeout import Timeout from .util.url import Url, _encode_target from .util.url import _normalize_host as normalize_host -from .util.url import parse_url -from .util.util import to_str +from .util.url import get_host, parse_url -if typing.TYPE_CHECKING: - import ssl +try: # Platform-specific: Python 3 + import weakref - from typing_extensions import Self + weakref_finalize = weakref.finalize +except AttributeError: # Platform-specific: Python 2 + from .packages.backports.weakref_finalize import weakref_finalize - from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection +xrange = six.moves.xrange log = logging.getLogger(__name__) -_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] +_Default = object() # Pool objects -class ConnectionPool: +class ConnectionPool(object): """ Base class for all connection pools, such as :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. @@ -75,42 +77,33 @@ class ConnectionPool: target URIs. """ - scheme: str | None = None - QueueCls = queue.LifoQueue + scheme = None + QueueCls = LifoQueue - def __init__(self, host: str, port: int | None = None) -> None: + def __init__(self, host, port=None): if not host: raise LocationValueError("No host specified.") self.host = _normalize_host(host, scheme=self.scheme) + self._proxy_host = host.lower() self.port = port - # This property uses 'normalize_host()' (not '_normalize_host()') - # to avoid removing square braces around IPv6 addresses. - # This value is sent to `HTTPConnection.set_tunnel()` if called - # because square braces are required for HTTP CONNECT tunneling. - self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + def __str__(self): + return "%s(host=%r, port=%r)" % (type(self).__name__, self.host, self.port) - def __str__(self) -> str: - return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" - - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> typing.Literal[False]: + def __exit__(self, exc_type, exc_val, exc_tb): self.close() # Return False to re-raise any potential exceptions return False - def close(self) -> None: + def close(self): """ Close all pooled connections and disable the pool. """ + pass # This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 @@ -129,6 +122,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Port used for this HTTP Connection (None is equivalent to 80), passed into :class:`http.client.HTTPConnection`. + :param strict: + Causes BadStatusLine to be raised if the status line can't be parsed + as a valid HTTP/1.0 or 1.1 status line, passed into + :class:`http.client.HTTPConnection`. + + .. note:: + Only works in Python 2. This parameter is ignored in Python 3. + :param timeout: Socket timeout in seconds for each individual connection. This can be a float or integer, which sets the timeout for the HTTP request, @@ -170,25 +171,29 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """ scheme = "http" - ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + ConnectionCls = HTTPConnection + ResponseCls = HTTPResponse def __init__( self, - host: str, - port: int | None = None, - timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, - maxsize: int = 1, - block: bool = False, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - _proxy: Url | None = None, - _proxy_headers: typing.Mapping[str, str] | None = None, - _proxy_config: ProxyConfig | None = None, - **conn_kw: typing.Any, + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + _proxy_config=None, + **conn_kw ): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) + self.strict = strict + if not isinstance(timeout, Timeout): timeout = Timeout.from_float(timeout) @@ -198,7 +203,7 @@ def __init__( self.timeout = timeout self.retries = retries - self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize) + self.pool = self.QueueCls(maxsize) self.block = block self.proxy = _proxy @@ -206,7 +211,7 @@ def __init__( self.proxy_config = _proxy_config # Fill the queue up so that doing get() on it will block properly - for _ in range(maxsize): + for _ in xrange(maxsize): self.pool.put(None) # These are mostly for testing and debugging purposes. @@ -231,9 +236,9 @@ def __init__( # Close all the HTTPConnections in the pool before the # HTTPConnectionPool object is garbage collected. - weakref.finalize(self, _close_pool_connections, pool) + weakref_finalize(self, _close_pool_connections, pool) - def _new_conn(self) -> BaseHTTPConnection: + def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. """ @@ -249,11 +254,12 @@ def _new_conn(self) -> BaseHTTPConnection: host=self.host, port=self.port, timeout=self.timeout.connect_timeout, - **self.conn_kw, + strict=self.strict, + **self.conn_kw ) return conn - def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + def _get_conn(self, timeout=None): """ Get a connection. Will return a pooled connection if one is available. @@ -266,32 +272,33 @@ def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: :prop:`.block` is ``True``. """ conn = None - - if self.pool is None: - raise ClosedPoolError(self, "Pool is closed.") - try: conn = self.pool.get(block=self.block, timeout=timeout) except AttributeError: # self.pool is None - raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + raise ClosedPoolError(self, "Pool is closed.") except queue.Empty: if self.block: raise EmptyPoolError( self, - "Pool is empty and a new connection can't be opened due to blocking mode.", - ) from None + "Pool reached maximum size and no more connections are allowed.", + ) pass # Oh well, we'll create a new connection then # If this is a persistent connection, check if it got disconnected if conn and is_connection_dropped(conn): log.debug("Resetting dropped connection: %s", self.host) conn.close() + if getattr(conn, "auto_open", 1) == 0: + # This is a proxied connection that has been mutated by + # http.client._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None return conn or self._new_conn() - def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + def _put_conn(self, conn): """ Put a connection back into the pool. @@ -305,47 +312,36 @@ def _put_conn(self, conn: BaseHTTPConnection | None) -> None: If the pool is closed, then the connection will be closed and discarded. """ - if self.pool is not None: - try: - self.pool.put(conn, block=False) - return # Everything is dandy, done. - except AttributeError: - # self.pool is None. - pass - except queue.Full: - # Connection never got put back into the pool, close it. - if conn: - conn.close() - - if self.block: - # This should never happen if you got the conn from self._get_conn - raise FullPoolError( - self, - "Pool reached maximum size and no more connections are allowed.", - ) from None - - log.warning( - "Connection pool is full, discarding connection: %s. Connection pool size: %s", - self.host, - self.pool.qsize(), - ) - + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # This should never happen if self.block == True + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) # Connection never got put back into the pool, close it. if conn: conn.close() - def _validate_conn(self, conn: BaseHTTPConnection) -> None: + def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ + pass - def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + def _prepare_proxy(self, conn): # Nothing to do for HTTP connections. pass - def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + def _get_timeout(self, timeout): """Helper that always returns a :class:`urllib3.util.Timeout`""" - if timeout is _DEFAULT_TIMEOUT: + if timeout is _Default: return self.timeout.clone() if isinstance(timeout, Timeout): @@ -355,40 +351,34 @@ def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: # can be removed later return Timeout.from_float(timeout) - def _raise_timeout( - self, - err: BaseSSLError | OSError | SocketTimeout, - url: str, - timeout_value: _TYPE_TIMEOUT | None, - ) -> None: + def _raise_timeout(self, err, url, timeout_value): """Is the error actually a timeout? Will raise a ReadTimeout or pass""" if isinstance(err, SocketTimeout): raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={timeout_value})" - ) from err + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) - # See the above comment about EAGAIN in Python 3. + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error if hasattr(err, "errno") and err.errno in _blocking_errnos: raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={timeout_value})" - ) from err + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + if "timed out" in str(err) or "did not complete (read)" in str( + err + ): # Python < 2.7.4 + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % timeout_value + ) def _make_request( - self, - conn: BaseHTTPConnection, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | None = None, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - chunked: bool = False, - response_conn: BaseHTTPConnection | None = None, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> BaseHTTPResponse: + self, conn, method, url, timeout=_Default, chunked=False, **httplib_request_kw + ): """ Perform a request on a given urllib connection object taken from our pool. @@ -396,61 +386,12 @@ def _make_request( :param conn: a connection from one of our connection pools - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param retries: - Configure the number of retries to allow before raising a - :class:`~urllib3.exceptions.MaxRetryError` exception. - - Pass ``None`` to retry until you receive a response. Pass a - :class:`~urllib3.util.retry.Retry` object for fine-grained control - over different types of retries. - Pass an integer number to retry connection errors that many times, - but no other types of errors. Pass zero to never retry. - - If ``False``, then retries are disabled and any exception is raised - immediately. Also, instead of raising a MaxRetryError on redirects, - the redirect response will be returned. - - :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. - :param timeout: - If specified, overrides the default timeout for this one - request. It may be a float (in seconds) or an instance of - :class:`urllib3.util.Timeout`. - - :param chunked: - If True, urllib3 will send the body using chunked transfer - encoding. Otherwise, urllib3 will send the body using the standard - content-length form. Defaults to False. - - :param response_conn: - Set this to ``None`` if you will handle releasing the connection or - set the connection to have the response release it. - - :param preload_content: - If True, the response's body will be preloaded during construction. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - - :param enforce_content_length: - Enforce content length checking. Body returned by server must match - value of Content-Length header, if present. Otherwise, raise error. + Socket timeout in seconds for the request. This can be a + float or integer, which will set the same timeout value for + the socket connect and the socket read, or an instance of + :class:`urllib3.util.Timeout`, which gives you more fine-grained + control over your timeouts. """ self.num_requests += 1 @@ -458,66 +399,44 @@ def _make_request( timeout_obj.start_connect() conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + # Trigger any extra validation we need to do. try: - # Trigger any extra validation we need to do. - try: - self._validate_conn(conn) - except (SocketTimeout, BaseSSLError) as e: - self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) - raise - - # _validate_conn() starts the connection to an HTTPS proxy - # so we need to wrap errors with 'ProxyError' here too. - except ( - OSError, - NewConnectionError, - TimeoutError, - BaseSSLError, - CertificateError, - SSLError, - ) as e: - new_e: Exception = e - if isinstance(e, (BaseSSLError, CertificateError)): - new_e = SSLError(e) - # If the connection didn't successfully connect to it's proxy - # then there - if isinstance( - new_e, (OSError, NewConnectionError, TimeoutError, SSLError) - ) and (conn and conn.proxy and not conn.has_connected_to_proxy): - new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) - raise new_e + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise # conn.request() calls http.client.*.request, not the method in # urllib3.request. It also calls makefile (recv) on the socket. try: - conn.request( - method, - url, - body=body, - headers=headers, - chunked=chunked, - preload_content=preload_content, - decode_content=decode_content, - enforce_content_length=enforce_content_length, - ) + if chunked: + conn.request_chunked(method, url, **httplib_request_kw) + else: + conn.request(method, url, **httplib_request_kw) # We are swallowing BrokenPipeError (errno.EPIPE) since the server is # legitimately able to close the connection after sending a valid response. # With this behaviour, the received response is still readable. except BrokenPipeError: + # Python 3 pass - except OSError as e: - # MacOS/Linux - # EPROTOTYPE and ECONNRESET are needed on macOS + except IOError as e: + # Python 2 and macOS/Linux + # EPIPE and ESHUTDOWN are BrokenPipeError on Python 2, and EPROTOTYPE is needed on macOS # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ - # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. - if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: + if e.errno not in { + errno.EPIPE, + errno.ESHUTDOWN, + errno.EPROTOTYPE, + }: raise # Reset the timeout for the recv() on the socket read_timeout = timeout_obj.read_timeout - if not conn.is_closed: + # App Engine doesn't have a sock attr + if getattr(conn, "sock", None): # In Python 3 socket.py will catch EAGAIN and return None when you # try and read into the file pointer created by http.client, which # instead raises a BadStatusLine exception. Instead of catching @@ -525,22 +444,33 @@ def _make_request( # timeouts, check for a zero timeout before making the request. if read_timeout == 0: raise ReadTimeoutError( - self, url, f"Read timed out. (read timeout={read_timeout})" + self, url, "Read timed out. (read timeout=%s)" % read_timeout ) - conn.timeout = read_timeout + if read_timeout is Timeout.DEFAULT_TIMEOUT: + conn.sock.settimeout(socket.getdefaulttimeout()) + else: # None or a value + conn.sock.settimeout(read_timeout) # Receive the response from the server try: - response = conn.getresponse() - except (BaseSSLError, OSError) as e: + try: + # Python 2.7, use buffering of HTTP responses + httplib_response = conn.getresponse(buffering=True) + except TypeError: + # Python 3 + try: + httplib_response = conn.getresponse() + except BaseException as e: + # Remove the TypeError from the exception chain in + # Python 3 (including for exceptions like SystemExit). + # Otherwise it looks like a bug in the code. + six.raise_from(e, None) + except (SocketTimeout, BaseSSLError, SocketError) as e: self._raise_timeout(err=e, url=url, timeout_value=read_timeout) raise - # Set properties that are used by the pooling layer. - response.retries = retries - response._connection = response_conn # type: ignore[attr-defined] - response._pool = self # type: ignore[attr-defined] - + # AppEngine doesn't have a version attr. + http_version = getattr(conn, "_http_vsn_str", "HTTP/?") log.debug( '%s://%s:%s "%s %s %s" %s %s', self.scheme, @@ -548,14 +478,27 @@ def _make_request( self.port, method, url, - response.version_string, - response.status, - response.length_remaining, + http_version, + httplib_response.status, + httplib_response.length, ) - return response + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: # Platform-specific: Python 3 + log.warning( + "Failed to parse headers (url=%s): %s", + self._absolute_url(url), + hpe, + exc_info=True, + ) + + return httplib_response + + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url - def close(self) -> None: + def close(self): """ Close all pooled connections and disable the pool. """ @@ -567,7 +510,7 @@ def close(self) -> None: # Close all the HTTPConnections in the pool. _close_pool_connections(old_pool) - def is_same_host(self, url: str) -> bool: + def is_same_host(self, url): """ Check if the given ``url`` is a member of the same host as this connection pool. @@ -576,8 +519,7 @@ def is_same_host(self, url: str) -> bool: return True # TODO: Add optional support for socket.gethostbyname checking. - scheme, _, host, port, *_ = parse_url(url) - scheme = scheme or "http" + scheme, host, port = get_host(url) if host is not None: host = _normalize_host(host, scheme=scheme) @@ -589,24 +531,22 @@ def is_same_host(self, url: str) -> bool: return (scheme, host, port) == (self.scheme, self.host, self.port) - def urlopen( # type: ignore[override] + def urlopen( self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - redirect: bool = True, - assert_same_host: bool = True, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - pool_timeout: int | None = None, - release_conn: bool | None = None, - chunked: bool = False, - body_pos: _TYPE_BODY_POSITION | None = None, - preload_content: bool = True, - decode_content: bool = True, - **response_kw: typing.Any, - ) -> BaseHTTPResponse: + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + assert_same_host=True, + timeout=_Default, + pool_timeout=None, + release_conn=None, + chunked=False, + body_pos=None, + **response_kw + ): """ Get a connection from the pool and perform an HTTP request. This is the lowest level call for making a request, so you'll need to specify all @@ -614,8 +554,8 @@ def urlopen( # type: ignore[override] .. note:: - More commonly, it's appropriate to use a convenience method - such as :meth:`request`. + More commonly, it's appropriate to use a convenience method provided + by :class:`.RequestMethods`, such as :meth:`request`. .. note:: @@ -643,7 +583,7 @@ def urlopen( # type: ignore[override] Configure the number of retries to allow before raising a :class:`~urllib3.exceptions.MaxRetryError` exception. - If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + Pass ``None`` to retry until you receive a response. Pass a :class:`~urllib3.util.retry.Retry` object for fine-grained control over different types of retries. Pass an integer number to retry connection errors that many times, @@ -675,13 +615,6 @@ def urlopen( # type: ignore[override] block for ``pool_timeout`` seconds and raise EmptyPoolError if no connection is available within the time period. - :param bool preload_content: - If True, the response's body will be preloaded into memory. - - :param bool decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - :param release_conn: If False, then the urlopen call will not release the connection back into the pool once a response is received (but will release if @@ -689,10 +622,10 @@ def urlopen( # type: ignore[override] `preload_content=True`). This is useful if you're not preloading the response's content immediately. You will need to call ``r.release_conn()`` on the response ``r`` to return the connection - back into the pool. If None, it takes the value of ``preload_content`` - which defaults to ``True``. + back into the pool. If None, it takes the value of + ``response_kw.get('preload_content', True)``. - :param bool chunked: + :param chunked: If True, urllib3 will send the body using chunked transfer encoding. Otherwise, urllib3 will send the body using the standard content-length form. Defaults to False. @@ -701,7 +634,12 @@ def urlopen( # type: ignore[override] Position to seek to in file-like body in the event of a retry or redirect. Typically this won't need to be set because urllib3 will auto-populate the value when needed. + + :param \\**response_kw: + Additional parameters are passed to + :meth:`urllib3.response.HTTPResponse.from_httplib` """ + parsed_url = parse_url(url) destination_scheme = parsed_url.scheme @@ -712,7 +650,7 @@ def urlopen( # type: ignore[override] retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: - release_conn = preload_content + release_conn = response_kw.get("preload_content", True) # Check host if assert_same_host and not self.is_same_host(url): @@ -720,9 +658,9 @@ def urlopen( # type: ignore[override] # Ensure that the URL we're connecting to is properly encoded if url.startswith("/"): - url = to_str(_encode_target(url)) + url = six.ensure_str(_encode_target(url)) else: - url = to_str(parsed_url.url) + url = six.ensure_str(parsed_url.url) conn = None @@ -745,8 +683,8 @@ def urlopen( # type: ignore[override] # have to copy the headers dict so we can safely change it without those # changes being reflected in anyone else's copy. if not http_tunnel_required: - headers = headers.copy() # type: ignore[attr-defined] - headers.update(self.proxy_headers) # type: ignore[union-attr] + headers = headers.copy() + headers.update(self.proxy_headers) # Must keep the exception bound to a separate variable or else Python 3 # complains about UnboundLocalError. @@ -765,26 +703,16 @@ def urlopen( # type: ignore[override] timeout_obj = self._get_timeout(timeout) conn = self._get_conn(timeout=pool_timeout) - conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment] + conn.timeout = timeout_obj.connect_timeout - # Is this a closed/new connection that requires CONNECT tunnelling? - if self.proxy is not None and http_tunnel_required and conn.is_closed: - try: - self._prepare_proxy(conn) - except (BaseSSLError, OSError, SocketTimeout) as e: - self._raise_timeout( - err=e, url=self.proxy.url, timeout_value=conn.timeout - ) - raise - - # If we're going to release the connection in ``finally:``, then - # the response doesn't need to know about the connection. Otherwise - # it will also try to release it and we'll have a double-release - # mess. - response_conn = conn if not release_conn else None + is_new_proxy_conn = self.proxy is not None and not getattr( + conn, "sock", None + ) + if is_new_proxy_conn and http_tunnel_required: + self._prepare_proxy(conn) - # Make the request on the HTTPConnection object - response = self._make_request( + # Make the request on the httplib connection object. + httplib_response = self._make_request( conn, method, url, @@ -792,11 +720,24 @@ def urlopen( # type: ignore[override] body=body, headers=headers, chunked=chunked, + ) + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Pass method to Response for length checking + response_kw["request_method"] = method + + # Import httplib's response into our own wrapper object + response = self.ResponseCls.from_httplib( + httplib_response, + pool=self, + connection=response_conn, retries=retries, - response_conn=response_conn, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Everything went great! @@ -811,35 +752,54 @@ def urlopen( # type: ignore[override] except ( TimeoutError, HTTPException, - OSError, + SocketError, ProtocolError, BaseSSLError, SSLError, CertificateError, - ProxyError, ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False - new_e: Exception = e - if isinstance(e, (BaseSSLError, CertificateError)): - new_e = SSLError(e) - if isinstance( - new_e, - ( - OSError, - NewConnectionError, - TimeoutError, - SSLError, - HTTPException, - ), - ) and (conn and conn.proxy and not conn.has_connected_to_proxy): - new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) - elif isinstance(new_e, (OSError, HTTPException)): - new_e = ProtocolError("Connection aborted.", new_e) + + def _is_ssl_error_message_from_http_proxy(ssl_error): + # We're trying to detect the message 'WRONG_VERSION_NUMBER' but + # SSLErrors are kinda all over the place when it comes to the message, + # so we try to cover our bases here! + message = " ".join(re.split("[^a-z]", str(ssl_error).lower())) + return ( + "wrong version number" in message + or "unknown protocol" in message + or "record layer failure" in message + ) + + # Try to detect a common user error with proxies which is to + # set an HTTP proxy to be HTTPS when it should be 'http://' + # (ie {'http': 'http://proxy', 'https': 'https://proxy'}) + # Instead we add a nice error message and point to a URL. + if ( + isinstance(e, BaseSSLError) + and self.proxy + and _is_ssl_error_message_from_http_proxy(e) + and conn.proxy + and conn.proxy.scheme == "https" + ): + e = ProxyError( + "Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#https-proxy-error-http-proxy", + SSLError(e), + ) + elif isinstance(e, (BaseSSLError, CertificateError)): + e = SSLError(e) + elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: + e = ProxyError("Cannot connect to proxy.", e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError("Connection aborted.", e) retries = retries.increment( - method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2] + method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2] ) retries.sleep() @@ -852,9 +812,7 @@ def urlopen( # type: ignore[override] # to throw the connection away unless explicitly told not to. # Close the connection, set the variable to None, and make sure # we put the None back in the pool to avoid leaking it. - if conn: - conn.close() - conn = None + conn = conn and conn.close() release_this_conn = True if release_this_conn: @@ -881,9 +839,7 @@ def urlopen( # type: ignore[override] release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Handle redirect? @@ -920,9 +876,7 @@ def urlopen( # type: ignore[override] release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) # Check if we should retry the HTTP response. @@ -952,9 +906,7 @@ def urlopen( # type: ignore[override] release_conn=release_conn, chunked=chunked, body_pos=body_pos, - preload_content=preload_content, - decode_content=decode_content, - **response_kw, + **response_kw ) return response @@ -975,35 +927,37 @@ class HTTPSConnectionPool(HTTPConnectionPool): """ scheme = "https" - ConnectionCls: type[BaseHTTPSConnection] = HTTPSConnection + ConnectionCls = HTTPSConnection def __init__( self, - host: str, - port: int | None = None, - timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, - maxsize: int = 1, - block: bool = False, - headers: typing.Mapping[str, str] | None = None, - retries: Retry | bool | int | None = None, - _proxy: Url | None = None, - _proxy_headers: typing.Mapping[str, str] | None = None, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - ssl_version: int | str | None = None, - ssl_minimum_version: ssl.TLSVersion | None = None, - ssl_maximum_version: ssl.TLSVersion | None = None, - assert_hostname: str | typing.Literal[False] | None = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - **conn_kw: typing.Any, - ) -> None: - super().__init__( + host, + port=None, + strict=False, + timeout=Timeout.DEFAULT_TIMEOUT, + maxsize=1, + block=False, + headers=None, + retries=None, + _proxy=None, + _proxy_headers=None, + key_file=None, + cert_file=None, + cert_reqs=None, + key_password=None, + ca_certs=None, + ssl_version=None, + assert_hostname=None, + assert_fingerprint=None, + ca_cert_dir=None, + **conn_kw + ): + + HTTPConnectionPool.__init__( + self, host, port, + strict, timeout, maxsize, block, @@ -1011,7 +965,7 @@ def __init__( retries, _proxy, _proxy_headers, - **conn_kw, + **conn_kw ) self.key_file = key_file @@ -1021,29 +975,47 @@ def __init__( self.ca_certs = ca_certs self.ca_cert_dir = ca_cert_dir self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint - def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] - """Establishes a tunnel connection through HTTP CONNECT.""" - if self.proxy and self.proxy.scheme == "https": - tunnel_scheme = "https" - else: - tunnel_scheme = "http" + def _prepare_conn(self, conn): + """ + Prepare the ``connection`` for :meth:`urllib3.util.ssl_wrap_socket` + and establish the tunnel if proxy is used. + """ + + if isinstance(conn, VerifiedHTTPSConnection): + conn.set_cert( + key_file=self.key_file, + key_password=self.key_password, + cert_file=self.cert_file, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + conn.ssl_version = self.ssl_version + return conn + + def _prepare_proxy(self, conn): + """ + Establishes a tunnel connection through HTTP CONNECT. + + Tunnel connection is established early because otherwise httplib would + improperly set Host: header to proxy's IP:port. + """ + + conn.set_tunnel(self._proxy_host, self.port, self.proxy_headers) + + if self.proxy.scheme == "https": + conn.tls_in_tls_required = True - conn.set_tunnel( - scheme=tunnel_scheme, - host=self._tunnel_host, - port=self.port, - headers=self.proxy_headers, - ) conn.connect() - def _new_conn(self) -> BaseHTTPSConnection: + def _new_conn(self): """ - Return a fresh :class:`urllib3.connection.HTTPConnection`. + Return a fresh :class:`http.client.HTTPSConnection`. """ self.num_connections += 1 log.debug( @@ -1053,59 +1025,64 @@ def _new_conn(self) -> BaseHTTPSConnection: self.port or "443", ) - if not self.ConnectionCls or self.ConnectionCls is DummyConnection: # type: ignore[comparison-overlap] - raise ImportError( + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: + raise SSLError( "Can't connect to HTTPS URL because the SSL module is not available." ) - actual_host: str = self.host + actual_host = self.host actual_port = self.port - if self.proxy is not None and self.proxy.host is not None: + if self.proxy is not None: actual_host = self.proxy.host actual_port = self.proxy.port - return self.ConnectionCls( + conn = self.ConnectionCls( host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, + strict=self.strict, cert_file=self.cert_file, key_file=self.key_file, key_password=self.key_password, - cert_reqs=self.cert_reqs, - ca_certs=self.ca_certs, - ca_cert_dir=self.ca_cert_dir, - assert_hostname=self.assert_hostname, - assert_fingerprint=self.assert_fingerprint, - ssl_version=self.ssl_version, - ssl_minimum_version=self.ssl_minimum_version, - ssl_maximum_version=self.ssl_maximum_version, - **self.conn_kw, + **self.conn_kw ) - def _validate_conn(self, conn: BaseHTTPConnection) -> None: + return self._prepare_conn(conn) + + def _validate_conn(self, conn): """ Called right before a request is made, after the socket is created. """ - super()._validate_conn(conn) + super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. - if conn.is_closed: + if not getattr(conn, "sock", None): # AppEngine might not have `.sock` conn.connect() - # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 - if not conn.is_verified and not conn.proxy_is_verified: + if not conn.is_verified: + warnings.warn( + ( + "Unverified HTTPS request is being made to host '%s'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" % conn.host + ), + InsecureRequestWarning, + ) + + if getattr(conn, "proxy_is_verified", None) is False: warnings.warn( ( - f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Unverified HTTPS connection done to an HTTPS proxy. " "Adding certificate verification is strongly advised. See: " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" - "#tls-warnings" + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings" ), InsecureRequestWarning, ) -def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: +def connection_from_url(url, **kw): """ Given a url, return an :class:`.ConnectionPool` instance of its host. @@ -1125,24 +1102,15 @@ def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: >>> conn = connection_from_url('http://google.com/') >>> r = conn.request('GET', '/') """ - scheme, _, host, port, *_ = parse_url(url) - scheme = scheme or "http" + scheme, host, port = get_host(url) port = port or port_by_scheme.get(scheme, 80) if scheme == "https": - return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + return HTTPSConnectionPool(host, port=port, **kw) else: - return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] - + return HTTPConnectionPool(host, port=port, **kw) -@typing.overload -def _normalize_host(host: None, scheme: str | None) -> None: ... - -@typing.overload -def _normalize_host(host: str, scheme: str | None) -> str: ... - - -def _normalize_host(host: str | None, scheme: str | None) -> str | None: +def _normalize_host(host, scheme): """ Normalize hosts for comparisons and use with sockets. """ @@ -1155,19 +1123,12 @@ def _normalize_host(host: str | None, scheme: str | None) -> str | None: # Instead, we need to make sure we never pass ``None`` as the port. # However, for backward compatibility reasons we can't actually # *assert* that. See http://bugs.python.org/issue28539 - if host and host.startswith("[") and host.endswith("]"): + if host.startswith("[") and host.endswith("]"): host = host[1:-1] return host -def _url_from_pool( - pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None -) -> str: - """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" - return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url - - -def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: +def _close_pool_connections(pool): """Drains a queue of connections and closes each one.""" try: while True: diff --git a/newrelic/packages/urllib3/contrib/_appengine_environ.py b/newrelic/packages/urllib3/contrib/_appengine_environ.py new file mode 100644 index 0000000000..8765b907d7 --- /dev/null +++ b/newrelic/packages/urllib3/contrib/_appengine_environ.py @@ -0,0 +1,36 @@ +""" +This module provides means to detect the App Engine environment. +""" + +import os + + +def is_appengine(): + return is_local_appengine() or is_prod_appengine() + + +def is_appengine_sandbox(): + """Reports if the app is running in the first generation sandbox. + + The second generation runtimes are technically still in a sandbox, but it + is much less restrictive, so generally you shouldn't need to check for it. + see https://cloud.google.com/appengine/docs/standard/runtimes + """ + return is_appengine() and os.environ["APPENGINE_RUNTIME"] == "python27" + + +def is_local_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Development/") + + +def is_prod_appengine(): + return "APPENGINE_RUNTIME" in os.environ and os.environ.get( + "SERVER_SOFTWARE", "" + ).startswith("Google App Engine/") + + +def is_prod_appengine_mvms(): + """Deprecated.""" + return False diff --git a/newrelic/packages/urllib3/contrib/_securetransport/__init__.py b/newrelic/packages/urllib3/contrib/_securetransport/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/newrelic/packages/urllib3/contrib/_securetransport/bindings.py b/newrelic/packages/urllib3/contrib/_securetransport/bindings.py new file mode 100644 index 0000000000..264d564dbd --- /dev/null +++ b/newrelic/packages/urllib3/contrib/_securetransport/bindings.py @@ -0,0 +1,519 @@ +""" +This module uses ctypes to bind a whole bunch of functions and constants from +SecureTransport. The goal here is to provide the low-level API to +SecureTransport. These are essentially the C-level functions and constants, and +they're pretty gross to work with. + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import platform +from ctypes import ( + CDLL, + CFUNCTYPE, + POINTER, + c_bool, + c_byte, + c_char_p, + c_int32, + c_long, + c_size_t, + c_uint32, + c_ulong, + c_void_p, +) +from ctypes.util import find_library + +from ...packages.six import raise_from + +if platform.system() != "Darwin": + raise ImportError("Only macOS is supported") + +version = platform.mac_ver()[0] +version_info = tuple(map(int, version.split("."))) +if version_info < (10, 8): + raise OSError( + "Only OS X 10.8 and newer are supported, not %s.%s" + % (version_info[0], version_info[1]) + ) + + +def load_cdll(name, macos10_16_path): + """Loads a CDLL by name, falling back to known path on 10.16+""" + try: + # Big Sur is technically 11 but we use 10.16 due to the Big Sur + # beta being labeled as 10.16. + if version_info >= (10, 16): + path = macos10_16_path + else: + path = find_library(name) + if not path: + raise OSError # Caught and reraised as 'ImportError' + return CDLL(path, use_errno=True) + except OSError: + raise_from(ImportError("The library %s failed to load" % name), None) + + +Security = load_cdll( + "Security", "/System/Library/Frameworks/Security.framework/Security" +) +CoreFoundation = load_cdll( + "CoreFoundation", + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", +) + + +Boolean = c_bool +CFIndex = c_long +CFStringEncoding = c_uint32 +CFData = c_void_p +CFString = c_void_p +CFArray = c_void_p +CFMutableArray = c_void_p +CFDictionary = c_void_p +CFError = c_void_p +CFType = c_void_p +CFTypeID = c_ulong + +CFTypeRef = POINTER(CFType) +CFAllocatorRef = c_void_p + +OSStatus = c_int32 + +CFDataRef = POINTER(CFData) +CFStringRef = POINTER(CFString) +CFArrayRef = POINTER(CFArray) +CFMutableArrayRef = POINTER(CFMutableArray) +CFDictionaryRef = POINTER(CFDictionary) +CFArrayCallBacks = c_void_p +CFDictionaryKeyCallBacks = c_void_p +CFDictionaryValueCallBacks = c_void_p + +SecCertificateRef = POINTER(c_void_p) +SecExternalFormat = c_uint32 +SecExternalItemType = c_uint32 +SecIdentityRef = POINTER(c_void_p) +SecItemImportExportFlags = c_uint32 +SecItemImportExportKeyParameters = c_void_p +SecKeychainRef = POINTER(c_void_p) +SSLProtocol = c_uint32 +SSLCipherSuite = c_uint32 +SSLContextRef = POINTER(c_void_p) +SecTrustRef = POINTER(c_void_p) +SSLConnectionRef = c_uint32 +SecTrustResultType = c_uint32 +SecTrustOptionFlags = c_uint32 +SSLProtocolSide = c_uint32 +SSLConnectionType = c_uint32 +SSLSessionOption = c_uint32 + + +try: + Security.SecItemImport.argtypes = [ + CFDataRef, + CFStringRef, + POINTER(SecExternalFormat), + POINTER(SecExternalItemType), + SecItemImportExportFlags, + POINTER(SecItemImportExportKeyParameters), + SecKeychainRef, + POINTER(CFArrayRef), + ] + Security.SecItemImport.restype = OSStatus + + Security.SecCertificateGetTypeID.argtypes = [] + Security.SecCertificateGetTypeID.restype = CFTypeID + + Security.SecIdentityGetTypeID.argtypes = [] + Security.SecIdentityGetTypeID.restype = CFTypeID + + Security.SecKeyGetTypeID.argtypes = [] + Security.SecKeyGetTypeID.restype = CFTypeID + + Security.SecCertificateCreateWithData.argtypes = [CFAllocatorRef, CFDataRef] + Security.SecCertificateCreateWithData.restype = SecCertificateRef + + Security.SecCertificateCopyData.argtypes = [SecCertificateRef] + Security.SecCertificateCopyData.restype = CFDataRef + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SecIdentityCreateWithCertificate.argtypes = [ + CFTypeRef, + SecCertificateRef, + POINTER(SecIdentityRef), + ] + Security.SecIdentityCreateWithCertificate.restype = OSStatus + + Security.SecKeychainCreate.argtypes = [ + c_char_p, + c_uint32, + c_void_p, + Boolean, + c_void_p, + POINTER(SecKeychainRef), + ] + Security.SecKeychainCreate.restype = OSStatus + + Security.SecKeychainDelete.argtypes = [SecKeychainRef] + Security.SecKeychainDelete.restype = OSStatus + + Security.SecPKCS12Import.argtypes = [ + CFDataRef, + CFDictionaryRef, + POINTER(CFArrayRef), + ] + Security.SecPKCS12Import.restype = OSStatus + + SSLReadFunc = CFUNCTYPE(OSStatus, SSLConnectionRef, c_void_p, POINTER(c_size_t)) + SSLWriteFunc = CFUNCTYPE( + OSStatus, SSLConnectionRef, POINTER(c_byte), POINTER(c_size_t) + ) + + Security.SSLSetIOFuncs.argtypes = [SSLContextRef, SSLReadFunc, SSLWriteFunc] + Security.SSLSetIOFuncs.restype = OSStatus + + Security.SSLSetPeerID.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerID.restype = OSStatus + + Security.SSLSetCertificate.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetCertificate.restype = OSStatus + + Security.SSLSetCertificateAuthorities.argtypes = [SSLContextRef, CFTypeRef, Boolean] + Security.SSLSetCertificateAuthorities.restype = OSStatus + + Security.SSLSetConnection.argtypes = [SSLContextRef, SSLConnectionRef] + Security.SSLSetConnection.restype = OSStatus + + Security.SSLSetPeerDomainName.argtypes = [SSLContextRef, c_char_p, c_size_t] + Security.SSLSetPeerDomainName.restype = OSStatus + + Security.SSLHandshake.argtypes = [SSLContextRef] + Security.SSLHandshake.restype = OSStatus + + Security.SSLRead.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLRead.restype = OSStatus + + Security.SSLWrite.argtypes = [SSLContextRef, c_char_p, c_size_t, POINTER(c_size_t)] + Security.SSLWrite.restype = OSStatus + + Security.SSLClose.argtypes = [SSLContextRef] + Security.SSLClose.restype = OSStatus + + Security.SSLGetNumberSupportedCiphers.argtypes = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberSupportedCiphers.restype = OSStatus + + Security.SSLGetSupportedCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetSupportedCiphers.restype = OSStatus + + Security.SSLSetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + c_size_t, + ] + Security.SSLSetEnabledCiphers.restype = OSStatus + + Security.SSLGetNumberEnabledCiphers.argtype = [SSLContextRef, POINTER(c_size_t)] + Security.SSLGetNumberEnabledCiphers.restype = OSStatus + + Security.SSLGetEnabledCiphers.argtypes = [ + SSLContextRef, + POINTER(SSLCipherSuite), + POINTER(c_size_t), + ] + Security.SSLGetEnabledCiphers.restype = OSStatus + + Security.SSLGetNegotiatedCipher.argtypes = [SSLContextRef, POINTER(SSLCipherSuite)] + Security.SSLGetNegotiatedCipher.restype = OSStatus + + Security.SSLGetNegotiatedProtocolVersion.argtypes = [ + SSLContextRef, + POINTER(SSLProtocol), + ] + Security.SSLGetNegotiatedProtocolVersion.restype = OSStatus + + Security.SSLCopyPeerTrust.argtypes = [SSLContextRef, POINTER(SecTrustRef)] + Security.SSLCopyPeerTrust.restype = OSStatus + + Security.SecTrustSetAnchorCertificates.argtypes = [SecTrustRef, CFArrayRef] + Security.SecTrustSetAnchorCertificates.restype = OSStatus + + Security.SecTrustSetAnchorCertificatesOnly.argstypes = [SecTrustRef, Boolean] + Security.SecTrustSetAnchorCertificatesOnly.restype = OSStatus + + Security.SecTrustEvaluate.argtypes = [SecTrustRef, POINTER(SecTrustResultType)] + Security.SecTrustEvaluate.restype = OSStatus + + Security.SecTrustGetCertificateCount.argtypes = [SecTrustRef] + Security.SecTrustGetCertificateCount.restype = CFIndex + + Security.SecTrustGetCertificateAtIndex.argtypes = [SecTrustRef, CFIndex] + Security.SecTrustGetCertificateAtIndex.restype = SecCertificateRef + + Security.SSLCreateContext.argtypes = [ + CFAllocatorRef, + SSLProtocolSide, + SSLConnectionType, + ] + Security.SSLCreateContext.restype = SSLContextRef + + Security.SSLSetSessionOption.argtypes = [SSLContextRef, SSLSessionOption, Boolean] + Security.SSLSetSessionOption.restype = OSStatus + + Security.SSLSetProtocolVersionMin.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMin.restype = OSStatus + + Security.SSLSetProtocolVersionMax.argtypes = [SSLContextRef, SSLProtocol] + Security.SSLSetProtocolVersionMax.restype = OSStatus + + try: + Security.SSLSetALPNProtocols.argtypes = [SSLContextRef, CFArrayRef] + Security.SSLSetALPNProtocols.restype = OSStatus + except AttributeError: + # Supported only in 10.12+ + pass + + Security.SecCopyErrorMessageString.argtypes = [OSStatus, c_void_p] + Security.SecCopyErrorMessageString.restype = CFStringRef + + Security.SSLReadFunc = SSLReadFunc + Security.SSLWriteFunc = SSLWriteFunc + Security.SSLContextRef = SSLContextRef + Security.SSLProtocol = SSLProtocol + Security.SSLCipherSuite = SSLCipherSuite + Security.SecIdentityRef = SecIdentityRef + Security.SecKeychainRef = SecKeychainRef + Security.SecTrustRef = SecTrustRef + Security.SecTrustResultType = SecTrustResultType + Security.SecExternalFormat = SecExternalFormat + Security.OSStatus = OSStatus + + Security.kSecImportExportPassphrase = CFStringRef.in_dll( + Security, "kSecImportExportPassphrase" + ) + Security.kSecImportItemIdentity = CFStringRef.in_dll( + Security, "kSecImportItemIdentity" + ) + + # CoreFoundation time! + CoreFoundation.CFRetain.argtypes = [CFTypeRef] + CoreFoundation.CFRetain.restype = CFTypeRef + + CoreFoundation.CFRelease.argtypes = [CFTypeRef] + CoreFoundation.CFRelease.restype = None + + CoreFoundation.CFGetTypeID.argtypes = [CFTypeRef] + CoreFoundation.CFGetTypeID.restype = CFTypeID + + CoreFoundation.CFStringCreateWithCString.argtypes = [ + CFAllocatorRef, + c_char_p, + CFStringEncoding, + ] + CoreFoundation.CFStringCreateWithCString.restype = CFStringRef + + CoreFoundation.CFStringGetCStringPtr.argtypes = [CFStringRef, CFStringEncoding] + CoreFoundation.CFStringGetCStringPtr.restype = c_char_p + + CoreFoundation.CFStringGetCString.argtypes = [ + CFStringRef, + c_char_p, + CFIndex, + CFStringEncoding, + ] + CoreFoundation.CFStringGetCString.restype = c_bool + + CoreFoundation.CFDataCreate.argtypes = [CFAllocatorRef, c_char_p, CFIndex] + CoreFoundation.CFDataCreate.restype = CFDataRef + + CoreFoundation.CFDataGetLength.argtypes = [CFDataRef] + CoreFoundation.CFDataGetLength.restype = CFIndex + + CoreFoundation.CFDataGetBytePtr.argtypes = [CFDataRef] + CoreFoundation.CFDataGetBytePtr.restype = c_void_p + + CoreFoundation.CFDictionaryCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + POINTER(CFTypeRef), + CFIndex, + CFDictionaryKeyCallBacks, + CFDictionaryValueCallBacks, + ] + CoreFoundation.CFDictionaryCreate.restype = CFDictionaryRef + + CoreFoundation.CFDictionaryGetValue.argtypes = [CFDictionaryRef, CFTypeRef] + CoreFoundation.CFDictionaryGetValue.restype = CFTypeRef + + CoreFoundation.CFArrayCreate.argtypes = [ + CFAllocatorRef, + POINTER(CFTypeRef), + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreate.restype = CFArrayRef + + CoreFoundation.CFArrayCreateMutable.argtypes = [ + CFAllocatorRef, + CFIndex, + CFArrayCallBacks, + ] + CoreFoundation.CFArrayCreateMutable.restype = CFMutableArrayRef + + CoreFoundation.CFArrayAppendValue.argtypes = [CFMutableArrayRef, c_void_p] + CoreFoundation.CFArrayAppendValue.restype = None + + CoreFoundation.CFArrayGetCount.argtypes = [CFArrayRef] + CoreFoundation.CFArrayGetCount.restype = CFIndex + + CoreFoundation.CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex] + CoreFoundation.CFArrayGetValueAtIndex.restype = c_void_p + + CoreFoundation.kCFAllocatorDefault = CFAllocatorRef.in_dll( + CoreFoundation, "kCFAllocatorDefault" + ) + CoreFoundation.kCFTypeArrayCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeArrayCallBacks" + ) + CoreFoundation.kCFTypeDictionaryKeyCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryKeyCallBacks" + ) + CoreFoundation.kCFTypeDictionaryValueCallBacks = c_void_p.in_dll( + CoreFoundation, "kCFTypeDictionaryValueCallBacks" + ) + + CoreFoundation.CFTypeRef = CFTypeRef + CoreFoundation.CFArrayRef = CFArrayRef + CoreFoundation.CFStringRef = CFStringRef + CoreFoundation.CFDictionaryRef = CFDictionaryRef + +except (AttributeError): + raise ImportError("Error initializing ctypes") + + +class CFConst(object): + """ + A class object that acts as essentially a namespace for CoreFoundation + constants. + """ + + kCFStringEncodingUTF8 = CFStringEncoding(0x08000100) + + +class SecurityConst(object): + """ + A class object that acts as essentially a namespace for Security constants. + """ + + kSSLSessionOptionBreakOnServerAuth = 0 + + kSSLProtocol2 = 1 + kSSLProtocol3 = 2 + kTLSProtocol1 = 4 + kTLSProtocol11 = 7 + kTLSProtocol12 = 8 + # SecureTransport does not support TLS 1.3 even if there's a constant for it + kTLSProtocol13 = 10 + kTLSProtocolMaxSupported = 999 + + kSSLClientSide = 1 + kSSLStreamType = 0 + + kSecFormatPEMSequence = 10 + + kSecTrustResultInvalid = 0 + kSecTrustResultProceed = 1 + # This gap is present on purpose: this was kSecTrustResultConfirm, which + # is deprecated. + kSecTrustResultDeny = 3 + kSecTrustResultUnspecified = 4 + kSecTrustResultRecoverableTrustFailure = 5 + kSecTrustResultFatalTrustFailure = 6 + kSecTrustResultOtherError = 7 + + errSSLProtocol = -9800 + errSSLWouldBlock = -9803 + errSSLClosedGraceful = -9805 + errSSLClosedNoNotify = -9816 + errSSLClosedAbort = -9806 + + errSSLXCertChainInvalid = -9807 + errSSLCrypto = -9809 + errSSLInternal = -9810 + errSSLCertExpired = -9814 + errSSLCertNotYetValid = -9815 + errSSLUnknownRootCert = -9812 + errSSLNoRootCert = -9813 + errSSLHostNameMismatch = -9843 + errSSLPeerHandshakeFail = -9824 + errSSLPeerUserCancelled = -9839 + errSSLWeakPeerEphemeralDHKey = -9850 + errSSLServerAuthCompleted = -9841 + errSSLRecordOverflow = -9847 + + errSecVerifyFailed = -67808 + errSecNoTrustSettings = -25263 + errSecItemNotFound = -25300 + errSecInvalidTrustSettings = -25262 + + # Cipher suites. We only pick the ones our default cipher string allows. + # Source: https://developer.apple.com/documentation/security/1550981-ssl_cipher_suite_values + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030 + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8 + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024 + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028 + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014 + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027 + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009 + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067 + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033 + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035 + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F + TLS_AES_128_GCM_SHA256 = 0x1301 + TLS_AES_256_GCM_SHA384 = 0x1302 + TLS_AES_128_CCM_8_SHA256 = 0x1305 + TLS_AES_128_CCM_SHA256 = 0x1304 diff --git a/newrelic/packages/urllib3/contrib/_securetransport/low_level.py b/newrelic/packages/urllib3/contrib/_securetransport/low_level.py new file mode 100644 index 0000000000..fa0b245d27 --- /dev/null +++ b/newrelic/packages/urllib3/contrib/_securetransport/low_level.py @@ -0,0 +1,397 @@ +""" +Low-level helpers for the SecureTransport bindings. + +These are Python functions that are not directly related to the high-level APIs +but are necessary to get them to work. They include a whole bunch of low-level +CoreFoundation messing about and memory management. The concerns in this module +are almost entirely about trying to avoid memory leaks and providing +appropriate and useful assistance to the higher-level code. +""" +import base64 +import ctypes +import itertools +import os +import re +import ssl +import struct +import tempfile + +from .bindings import CFConst, CoreFoundation, Security + +# This regular expression is used to grab PEM data out of a PEM bundle. +_PEM_CERTS_RE = re.compile( + b"-----BEGIN CERTIFICATE-----\n(.*?)\n-----END CERTIFICATE-----", re.DOTALL +) + + +def _cf_data_from_bytes(bytestring): + """ + Given a bytestring, create a CFData object from it. This CFData object must + be CFReleased by the caller. + """ + return CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, bytestring, len(bytestring) + ) + + +def _cf_dictionary_from_tuples(tuples): + """ + Given a list of Python tuples, create an associated CFDictionary. + """ + dictionary_size = len(tuples) + + # We need to get the dictionary keys and values out in the same order. + keys = (t[0] for t in tuples) + values = (t[1] for t in tuples) + cf_keys = (CoreFoundation.CFTypeRef * dictionary_size)(*keys) + cf_values = (CoreFoundation.CFTypeRef * dictionary_size)(*values) + + return CoreFoundation.CFDictionaryCreate( + CoreFoundation.kCFAllocatorDefault, + cf_keys, + cf_values, + dictionary_size, + CoreFoundation.kCFTypeDictionaryKeyCallBacks, + CoreFoundation.kCFTypeDictionaryValueCallBacks, + ) + + +def _cfstr(py_bstr): + """ + Given a Python binary data, create a CFString. + The string must be CFReleased by the caller. + """ + c_str = ctypes.c_char_p(py_bstr) + cf_str = CoreFoundation.CFStringCreateWithCString( + CoreFoundation.kCFAllocatorDefault, + c_str, + CFConst.kCFStringEncodingUTF8, + ) + return cf_str + + +def _create_cfstring_array(lst): + """ + Given a list of Python binary data, create an associated CFMutableArray. + The array must be CFReleased by the caller. + + Raises an ssl.SSLError on failure. + """ + cf_arr = None + try: + cf_arr = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cf_arr: + raise MemoryError("Unable to allocate memory!") + for item in lst: + cf_str = _cfstr(item) + if not cf_str: + raise MemoryError("Unable to allocate memory!") + try: + CoreFoundation.CFArrayAppendValue(cf_arr, cf_str) + finally: + CoreFoundation.CFRelease(cf_str) + except BaseException as e: + if cf_arr: + CoreFoundation.CFRelease(cf_arr) + raise ssl.SSLError("Unable to allocate array: %s" % (e,)) + return cf_arr + + +def _cf_string_to_unicode(value): + """ + Creates a Unicode string from a CFString object. Used entirely for error + reporting. + + Yes, it annoys me quite a lot that this function is this complex. + """ + value_as_void_p = ctypes.cast(value, ctypes.POINTER(ctypes.c_void_p)) + + string = CoreFoundation.CFStringGetCStringPtr( + value_as_void_p, CFConst.kCFStringEncodingUTF8 + ) + if string is None: + buffer = ctypes.create_string_buffer(1024) + result = CoreFoundation.CFStringGetCString( + value_as_void_p, buffer, 1024, CFConst.kCFStringEncodingUTF8 + ) + if not result: + raise OSError("Error copying C string from CFStringRef") + string = buffer.value + if string is not None: + string = string.decode("utf-8") + return string + + +def _assert_no_error(error, exception_class=None): + """ + Checks the return code and throws an exception if there is an error to + report + """ + if error == 0: + return + + cf_error_string = Security.SecCopyErrorMessageString(error, None) + output = _cf_string_to_unicode(cf_error_string) + CoreFoundation.CFRelease(cf_error_string) + + if output is None or output == u"": + output = u"OSStatus %s" % error + + if exception_class is None: + exception_class = ssl.SSLError + + raise exception_class(output) + + +def _cert_array_from_pem(pem_bundle): + """ + Given a bundle of certs in PEM format, turns them into a CFArray of certs + that can be used to validate a cert chain. + """ + # Normalize the PEM bundle's line endings. + pem_bundle = pem_bundle.replace(b"\r\n", b"\n") + + der_certs = [ + base64.b64decode(match.group(1)) for match in _PEM_CERTS_RE.finditer(pem_bundle) + ] + if not der_certs: + raise ssl.SSLError("No root certificates specified") + + cert_array = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + if not cert_array: + raise ssl.SSLError("Unable to allocate memory!") + + try: + for der_bytes in der_certs: + certdata = _cf_data_from_bytes(der_bytes) + if not certdata: + raise ssl.SSLError("Unable to allocate memory!") + cert = Security.SecCertificateCreateWithData( + CoreFoundation.kCFAllocatorDefault, certdata + ) + CoreFoundation.CFRelease(certdata) + if not cert: + raise ssl.SSLError("Unable to build cert object!") + + CoreFoundation.CFArrayAppendValue(cert_array, cert) + CoreFoundation.CFRelease(cert) + except Exception: + # We need to free the array before the exception bubbles further. + # We only want to do that if an error occurs: otherwise, the caller + # should free. + CoreFoundation.CFRelease(cert_array) + raise + + return cert_array + + +def _is_cert(item): + """ + Returns True if a given CFTypeRef is a certificate. + """ + expected = Security.SecCertificateGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _is_identity(item): + """ + Returns True if a given CFTypeRef is an identity. + """ + expected = Security.SecIdentityGetTypeID() + return CoreFoundation.CFGetTypeID(item) == expected + + +def _temporary_keychain(): + """ + This function creates a temporary Mac keychain that we can use to work with + credentials. This keychain uses a one-time password and a temporary file to + store the data. We expect to have one keychain per socket. The returned + SecKeychainRef must be freed by the caller, including calling + SecKeychainDelete. + + Returns a tuple of the SecKeychainRef and the path to the temporary + directory that contains it. + """ + # Unfortunately, SecKeychainCreate requires a path to a keychain. This + # means we cannot use mkstemp to use a generic temporary file. Instead, + # we're going to create a temporary directory and a filename to use there. + # This filename will be 8 random bytes expanded into base64. We also need + # some random bytes to password-protect the keychain we're creating, so we + # ask for 40 random bytes. + random_bytes = os.urandom(40) + filename = base64.b16encode(random_bytes[:8]).decode("utf-8") + password = base64.b16encode(random_bytes[8:]) # Must be valid UTF-8 + tempdirectory = tempfile.mkdtemp() + + keychain_path = os.path.join(tempdirectory, filename).encode("utf-8") + + # We now want to create the keychain itself. + keychain = Security.SecKeychainRef() + status = Security.SecKeychainCreate( + keychain_path, len(password), password, False, None, ctypes.byref(keychain) + ) + _assert_no_error(status) + + # Having created the keychain, we want to pass it off to the caller. + return keychain, tempdirectory + + +def _load_items_from_file(keychain, path): + """ + Given a single file, loads all the trust objects from it into arrays and + the keychain. + Returns a tuple of lists: the first list is a list of identities, the + second a list of certs. + """ + certificates = [] + identities = [] + result_array = None + + with open(path, "rb") as f: + raw_filedata = f.read() + + try: + filedata = CoreFoundation.CFDataCreate( + CoreFoundation.kCFAllocatorDefault, raw_filedata, len(raw_filedata) + ) + result_array = CoreFoundation.CFArrayRef() + result = Security.SecItemImport( + filedata, # cert data + None, # Filename, leaving it out for now + None, # What the type of the file is, we don't care + None, # what's in the file, we don't care + 0, # import flags + None, # key params, can include passphrase in the future + keychain, # The keychain to insert into + ctypes.byref(result_array), # Results + ) + _assert_no_error(result) + + # A CFArray is not very useful to us as an intermediary + # representation, so we are going to extract the objects we want + # and then free the array. We don't need to keep hold of keys: the + # keychain already has them! + result_count = CoreFoundation.CFArrayGetCount(result_array) + for index in range(result_count): + item = CoreFoundation.CFArrayGetValueAtIndex(result_array, index) + item = ctypes.cast(item, CoreFoundation.CFTypeRef) + + if _is_cert(item): + CoreFoundation.CFRetain(item) + certificates.append(item) + elif _is_identity(item): + CoreFoundation.CFRetain(item) + identities.append(item) + finally: + if result_array: + CoreFoundation.CFRelease(result_array) + + CoreFoundation.CFRelease(filedata) + + return (identities, certificates) + + +def _load_client_cert_chain(keychain, *paths): + """ + Load certificates and maybe keys from a number of files. Has the end goal + of returning a CFArray containing one SecIdentityRef, and then zero or more + SecCertificateRef objects, suitable for use as a client certificate trust + chain. + """ + # Ok, the strategy. + # + # This relies on knowing that macOS will not give you a SecIdentityRef + # unless you have imported a key into a keychain. This is a somewhat + # artificial limitation of macOS (for example, it doesn't necessarily + # affect iOS), but there is nothing inside Security.framework that lets you + # get a SecIdentityRef without having a key in a keychain. + # + # So the policy here is we take all the files and iterate them in order. + # Each one will use SecItemImport to have one or more objects loaded from + # it. We will also point at a keychain that macOS can use to work with the + # private key. + # + # Once we have all the objects, we'll check what we actually have. If we + # already have a SecIdentityRef in hand, fab: we'll use that. Otherwise, + # we'll take the first certificate (which we assume to be our leaf) and + # ask the keychain to give us a SecIdentityRef with that cert's associated + # key. + # + # We'll then return a CFArray containing the trust chain: one + # SecIdentityRef and then zero-or-more SecCertificateRef objects. The + # responsibility for freeing this CFArray will be with the caller. This + # CFArray must remain alive for the entire connection, so in practice it + # will be stored with a single SSLSocket, along with the reference to the + # keychain. + certificates = [] + identities = [] + + # Filter out bad paths. + paths = (path for path in paths if path) + + try: + for file_path in paths: + new_identities, new_certs = _load_items_from_file(keychain, file_path) + identities.extend(new_identities) + certificates.extend(new_certs) + + # Ok, we have everything. The question is: do we have an identity? If + # not, we want to grab one from the first cert we have. + if not identities: + new_identity = Security.SecIdentityRef() + status = Security.SecIdentityCreateWithCertificate( + keychain, certificates[0], ctypes.byref(new_identity) + ) + _assert_no_error(status) + identities.append(new_identity) + + # We now want to release the original certificate, as we no longer + # need it. + CoreFoundation.CFRelease(certificates.pop(0)) + + # We now need to build a new CFArray that holds the trust chain. + trust_chain = CoreFoundation.CFArrayCreateMutable( + CoreFoundation.kCFAllocatorDefault, + 0, + ctypes.byref(CoreFoundation.kCFTypeArrayCallBacks), + ) + for item in itertools.chain(identities, certificates): + # ArrayAppendValue does a CFRetain on the item. That's fine, + # because the finally block will release our other refs to them. + CoreFoundation.CFArrayAppendValue(trust_chain, item) + + return trust_chain + finally: + for obj in itertools.chain(identities, certificates): + CoreFoundation.CFRelease(obj) + + +TLS_PROTOCOL_VERSIONS = { + "SSLv2": (0, 2), + "SSLv3": (3, 0), + "TLSv1": (3, 1), + "TLSv1.1": (3, 2), + "TLSv1.2": (3, 3), +} + + +def _build_tls_unknown_ca_alert(version): + """ + Builds a TLS alert record for an unknown CA. + """ + ver_maj, ver_min = TLS_PROTOCOL_VERSIONS[version] + severity_fatal = 0x02 + description_unknown_ca = 0x30 + msg = struct.pack(">BB", severity_fatal, description_unknown_ca) + msg_len = len(msg) + record_type_alert = 0x15 + record = struct.pack(">BBBH", record_type_alert, ver_maj, ver_min, msg_len) + msg + return record diff --git a/newrelic/packages/urllib3/contrib/appengine.py b/newrelic/packages/urllib3/contrib/appengine.py new file mode 100644 index 0000000000..a5a6d91035 --- /dev/null +++ b/newrelic/packages/urllib3/contrib/appengine.py @@ -0,0 +1,314 @@ +""" +This module provides a pool manager that uses Google App Engine's +`URLFetch Service `_. + +Example usage:: + + from urllib3 import PoolManager + from urllib3.contrib.appengine import AppEngineManager, is_appengine_sandbox + + if is_appengine_sandbox(): + # AppEngineManager uses AppEngine's URLFetch API behind the scenes + http = AppEngineManager() + else: + # PoolManager uses a socket-level API behind the scenes + http = PoolManager() + + r = http.request('GET', 'https://google.com/') + +There are `limitations `_ to the URLFetch service and it may not be +the best choice for your application. There are three options for using +urllib3 on Google App Engine: + +1. You can use :class:`AppEngineManager` with URLFetch. URLFetch is + cost-effective in many circumstances as long as your usage is within the + limitations. +2. You can use a normal :class:`~urllib3.PoolManager` by enabling sockets. + Sockets also have `limitations and restrictions + `_ and have a lower free quota than URLFetch. + To use sockets, be sure to specify the following in your ``app.yaml``:: + + env_variables: + GAE_USE_SOCKETS_HTTPLIB : 'true' + +3. If you are using `App Engine Flexible +`_, you can use the standard +:class:`PoolManager` without any configuration or special environment variables. +""" + +from __future__ import absolute_import + +import io +import logging +import warnings + +from ..exceptions import ( + HTTPError, + HTTPWarning, + MaxRetryError, + ProtocolError, + SSLError, + TimeoutError, +) +from ..packages.six.moves.urllib.parse import urljoin +from ..request import RequestMethods +from ..response import HTTPResponse +from ..util.retry import Retry +from ..util.timeout import Timeout +from . import _appengine_environ + +try: + from google.appengine.api import urlfetch +except ImportError: + urlfetch = None + + +log = logging.getLogger(__name__) + + +class AppEnginePlatformWarning(HTTPWarning): + pass + + +class AppEnginePlatformError(HTTPError): + pass + + +class AppEngineManager(RequestMethods): + """ + Connection manager for Google App Engine sandbox applications. + + This manager uses the URLFetch service directly instead of using the + emulated httplib, and is subject to URLFetch limitations as described in + the App Engine documentation `here + `_. + + Notably it will raise an :class:`AppEnginePlatformError` if: + * URLFetch is not available. + * If you attempt to use this on App Engine Flexible, as full socket + support is available. + * If a request size is more than 10 megabytes. + * If a response size is more than 32 megabytes. + * If you use an unsupported request method such as OPTIONS. + + Beyond those cases, it will raise normal urllib3 errors. + """ + + def __init__( + self, + headers=None, + retries=None, + validate_certificate=True, + urlfetch_retries=True, + ): + if not urlfetch: + raise AppEnginePlatformError( + "URLFetch is not available in this environment." + ) + + warnings.warn( + "urllib3 is using URLFetch on Google App Engine sandbox instead " + "of sockets. To use sockets directly instead of URLFetch see " + "https://urllib3.readthedocs.io/en/1.26.x/reference/urllib3.contrib.html.", + AppEnginePlatformWarning, + ) + + RequestMethods.__init__(self, headers) + self.validate_certificate = validate_certificate + self.urlfetch_retries = urlfetch_retries + + self.retries = retries or Retry.DEFAULT + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + # Return False to re-raise any potential exceptions + return False + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=None, + redirect=True, + timeout=Timeout.DEFAULT_TIMEOUT, + **response_kw + ): + + retries = self._get_retries(retries, redirect) + + try: + follow_redirects = redirect and retries.redirect != 0 and retries.total + response = urlfetch.fetch( + url, + payload=body, + method=method, + headers=headers or {}, + allow_truncated=False, + follow_redirects=self.urlfetch_retries and follow_redirects, + deadline=self._get_absolute_timeout(timeout), + validate_certificate=self.validate_certificate, + ) + except urlfetch.DeadlineExceededError as e: + raise TimeoutError(self, e) + + except urlfetch.InvalidURLError as e: + if "too large" in str(e): + raise AppEnginePlatformError( + "URLFetch request too large, URLFetch only " + "supports requests up to 10mb in size.", + e, + ) + raise ProtocolError(e) + + except urlfetch.DownloadError as e: + if "Too many redirects" in str(e): + raise MaxRetryError(self, url, reason=e) + raise ProtocolError(e) + + except urlfetch.ResponseTooLargeError as e: + raise AppEnginePlatformError( + "URLFetch response too large, URLFetch only supports" + "responses up to 32mb in size.", + e, + ) + + except urlfetch.SSLCertificateError as e: + raise SSLError(e) + + except urlfetch.InvalidMethodError as e: + raise AppEnginePlatformError( + "URLFetch does not support method: %s" % method, e + ) + + http_response = self._urlfetch_response_to_http_response( + response, retries=retries, **response_kw + ) + + # Handle redirect? + redirect_location = redirect and http_response.get_redirect_location() + if redirect_location: + # Check for redirect response + if self.urlfetch_retries and retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + else: + if http_response.status == 303: + method = "GET" + + try: + retries = retries.increment( + method, url, response=http_response, _pool=self + ) + except MaxRetryError: + if retries.raise_on_redirect: + raise MaxRetryError(self, url, "too many redirects") + return http_response + + retries.sleep_for_retry(http_response) + log.debug("Redirecting %s -> %s", url, redirect_location) + redirect_url = urljoin(url, redirect_location) + return self.urlopen( + method, + redirect_url, + body, + headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(http_response.headers.get("Retry-After")) + if retries.is_retry(method, http_response.status, has_retry_after): + retries = retries.increment(method, url, response=http_response, _pool=self) + log.debug("Retry: %s", url) + retries.sleep(http_response) + return self.urlopen( + method, + url, + body=body, + headers=headers, + retries=retries, + redirect=redirect, + timeout=timeout, + **response_kw + ) + + return http_response + + def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): + + if is_prod_appengine(): + # Production GAE handles deflate encoding automatically, but does + # not remove the encoding header. + content_encoding = urlfetch_resp.headers.get("content-encoding") + + if content_encoding == "deflate": + del urlfetch_resp.headers["content-encoding"] + + transfer_encoding = urlfetch_resp.headers.get("transfer-encoding") + # We have a full response's content, + # so let's make sure we don't report ourselves as chunked data. + if transfer_encoding == "chunked": + encodings = transfer_encoding.split(",") + encodings.remove("chunked") + urlfetch_resp.headers["transfer-encoding"] = ",".join(encodings) + + original_response = HTTPResponse( + # In order for decoding to work, we must present the content as + # a file-like object. + body=io.BytesIO(urlfetch_resp.content), + msg=urlfetch_resp.header_msg, + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + **response_kw + ) + + return HTTPResponse( + body=io.BytesIO(urlfetch_resp.content), + headers=urlfetch_resp.headers, + status=urlfetch_resp.status_code, + original_response=original_response, + **response_kw + ) + + def _get_absolute_timeout(self, timeout): + if timeout is Timeout.DEFAULT_TIMEOUT: + return None # Defer to URLFetch's default. + if isinstance(timeout, Timeout): + if timeout._read is not None or timeout._connect is not None: + warnings.warn( + "URLFetch does not support granular timeout settings, " + "reverting to total or default URLFetch timeout.", + AppEnginePlatformWarning, + ) + return timeout.total + return timeout + + def _get_retries(self, retries, redirect): + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if retries.connect or retries.read or retries.redirect: + warnings.warn( + "URLFetch only supports total retries and does not " + "recognize connect, read, or redirect retry parameters.", + AppEnginePlatformWarning, + ) + + return retries + + +# Alias methods from _appengine_environ to maintain public API interface. + +is_appengine = _appengine_environ.is_appengine +is_appengine_sandbox = _appengine_environ.is_appengine_sandbox +is_local_appengine = _appengine_environ.is_local_appengine +is_prod_appengine = _appengine_environ.is_prod_appengine +is_prod_appengine_mvms = _appengine_environ.is_prod_appengine_mvms diff --git a/newrelic/packages/urllib3/contrib/emscripten/__init__.py b/newrelic/packages/urllib3/contrib/emscripten/__init__.py deleted file mode 100644 index e5b62b25e9..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import urllib3.connection - -from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool -from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection - - -def inject_into_urllib3() -> None: - # override connection classes to use emscripten specific classes - # n.b. mypy complains about the overriding of classes below - # if it isn't ignored - HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection - HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection - urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] - urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] - urllib3.connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection # type: ignore[assignment] diff --git a/newrelic/packages/urllib3/contrib/emscripten/connection.py b/newrelic/packages/urllib3/contrib/emscripten/connection.py deleted file mode 100644 index 63f79dd3be..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/connection.py +++ /dev/null @@ -1,260 +0,0 @@ -from __future__ import annotations - -import os -import typing - -# use http.client.HTTPException for consistency with non-emscripten -from http.client import HTTPException as HTTPException # noqa: F401 -from http.client import ResponseNotReady - -from ..._base_connection import _TYPE_BODY -from ...connection import HTTPConnection, ProxyConfig, port_by_scheme -from ...exceptions import TimeoutError -from ...response import BaseHTTPResponse -from ...util.connection import _TYPE_SOCKET_OPTIONS -from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT -from ...util.url import Url -from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request -from .request import EmscriptenRequest -from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse - -if typing.TYPE_CHECKING: - from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection - - -class EmscriptenHTTPConnection: - default_port: typing.ClassVar[int] = port_by_scheme["http"] - default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] - - timeout: None | (float) - - host: str - port: int - blocksize: int - source_address: tuple[str, int] | None - socket_options: _TYPE_SOCKET_OPTIONS | None - - proxy: Url | None - proxy_config: ProxyConfig | None - - is_verified: bool = False - proxy_is_verified: bool | None = None - - response_class: type[BaseHTTPResponse] = EmscriptenHttpResponseWrapper - _response: EmscriptenResponse | None - - def __init__( - self, - host: str, - port: int = 0, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 8192, - socket_options: _TYPE_SOCKET_OPTIONS | None = None, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - ) -> None: - self.host = host - self.port = port - self.timeout = timeout if isinstance(timeout, float) else 0.0 - self.scheme = "http" - self._closed = True - self._response = None - # ignore these things because we don't - # have control over that stuff - self.proxy = None - self.proxy_config = None - self.blocksize = blocksize - self.source_address = None - self.socket_options = None - self.is_verified = False - - def set_tunnel( - self, - host: str, - port: int | None = 0, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - pass - - def connect(self) -> None: - pass - - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - # We know *at least* botocore is depending on the order of the - # first 3 parameters so to be safe we only mark the later ones - # as keyword-only to ensure we have space to extend. - *, - chunked: bool = False, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - ) -> None: - self._closed = False - if url.startswith("/"): - if self.port is not None: - port = f":{self.port}" - else: - port = "" - # no scheme / host / port included, make a full url - url = f"{self.scheme}://{self.host}{port}{url}" - request = EmscriptenRequest( - url=url, - method=method, - timeout=self.timeout if self.timeout else 0, - decode_content=decode_content, - ) - request.set_body(body) - if headers: - for k, v in headers.items(): - request.set_header(k, v) - self._response = None - try: - if not preload_content: - self._response = send_streaming_request(request) - if self._response is None: - self._response = send_request(request) - except _TimeoutError as e: - raise TimeoutError(e.message) from e - except _RequestError as e: - raise HTTPException(e.message) from e - - def getresponse(self) -> BaseHTTPResponse: - if self._response is not None: - return EmscriptenHttpResponseWrapper( - internal_response=self._response, - url=self._response.request.url, - connection=self, - ) - else: - raise ResponseNotReady() - - def close(self) -> None: - self._closed = True - self._response = None - - @property - def is_closed(self) -> bool: - """Whether the connection either is brand new or has been previously closed. - If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` - properties must be False. - """ - return self._closed - - @property - def is_connected(self) -> bool: - """Whether the connection is actively connected to any origin (proxy or target)""" - return True - - @property - def has_connected_to_proxy(self) -> bool: - """Whether the connection has successfully connected to its proxy. - This returns False if no proxy is in use. Used to determine whether - errors are coming from the proxy layer or from tunnelling to the target origin. - """ - return False - - -class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): - default_port = port_by_scheme["https"] - # all this is basically ignored, as browser handles https - cert_reqs: int | str | None = None - ca_certs: str | None = None - ca_cert_dir: str | None = None - ca_cert_data: None | str | bytes = None - cert_file: str | None - key_file: str | None - key_password: str | None - ssl_context: typing.Any | None - ssl_version: int | str | None = None - ssl_minimum_version: int | None = None - ssl_maximum_version: int | None = None - assert_hostname: None | str | typing.Literal[False] - assert_fingerprint: str | None = None - - def __init__( - self, - host: str, - port: int = 0, - *, - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - blocksize: int = 16384, - socket_options: ( - None | _TYPE_SOCKET_OPTIONS - ) = HTTPConnection.default_socket_options, - proxy: Url | None = None, - proxy_config: ProxyConfig | None = None, - cert_reqs: int | str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - server_hostname: str | None = None, - ssl_context: typing.Any | None = None, - ca_certs: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - ssl_version: int | str | None = None, # Deprecated - cert_file: str | None = None, - key_file: str | None = None, - key_password: str | None = None, - ) -> None: - super().__init__( - host, - port=port, - timeout=timeout, - source_address=source_address, - blocksize=blocksize, - socket_options=socket_options, - proxy=proxy, - proxy_config=proxy_config, - ) - self.scheme = "https" - - self.key_file = key_file - self.cert_file = cert_file - self.key_password = key_password - self.ssl_context = ssl_context - self.server_hostname = server_hostname - self.assert_hostname = assert_hostname - self.assert_fingerprint = assert_fingerprint - self.ssl_version = ssl_version - self.ssl_minimum_version = ssl_minimum_version - self.ssl_maximum_version = ssl_maximum_version - self.ca_certs = ca_certs and os.path.expanduser(ca_certs) - self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) - self.ca_cert_data = ca_cert_data - - self.cert_reqs = None - - # The browser will automatically verify all requests. - # We have no control over that setting. - self.is_verified = True - - def set_cert( - self, - key_file: str | None = None, - cert_file: str | None = None, - cert_reqs: int | str | None = None, - key_password: str | None = None, - ca_certs: str | None = None, - assert_hostname: None | str | typing.Literal[False] = None, - assert_fingerprint: str | None = None, - ca_cert_dir: str | None = None, - ca_cert_data: None | str | bytes = None, - ) -> None: - pass - - -# verify that this class implements BaseHTTP(s) connection correctly -if typing.TYPE_CHECKING: - _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) - _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/newrelic/packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/newrelic/packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js deleted file mode 100644 index faf141e1fa..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js +++ /dev/null @@ -1,110 +0,0 @@ -let Status = { - SUCCESS_HEADER: -1, - SUCCESS_EOF: -2, - ERROR_TIMEOUT: -3, - ERROR_EXCEPTION: -4, -}; - -let connections = new Map(); -let nextConnectionID = 1; -const encoder = new TextEncoder(); - -self.addEventListener("message", async function (event) { - if (event.data.close) { - let connectionID = event.data.close; - connections.delete(connectionID); - return; - } else if (event.data.getMore) { - let connectionID = event.data.getMore; - let { curOffset, value, reader, intBuffer, byteBuffer } = - connections.get(connectionID); - // if we still have some in buffer, then just send it back straight away - if (!value || curOffset >= value.length) { - // read another buffer if required - try { - let readResponse = await reader.read(); - - if (readResponse.done) { - // read everything - clear connection and return - connections.delete(connectionID); - Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); - Atomics.notify(intBuffer, 0); - // finished reading successfully - // return from event handler - return; - } - curOffset = 0; - connections.get(connectionID).value = readResponse.value; - value = readResponse.value; - } catch (error) { - console.log("Request exception:", error); - let errorBytes = encoder.encode(error.message); - let written = errorBytes.length; - byteBuffer.set(errorBytes); - intBuffer[1] = written; - Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); - Atomics.notify(intBuffer, 0); - } - } - - // send as much buffer as we can - let curLen = value.length - curOffset; - if (curLen > byteBuffer.length) { - curLen = byteBuffer.length; - } - byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); - - Atomics.store(intBuffer, 0, curLen); // store current length in bytes - Atomics.notify(intBuffer, 0); - curOffset += curLen; - connections.get(connectionID).curOffset = curOffset; - - return; - } else { - // start fetch - let connectionID = nextConnectionID; - nextConnectionID += 1; - const intBuffer = new Int32Array(event.data.buffer); - const byteBuffer = new Uint8Array(event.data.buffer, 8); - try { - const response = await fetch(event.data.url, event.data.fetchParams); - // return the headers first via textencoder - var headers = []; - for (const pair of response.headers.entries()) { - headers.push([pair[0], pair[1]]); - } - let headerObj = { - headers: headers, - status: response.status, - connectionID, - }; - const headerText = JSON.stringify(headerObj); - let headerBytes = encoder.encode(headerText); - let written = headerBytes.length; - byteBuffer.set(headerBytes); - intBuffer[1] = written; - // make a connection - connections.set(connectionID, { - reader: response.body.getReader(), - intBuffer: intBuffer, - byteBuffer: byteBuffer, - value: undefined, - curOffset: 0, - }); - // set header ready - Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); - Atomics.notify(intBuffer, 0); - // all fetching after this goes through a new postmessage call with getMore - // this allows for parallel requests - } catch (error) { - console.log("Request exception:", error); - let errorBytes = encoder.encode(error.message); - let written = errorBytes.length; - byteBuffer.set(errorBytes); - intBuffer[1] = written; - Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); - Atomics.notify(intBuffer, 0); - } - } -}); -self.postMessage({ inited: true }); diff --git a/newrelic/packages/urllib3/contrib/emscripten/fetch.py b/newrelic/packages/urllib3/contrib/emscripten/fetch.py deleted file mode 100644 index 612cfddc4c..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/fetch.py +++ /dev/null @@ -1,726 +0,0 @@ -""" -Support for streaming http requests in emscripten. - -A few caveats - - -If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled -https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md -*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the -JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case -timeouts and streaming should just work. - -Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. - -This approach has several caveats: - -Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. -Streaming only works if you're running pyodide in a web worker. - -Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch -operation, so it requires that you have crossOriginIsolation enabled, by serving over https -(or from localhost) with the two headers below set: - - Cross-Origin-Opener-Policy: same-origin - Cross-Origin-Embedder-Policy: require-corp - -You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in -JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole -request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. - -Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once -control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. - -NB: in this code, there are a lot of JavaScript objects. They are named js_* -to make it clear what type of object they are. -""" - -from __future__ import annotations - -import io -import json -from email.parser import Parser -from importlib.resources import files -from typing import TYPE_CHECKING, Any - -import js # type: ignore[import-not-found] -from pyodide.ffi import ( # type: ignore[import-not-found] - JsArray, - JsException, - JsProxy, - to_js, -) - -if TYPE_CHECKING: - from typing_extensions import Buffer - -from .request import EmscriptenRequest -from .response import EmscriptenResponse - -""" -There are some headers that trigger unintended CORS preflight requests. -See also https://github.com/koenvo/pyodide-http/issues/22 -""" -HEADERS_TO_IGNORE = ("user-agent",) - -SUCCESS_HEADER = -1 -SUCCESS_EOF = -2 -ERROR_TIMEOUT = -3 -ERROR_EXCEPTION = -4 - - -class _RequestError(Exception): - def __init__( - self, - message: str | None = None, - *, - request: EmscriptenRequest | None = None, - response: EmscriptenResponse | None = None, - ): - self.request = request - self.response = response - self.message = message - super().__init__(self.message) - - -class _StreamingError(_RequestError): - pass - - -class _TimeoutError(_RequestError): - pass - - -def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: - return to_js(dict_val, dict_converter=js.Object.fromEntries) - - -class _ReadStream(io.RawIOBase): - def __init__( - self, - int_buffer: JsArray, - byte_buffer: JsArray, - timeout: float, - worker: JsProxy, - connection_id: int, - request: EmscriptenRequest, - ): - self.int_buffer = int_buffer - self.byte_buffer = byte_buffer - self.read_pos = 0 - self.read_len = 0 - self.connection_id = connection_id - self.worker = worker - self.timeout = int(1000 * timeout) if timeout > 0 else None - self.is_live = True - self._is_closed = False - self.request: EmscriptenRequest | None = request - - def __del__(self) -> None: - self.close() - - # this is compatible with _base_connection - def is_closed(self) -> bool: - return self._is_closed - - # for compatibility with RawIOBase - @property - def closed(self) -> bool: - return self.is_closed() - - def close(self) -> None: - if self.is_closed(): - return - self.read_len = 0 - self.read_pos = 0 - self.int_buffer = None - self.byte_buffer = None - self._is_closed = True - self.request = None - if self.is_live: - self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) - self.is_live = False - super().close() - - def readable(self) -> bool: - return True - - def writable(self) -> bool: - return False - - def seekable(self) -> bool: - return False - - def readinto(self, byte_obj: Buffer) -> int: - if not self.int_buffer: - raise _StreamingError( - "No buffer for stream in _ReadStream.readinto", - request=self.request, - response=None, - ) - if self.read_len == 0: - # wait for the worker to send something - js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) - self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) - if ( - js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) - == "timed-out" - ): - raise _TimeoutError - data_len = self.int_buffer[0] - if data_len > 0: - self.read_len = data_len - self.read_pos = 0 - elif data_len == ERROR_EXCEPTION: - string_len = self.int_buffer[1] - # decode the error string - js_decoder = js.TextDecoder.new() - json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) - raise _StreamingError( - f"Exception thrown in fetch: {json_str}", - request=self.request, - response=None, - ) - else: - # EOF, free the buffers and return zero - # and free the request - self.is_live = False - self.close() - return 0 - # copy from int32array to python bytes - ret_length = min(self.read_len, len(memoryview(byte_obj))) - subarray = self.byte_buffer.subarray( - self.read_pos, self.read_pos + ret_length - ).to_py() - memoryview(byte_obj)[0:ret_length] = subarray - self.read_len -= ret_length - self.read_pos += ret_length - return ret_length - - -class _StreamingFetcher: - def __init__(self) -> None: - # make web-worker and data buffer on startup - self.streaming_ready = False - streaming_worker_code = ( - files(__package__) - .joinpath("emscripten_fetch_worker.js") - .read_text(encoding="utf-8") - ) - js_data_blob = js.Blob.new( - to_js([streaming_worker_code], create_pyproxies=False), - _obj_from_dict({"type": "application/javascript"}), - ) - - def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: - def onMsg(e: JsProxy) -> None: - self.streaming_ready = True - js_resolve_fn(e) - - def onErr(e: JsProxy) -> None: - js_reject_fn(e) # Defensive: never happens in ci - - self.js_worker.onmessage = onMsg - self.js_worker.onerror = onErr - - js_data_url = js.URL.createObjectURL(js_data_blob) - self.js_worker = js.globalThis.Worker.new(js_data_url) - self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) - - def send(self, request: EmscriptenRequest) -> EmscriptenResponse: - headers = { - k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE - } - - body = request.body - fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} - # start the request off in the worker - timeout = int(1000 * request.timeout) if request.timeout > 0 else None - js_shared_buffer = js.SharedArrayBuffer.new(1048576) - js_int_buffer = js.Int32Array.new(js_shared_buffer) - js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) - - js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) - js.Atomics.notify(js_int_buffer, 0) - js_absolute_url = js.URL.new(request.url, js.location).href - self.js_worker.postMessage( - _obj_from_dict( - { - "buffer": js_shared_buffer, - "url": js_absolute_url, - "fetchParams": fetch_data, - } - ) - ) - # wait for the worker to send something - js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) - if js_int_buffer[0] == ERROR_TIMEOUT: - raise _TimeoutError( - "Timeout connecting to streaming request", - request=request, - response=None, - ) - elif js_int_buffer[0] == SUCCESS_HEADER: - # got response - # header length is in second int of intBuffer - string_len = js_int_buffer[1] - # decode the rest to a JSON string - js_decoder = js.TextDecoder.new() - # this does a copy (the slice) because decode can't work on shared array - # for some silly reason - json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) - # get it as an object - response_obj = json.loads(json_str) - return EmscriptenResponse( - request=request, - status_code=response_obj["status"], - headers=response_obj["headers"], - body=_ReadStream( - js_int_buffer, - js_byte_buffer, - request.timeout, - self.js_worker, - response_obj["connectionID"], - request, - ), - ) - elif js_int_buffer[0] == ERROR_EXCEPTION: - string_len = js_int_buffer[1] - # decode the error string - js_decoder = js.TextDecoder.new() - json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) - raise _StreamingError( - f"Exception thrown in fetch: {json_str}", request=request, response=None - ) - else: - raise _StreamingError( - f"Unknown status from worker in fetch: {js_int_buffer[0]}", - request=request, - response=None, - ) - - -class _JSPIReadStream(io.RawIOBase): - """ - A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch - response. This requires support for WebAssembly JavaScript Promise Integration - in the containing browser, and for pyodide to be launched via runPythonAsync. - - :param js_read_stream: - The JavaScript stream reader - - :param timeout: - Timeout in seconds - - :param request: - The request we're handling - - :param response: - The response this stream relates to - - :param js_abort_controller: - A JavaScript AbortController object, used for timeouts - """ - - def __init__( - self, - js_read_stream: Any, - timeout: float, - request: EmscriptenRequest, - response: EmscriptenResponse, - js_abort_controller: Any, # JavaScript AbortController for timeouts - ): - self.js_read_stream = js_read_stream - self.timeout = timeout - self._is_closed = False - self._is_done = False - self.request: EmscriptenRequest | None = request - self.response: EmscriptenResponse | None = response - self.current_buffer = None - self.current_buffer_pos = 0 - self.js_abort_controller = js_abort_controller - - def __del__(self) -> None: - self.close() - - # this is compatible with _base_connection - def is_closed(self) -> bool: - return self._is_closed - - # for compatibility with RawIOBase - @property - def closed(self) -> bool: - return self.is_closed() - - def close(self) -> None: - if self.is_closed(): - return - self.read_len = 0 - self.read_pos = 0 - self.js_read_stream.cancel() - self.js_read_stream = None - self._is_closed = True - self._is_done = True - self.request = None - self.response = None - super().close() - - def readable(self) -> bool: - return True - - def writable(self) -> bool: - return False - - def seekable(self) -> bool: - return False - - def _get_next_buffer(self) -> bool: - result_js = _run_sync_with_timeout( - self.js_read_stream.read(), - self.timeout, - self.js_abort_controller, - request=self.request, - response=self.response, - ) - if result_js.done: - self._is_done = True - return False - else: - self.current_buffer = result_js.value.to_py() - self.current_buffer_pos = 0 - return True - - def readinto(self, byte_obj: Buffer) -> int: - if self.current_buffer is None: - if not self._get_next_buffer() or self.current_buffer is None: - self.close() - return 0 - ret_length = min( - len(byte_obj), len(self.current_buffer) - self.current_buffer_pos - ) - byte_obj[0:ret_length] = self.current_buffer[ - self.current_buffer_pos : self.current_buffer_pos + ret_length - ] - self.current_buffer_pos += ret_length - if self.current_buffer_pos == len(self.current_buffer): - self.current_buffer = None - return ret_length - - -# check if we are in a worker or not -def is_in_browser_main_thread() -> bool: - return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window - - -def is_cross_origin_isolated() -> bool: - return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated - - -def is_in_node() -> bool: - return ( - hasattr(js, "process") - and hasattr(js.process, "release") - and hasattr(js.process.release, "name") - and js.process.release.name == "node" - ) - - -def is_worker_available() -> bool: - return hasattr(js, "Worker") and hasattr(js, "Blob") - - -_fetcher: _StreamingFetcher | None = None - -if is_worker_available() and ( - (is_cross_origin_isolated() and not is_in_browser_main_thread()) - and (not is_in_node()) -): - _fetcher = _StreamingFetcher() -else: - _fetcher = None - - -NODE_JSPI_ERROR = ( - "urllib3 only works in Node.js with pyodide.runPythonAsync" - " and requires the flag --experimental-wasm-stack-switching in " - " versions of node <24." -) - - -def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: - if has_jspi(): - return send_jspi_request(request, True) - elif is_in_node(): - raise _RequestError( - message=NODE_JSPI_ERROR, - request=request, - response=None, - ) - - if _fetcher and streaming_ready(): - return _fetcher.send(request) - else: - _show_streaming_warning() - return None - - -_SHOWN_TIMEOUT_WARNING = False - - -def _show_timeout_warning() -> None: - global _SHOWN_TIMEOUT_WARNING - if not _SHOWN_TIMEOUT_WARNING: - _SHOWN_TIMEOUT_WARNING = True - message = "Warning: Timeout is not available on main browser thread" - js.console.warn(message) - - -_SHOWN_STREAMING_WARNING = False - - -def _show_streaming_warning() -> None: - global _SHOWN_STREAMING_WARNING - if not _SHOWN_STREAMING_WARNING: - _SHOWN_STREAMING_WARNING = True - message = "Can't stream HTTP requests because: \n" - if not is_cross_origin_isolated(): - message += " Page is not cross-origin isolated\n" - if is_in_browser_main_thread(): - message += " Python is running in main browser thread\n" - if not is_worker_available(): - message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in - if streaming_ready() is False: - message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch -is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" - from js import console - - console.warn(message) - - -def send_request(request: EmscriptenRequest) -> EmscriptenResponse: - if has_jspi(): - return send_jspi_request(request, False) - elif is_in_node(): - raise _RequestError( - message=NODE_JSPI_ERROR, - request=request, - response=None, - ) - try: - js_xhr = js.XMLHttpRequest.new() - - if not is_in_browser_main_thread(): - js_xhr.responseType = "arraybuffer" - if request.timeout: - js_xhr.timeout = int(request.timeout * 1000) - else: - js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") - if request.timeout: - # timeout isn't available on the main thread - show a warning in console - # if it is set - _show_timeout_warning() - - js_xhr.open(request.method, request.url, False) - for name, value in request.headers.items(): - if name.lower() not in HEADERS_TO_IGNORE: - js_xhr.setRequestHeader(name, value) - - js_xhr.send(to_js(request.body)) - - headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) - - if not is_in_browser_main_thread(): - body = js_xhr.response.to_py().tobytes() - else: - body = js_xhr.response.encode("ISO-8859-15") - return EmscriptenResponse( - status_code=js_xhr.status, headers=headers, body=body, request=request - ) - except JsException as err: - if err.name == "TimeoutError": - raise _TimeoutError(err.message, request=request) - elif err.name == "NetworkError": - raise _RequestError(err.message, request=request) - else: - # general http error - raise _RequestError(err.message, request=request) - - -def send_jspi_request( - request: EmscriptenRequest, streaming: bool -) -> EmscriptenResponse: - """ - Send a request using WebAssembly JavaScript Promise Integration - to wrap the asynchronous JavaScript fetch api (experimental). - - :param request: - Request to send - - :param streaming: - Whether to stream the response - - :return: The response object - :rtype: EmscriptenResponse - """ - timeout = request.timeout - js_abort_controller = js.AbortController.new() - headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} - req_body = request.body - fetch_data = { - "headers": headers, - "body": to_js(req_body), - "method": request.method, - "signal": js_abort_controller.signal, - } - # Node.js returns the whole response (unlike opaqueredirect in browsers), - # so urllib3 can set `redirect: manual` to control redirects itself. - # https://stackoverflow.com/a/78524615 - if _is_node_js(): - fetch_data["redirect"] = "manual" - # Call JavaScript fetch (async api, returns a promise) - fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) - # Now suspend WebAssembly until we resolve that promise - # or time out. - response_js = _run_sync_with_timeout( - fetcher_promise_js, - timeout, - js_abort_controller, - request=request, - response=None, - ) - headers = {} - header_iter = response_js.headers.entries() - while True: - iter_value_js = header_iter.next() - if getattr(iter_value_js, "done", False): - break - else: - headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) - status_code = response_js.status - body: bytes | io.RawIOBase = b"" - - response = EmscriptenResponse( - status_code=status_code, headers=headers, body=b"", request=request - ) - if streaming: - # get via inputstream - if response_js.body is not None: - # get a reader from the fetch response - body_stream_js = response_js.body.getReader() - body = _JSPIReadStream( - body_stream_js, timeout, request, response, js_abort_controller - ) - else: - # get directly via arraybuffer - # n.b. this is another async JavaScript call. - body = _run_sync_with_timeout( - response_js.arrayBuffer(), - timeout, - js_abort_controller, - request=request, - response=response, - ).to_py() - response.body = body - return response - - -def _run_sync_with_timeout( - promise: Any, - timeout: float, - js_abort_controller: Any, - request: EmscriptenRequest | None, - response: EmscriptenResponse | None, -) -> Any: - """ - Await a JavaScript promise synchronously with a timeout which is implemented - via the AbortController - - :param promise: - Javascript promise to await - - :param timeout: - Timeout in seconds - - :param js_abort_controller: - A JavaScript AbortController object, used on timeout - - :param request: - The request being handled - - :param response: - The response being handled (if it exists yet) - - :raises _TimeoutError: If the request times out - :raises _RequestError: If the request raises a JavaScript exception - - :return: The result of awaiting the promise. - """ - timer_id = None - if timeout > 0: - timer_id = js.setTimeout( - js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) - ) - try: - from pyodide.ffi import run_sync - - # run_sync here uses WebAssembly JavaScript Promise Integration to - # suspend python until the JavaScript promise resolves. - return run_sync(promise) - except JsException as err: - if err.name == "AbortError": - raise _TimeoutError( - message="Request timed out", request=request, response=response - ) - else: - raise _RequestError(message=err.message, request=request, response=response) - finally: - if timer_id is not None: - js.clearTimeout(timer_id) - - -def has_jspi() -> bool: - """ - Return true if jspi can be used. - - This requires both browser support and also WebAssembly - to be in the correct state - i.e. that the javascript - call into python was async not sync. - - :return: True if jspi can be used. - :rtype: bool - """ - try: - from pyodide.ffi import can_run_sync, run_sync # noqa: F401 - - return bool(can_run_sync()) - except ImportError: - return False - - -def _is_node_js() -> bool: - """ - Check if we are in Node.js. - - :return: True if we are in Node.js. - :rtype: bool - """ - return ( - hasattr(js, "process") - and hasattr(js.process, "release") - # According to the Node.js documentation, the release name is always "node". - and js.process.release.name == "node" - ) - - -def streaming_ready() -> bool | None: - if _fetcher: - return _fetcher.streaming_ready - else: - return None # no fetcher, return None to signify that - - -async def wait_for_streaming_ready() -> bool: - if _fetcher: - await _fetcher.js_worker_ready_promise - return True - else: - return False diff --git a/newrelic/packages/urllib3/contrib/emscripten/request.py b/newrelic/packages/urllib3/contrib/emscripten/request.py deleted file mode 100644 index e692e692bd..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/request.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass, field - -from ..._base_connection import _TYPE_BODY - - -@dataclass -class EmscriptenRequest: - method: str - url: str - params: dict[str, str] | None = None - body: _TYPE_BODY | None = None - headers: dict[str, str] = field(default_factory=dict) - timeout: float = 0 - decode_content: bool = True - - def set_header(self, name: str, value: str) -> None: - self.headers[name.capitalize()] = value - - def set_body(self, body: _TYPE_BODY | None) -> None: - self.body = body diff --git a/newrelic/packages/urllib3/contrib/emscripten/response.py b/newrelic/packages/urllib3/contrib/emscripten/response.py deleted file mode 100644 index cb1088a182..0000000000 --- a/newrelic/packages/urllib3/contrib/emscripten/response.py +++ /dev/null @@ -1,277 +0,0 @@ -from __future__ import annotations - -import json as _json -import logging -import typing -from contextlib import contextmanager -from dataclasses import dataclass -from http.client import HTTPException as HTTPException -from io import BytesIO, IOBase - -from ...exceptions import InvalidHeader, TimeoutError -from ...response import BaseHTTPResponse -from ...util.retry import Retry -from .request import EmscriptenRequest - -if typing.TYPE_CHECKING: - from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection - -log = logging.getLogger(__name__) - - -@dataclass -class EmscriptenResponse: - status_code: int - headers: dict[str, str] - body: IOBase | bytes - request: EmscriptenRequest - - -class EmscriptenHttpResponseWrapper(BaseHTTPResponse): - def __init__( - self, - internal_response: EmscriptenResponse, - url: str | None = None, - connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, - ): - self._pool = None # set by pool class - self._body = None - self._response = internal_response - self._url = url - self._connection = connection - self._closed = False - super().__init__( - headers=internal_response.headers, - status=internal_response.status_code, - request_url=url, - version=0, - version_string="HTTP/?", - reason="", - decode_content=True, - ) - self.length_remaining = self._init_length(self._response.request.method) - self.length_is_certain = False - - @property - def url(self) -> str | None: - return self._url - - @url.setter - def url(self, url: str | None) -> None: - self._url = url - - @property - def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: - return self._connection - - @property - def retries(self) -> Retry | None: - return self._retries - - @retries.setter - def retries(self, retries: Retry | None) -> None: - # Override the request_url if retries has a redirect location. - self._retries = retries - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Generator[bytes]: - """ - A generator wrapper for the read() method. A call will block until - ``amt`` bytes have been read from the connection or until the - connection is closed. - - :param amt: - How much of the content to read. The generator will return up to - much data per iteration, but may return less. This is particularly - likely when using compressed data. However, the empty string will - never be returned. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - while True: - data = self.read(amt=amt, decode_content=decode_content) - - if data: - yield data - else: - break - - def _init_length(self, request_method: str | None) -> int | None: - length: int | None - content_length: str | None = self.headers.get("content-length") - - if content_length is not None: - try: - # RFC 7230 section 3.3.2 specifies multiple content lengths can - # be sent in a single Content-Length header - # (e.g. Content-Length: 42, 42). This line ensures the values - # are all valid ints and that as long as the `set` length is 1, - # all values are the same. Otherwise, the header is invalid. - lengths = {int(val) for val in content_length.split(",")} - if len(lengths) > 1: - raise InvalidHeader( - "Content-Length contained multiple " - "unmatching values (%s)" % content_length - ) - length = lengths.pop() - except ValueError: - length = None - else: - if length < 0: - length = None - - else: # if content_length is None - length = None - - # Check for responses that shouldn't include a body - if ( - self.status in (204, 304) - or 100 <= self.status < 200 - or request_method == "HEAD" - ): - length = 0 - - return length - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, # ignored because browser decodes always - cache_content: bool = False, - ) -> bytes: - if ( - self._closed - or self._response is None - or (isinstance(self._response.body, IOBase) and self._response.body.closed) - ): - return b"" - - with self._error_catcher(): - # body has been preloaded as a string by XmlHttpRequest - if not isinstance(self._response.body, IOBase): - self.length_remaining = len(self._response.body) - self.length_is_certain = True - # wrap body in IOStream - self._response.body = BytesIO(self._response.body) - if amt is not None and amt >= 0: - # don't cache partial content - cache_content = False - data = self._response.body.read(amt) - else: # read all we can (and cache it) - data = self._response.body.read() - if cache_content: - self._body = data - if self.length_remaining is not None: - self.length_remaining = max(self.length_remaining - len(data), 0) - if len(data) == 0 or ( - self.length_is_certain and self.length_remaining == 0 - ): - # definitely finished reading, close response stream - self._response.body.close() - return typing.cast(bytes, data) - - def read_chunked( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> typing.Generator[bytes]: - # chunked is handled by browser - while True: - bytes = self.read(amt, decode_content) - if not bytes: - break - yield bytes - - def release_conn(self) -> None: - if not self._pool or not self._connection: - return None - - self._pool._put_conn(self._connection) - self._connection = None - - def drain_conn(self) -> None: - self.close() - - @property - def data(self) -> bytes: - if self._body: - return self._body - else: - return self.read(cache_content=True) - - def json(self) -> typing.Any: - """ - Deserializes the body of the HTTP response as a Python object. - - The body of the HTTP response must be encoded using UTF-8, as per - `RFC 8529 Section 8.1 `_. - - To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to - your custom decoder instead. - - If the body of the HTTP response is not decodable to UTF-8, a - `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a - valid JSON document, a `json.JSONDecodeError` will be raised. - - Read more :ref:`here `. - - :returns: The body of the HTTP response as a Python object. - """ - data = self.data.decode("utf-8") - return _json.loads(data) - - def close(self) -> None: - if not self._closed: - if isinstance(self._response.body, IOBase): - self._response.body.close() - if self._connection: - self._connection.close() - self._connection = None - self._closed = True - - @contextmanager - def _error_catcher(self) -> typing.Generator[None]: - """ - Catch Emscripten specific exceptions thrown by fetch.py, - instead re-raising urllib3 variants, so that low-level exceptions - are not leaked in the high-level api. - - On exit, release the connection back to the pool. - """ - from .fetch import _RequestError, _TimeoutError # avoid circular import - - clean_exit = False - - try: - yield - # If no exception is thrown, we should avoid cleaning up - # unnecessarily. - clean_exit = True - except _TimeoutError as e: - raise TimeoutError(str(e)) - except _RequestError as e: - raise HTTPException(str(e)) - finally: - # If we didn't terminate cleanly, we need to throw away our - # connection. - if not clean_exit: - # The response may not be closed but we're not going to use it - # anymore so close it now - if ( - isinstance(self._response.body, IOBase) - and not self._response.body.closed - ): - self._response.body.close() - # release the connection back to the pool - self.release_conn() - else: - # If we have read everything from the response stream, - # return the connection back to the pool. - if ( - isinstance(self._response.body, IOBase) - and self._response.body.closed - ): - self.release_conn() diff --git a/newrelic/packages/urllib3/contrib/ntlmpool.py b/newrelic/packages/urllib3/contrib/ntlmpool.py new file mode 100644 index 0000000000..471665754e --- /dev/null +++ b/newrelic/packages/urllib3/contrib/ntlmpool.py @@ -0,0 +1,130 @@ +""" +NTLM authenticating pool, contributed by erikcederstran + +Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 +""" +from __future__ import absolute_import + +import warnings +from logging import getLogger + +from ntlm import ntlm + +from .. import HTTPSConnectionPool +from ..packages.six.moves.http_client import HTTPSConnection + +warnings.warn( + "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed " + "in urllib3 v2.0 release, urllib3 is not able to support it properly due " + "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. " + "If you are a user of this module please comment in the mentioned issue.", + DeprecationWarning, +) + +log = getLogger(__name__) + + +class NTLMConnectionPool(HTTPSConnectionPool): + """ + Implements an NTLM authentication version of an urllib3 connection pool + """ + + scheme = "https" + + def __init__(self, user, pw, authurl, *args, **kwargs): + """ + authurl is a random URL on the server that is protected by NTLM. + user is the Windows user, probably in the DOMAIN\\username format. + pw is the password for the user. + """ + super(NTLMConnectionPool, self).__init__(*args, **kwargs) + self.authurl = authurl + self.rawuser = user + user_parts = user.split("\\", 1) + self.domain = user_parts[0].upper() + self.user = user_parts[1] + self.pw = pw + + def _new_conn(self): + # Performs the NTLM handshake that secures the connection. The socket + # must be kept open while requests are performed. + self.num_connections += 1 + log.debug( + "Starting NTLM HTTPS connection no. %d: https://%s%s", + self.num_connections, + self.host, + self.authurl, + ) + + headers = {"Connection": "Keep-Alive"} + req_header = "Authorization" + resp_header = "www-authenticate" + + conn = HTTPSConnection(host=self.host, port=self.port) + + # Send negotiation message + headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE( + self.rawuser + ) + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + reshdr = dict(res.headers) + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", reshdr) + log.debug("Response data: %s [...]", res.read(100)) + + # Remove the reference to the socket, so that it can not be closed by + # the response object (we want to keep the socket open) + res.fp = None + + # Server should respond with a challenge message + auth_header_values = reshdr[resp_header].split(", ") + auth_header_value = None + for s in auth_header_values: + if s[:5] == "NTLM ": + auth_header_value = s[5:] + if auth_header_value is None: + raise Exception( + "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header]) + ) + + # Send authentication message + ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE( + auth_header_value + ) + auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE( + ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags + ) + headers[req_header] = "NTLM %s" % auth_msg + log.debug("Request headers: %s", headers) + conn.request("GET", self.authurl, None, headers) + res = conn.getresponse() + log.debug("Response status: %s %s", res.status, res.reason) + log.debug("Response headers: %s", dict(res.headers)) + log.debug("Response data: %s [...]", res.read()[:100]) + if res.status != 200: + if res.status == 401: + raise Exception("Server rejected request: wrong username or password") + raise Exception("Wrong server response: %s %s" % (res.status, res.reason)) + + res.fp = None + log.debug("Connection established") + return conn + + def urlopen( + self, + method, + url, + body=None, + headers=None, + retries=3, + redirect=True, + assert_same_host=True, + ): + if headers is None: + headers = {} + headers["Connection"] = "Keep-Alive" + return super(NTLMConnectionPool, self).urlopen( + method, url, body, headers, retries, redirect, assert_same_host + ) diff --git a/newrelic/packages/urllib3/contrib/pyopenssl.py b/newrelic/packages/urllib3/contrib/pyopenssl.py index 8e05d3d785..1ed214b1d7 100644 --- a/newrelic/packages/urllib3/contrib/pyopenssl.py +++ b/newrelic/packages/urllib3/contrib/pyopenssl.py @@ -1,17 +1,17 @@ """ -Module for using pyOpenSSL as a TLS backend. This module was relevant before -the standard library ``ssl`` module supported SNI, but now that we've dropped -support for Python 2.7 all relevant Python versions support SNI so -**this module is no longer recommended**. +TLS with SNI_-support for Python 2. Follow these instructions if you would +like to verify TLS certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. This needs the following packages installed: * `pyOpenSSL`_ (tested with 16.0.0) * `cryptography`_ (minimum 1.3.4, from pyopenssl) -* `idna`_ (minimum 2.0) +* `idna`_ (minimum 2.0, from cryptography) -However, pyOpenSSL depends on cryptography, so while we use all three directly here we -end up having relatively few packages required. +However, pyopenssl depends on cryptography, which depends on idna, so while we +use all three directly here we end up having relatively few packages required. You can install them with the following command: @@ -33,46 +33,75 @@ except ImportError: pass +Now you can use :mod:`urllib3` as you normally would, and it will support SNI +when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +compression in Python 2 (see `CRIME attack`_). + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) .. _pyopenssl: https://www.pyopenssl.org .. _cryptography: https://cryptography.io .. _idna: https://github.com/kjd/idna """ +from __future__ import absolute_import -from __future__ import annotations - -import OpenSSL.SSL # type: ignore[import-not-found] +import OpenSSL.crypto +import OpenSSL.SSL from cryptography import x509 +from cryptography.hazmat.backends.openssl import backend as openssl_backend try: - from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] + from cryptography.x509 import UnsupportedExtension except ImportError: # UnsupportedExtension is gone in cryptography >= 2.1.0 - class UnsupportedExtension(Exception): # type: ignore[no-redef] + class UnsupportedExtension(Exception): pass -import logging -import ssl -import typing from io import BytesIO -from socket import socket as socket_cls +from socket import error as SocketError from socket import timeout -from .. import util +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile -if typing.TYPE_CHECKING: - from OpenSSL.crypto import X509 # type: ignore[import-not-found] +import logging +import ssl +import sys +import warnings +from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT + +warnings.warn( + "'urllib3.contrib.pyopenssl' module is deprecated and will be removed " + "in a future release of urllib3 2.x. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2680", + category=DeprecationWarning, + stacklevel=2, +) __all__ = ["inject_into_urllib3", "extract_from_urllib3"] +# SNI always works. +HAS_SNI = True + # Map from urllib3 to PyOpenSSL compatible parameter-values. -_openssl_versions: dict[int, int] = { - util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] - util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] +_openssl_versions = { + util.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, + PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } +if hasattr(ssl, "PROTOCOL_SSLv3") and hasattr(OpenSSL.SSL, "SSLv3_METHOD"): + _openssl_versions[ssl.PROTOCOL_SSLv3] = OpenSSL.SSL.SSLv3_METHOD + if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD @@ -86,77 +115,43 @@ class UnsupportedExtension(Exception): # type: ignore[no-redef] ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -_openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()} - -# The SSLvX values are the most likely to be missing in the future -# but we check them all just to be sure. -_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( - OpenSSL.SSL, "OP_NO_SSLv3", 0 -) -_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) -_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) -_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) -_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) - -_openssl_to_ssl_minimum_version: dict[int, int] = { - ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, - ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, - ssl.TLSVersion.TLSv1_3: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 - ), - ssl.TLSVersion.MAXIMUM_SUPPORTED: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 - ), -} -_openssl_to_ssl_maximum_version: dict[int, int] = { - ssl.TLSVersion.MINIMUM_SUPPORTED: ( - _OP_NO_SSLv2_OR_SSLv3 - | _OP_NO_TLSv1 - | _OP_NO_TLSv1_1 - | _OP_NO_TLSv1_2 - | _OP_NO_TLSv1_3 - ), - ssl.TLSVersion.TLSv1: ( - _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 - ), - ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, - ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, - ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, - ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, -} +_openssl_to_stdlib_verify = dict((v, k) for k, v in _stdlib_to_openssl_verify.items()) # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 +orig_util_HAS_SNI = util.HAS_SNI orig_util_SSLContext = util.ssl_.SSLContext log = logging.getLogger(__name__) -def inject_into_urllib3() -> None: +def inject_into_urllib3(): "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." _validate_dependencies_met() - util.SSLContext = PyOpenSSLContext # type: ignore[assignment] - util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.SSLContext = PyOpenSSLContext + util.ssl_.SSLContext = PyOpenSSLContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI util.IS_PYOPENSSL = True util.ssl_.IS_PYOPENSSL = True -def extract_from_urllib3() -> None: +def extract_from_urllib3(): "Undo monkey-patching by :func:`inject_into_urllib3`." util.SSLContext = orig_util_SSLContext util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI util.IS_PYOPENSSL = False util.ssl_.IS_PYOPENSSL = False -def _validate_dependencies_met() -> None: +def _validate_dependencies_met(): """ Verifies that PyOpenSSL's package-level dependencies have been met. Throws `ImportError` if they are not met. @@ -182,7 +177,7 @@ def _validate_dependencies_met() -> None: ) -def _dnsname_to_stdlib(name: str) -> str | None: +def _dnsname_to_stdlib(name): """ Converts a dNSName SubjectAlternativeName field to the form used by the standard library on the given Python version. @@ -196,7 +191,7 @@ def _dnsname_to_stdlib(name: str) -> str | None: the name given should be skipped. """ - def idna_encode(name: str) -> bytes | None: + def idna_encode(name): """ Borrowed wholesale from the Python Cryptography Project. It turns out that we can't just safely call `idna.encode`: it can explode for @@ -205,7 +200,7 @@ def idna_encode(name: str) -> bytes | None: import idna try: - for prefix in ["*.", "."]: + for prefix in [u"*.", u"."]: if name.startswith(prefix): name = name[len(prefix) :] return prefix.encode("ascii") + idna.encode(name) @@ -217,17 +212,24 @@ def idna_encode(name: str) -> bytes | None: if ":" in name: return name - encoded_name = idna_encode(name) - if encoded_name is None: + name = idna_encode(name) + if name is None: return None - return encoded_name.decode("utf-8") + elif sys.version_info >= (3, 0): + name = name.decode("utf-8") + return name -def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: +def get_subj_alt_name(peer_cert): """ Given an PyOpenSSL certificate, provides all the subject alternative names. """ - cert = peer_cert.to_cryptography() + # Pass the cert to cryptography, which has much better APIs for this. + if hasattr(peer_cert, "to_cryptography"): + cert = peer_cert.to_cryptography() + else: + der = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, peer_cert) + cert = x509.load_der_x509_certificate(der, openssl_backend) # We want to find the SAN extension. Ask Cryptography to locate it (it's # faster than looping in Python) @@ -271,94 +273,93 @@ def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: return names -class WrappedSocket: - """API-compatibility wrapper for Python OpenSSL's Connection-class.""" +class WrappedSocket(object): + """API-compatibility wrapper for Python OpenSSL's Connection-class. - def __init__( - self, - connection: OpenSSL.SSL.Connection, - socket: socket_cls, - suppress_ragged_eofs: bool = True, - ) -> None: + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + """ + + def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection self.socket = socket self.suppress_ragged_eofs = suppress_ragged_eofs - self._io_refs = 0 + self._makefile_refs = 0 self._closed = False - def fileno(self) -> int: + def fileno(self): return self.socket.fileno() # Copy-pasted from Python 3.5 source code - def _decref_socketios(self) -> None: - if self._io_refs > 0: - self._io_refs -= 1 + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 if self._closed: self.close() - def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + def recv(self, *args, **kwargs): try: data = self.connection.recv(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return b"" else: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return b"" else: raise - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout("The read operation timed out") from e + raise timeout("The read operation timed out") else: return self.recv(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"read error: {e!r}") from e + raise ssl.SSLError("read error: %r" % e) else: - return data # type: ignore[no-any-return] + return data - def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + def recv_into(self, *args, **kwargs): try: - return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + return self.connection.recv_into(*args, **kwargs) except OpenSSL.SSL.SysCallError as e: if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): return 0 else: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) except OpenSSL.SSL.ZeroReturnError: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return 0 else: raise - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(self.socket, self.socket.gettimeout()): - raise timeout("The read operation timed out") from e + raise timeout("The read operation timed out") else: return self.recv_into(*args, **kwargs) # TLS 1.3 post-handshake authentication except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"read error: {e!r}") from e + raise ssl.SSLError("read error: %r" % e) - def settimeout(self, timeout: float) -> None: + def settimeout(self, timeout): return self.socket.settimeout(timeout) - def _send_until_done(self, data: bytes) -> int: + def _send_until_done(self, data): while True: try: - return self.connection.send(data) # type: ignore[no-any-return] - except OpenSSL.SSL.WantWriteError as e: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: if not util.wait_for_write(self.socket, self.socket.gettimeout()): - raise timeout() from e + raise timeout() continue except OpenSSL.SSL.SysCallError as e: - raise OSError(e.args[0], str(e)) from e + raise SocketError(str(e)) - def sendall(self, data: bytes) -> None: + def sendall(self, data): total_sent = 0 while total_sent < len(data): sent = self._send_until_done( @@ -366,151 +367,135 @@ def sendall(self, data: bytes) -> None: ) total_sent += sent - def shutdown(self, how: int) -> None: - try: - self.connection.shutdown() - except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"shutdown error: {e!r}") from e - - def close(self) -> None: - self._closed = True - if self._io_refs <= 0: - self._real_close() + def shutdown(self): + # FIXME rethrow compatible exceptions should we ever use this + self.connection.shutdown() - def _real_close(self) -> None: - try: - return self.connection.close() # type: ignore[no-any-return] - except OpenSSL.SSL.Error: - return + def close(self): + if self._makefile_refs < 1: + try: + self._closed = True + return self.connection.close() + except OpenSSL.SSL.Error: + return + else: + self._makefile_refs -= 1 - def getpeercert( - self, binary_form: bool = False - ) -> dict[str, list[typing.Any]] | None: + def getpeercert(self, binary_form=False): x509 = self.connection.get_peer_certificate() if not x509: - return x509 # type: ignore[no-any-return] + return x509 if binary_form: - return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) return { - "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subject": ((("commonName", x509.get_subject().CN),),), "subjectAltName": get_subj_alt_name(x509), } - def version(self) -> str: - return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + def version(self): + return self.connection.get_protocol_version_name() - def selected_alpn_protocol(self) -> str | None: - alpn_proto = self.connection.get_alpn_proto_negotiated() - return alpn_proto.decode() if alpn_proto else None + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 -WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] +if _fileobject: # Platform-specific: Python 2 + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) -class PyOpenSSLContext: +else: # Platform-specific: Python 3 + makefile = backport_makefile + +WrappedSocket.makefile = makefile + + +class PyOpenSSLContext(object): """ I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible for translating the interface of the standard library ``SSLContext`` object to calls into PyOpenSSL. """ - def __init__(self, protocol: int) -> None: + def __init__(self, protocol): self.protocol = _openssl_versions[protocol] self._ctx = OpenSSL.SSL.Context(self.protocol) self._options = 0 self.check_hostname = False - self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED - self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED - self._verify_flags: int = ssl.VERIFY_X509_TRUSTED_FIRST @property - def options(self) -> int: + def options(self): return self._options @options.setter - def options(self, value: int) -> None: + def options(self, value): self._options = value - self._set_ctx_options() - - @property - def verify_flags(self) -> int: - return self._verify_flags - - @verify_flags.setter - def verify_flags(self, value: int) -> None: - self._verify_flags = value - self._ctx.get_cert_store().set_flags(self._verify_flags) + self._ctx.set_options(value) @property - def verify_mode(self) -> int: + def verify_mode(self): return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] @verify_mode.setter - def verify_mode(self, value: ssl.VerifyMode) -> None: + def verify_mode(self, value): self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) - def set_default_verify_paths(self) -> None: + def set_default_verify_paths(self): self._ctx.set_default_verify_paths() - def set_ciphers(self, ciphers: bytes | str) -> None: - if isinstance(ciphers, str): + def set_ciphers(self, ciphers): + if isinstance(ciphers, six.text_type): ciphers = ciphers.encode("utf-8") self._ctx.set_cipher_list(ciphers) - def load_verify_locations( - self, - cafile: str | None = None, - capath: str | None = None, - cadata: bytes | None = None, - ) -> None: + def load_verify_locations(self, cafile=None, capath=None, cadata=None): if cafile is not None: - cafile = cafile.encode("utf-8") # type: ignore[assignment] + cafile = cafile.encode("utf-8") if capath is not None: - capath = capath.encode("utf-8") # type: ignore[assignment] + capath = capath.encode("utf-8") try: self._ctx.load_verify_locations(cafile, capath) if cadata is not None: self._ctx.load_verify_locations(BytesIO(cadata)) except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e + raise ssl.SSLError("unable to load trusted certificates: %r" % e) - def load_cert_chain( - self, - certfile: str, - keyfile: str | None = None, - password: str | None = None, - ) -> None: - try: - self._ctx.use_certificate_chain_file(certfile) - if password is not None: - if not isinstance(password, bytes): - password = password.encode("utf-8") # type: ignore[assignment] - self._ctx.set_passwd_cb(lambda *_: password) - self._ctx.use_privatekey_file(keyfile or certfile) - except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, six.binary_type): + password = password.encode("utf-8") + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) - def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: - protocols = [util.util.to_bytes(p, "ascii") for p in protocols] - return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + def set_alpn_protocols(self, protocols): + protocols = [six.ensure_binary(p) for p in protocols] + return self._ctx.set_alpn_protos(protocols) def wrap_socket( self, - sock: socket_cls, - server_side: bool = False, - do_handshake_on_connect: bool = True, - suppress_ragged_eofs: bool = True, - server_hostname: bytes | str | None = None, - ) -> WrappedSocket: + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): cnx = OpenSSL.SSL.Connection(self._ctx, sock) - # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 - if server_hostname and not util.ssl_.is_ipaddress(server_hostname): - if isinstance(server_hostname, str): - server_hostname = server_hostname.encode("utf-8") + if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3 + server_hostname = server_hostname.encode("utf-8") + + if server_hostname is not None: cnx.set_tlsext_host_name(server_hostname) cnx.set_connect_state() @@ -518,47 +503,16 @@ def wrap_socket( while True: try: cnx.do_handshake() - except OpenSSL.SSL.WantReadError as e: + except OpenSSL.SSL.WantReadError: if not util.wait_for_read(sock, sock.gettimeout()): - raise timeout("select timed out") from e + raise timeout("select timed out") continue except OpenSSL.SSL.Error as e: - raise ssl.SSLError(f"bad handshake: {e!r}") from e + raise ssl.SSLError("bad handshake: %r" % e) break return WrappedSocket(cnx, sock) - def _set_ctx_options(self) -> None: - self._ctx.set_options( - self._options - | _openssl_to_ssl_minimum_version[self._minimum_version] - | _openssl_to_ssl_maximum_version[self._maximum_version] - ) - - @property - def minimum_version(self) -> int: - return self._minimum_version - @minimum_version.setter - def minimum_version(self, minimum_version: int) -> None: - self._minimum_version = minimum_version - self._set_ctx_options() - - @property - def maximum_version(self) -> int: - return self._maximum_version - - @maximum_version.setter - def maximum_version(self, maximum_version: int) -> None: - self._maximum_version = maximum_version - self._set_ctx_options() - - -def _verify_callback( - cnx: OpenSSL.SSL.Connection, - x509: X509, - err_no: int, - err_depth: int, - return_code: int, -) -> bool: +def _verify_callback(cnx, x509, err_no, err_depth, return_code): return err_no == 0 diff --git a/newrelic/packages/urllib3/contrib/securetransport.py b/newrelic/packages/urllib3/contrib/securetransport.py new file mode 100644 index 0000000000..e311c0c899 --- /dev/null +++ b/newrelic/packages/urllib3/contrib/securetransport.py @@ -0,0 +1,920 @@ +""" +SecureTranport support for urllib3 via ctypes. + +This makes platform-native TLS available to urllib3 users on macOS without the +use of a compiler. This is an important feature because the Python Package +Index is moving to become a TLSv1.2-or-higher server, and the default OpenSSL +that ships with macOS is not capable of doing TLSv1.2. The only way to resolve +this is to give macOS users an alternative solution to the problem, and that +solution is to use SecureTransport. + +We use ctypes here because this solution must not require a compiler. That's +because pip is not allowed to require a compiler either. + +This is not intended to be a seriously long-term solution to this problem. +The hope is that PEP 543 will eventually solve this issue for us, at which +point we can retire this contrib module. But in the short term, we need to +solve the impending tire fire that is Python on Mac without this kind of +contrib module. So...here we are. + +To use this module, simply import and inject it:: + + import urllib3.contrib.securetransport + urllib3.contrib.securetransport.inject_into_urllib3() + +Happy TLSing! + +This code is a bastardised version of the code found in Will Bond's oscrypto +library. An enormous debt is owed to him for blazing this trail for us. For +that reason, this code should be considered to be covered both by urllib3's +license and by oscrypto's: + +.. code-block:: + + Copyright (c) 2015-2016 Will Bond + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +""" +from __future__ import absolute_import + +import contextlib +import ctypes +import errno +import os.path +import shutil +import socket +import ssl +import struct +import threading +import weakref + +from .. import util +from ..packages import six +from ..util.ssl_ import PROTOCOL_TLS_CLIENT +from ._securetransport.bindings import CoreFoundation, Security, SecurityConst +from ._securetransport.low_level import ( + _assert_no_error, + _build_tls_unknown_ca_alert, + _cert_array_from_pem, + _create_cfstring_array, + _load_client_cert_chain, + _temporary_keychain, +) + +try: # Platform-specific: Python 2 + from socket import _fileobject +except ImportError: # Platform-specific: Python 3 + _fileobject = None + from ..packages.backports.makefile import backport_makefile + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# SNI always works +HAS_SNI = True + +orig_util_HAS_SNI = util.HAS_SNI +orig_util_SSLContext = util.ssl_.SSLContext + +# This dictionary is used by the read callback to obtain a handle to the +# calling wrapped socket. This is a pretty silly approach, but for now it'll +# do. I feel like I should be able to smuggle a handle to the wrapped socket +# directly in the SSLConnectionRef, but for now this approach will work I +# guess. +# +# We need to lock around this structure for inserts, but we don't do it for +# reads/writes in the callbacks. The reasoning here goes as follows: +# +# 1. It is not possible to call into the callbacks before the dictionary is +# populated, so once in the callback the id must be in the dictionary. +# 2. The callbacks don't mutate the dictionary, they only read from it, and +# so cannot conflict with any of the insertions. +# +# This is good: if we had to lock in the callbacks we'd drastically slow down +# the performance of this code. +_connection_refs = weakref.WeakValueDictionary() +_connection_ref_lock = threading.Lock() + +# Limit writes to 16kB. This is OpenSSL's limit, but we'll cargo-cult it over +# for no better reason than we need *a* limit, and this one is right there. +SSL_WRITE_BLOCKSIZE = 16384 + +# This is our equivalent of util.ssl_.DEFAULT_CIPHERS, but expanded out to +# individual cipher suites. We need to do this because this is how +# SecureTransport wants them. +CIPHER_SUITES = [ + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + SecurityConst.TLS_AES_256_GCM_SHA384, + SecurityConst.TLS_AES_128_GCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_GCM_SHA384, + SecurityConst.TLS_RSA_WITH_AES_128_GCM_SHA256, + SecurityConst.TLS_AES_128_CCM_8_SHA256, + SecurityConst.TLS_AES_128_CCM_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA256, + SecurityConst.TLS_RSA_WITH_AES_256_CBC_SHA, + SecurityConst.TLS_RSA_WITH_AES_128_CBC_SHA, +] + +# Basically this is simple: for PROTOCOL_SSLv23 we turn it into a low of +# TLSv1 and a high of TLSv1.2. For everything else, we pin to that version. +# TLSv1 to 1.2 are supported on macOS 10.8+ +_protocol_to_min_max = { + util.PROTOCOL_TLS: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), + PROTOCOL_TLS_CLIENT: (SecurityConst.kTLSProtocol1, SecurityConst.kTLSProtocol12), +} + +if hasattr(ssl, "PROTOCOL_SSLv2"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv2] = ( + SecurityConst.kSSLProtocol2, + SecurityConst.kSSLProtocol2, + ) +if hasattr(ssl, "PROTOCOL_SSLv3"): + _protocol_to_min_max[ssl.PROTOCOL_SSLv3] = ( + SecurityConst.kSSLProtocol3, + SecurityConst.kSSLProtocol3, + ) +if hasattr(ssl, "PROTOCOL_TLSv1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1] = ( + SecurityConst.kTLSProtocol1, + SecurityConst.kTLSProtocol1, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_1"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_1] = ( + SecurityConst.kTLSProtocol11, + SecurityConst.kTLSProtocol11, + ) +if hasattr(ssl, "PROTOCOL_TLSv1_2"): + _protocol_to_min_max[ssl.PROTOCOL_TLSv1_2] = ( + SecurityConst.kTLSProtocol12, + SecurityConst.kTLSProtocol12, + ) + + +def inject_into_urllib3(): + """ + Monkey-patch urllib3 with SecureTransport-backed SSL-support. + """ + util.SSLContext = SecureTransportContext + util.ssl_.SSLContext = SecureTransportContext + util.HAS_SNI = HAS_SNI + util.ssl_.HAS_SNI = HAS_SNI + util.IS_SECURETRANSPORT = True + util.ssl_.IS_SECURETRANSPORT = True + + +def extract_from_urllib3(): + """ + Undo monkey-patching by :func:`inject_into_urllib3`. + """ + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.HAS_SNI = orig_util_HAS_SNI + util.ssl_.HAS_SNI = orig_util_HAS_SNI + util.IS_SECURETRANSPORT = False + util.ssl_.IS_SECURETRANSPORT = False + + +def _read_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport read callback. This is called by ST to request that data + be returned from the socket. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + requested_length = data_length_pointer[0] + + timeout = wrapped_socket.gettimeout() + error = None + read_count = 0 + + try: + while read_count < requested_length: + if timeout is None or timeout >= 0: + if not util.wait_for_read(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + + remaining = requested_length - read_count + buffer = (ctypes.c_char * remaining).from_address( + data_buffer + read_count + ) + chunk_size = base_socket.recv_into(buffer, remaining) + read_count += chunk_size + if not chunk_size: + if not read_count: + return SecurityConst.errSSLClosedGraceful + break + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = read_count + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = read_count + + if read_count != requested_length: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +def _write_callback(connection_id, data_buffer, data_length_pointer): + """ + SecureTransport write callback. This is called by ST to request that data + actually be sent on the network. + """ + wrapped_socket = None + try: + wrapped_socket = _connection_refs.get(connection_id) + if wrapped_socket is None: + return SecurityConst.errSSLInternal + base_socket = wrapped_socket.socket + + bytes_to_write = data_length_pointer[0] + data = ctypes.string_at(data_buffer, bytes_to_write) + + timeout = wrapped_socket.gettimeout() + error = None + sent = 0 + + try: + while sent < bytes_to_write: + if timeout is None or timeout >= 0: + if not util.wait_for_write(base_socket, timeout): + raise socket.error(errno.EAGAIN, "timed out") + chunk_sent = base_socket.send(data) + sent += chunk_sent + + # This has some needless copying here, but I'm not sure there's + # much value in optimising this data path. + data = data[chunk_sent:] + except (socket.error) as e: + error = e.errno + + if error is not None and error != errno.EAGAIN: + data_length_pointer[0] = sent + if error == errno.ECONNRESET or error == errno.EPIPE: + return SecurityConst.errSSLClosedAbort + raise + + data_length_pointer[0] = sent + + if sent != bytes_to_write: + return SecurityConst.errSSLWouldBlock + + return 0 + except Exception as e: + if wrapped_socket is not None: + wrapped_socket._exception = e + return SecurityConst.errSSLInternal + + +# We need to keep these two objects references alive: if they get GC'd while +# in use then SecureTransport could attempt to call a function that is in freed +# memory. That would be...uh...bad. Yeah, that's the word. Bad. +_read_callback_pointer = Security.SSLReadFunc(_read_callback) +_write_callback_pointer = Security.SSLWriteFunc(_write_callback) + + +class WrappedSocket(object): + """ + API-compatibility wrapper for Python's OpenSSL wrapped socket object. + + Note: _makefile_refs, _drop(), and _reuse() are needed for the garbage + collector of PyPy. + """ + + def __init__(self, socket): + self.socket = socket + self.context = None + self._makefile_refs = 0 + self._closed = False + self._exception = None + self._keychain = None + self._keychain_dir = None + self._client_cert_chain = None + + # We save off the previously-configured timeout and then set it to + # zero. This is done because we use select and friends to handle the + # timeouts, but if we leave the timeout set on the lower socket then + # Python will "kindly" call select on that socket again for us. Avoid + # that by forcing the timeout to zero. + self._timeout = self.socket.gettimeout() + self.socket.settimeout(0) + + @contextlib.contextmanager + def _raise_on_error(self): + """ + A context manager that can be used to wrap calls that do I/O from + SecureTransport. If any of the I/O callbacks hit an exception, this + context manager will correctly propagate the exception after the fact. + This avoids silently swallowing those exceptions. + + It also correctly forces the socket closed. + """ + self._exception = None + + # We explicitly don't catch around this yield because in the unlikely + # event that an exception was hit in the block we don't want to swallow + # it. + yield + if self._exception is not None: + exception, self._exception = self._exception, None + self.close() + raise exception + + def _set_ciphers(self): + """ + Sets up the allowed ciphers. By default this matches the set in + util.ssl_.DEFAULT_CIPHERS, at least as supported by macOS. This is done + custom and doesn't allow changing at this time, mostly because parsing + OpenSSL cipher strings is going to be a freaking nightmare. + """ + ciphers = (Security.SSLCipherSuite * len(CIPHER_SUITES))(*CIPHER_SUITES) + result = Security.SSLSetEnabledCiphers( + self.context, ciphers, len(CIPHER_SUITES) + ) + _assert_no_error(result) + + def _set_alpn_protocols(self, protocols): + """ + Sets up the ALPN protocols on the context. + """ + if not protocols: + return + protocols_arr = _create_cfstring_array(protocols) + try: + result = Security.SSLSetALPNProtocols(self.context, protocols_arr) + _assert_no_error(result) + finally: + CoreFoundation.CFRelease(protocols_arr) + + def _custom_validate(self, verify, trust_bundle): + """ + Called when we have set custom validation. We do this in two cases: + first, when cert validation is entirely disabled; and second, when + using a custom trust DB. + Raises an SSLError if the connection is not trusted. + """ + # If we disabled cert validation, just say: cool. + if not verify: + return + + successes = ( + SecurityConst.kSecTrustResultUnspecified, + SecurityConst.kSecTrustResultProceed, + ) + try: + trust_result = self._evaluate_trust(trust_bundle) + if trust_result in successes: + return + reason = "error code: %d" % (trust_result,) + except Exception as e: + # Do not trust on error + reason = "exception: %r" % (e,) + + # SecureTransport does not send an alert nor shuts down the connection. + rec = _build_tls_unknown_ca_alert(self.version()) + self.socket.sendall(rec) + # close the connection immediately + # l_onoff = 1, activate linger + # l_linger = 0, linger for 0 seoncds + opts = struct.pack("ii", 1, 0) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, opts) + self.close() + raise ssl.SSLError("certificate verify failed, %s" % reason) + + def _evaluate_trust(self, trust_bundle): + # We want data in memory, so load it up. + if os.path.isfile(trust_bundle): + with open(trust_bundle, "rb") as f: + trust_bundle = f.read() + + cert_array = None + trust = Security.SecTrustRef() + + try: + # Get a CFArray that contains the certs we want. + cert_array = _cert_array_from_pem(trust_bundle) + + # Ok, now the hard part. We want to get the SecTrustRef that ST has + # created for this connection, shove our CAs into it, tell ST to + # ignore everything else it knows, and then ask if it can build a + # chain. This is a buuuunch of code. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + raise ssl.SSLError("Failed to copy trust reference") + + result = Security.SecTrustSetAnchorCertificates(trust, cert_array) + _assert_no_error(result) + + result = Security.SecTrustSetAnchorCertificatesOnly(trust, True) + _assert_no_error(result) + + trust_result = Security.SecTrustResultType() + result = Security.SecTrustEvaluate(trust, ctypes.byref(trust_result)) + _assert_no_error(result) + finally: + if trust: + CoreFoundation.CFRelease(trust) + + if cert_array is not None: + CoreFoundation.CFRelease(cert_array) + + return trust_result.value + + def handshake( + self, + server_hostname, + verify, + trust_bundle, + min_version, + max_version, + client_cert, + client_key, + client_key_passphrase, + alpn_protocols, + ): + """ + Actually performs the TLS handshake. This is run automatically by + wrapped socket, and shouldn't be needed in user code. + """ + # First, we do the initial bits of connection setup. We need to create + # a context, set its I/O funcs, and set the connection reference. + self.context = Security.SSLCreateContext( + None, SecurityConst.kSSLClientSide, SecurityConst.kSSLStreamType + ) + result = Security.SSLSetIOFuncs( + self.context, _read_callback_pointer, _write_callback_pointer + ) + _assert_no_error(result) + + # Here we need to compute the handle to use. We do this by taking the + # id of self modulo 2**31 - 1. If this is already in the dictionary, we + # just keep incrementing by one until we find a free space. + with _connection_ref_lock: + handle = id(self) % 2147483647 + while handle in _connection_refs: + handle = (handle + 1) % 2147483647 + _connection_refs[handle] = self + + result = Security.SSLSetConnection(self.context, handle) + _assert_no_error(result) + + # If we have a server hostname, we should set that too. + if server_hostname: + if not isinstance(server_hostname, bytes): + server_hostname = server_hostname.encode("utf-8") + + result = Security.SSLSetPeerDomainName( + self.context, server_hostname, len(server_hostname) + ) + _assert_no_error(result) + + # Setup the ciphers. + self._set_ciphers() + + # Setup the ALPN protocols. + self._set_alpn_protocols(alpn_protocols) + + # Set the minimum and maximum TLS versions. + result = Security.SSLSetProtocolVersionMin(self.context, min_version) + _assert_no_error(result) + + result = Security.SSLSetProtocolVersionMax(self.context, max_version) + _assert_no_error(result) + + # If there's a trust DB, we need to use it. We do that by telling + # SecureTransport to break on server auth. We also do that if we don't + # want to validate the certs at all: we just won't actually do any + # authing in that case. + if not verify or trust_bundle is not None: + result = Security.SSLSetSessionOption( + self.context, SecurityConst.kSSLSessionOptionBreakOnServerAuth, True + ) + _assert_no_error(result) + + # If there's a client cert, we need to use it. + if client_cert: + self._keychain, self._keychain_dir = _temporary_keychain() + self._client_cert_chain = _load_client_cert_chain( + self._keychain, client_cert, client_key + ) + result = Security.SSLSetCertificate(self.context, self._client_cert_chain) + _assert_no_error(result) + + while True: + with self._raise_on_error(): + result = Security.SSLHandshake(self.context) + + if result == SecurityConst.errSSLWouldBlock: + raise socket.timeout("handshake timed out") + elif result == SecurityConst.errSSLServerAuthCompleted: + self._custom_validate(verify, trust_bundle) + continue + else: + _assert_no_error(result) + break + + def fileno(self): + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self): + if self._makefile_refs > 0: + self._makefile_refs -= 1 + if self._closed: + self.close() + + def recv(self, bufsiz): + buffer = ctypes.create_string_buffer(bufsiz) + bytes_read = self.recv_into(buffer, bufsiz) + data = buffer[:bytes_read] + return data + + def recv_into(self, buffer, nbytes=None): + # Read short on EOF. + if self._closed: + return 0 + + if nbytes is None: + nbytes = len(buffer) + + buffer = (ctypes.c_char * nbytes).from_buffer(buffer) + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLRead( + self.context, buffer, nbytes, ctypes.byref(processed_bytes) + ) + + # There are some result codes that we want to treat as "not always + # errors". Specifically, those are errSSLWouldBlock, + # errSSLClosedGraceful, and errSSLClosedNoNotify. + if result == SecurityConst.errSSLWouldBlock: + # If we didn't process any bytes, then this was just a time out. + # However, we can get errSSLWouldBlock in situations when we *did* + # read some data, and in those cases we should just read "short" + # and return. + if processed_bytes.value == 0: + # Timed out, no data read. + raise socket.timeout("recv timed out") + elif result in ( + SecurityConst.errSSLClosedGraceful, + SecurityConst.errSSLClosedNoNotify, + ): + # The remote peer has closed this connection. We should do so as + # well. Note that we don't actually return here because in + # principle this could actually be fired along with return data. + # It's unlikely though. + self.close() + else: + _assert_no_error(result) + + # Ok, we read and probably succeeded. We should return whatever data + # was actually read. + return processed_bytes.value + + def settimeout(self, timeout): + self._timeout = timeout + + def gettimeout(self): + return self._timeout + + def send(self, data): + processed_bytes = ctypes.c_size_t(0) + + with self._raise_on_error(): + result = Security.SSLWrite( + self.context, data, len(data), ctypes.byref(processed_bytes) + ) + + if result == SecurityConst.errSSLWouldBlock and processed_bytes.value == 0: + # Timed out + raise socket.timeout("send timed out") + else: + _assert_no_error(result) + + # We sent, and probably succeeded. Tell them how much we sent. + return processed_bytes.value + + def sendall(self, data): + total_sent = 0 + while total_sent < len(data): + sent = self.send(data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE]) + total_sent += sent + + def shutdown(self): + with self._raise_on_error(): + Security.SSLClose(self.context) + + def close(self): + # TODO: should I do clean shutdown here? Do I have to? + if self._makefile_refs < 1: + self._closed = True + if self.context: + CoreFoundation.CFRelease(self.context) + self.context = None + if self._client_cert_chain: + CoreFoundation.CFRelease(self._client_cert_chain) + self._client_cert_chain = None + if self._keychain: + Security.SecKeychainDelete(self._keychain) + CoreFoundation.CFRelease(self._keychain) + shutil.rmtree(self._keychain_dir) + self._keychain = self._keychain_dir = None + return self.socket.close() + else: + self._makefile_refs -= 1 + + def getpeercert(self, binary_form=False): + # Urgh, annoying. + # + # Here's how we do this: + # + # 1. Call SSLCopyPeerTrust to get hold of the trust object for this + # connection. + # 2. Call SecTrustGetCertificateAtIndex for index 0 to get the leaf. + # 3. To get the CN, call SecCertificateCopyCommonName and process that + # string so that it's of the appropriate type. + # 4. To get the SAN, we need to do something a bit more complex: + # a. Call SecCertificateCopyValues to get the data, requesting + # kSecOIDSubjectAltName. + # b. Mess about with this dictionary to try to get the SANs out. + # + # This is gross. Really gross. It's going to be a few hundred LoC extra + # just to repeat something that SecureTransport can *already do*. So my + # operating assumption at this time is that what we want to do is + # instead to just flag to urllib3 that it shouldn't do its own hostname + # validation when using SecureTransport. + if not binary_form: + raise ValueError("SecureTransport only supports dumping binary certs") + trust = Security.SecTrustRef() + certdata = None + der_bytes = None + + try: + # Grab the trust store. + result = Security.SSLCopyPeerTrust(self.context, ctypes.byref(trust)) + _assert_no_error(result) + if not trust: + # Probably we haven't done the handshake yet. No biggie. + return None + + cert_count = Security.SecTrustGetCertificateCount(trust) + if not cert_count: + # Also a case that might happen if we haven't handshaked. + # Handshook? Handshaken? + return None + + leaf = Security.SecTrustGetCertificateAtIndex(trust, 0) + assert leaf + + # Ok, now we want the DER bytes. + certdata = Security.SecCertificateCopyData(leaf) + assert certdata + + data_length = CoreFoundation.CFDataGetLength(certdata) + data_buffer = CoreFoundation.CFDataGetBytePtr(certdata) + der_bytes = ctypes.string_at(data_buffer, data_length) + finally: + if certdata: + CoreFoundation.CFRelease(certdata) + if trust: + CoreFoundation.CFRelease(trust) + + return der_bytes + + def version(self): + protocol = Security.SSLProtocol() + result = Security.SSLGetNegotiatedProtocolVersion( + self.context, ctypes.byref(protocol) + ) + _assert_no_error(result) + if protocol.value == SecurityConst.kTLSProtocol13: + raise ssl.SSLError("SecureTransport does not support TLS 1.3") + elif protocol.value == SecurityConst.kTLSProtocol12: + return "TLSv1.2" + elif protocol.value == SecurityConst.kTLSProtocol11: + return "TLSv1.1" + elif protocol.value == SecurityConst.kTLSProtocol1: + return "TLSv1" + elif protocol.value == SecurityConst.kSSLProtocol3: + return "SSLv3" + elif protocol.value == SecurityConst.kSSLProtocol2: + return "SSLv2" + else: + raise ssl.SSLError("Unknown TLS version: %r" % protocol) + + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + + +if _fileobject: # Platform-specific: Python 2 + + def makefile(self, mode, bufsize=-1): + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) + +else: # Platform-specific: Python 3 + + def makefile(self, mode="r", buffering=None, *args, **kwargs): + # We disable buffering with SecureTransport because it conflicts with + # the buffering that ST does internally (see issue #1153 for more). + buffering = 0 + return backport_makefile(self, mode, buffering, *args, **kwargs) + + +WrappedSocket.makefile = makefile + + +class SecureTransportContext(object): + """ + I am a wrapper class for the SecureTransport library, to translate the + interface of the standard library ``SSLContext`` object to calls into + SecureTransport. + """ + + def __init__(self, protocol): + self._min_version, self._max_version = _protocol_to_min_max[protocol] + self._options = 0 + self._verify = False + self._trust_bundle = None + self._client_cert = None + self._client_key = None + self._client_key_passphrase = None + self._alpn_protocols = None + + @property + def check_hostname(self): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + return True + + @check_hostname.setter + def check_hostname(self, value): + """ + SecureTransport cannot have its hostname checking disabled. For more, + see the comment on getpeercert() in this file. + """ + pass + + @property + def options(self): + # TODO: Well, crap. + # + # So this is the bit of the code that is the most likely to cause us + # trouble. Essentially we need to enumerate all of the SSL options that + # users might want to use and try to see if we can sensibly translate + # them, or whether we should just ignore them. + return self._options + + @options.setter + def options(self, value): + # TODO: Update in line with above. + self._options = value + + @property + def verify_mode(self): + return ssl.CERT_REQUIRED if self._verify else ssl.CERT_NONE + + @verify_mode.setter + def verify_mode(self, value): + self._verify = True if value == ssl.CERT_REQUIRED else False + + def set_default_verify_paths(self): + # So, this has to do something a bit weird. Specifically, what it does + # is nothing. + # + # This means that, if we had previously had load_verify_locations + # called, this does not undo that. We need to do that because it turns + # out that the rest of the urllib3 code will attempt to load the + # default verify paths if it hasn't been told about any paths, even if + # the context itself was sometime earlier. We resolve that by just + # ignoring it. + pass + + def load_default_certs(self): + return self.set_default_verify_paths() + + def set_ciphers(self, ciphers): + # For now, we just require the default cipher string. + if ciphers != util.ssl_.DEFAULT_CIPHERS: + raise ValueError("SecureTransport doesn't support custom cipher strings") + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + # OK, we only really support cadata and cafile. + if capath is not None: + raise ValueError("SecureTransport does not support cert directories") + + # Raise if cafile does not exist. + if cafile is not None: + with open(cafile): + pass + + self._trust_bundle = cafile or cadata + + def load_cert_chain(self, certfile, keyfile=None, password=None): + self._client_cert = certfile + self._client_key = keyfile + self._client_cert_passphrase = password + + def set_alpn_protocols(self, protocols): + """ + Sets the ALPN protocols that will later be set on the context. + + Raises a NotImplementedError if ALPN is not supported. + """ + if not hasattr(Security, "SSLSetALPNProtocols"): + raise NotImplementedError( + "SecureTransport supports ALPN only in macOS 10.12+" + ) + self._alpn_protocols = [six.ensure_binary(p) for p in protocols] + + def wrap_socket( + self, + sock, + server_side=False, + do_handshake_on_connect=True, + suppress_ragged_eofs=True, + server_hostname=None, + ): + # So, what do we do here? Firstly, we assert some properties. This is a + # stripped down shim, so there is some functionality we don't support. + # See PEP 543 for the real deal. + assert not server_side + assert do_handshake_on_connect + assert suppress_ragged_eofs + + # Ok, we're good to go. Now we want to create the wrapped socket object + # and store it in the appropriate place. + wrapped_socket = WrappedSocket(sock) + + # Now we can handshake + wrapped_socket.handshake( + server_hostname, + self._verify, + self._trust_bundle, + self._min_version, + self._max_version, + self._client_cert, + self._client_key, + self._client_key_passphrase, + self._alpn_protocols, + ) + return wrapped_socket diff --git a/newrelic/packages/urllib3/contrib/socks.py b/newrelic/packages/urllib3/contrib/socks.py index e3239b569d..c326e80dd1 100644 --- a/newrelic/packages/urllib3/contrib/socks.py +++ b/newrelic/packages/urllib3/contrib/socks.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ This module contains provisional support for SOCKS proxies from within urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and @@ -37,11 +38,10 @@ proxy_url="socks5h://:@proxy-host" """ - -from __future__ import annotations +from __future__ import absolute_import try: - import socks # type: ignore[import-untyped] + import socks except ImportError: import warnings @@ -51,13 +51,13 @@ ( "SOCKS support in urllib3 requires the installation of optional " "dependencies: specifically, PySocks. For more information, see " - "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies" + "https://urllib3.readthedocs.io/en/1.26.x/contrib.html#socks-proxies" ), DependencyWarning, ) raise -import typing +from socket import error as SocketError from socket import timeout as SocketTimeout from ..connection import HTTPConnection, HTTPSConnection @@ -69,16 +69,7 @@ try: import ssl except ImportError: - ssl = None # type: ignore[assignment] - - -class _TYPE_SOCKS_OPTIONS(typing.TypedDict): - socks_version: int - proxy_host: str | None - proxy_port: str | None - username: str | None - password: str | None - rdns: bool + ssl = None class SOCKSConnection(HTTPConnection): @@ -86,20 +77,15 @@ class SOCKSConnection(HTTPConnection): A plain-text HTTP connection that connects via a SOCKS proxy. """ - def __init__( - self, - _socks_options: _TYPE_SOCKS_OPTIONS, - *args: typing.Any, - **kwargs: typing.Any, - ) -> None: - self._socks_options = _socks_options - super().__init__(*args, **kwargs) - - def _new_conn(self) -> socks.socksocket: + def __init__(self, *args, **kwargs): + self._socks_options = kwargs.pop("_socks_options") + super(SOCKSConnection, self).__init__(*args, **kwargs) + + def _new_conn(self): """ Establish a new connection via the SOCKS proxy. """ - extra_kw: dict[str, typing.Any] = {} + extra_kw = {} if self.source_address: extra_kw["source_address"] = self.source_address @@ -116,14 +102,15 @@ def _new_conn(self) -> socks.socksocket: proxy_password=self._socks_options["password"], proxy_rdns=self._socks_options["rdns"], timeout=self.timeout, - **extra_kw, + **extra_kw ) - except SocketTimeout as e: + except SocketTimeout: raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) except socks.ProxyError as e: # This is fragile as hell, but it seems to be the only way to raise @@ -133,23 +120,22 @@ def _new_conn(self) -> socks.socksocket: if isinstance(error, SocketTimeout): raise ConnectTimeoutError( self, - f"Connection to {self.host} timed out. (connect timeout={self.timeout})", - ) from e + "Connection to %s timed out. (connect timeout=%s)" + % (self.host, self.timeout), + ) else: - # Adding `from e` messes with coverage somehow, so it's omitted. - # See #2386. raise NewConnectionError( - self, f"Failed to establish a new connection: {error}" + self, "Failed to establish a new connection: %s" % error ) else: raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e + self, "Failed to establish a new connection: %s" % e + ) - except OSError as e: # Defensive: PySocks should catch all these. + except SocketError as e: # Defensive: PySocks should catch all these. raise NewConnectionError( - self, f"Failed to establish a new connection: {e}" - ) from e + self, "Failed to establish a new connection: %s" % e + ) return conn @@ -183,12 +169,12 @@ class SOCKSProxyManager(PoolManager): def __init__( self, - proxy_url: str, - username: str | None = None, - password: str | None = None, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - **connection_pool_kw: typing.Any, + proxy_url, + username=None, + password=None, + num_pools=10, + headers=None, + **connection_pool_kw ): parsed = parse_url(proxy_url) @@ -209,7 +195,7 @@ def __init__( socks_version = socks.PROXY_TYPE_SOCKS4 rdns = True else: - raise ValueError(f"Unable to determine SOCKS version from {proxy_url}") + raise ValueError("Unable to determine SOCKS version from %s" % proxy_url) self.proxy_url = proxy_url @@ -223,6 +209,8 @@ def __init__( } connection_pool_kw["_socks_options"] = socks_options - super().__init__(num_pools, headers, **connection_pool_kw) + super(SOCKSProxyManager, self).__init__( + num_pools, headers, **connection_pool_kw + ) self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/newrelic/packages/urllib3/exceptions.py b/newrelic/packages/urllib3/exceptions.py index 58723faeb0..cba6f3f560 100644 --- a/newrelic/packages/urllib3/exceptions.py +++ b/newrelic/packages/urllib3/exceptions.py @@ -1,16 +1,6 @@ -from __future__ import annotations +from __future__ import absolute_import -import socket -import typing -import warnings -from email.errors import MessageDefect -from http.client import IncompleteRead as httplib_IncompleteRead - -if typing.TYPE_CHECKING: - from .connection import HTTPConnection - from .connectionpool import ConnectionPool - from .response import HTTPResponse - from .util.retry import Retry +from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead # Base Exceptions @@ -18,61 +8,64 @@ class HTTPError(Exception): """Base exception used by this module.""" + pass + class HTTPWarning(Warning): """Base warning used by this module.""" - -_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + pass class PoolError(HTTPError): """Base exception for errors caused within a pool.""" - def __init__(self, pool: ConnectionPool, message: str) -> None: + def __init__(self, pool, message): self.pool = pool - self._message = message - super().__init__(f"{pool}: {message}") + HTTPError.__init__(self, "%s: %s" % (pool, message)) - def __reduce__(self) -> _TYPE_REDUCE_RESULT: + def __reduce__(self): # For pickling purposes. - return self.__class__, (None, self._message) + return self.__class__, (None, None) class RequestError(PoolError): """Base exception for PoolErrors that have associated URLs.""" - def __init__(self, pool: ConnectionPool, url: str | None, message: str) -> None: + def __init__(self, pool, url, message): self.url = url - super().__init__(pool, message) + PoolError.__init__(self, pool, message) - def __reduce__(self) -> _TYPE_REDUCE_RESULT: + def __reduce__(self): # For pickling purposes. - return self.__class__, (None, self.url, self._message) + return self.__class__, (None, self.url, None) class SSLError(HTTPError): """Raised when SSL certificate fails in an HTTPS connection.""" + pass + class ProxyError(HTTPError): """Raised when the connection to a proxy fails.""" - # The original error is also available as __cause__. - original_error: Exception - - def __init__(self, message: str, error: Exception) -> None: - super().__init__(message, error) + def __init__(self, message, error, *args): + super(ProxyError, self).__init__(message, error, *args) self.original_error = error class DecodeError(HTTPError): """Raised when automatic decoding based on Content-Type fails.""" + pass + class ProtocolError(HTTPError): """Raised when something unexpected happens mid-request/response.""" + pass + #: Renamed to ProtocolError but aliased for backwards compatibility. ConnectionError = ProtocolError @@ -86,40 +79,33 @@ class MaxRetryError(RequestError): :param pool: The connection pool :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` - :param str url: The requested Url - :param reason: The underlying error - :type reason: :class:`Exception` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error """ - def __init__( - self, pool: ConnectionPool, url: str | None, reason: Exception | None = None - ) -> None: + def __init__(self, pool, url, reason=None): self.reason = reason - message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" - - super().__init__(pool, url, message) + message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason) - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (None, self.url, self.reason) + RequestError.__init__(self, pool, url, message) class HostChangedError(RequestError): """Raised when an existing pool gets a request for a foreign host.""" - def __init__( - self, pool: ConnectionPool, url: str, retries: Retry | int = 3 - ) -> None: - message = f"Tried to open a foreign host with url: {url}" - super().__init__(pool, url, message) + def __init__(self, pool, url, retries=3): + message = "Tried to open a foreign host with url: %s" % url + RequestError.__init__(self, pool, url, message) self.retries = retries class TimeoutStateError(HTTPError): """Raised when passing an invalid state to a timeout""" + pass + class TimeoutError(HTTPError): """Raised when a socket timeout error occurs. @@ -128,77 +114,53 @@ class TimeoutError(HTTPError): ` and :exc:`ConnectTimeoutErrors `. """ + pass + class ReadTimeoutError(TimeoutError, RequestError): """Raised when a socket timeout occurs while receiving data from a server""" + pass + # This timeout error does not have a URL attached and needs to inherit from the # base HTTPError class ConnectTimeoutError(TimeoutError): """Raised when a socket timeout occurs while connecting to a server""" + pass -class NewConnectionError(ConnectTimeoutError, HTTPError): - """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" - - def __init__(self, conn: HTTPConnection, message: str) -> None: - self.conn = conn - self._message = message - super().__init__(f"{conn}: {message}") - - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (None, self._message) - - @property - def pool(self) -> HTTPConnection: - warnings.warn( - "The 'pool' property is deprecated and will be removed " - "in urllib3 v2.1.0. Use 'conn' instead.", - DeprecationWarning, - stacklevel=2, - ) - return self.conn - - -class NameResolutionError(NewConnectionError): - """Raised when host name resolution fails.""" - - def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): - message = f"Failed to resolve '{host}' ({reason})" - self._host = host - self._reason = reason - super().__init__(conn, message) +class NewConnectionError(ConnectTimeoutError, PoolError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" - def __reduce__(self) -> _TYPE_REDUCE_RESULT: - # For pickling purposes. - return self.__class__, (self._host, None, self._reason) + pass class EmptyPoolError(PoolError): """Raised when a pool runs out of connections and no more are allowed.""" - -class FullPoolError(PoolError): - """Raised when we try to add a connection to a full pool in blocking mode.""" + pass class ClosedPoolError(PoolError): """Raised when a request enters a pool after the pool has been closed.""" + pass + class LocationValueError(ValueError, HTTPError): """Raised when there is something wrong with a given URL input.""" + pass + class LocationParseError(LocationValueError): """Raised when get_host or similar fails to parse the URL input.""" - def __init__(self, location: str) -> None: - message = f"Failed to parse: {location}" - super().__init__(message) + def __init__(self, location): + message = "Failed to parse: %s" % location + HTTPError.__init__(self, message) self.location = location @@ -206,9 +168,9 @@ def __init__(self, location: str) -> None: class URLSchemeUnknown(LocationValueError): """Raised when a URL input has an unsupported scheme.""" - def __init__(self, scheme: str): - message = f"Not supported URL scheme {scheme}" - super().__init__(message) + def __init__(self, scheme): + message = "Not supported URL scheme %s" % scheme + super(URLSchemeUnknown, self).__init__(message) self.scheme = scheme @@ -223,22 +185,38 @@ class ResponseError(HTTPError): class SecurityWarning(HTTPWarning): """Warned when performing security reducing actions""" + pass + + +class SubjectAltNameWarning(SecurityWarning): + """Warned when connecting to a host with a certificate missing a SAN.""" + + pass + class InsecureRequestWarning(SecurityWarning): """Warned when making an unverified HTTPS request.""" - -class NotOpenSSLWarning(SecurityWarning): - """Warned when using unsupported SSL library""" + pass class SystemTimeWarning(SecurityWarning): """Warned when system time is suspected to be wrong""" + pass + class InsecurePlatformWarning(SecurityWarning): """Warned when certain TLS/SSL configuration is not available on a platform.""" + pass + + +class SNIMissingWarning(HTTPWarning): + """Warned when making a HTTPS request without SNI available.""" + + pass + class DependencyWarning(HTTPWarning): """ @@ -246,10 +224,14 @@ class DependencyWarning(HTTPWarning): dependencies. """ + pass + class ResponseNotChunked(ProtocolError, ValueError): """Response needs to be chunked in order to read it as chunks.""" + pass + class BodyNotHttplibCompatible(HTTPError): """ @@ -257,6 +239,8 @@ class BodyNotHttplibCompatible(HTTPError): (have an fp attribute which returns raw chunks) for read_chunked(). """ + pass + class IncompleteRead(HTTPError, httplib_IncompleteRead): """ @@ -266,14 +250,10 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead): for ``partial`` to avoid creating large objects on streamed reads. """ - partial: int # type: ignore[assignment] - expected: int + def __init__(self, partial, expected): + super(IncompleteRead, self).__init__(partial, expected) - def __init__(self, partial: int, expected: int) -> None: - self.partial = partial - self.expected = expected - - def __repr__(self) -> str: + def __repr__(self): return "IncompleteRead(%i bytes read, %i more expected)" % ( self.partial, self.expected, @@ -283,13 +263,14 @@ def __repr__(self) -> str: class InvalidChunkLength(HTTPError, httplib_IncompleteRead): """Invalid chunk length in a chunked response.""" - def __init__(self, response: HTTPResponse, length: bytes) -> None: - self.partial: int = response.tell() # type: ignore[assignment] - self.expected: int | None = response.length_remaining + def __init__(self, response, length): + super(InvalidChunkLength, self).__init__( + response.tell(), response.length_remaining + ) self.response = response self.length = length - def __repr__(self) -> str: + def __repr__(self): return "InvalidChunkLength(got length %r, %i bytes read)" % ( self.length, self.partial, @@ -299,13 +280,15 @@ def __repr__(self) -> str: class InvalidHeader(HTTPError): """The header provided was somehow invalid.""" + pass + class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): """ProxyManager does not support the supplied scheme""" # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. - def __init__(self, scheme: str | None) -> None: + def __init__(self, scheme): # 'localhost' is here because our URL parser parses # localhost:8080 -> scheme=localhost, remove if we fix this. if scheme == "localhost": @@ -313,23 +296,28 @@ def __init__(self, scheme: str | None) -> None: if scheme is None: message = "Proxy URL had no scheme, should start with http:// or https://" else: - message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" - super().__init__(message) + message = ( + "Proxy URL had unsupported scheme %s, should use http:// or https://" + % scheme + ) + super(ProxySchemeUnknown, self).__init__(message) class ProxySchemeUnsupported(ValueError): """Fetching HTTPS resources through HTTPS proxies is unsupported""" + pass + class HeaderParsingError(HTTPError): """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" - def __init__( - self, defects: list[MessageDefect], unparsed_data: bytes | str | None - ) -> None: - message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" - super().__init__(message) + def __init__(self, defects, unparsed_data): + message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data) + super(HeaderParsingError, self).__init__(message) class UnrewindableBodyError(HTTPError): """urllib3 encountered an error when trying to rewind a body""" + + pass diff --git a/newrelic/packages/urllib3/fields.py b/newrelic/packages/urllib3/fields.py index 97c4730cff..9d630f491d 100644 --- a/newrelic/packages/urllib3/fields.py +++ b/newrelic/packages/urllib3/fields.py @@ -1,20 +1,13 @@ -from __future__ import annotations +from __future__ import absolute_import import email.utils import mimetypes -import typing +import re -_TYPE_FIELD_VALUE = typing.Union[str, bytes] -_TYPE_FIELD_VALUE_TUPLE = typing.Union[ - _TYPE_FIELD_VALUE, - tuple[str, _TYPE_FIELD_VALUE], - tuple[str, _TYPE_FIELD_VALUE, str], -] +from .packages import six -def guess_content_type( - filename: str | None, default: str = "application/octet-stream" -) -> str: +def guess_content_type(filename, default="application/octet-stream"): """ Guess the "Content-Type" of a file. @@ -28,7 +21,7 @@ def guess_content_type( return default -def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: +def format_header_param_rfc2231(name, value): """ Helper function to format and quote a single header parameter using the strategy defined in RFC 2231. @@ -41,28 +34,14 @@ def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: The name of the parameter, a string expected to be ASCII only. :param value: The value of the parameter, provided as ``bytes`` or `str``. - :returns: + :ret: An RFC-2231-formatted unicode string. - - .. deprecated:: 2.0.0 - Will be removed in urllib3 v2.1.0. This is not valid for - ``multipart/form-data`` header parameters. """ - import warnings - - warnings.warn( - "'format_header_param_rfc2231' is deprecated and will be " - "removed in urllib3 v2.1.0. This is not valid for " - "multipart/form-data header parameters.", - DeprecationWarning, - stacklevel=2, - ) - - if isinstance(value, bytes): + if isinstance(value, six.binary_type): value = value.decode("utf-8") if not any(ch in value for ch in '"\\\r\n'): - result = f'{name}="{value}"' + result = u'%s="%s"' % (name, value) try: result.encode("ascii") except (UnicodeEncodeError, UnicodeDecodeError): @@ -70,87 +49,81 @@ def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: else: return result + if six.PY2: # Python 2: + value = value.encode("utf-8") + + # encode_rfc2231 accepts an encoded string and returns an ascii-encoded + # string in Python 2 but accepts and returns unicode strings in Python 3 value = email.utils.encode_rfc2231(value, "utf-8") - value = f"{name}*={value}" + value = "%s*=%s" % (name, value) + + if six.PY2: # Python 2: + value = value.decode("utf-8") return value -def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: +_HTML5_REPLACEMENTS = { + u"\u0022": u"%22", + # Replace "\" with "\\". + u"\u005C": u"\u005C\u005C", +} + +# All control characters from 0x00 to 0x1F *except* 0x1B. +_HTML5_REPLACEMENTS.update( + { + six.unichr(cc): u"%{:02X}".format(cc) + for cc in range(0x00, 0x1F + 1) + if cc not in (0x1B,) + } +) + + +def _replace_multiple(value, needles_and_replacements): + def replacer(match): + return needles_and_replacements[match.group(0)] + + pattern = re.compile( + r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()]) + ) + + result = pattern.sub(replacer, value) + + return result + + +def format_header_param_html5(name, value): """ - Format and quote a single multipart header parameter. + Helper function to format and quote a single header parameter using the + HTML5 strategy. - This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching - the behavior of current browser and curl versions. Values are - assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are - percent encoded. + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows the `HTML5 Working Draft + Section 4.10.22.7`_ and matches the behavior of curl and modern browsers. - .. _WHATWG HTML Standard: - https://html.spec.whatwg.org/multipage/ - form-control-infrastructure.html#multipart-form-data + .. _HTML5 Working Draft Section 4.10.22.7: + https://w3c.github.io/html/sec-forms.html#multipart-form-data :param name: - The name of the parameter, an ASCII-only ``str``. + The name of the parameter, a string expected to be ASCII only. :param value: - The value of the parameter, a ``str`` or UTF-8 encoded - ``bytes``. - :returns: - A string ``name="value"`` with the escaped value. - - .. versionchanged:: 2.0.0 - Matches the WHATWG HTML Standard as of 2021/06/10. Control - characters are no longer percent encoded. - - .. versionchanged:: 2.0.0 - Renamed from ``format_header_param_html5`` and - ``format_header_param``. The old names will be removed in - urllib3 v2.1.0. + The value of the parameter, provided as ``bytes`` or `str``. + :ret: + A unicode string, stripped of troublesome characters. """ - if isinstance(value, bytes): + if isinstance(value, six.binary_type): value = value.decode("utf-8") - # percent encode \n \r " - value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) - return f'{name}="{value}"' + value = _replace_multiple(value, _HTML5_REPLACEMENTS) - -def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: - """ - .. deprecated:: 2.0.0 - Renamed to :func:`format_multipart_header_param`. Will be - removed in urllib3 v2.1.0. - """ - import warnings - - warnings.warn( - "'format_header_param_html5' has been renamed to " - "'format_multipart_header_param'. The old name will be " - "removed in urllib3 v2.1.0.", - DeprecationWarning, - stacklevel=2, - ) - return format_multipart_header_param(name, value) + return u'%s="%s"' % (name, value) -def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: - """ - .. deprecated:: 2.0.0 - Renamed to :func:`format_multipart_header_param`. Will be - removed in urllib3 v2.1.0. - """ - import warnings - - warnings.warn( - "'format_header_param' has been renamed to " - "'format_multipart_header_param'. The old name will be " - "removed in urllib3 v2.1.0.", - DeprecationWarning, - stacklevel=2, - ) - return format_multipart_header_param(name, value) +# For backwards-compatibility. +format_header_param = format_header_param_html5 -class RequestField: +class RequestField(object): """ A data container for request body parameters. @@ -162,47 +135,29 @@ class RequestField: An optional filename of the request field. Must be unicode. :param headers: An optional dict-like object of headers to initially use for the field. - - .. versionchanged:: 2.0.0 - The ``header_formatter`` parameter is deprecated and will - be removed in urllib3 v2.1.0. + :param header_formatter: + An optional callable that is used to encode and format the headers. By + default, this is :func:`format_header_param_html5`. """ def __init__( self, - name: str, - data: _TYPE_FIELD_VALUE, - filename: str | None = None, - headers: typing.Mapping[str, str] | None = None, - header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + name, + data, + filename=None, + headers=None, + header_formatter=format_header_param_html5, ): self._name = name self._filename = filename self.data = data - self.headers: dict[str, str | None] = {} + self.headers = {} if headers: self.headers = dict(headers) - - if header_formatter is not None: - import warnings - - warnings.warn( - "The 'header_formatter' parameter is deprecated and " - "will be removed in urllib3 v2.1.0.", - DeprecationWarning, - stacklevel=2, - ) - self.header_formatter = header_formatter - else: - self.header_formatter = format_multipart_header_param + self.header_formatter = header_formatter @classmethod - def from_tuples( - cls, - fieldname: str, - value: _TYPE_FIELD_VALUE_TUPLE, - header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, - ) -> RequestField: + def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5): """ A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. @@ -219,10 +174,6 @@ def from_tuples( Field names and filenames must be unicode. """ - filename: str | None - content_type: str | None - data: _TYPE_FIELD_VALUE - if isinstance(value, tuple): if len(value) == 3: filename, data, content_type = value @@ -241,29 +192,20 @@ def from_tuples( return request_param - def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: + def _render_part(self, name, value): """ - Override this method to change how each multipart header - parameter is formatted. By default, this calls - :func:`format_multipart_header_param`. + Overridable helper function to format a single header parameter. By + default, this calls ``self.header_formatter``. :param name: - The name of the parameter, an ASCII-only ``str``. + The name of the parameter, a string expected to be ASCII only. :param value: - The value of the parameter, a ``str`` or UTF-8 encoded - ``bytes``. - - :meta public: + The value of the parameter, provided as a unicode string. """ + return self.header_formatter(name, value) - def _render_parts( - self, - header_parts: ( - dict[str, _TYPE_FIELD_VALUE | None] - | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] - ), - ) -> str: + def _render_parts(self, header_parts): """ Helper function to format and quote a single header. @@ -274,21 +216,18 @@ def _render_parts( A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format as `k1="v1"; k2="v2"; ...`. """ - iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] - parts = [] + iterable = header_parts if isinstance(header_parts, dict): iterable = header_parts.items() - else: - iterable = header_parts for name, value in iterable: if value is not None: parts.append(self._render_part(name, value)) - return "; ".join(parts) + return u"; ".join(parts) - def render_headers(self) -> str: + def render_headers(self): """ Renders the headers for this request field. """ @@ -297,45 +236,39 @@ def render_headers(self) -> str: sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] for sort_key in sort_keys: if self.headers.get(sort_key, False): - lines.append(f"{sort_key}: {self.headers[sort_key]}") + lines.append(u"%s: %s" % (sort_key, self.headers[sort_key])) for header_name, header_value in self.headers.items(): if header_name not in sort_keys: if header_value: - lines.append(f"{header_name}: {header_value}") + lines.append(u"%s: %s" % (header_name, header_value)) - lines.append("\r\n") - return "\r\n".join(lines) + lines.append(u"\r\n") + return u"\r\n".join(lines) def make_multipart( - self, - content_disposition: str | None = None, - content_type: str | None = None, - content_location: str | None = None, - ) -> None: + self, content_disposition=None, content_type=None, content_location=None + ): """ Makes this request field into a multipart request field. This method overrides "Content-Disposition", "Content-Type" and "Content-Location" headers to the request parameter. - :param content_disposition: - The 'Content-Disposition' of the request body. Defaults to 'form-data' :param content_type: The 'Content-Type' of the request body. :param content_location: The 'Content-Location' of the request body. """ - content_disposition = (content_disposition or "form-data") + "; ".join( + self.headers["Content-Disposition"] = content_disposition or u"form-data" + self.headers["Content-Disposition"] += u"; ".join( [ - "", + u"", self._render_parts( - (("name", self._name), ("filename", self._filename)) + ((u"name", self._name), (u"filename", self._filename)) ), ] ) - - self.headers["Content-Disposition"] = content_disposition self.headers["Content-Type"] = content_type self.headers["Content-Location"] = content_location diff --git a/newrelic/packages/urllib3/filepost.py b/newrelic/packages/urllib3/filepost.py index 14f70b05b4..36c9252c64 100644 --- a/newrelic/packages/urllib3/filepost.py +++ b/newrelic/packages/urllib3/filepost.py @@ -1,32 +1,28 @@ -from __future__ import annotations +from __future__ import absolute_import import binascii import codecs import os -import typing from io import BytesIO -from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField +from .fields import RequestField +from .packages import six +from .packages.six import b writer = codecs.lookup("utf-8")[3] -_TYPE_FIELDS_SEQUENCE = typing.Sequence[ - typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] -] -_TYPE_FIELDS = typing.Union[ - _TYPE_FIELDS_SEQUENCE, - typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], -] - -def choose_boundary() -> str: +def choose_boundary(): """ Our embarrassingly-simple replacement for mimetools.choose_boundary. """ - return binascii.hexlify(os.urandom(16)).decode() + boundary = binascii.hexlify(os.urandom(16)) + if not six.PY2: + boundary = boundary.decode("ascii") + return boundary -def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: +def iter_field_objects(fields): """ Iterate over fields. @@ -34,29 +30,42 @@ def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: :class:`~urllib3.fields.RequestField`. """ - iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] - - if isinstance(fields, typing.Mapping): - iterable = fields.items() + if isinstance(fields, dict): + i = six.iteritems(fields) else: - iterable = fields + i = iter(fields) - for field in iterable: + for field in i: if isinstance(field, RequestField): yield field else: yield RequestField.from_tuples(*field) -def encode_multipart_formdata( - fields: _TYPE_FIELDS, boundary: str | None = None -) -> tuple[bytes, str]: +def iter_fields(fields): + """ + .. deprecated:: 1.6 + + Iterate over fields. + + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. + + Supports list of (k, v) tuples and dicts. + """ + if isinstance(fields, dict): + return ((k, v) for k, v in six.iteritems(fields)) + + return ((k, v) for k, v in fields) + + +def encode_multipart_formdata(fields, boundary=None): """ Encode a dictionary of ``fields`` using the multipart/form-data MIME format. :param fields: Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). - Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. :param boundary: If not specified, then a random boundary will be generated using @@ -67,7 +76,7 @@ def encode_multipart_formdata( boundary = choose_boundary() for field in iter_field_objects(fields): - body.write(f"--{boundary}\r\n".encode("latin-1")) + body.write(b("--%s\r\n" % (boundary))) writer(body).write(field.render_headers()) data = field.data @@ -75,15 +84,15 @@ def encode_multipart_formdata( if isinstance(data, int): data = str(data) # Backwards compatibility - if isinstance(data, str): + if isinstance(data, six.text_type): writer(body).write(data) else: body.write(data) body.write(b"\r\n") - body.write(f"--{boundary}--\r\n".encode("latin-1")) + body.write(b("--%s--\r\n" % (boundary))) - content_type = f"multipart/form-data; boundary={boundary}" + content_type = str("multipart/form-data; boundary=%s" % boundary) return body.getvalue(), content_type diff --git a/newrelic/packages/urllib3/http2/__init__.py b/newrelic/packages/urllib3/http2/__init__.py deleted file mode 100644 index 133e1d8f23..0000000000 --- a/newrelic/packages/urllib3/http2/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import annotations - -from importlib.metadata import version - -__all__ = [ - "inject_into_urllib3", - "extract_from_urllib3", -] - -import typing - -orig_HTTPSConnection: typing.Any = None - - -def inject_into_urllib3() -> None: - # First check if h2 version is valid - h2_version = version("h2") - if not h2_version.startswith("4."): - raise ImportError( - "urllib3 v2 supports h2 version 4.x.x, currently " - f"the 'h2' module is compiled with {h2_version!r}. " - "See: https://github.com/urllib3/urllib3/issues/3290" - ) - - # Import here to avoid circular dependencies. - from .. import connection as urllib3_connection - from .. import util as urllib3_util - from ..connectionpool import HTTPSConnectionPool - from ..util import ssl_ as urllib3_util_ssl - from .connection import HTTP2Connection - - global orig_HTTPSConnection - orig_HTTPSConnection = urllib3_connection.HTTPSConnection - - HTTPSConnectionPool.ConnectionCls = HTTP2Connection - urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] - - # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. - urllib3_util.ALPN_PROTOCOLS = ["h2"] - urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] - - -def extract_from_urllib3() -> None: - from .. import connection as urllib3_connection - from .. import util as urllib3_util - from ..connectionpool import HTTPSConnectionPool - from ..util import ssl_ as urllib3_util_ssl - - HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection - urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] - - urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] - urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/newrelic/packages/urllib3/http2/connection.py b/newrelic/packages/urllib3/http2/connection.py deleted file mode 100644 index 0a026da0a8..0000000000 --- a/newrelic/packages/urllib3/http2/connection.py +++ /dev/null @@ -1,356 +0,0 @@ -from __future__ import annotations - -import logging -import re -import threading -import types -import typing - -import h2.config -import h2.connection -import h2.events - -from .._base_connection import _TYPE_BODY -from .._collections import HTTPHeaderDict -from ..connection import HTTPSConnection, _get_default_user_agent -from ..exceptions import ConnectionError -from ..response import BaseHTTPResponse - -orig_HTTPSConnection = HTTPSConnection - -T = typing.TypeVar("T") - -log = logging.getLogger(__name__) - -RE_IS_LEGAL_HEADER_NAME = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9a-z]+$") -RE_IS_ILLEGAL_HEADER_VALUE = re.compile(rb"[\0\x00\x0a\x0d\r\n]|^[ \r\n\t]|[ \r\n\t]$") - - -def _is_legal_header_name(name: bytes) -> bool: - """ - "An implementation that validates fields according to the definitions in Sections - 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not - include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) - - `http.client._is_legal_header_name` does not validate the field name according to the - HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. - - This does not allow for the `:` character in the header name, so should not - be used to validate pseudo-headers. - """ - return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) - - -def _is_illegal_header_value(value: bytes) -> bool: - """ - "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed - (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field - value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, - 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) - """ - return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) - - -class _LockedObject(typing.Generic[T]): - """ - A wrapper class that hides a specific object behind a lock. - The goal here is to provide a simple way to protect access to an object - that cannot safely be simultaneously accessed from multiple threads. The - intended use of this class is simple: take hold of it with a context - manager, which returns the protected object. - """ - - __slots__ = ( - "lock", - "_obj", - ) - - def __init__(self, obj: T): - self.lock = threading.RLock() - self._obj = obj - - def __enter__(self) -> T: - self.lock.acquire() - return self._obj - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> None: - self.lock.release() - - -class HTTP2Connection(HTTPSConnection): - def __init__( - self, host: str, port: int | None = None, **kwargs: typing.Any - ) -> None: - self._h2_conn = self._new_h2_conn() - self._h2_stream: int | None = None - self._headers: list[tuple[bytes, bytes]] = [] - - if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: - raise NotImplementedError("Proxies aren't supported with HTTP/2") - - super().__init__(host, port, **kwargs) - - if self._tunnel_host is not None: - raise NotImplementedError("Tunneling isn't supported with HTTP/2") - - def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: - config = h2.config.H2Configuration(client_side=True) - return _LockedObject(h2.connection.H2Connection(config=config)) - - def connect(self) -> None: - super().connect() - with self._h2_conn as conn: - conn.initiate_connection() - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - def putrequest( # type: ignore[override] - self, - method: str, - url: str, - **kwargs: typing.Any, - ) -> None: - """putrequest - This deviates from the HTTPConnection method signature since we never need to override - sending accept-encoding headers or the host header. - """ - if "skip_host" in kwargs: - raise NotImplementedError("`skip_host` isn't supported") - if "skip_accept_encoding" in kwargs: - raise NotImplementedError("`skip_accept_encoding` isn't supported") - - self._request_url = url or "/" - self._validate_path(url) # type: ignore[attr-defined] - - if ":" in self.host: - authority = f"[{self.host}]:{self.port or 443}" - else: - authority = f"{self.host}:{self.port or 443}" - - self._headers.append((b":scheme", b"https")) - self._headers.append((b":method", method.encode())) - self._headers.append((b":authority", authority.encode())) - self._headers.append((b":path", url.encode())) - - with self._h2_conn as conn: - self._h2_stream = conn.get_next_available_stream_id() - - def putheader(self, header: str | bytes, *values: str | bytes) -> None: # type: ignore[override] - # TODO SKIPPABLE_HEADERS from urllib3 are ignored. - header = header.encode() if isinstance(header, str) else header - header = header.lower() # A lot of upstream code uses capitalized headers. - if not _is_legal_header_name(header): - raise ValueError(f"Illegal header name {str(header)}") - - for value in values: - value = value.encode() if isinstance(value, str) else value - if _is_illegal_header_value(value): - raise ValueError(f"Illegal header value {str(value)}") - self._headers.append((header, value)) - - def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] - if self._h2_stream is None: - raise ConnectionError("Must call `putrequest` first.") - - with self._h2_conn as conn: - conn.send_headers( - stream_id=self._h2_stream, - headers=self._headers, - end_stream=(message_body is None), - ) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - self._headers = [] # Reset headers for the next request. - - def send(self, data: typing.Any) -> None: - """Send data to the server. - `data` can be: `str`, `bytes`, an iterable, or file-like objects - that support a .read() method. - """ - if self._h2_stream is None: - raise ConnectionError("Must call `putrequest` first.") - - with self._h2_conn as conn: - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - if hasattr(data, "read"): # file-like objects - while True: - chunk = data.read(self.blocksize) - if not chunk: - break - if isinstance(chunk, str): - chunk = chunk.encode() - conn.send_data(self._h2_stream, chunk, end_stream=False) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - conn.end_stream(self._h2_stream) - return - - if isinstance(data, str): # str -> bytes - data = data.encode() - - try: - if isinstance(data, bytes): - conn.send_data(self._h2_stream, data, end_stream=True) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - else: - for chunk in data: - conn.send_data(self._h2_stream, chunk, end_stream=False) - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - conn.end_stream(self._h2_stream) - except TypeError: - raise TypeError( - "`data` should be str, bytes, iterable, or file. got %r" - % type(data) - ) - - def set_tunnel( - self, - host: str, - port: int | None = None, - headers: typing.Mapping[str, str] | None = None, - scheme: str = "http", - ) -> None: - raise NotImplementedError( - "HTTP/2 does not support setting up a tunnel through a proxy" - ) - - def getresponse( # type: ignore[override] - self, - ) -> HTTP2Response: - status = None - data = bytearray() - with self._h2_conn as conn: - end_stream = False - while not end_stream: - # TODO: Arbitrary read value. - if received_data := self.sock.recv(65535): - events = conn.receive_data(received_data) - for event in events: - if isinstance(event, h2.events.ResponseReceived): - headers = HTTPHeaderDict() - for header, value in event.headers: - if header == b":status": - status = int(value.decode()) - else: - headers.add( - header.decode("ascii"), value.decode("ascii") - ) - - elif isinstance(event, h2.events.DataReceived): - data += event.data - conn.acknowledge_received_data( - event.flow_controlled_length, event.stream_id - ) - - elif isinstance(event, h2.events.StreamEnded): - end_stream = True - - if data_to_send := conn.data_to_send(): - self.sock.sendall(data_to_send) - - assert status is not None - return HTTP2Response( - status=status, - headers=headers, - request_url=self._request_url, - data=bytes(data), - ) - - def request( # type: ignore[override] - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - *, - preload_content: bool = True, - decode_content: bool = True, - enforce_content_length: bool = True, - **kwargs: typing.Any, - ) -> None: - """Send an HTTP/2 request""" - if "chunked" in kwargs: - # TODO this is often present from upstream. - # raise NotImplementedError("`chunked` isn't supported with HTTP/2") - pass - - if self.sock is not None: - self.sock.settimeout(self.timeout) - - self.putrequest(method, url) - - headers = headers or {} - for k, v in headers.items(): - if k.lower() == "transfer-encoding" and v == "chunked": - continue - else: - self.putheader(k, v) - - if b"user-agent" not in dict(self._headers): - self.putheader(b"user-agent", _get_default_user_agent()) - - if body: - self.endheaders(message_body=body) - self.send(body) - else: - self.endheaders() - - def close(self) -> None: - with self._h2_conn as conn: - try: - conn.close_connection() - if data := conn.data_to_send(): - self.sock.sendall(data) - except Exception: - pass - - # Reset all our HTTP/2 connection state. - self._h2_conn = self._new_h2_conn() - self._h2_stream = None - self._headers = [] - - super().close() - - -class HTTP2Response(BaseHTTPResponse): - # TODO: This is a woefully incomplete response object, but works for non-streaming. - def __init__( - self, - status: int, - headers: HTTPHeaderDict, - request_url: str, - data: bytes, - decode_content: bool = False, # TODO: support decoding - ) -> None: - super().__init__( - status=status, - headers=headers, - # Following CPython, we map HTTP versions to major * 10 + minor integers - version=20, - version_string="HTTP/2", - # No reason phrase in HTTP/2 - reason=None, - decode_content=decode_content, - request_url=request_url, - ) - self._data = data - self.length_remaining = 0 - - @property - def data(self) -> bytes: - return self._data - - def get_redirect_location(self) -> None: - return None - - def close(self) -> None: - pass diff --git a/newrelic/packages/urllib3/http2/probe.py b/newrelic/packages/urllib3/http2/probe.py deleted file mode 100644 index 9ea900764f..0000000000 --- a/newrelic/packages/urllib3/http2/probe.py +++ /dev/null @@ -1,87 +0,0 @@ -from __future__ import annotations - -import threading - - -class _HTTP2ProbeCache: - __slots__ = ( - "_lock", - "_cache_locks", - "_cache_values", - ) - - def __init__(self) -> None: - self._lock = threading.Lock() - self._cache_locks: dict[tuple[str, int], threading.RLock] = {} - self._cache_values: dict[tuple[str, int], bool | None] = {} - - def acquire_and_get(self, host: str, port: int) -> bool | None: - # By the end of this block we know that - # _cache_[values,locks] is available. - value = None - with self._lock: - key = (host, port) - try: - value = self._cache_values[key] - # If it's a known value we return right away. - if value is not None: - return value - except KeyError: - self._cache_locks[key] = threading.RLock() - self._cache_values[key] = None - - # If the value is unknown, we acquire the lock to signal - # to the requesting thread that the probe is in progress - # or that the current thread needs to return their findings. - key_lock = self._cache_locks[key] - key_lock.acquire() - try: - # If the by the time we get the lock the value has been - # updated we want to return the updated value. - value = self._cache_values[key] - - # In case an exception like KeyboardInterrupt is raised here. - except BaseException as e: # Defensive: - assert not isinstance(e, KeyError) # KeyError shouldn't be possible. - key_lock.release() - raise - - return value - - def set_and_release( - self, host: str, port: int, supports_http2: bool | None - ) -> None: - key = (host, port) - key_lock = self._cache_locks[key] - with key_lock: # Uses an RLock, so can be locked again from same thread. - if supports_http2 is None and self._cache_values[key] is not None: - raise ValueError( - "Cannot reset HTTP/2 support for origin after value has been set." - ) # Defensive: not expected in normal usage - - self._cache_values[key] = supports_http2 - key_lock.release() - - def _values(self) -> dict[tuple[str, int], bool | None]: - """This function is for testing purposes only. Gets the current state of the probe cache""" - with self._lock: - return {k: v for k, v in self._cache_values.items()} - - def _reset(self) -> None: - """This function is for testing purposes only. Reset the cache values""" - with self._lock: - self._cache_locks = {} - self._cache_values = {} - - -_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() - -set_and_release = _HTTP2_PROBE_CACHE.set_and_release -acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get -_values = _HTTP2_PROBE_CACHE._values -_reset = _HTTP2_PROBE_CACHE._reset - -__all__ = [ - "set_and_release", - "acquire_and_get", -] diff --git a/newrelic/packages/urllib3/packages/__init__.py b/newrelic/packages/urllib3/packages/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/newrelic/packages/urllib3/packages/backports/__init__.py b/newrelic/packages/urllib3/packages/backports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/newrelic/packages/urllib3/packages/backports/makefile.py b/newrelic/packages/urllib3/packages/backports/makefile.py new file mode 100644 index 0000000000..b8fb2154b6 --- /dev/null +++ b/newrelic/packages/urllib3/packages/backports/makefile.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +backports.makefile +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``socket.makefile`` method for use with anything that +wants to create a "fake" socket object. +""" +import io +from socket import SocketIO + + +def backport_makefile( + self, mode="r", buffering=None, encoding=None, errors=None, newline=None +): + """ + Backport of ``socket.makefile`` from Python 3.5. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = SocketIO(self, rawmode) + self._makefile_refs += 1 + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode + return text diff --git a/newrelic/packages/urllib3/packages/backports/weakref_finalize.py b/newrelic/packages/urllib3/packages/backports/weakref_finalize.py new file mode 100644 index 0000000000..a2f2966e54 --- /dev/null +++ b/newrelic/packages/urllib3/packages/backports/weakref_finalize.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +""" +backports.weakref_finalize +~~~~~~~~~~~~~~~~~~ + +Backports the Python 3 ``weakref.finalize`` method. +""" +from __future__ import absolute_import + +import itertools +import sys +from weakref import ref + +__all__ = ["weakref_finalize"] + + +class weakref_finalize(object): + """Class for finalization of weakrefable objects + finalize(obj, func, *args, **kwargs) returns a callable finalizer + object which will be called when obj is garbage collected. The + first time the finalizer is called it evaluates func(*arg, **kwargs) + and returns the result. After this the finalizer is dead, and + calling it just returns None. + When the program exits any remaining finalizers for which the + atexit attribute is true will be run in reverse order of creation. + By default atexit is true. + """ + + # Finalizer objects don't have any state of their own. They are + # just used as keys to lookup _Info objects in the registry. This + # ensures that they cannot be part of a ref-cycle. + + __slots__ = () + _registry = {} + _shutdown = False + _index_iter = itertools.count() + _dirty = False + _registered_with_atexit = False + + class _Info(object): + __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") + + def __init__(self, obj, func, *args, **kwargs): + if not self._registered_with_atexit: + # We may register the exit function more than once because + # of a thread race, but that is harmless + import atexit + + atexit.register(self._exitfunc) + weakref_finalize._registered_with_atexit = True + info = self._Info() + info.weakref = ref(obj, self) + info.func = func + info.args = args + info.kwargs = kwargs or None + info.atexit = True + info.index = next(self._index_iter) + self._registry[self] = info + weakref_finalize._dirty = True + + def __call__(self, _=None): + """If alive then mark as dead and return func(*args, **kwargs); + otherwise return None""" + info = self._registry.pop(self, None) + if info and not self._shutdown: + return info.func(*info.args, **(info.kwargs or {})) + + def detach(self): + """If alive then mark as dead and return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None and self._registry.pop(self, None): + return (obj, info.func, info.args, info.kwargs or {}) + + def peek(self): + """If alive then return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None: + return (obj, info.func, info.args, info.kwargs or {}) + + @property + def alive(self): + """Whether finalizer is alive""" + return self in self._registry + + @property + def atexit(self): + """Whether finalizer should be called at exit""" + info = self._registry.get(self) + return bool(info) and info.atexit + + @atexit.setter + def atexit(self, value): + info = self._registry.get(self) + if info: + info.atexit = bool(value) + + def __repr__(self): + info = self._registry.get(self) + obj = info and info.weakref() + if obj is None: + return "<%s object at %#x; dead>" % (type(self).__name__, id(self)) + else: + return "<%s object at %#x; for %r at %#x>" % ( + type(self).__name__, + id(self), + type(obj).__name__, + id(obj), + ) + + @classmethod + def _select_for_exit(cls): + # Return live finalizers marked for exit, oldest first + L = [(f, i) for (f, i) in cls._registry.items() if i.atexit] + L.sort(key=lambda item: item[1].index) + return [f for (f, i) in L] + + @classmethod + def _exitfunc(cls): + # At shutdown invoke finalizers for which atexit is true. + # This is called once all other non-daemonic threads have been + # joined. + reenable_gc = False + try: + if cls._registry: + import gc + + if gc.isenabled(): + reenable_gc = True + gc.disable() + pending = None + while True: + if pending is None or weakref_finalize._dirty: + pending = cls._select_for_exit() + weakref_finalize._dirty = False + if not pending: + break + f = pending.pop() + try: + # gc is disabled, so (assuming no daemonic + # threads) the following is the only line in + # this function which might trigger creation + # of a new finalizer + f() + except Exception: + sys.excepthook(*sys.exc_info()) + assert f not in cls._registry + finally: + # prevent any more finalizers from executing during shutdown + weakref_finalize._shutdown = True + if reenable_gc: + gc.enable() diff --git a/newrelic/packages/urllib3/packages/six.py b/newrelic/packages/urllib3/packages/six.py new file mode 100644 index 0000000000..f099a3dcd2 --- /dev/null +++ b/newrelic/packages/urllib3/packages/six.py @@ -0,0 +1,1076 @@ +# Copyright (c) 2010-2020 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.16.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = (str,) + integer_types = (int,) + class_types = (type,) + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = (basestring,) + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + def __len__(self): + return 1 << 31 + + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + +if PY34: + from importlib.util import spec_from_loader +else: + spec_from_loader = None + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def find_spec(self, fullname, path, target=None): + if fullname in self.known_modules: + return spec_from_loader(fullname, self) + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + + get_source = get_code # same as get_code + + def create_module(self, spec): + return self.load_module(spec.name) + + def exec_module(self, module): + pass + + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute( + "filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse" + ), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute( + "reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload" + ), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute( + "zip_longest", "itertools", "itertools", "izip_longest", "zip_longest" + ), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule( + "collections_abc", + "collections", + "collections.abc" if sys.version_info >= (3, 3) else "collections", + ), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), + MovedModule( + "_dummy_thread", + "dummy_thread", + "_dummy_thread" if sys.version_info < (3, 9) else "_thread", + ), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule( + "email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart" + ), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute( + "unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes" + ), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", + "moves.urllib.parse", +) + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", + "moves.urllib.error", +) + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", + "moves.urllib.request", +) + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module( + Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", + "moves.urllib.response", +) + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = ( + _urllib_robotparser_moved_attributes +) + +_importer._add_module( + Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", + "moves.urllib.robotparser", +) + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ["parse", "error", "request", "response", "robotparser"] + + +_importer._add_module( + Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib" +) + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + + def advance_iterator(it): + return it.next() + + +next = advance_iterator + + +try: + callable = callable +except NameError: + + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc( + get_unbound_function, """Get the function out of a possibly unbound function""" +) + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc( + iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary." +) + + +if PY3: + + def b(s): + return s.encode("latin-1") + + def u(s): + return s + + unichr = chr + import struct + + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + + StringIO = io.StringIO + BytesIO = io.BytesIO + del io + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" + _assertNotRegex = "assertNotRegex" +else: + + def b(s): + return s + + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r"\\", r"\\\\"), "unicode_escape") + + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + _assertNotRegex = "assertNotRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +def assertNotRegex(self, *args, **kwargs): + return getattr(self, _assertNotRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec ("""exec _code_ in _globs_, _locs_""") + + exec_( + """def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""" + ) + + +if sys.version_info[:2] > (3,): + exec_( + """def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""" + ) +else: + + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if ( + isinstance(fp, file) + and isinstance(data, unicode) + and fp.encoding is not None + ): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) + + +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + # This does exactly the same what the :func:`py3:functools.update_wrapper` + # function does on Python versions after 3.2. It sets the ``__wrapped__`` + # attribute on ``wrapper`` object and it doesn't raise an error if any of + # the attributes mentioned in ``assigned`` and ``updated`` are missing on + # ``wrapped`` object. + def _update_wrapper( + wrapper, + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + continue + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + wrapper.__wrapped__ = wrapped + return wrapper + + _update_wrapper.__doc__ = functools.update_wrapper.__doc__ + + def wraps( + wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES, + ): + return functools.partial( + _update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated + ) + + wraps.__doc__ = functools.wraps.__doc__ + +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + def __new__(cls, name, this_bases, d): + if sys.version_info[:2] >= (3, 7): + # This version introduced PEP 560 that requires a bit + # of extra care (we mimic what is done by __build_class__). + resolved_bases = types.resolve_bases(bases) + if resolved_bases is not bases: + d["__orig_bases__"] = bases + else: + resolved_bases = bases + return meta(name, resolved_bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + + return type.__new__(metaclass, "temporary_class", (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get("__slots__") + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop("__dict__", None) + orig_vars.pop("__weakref__", None) + if hasattr(cls, "__qualname__"): + orig_vars["__qualname__"] = cls.__qualname__ + return metaclass(cls.__name__, cls.__bases__, orig_vars) + + return wrapper + + +def ensure_binary(s, encoding="utf-8", errors="strict"): + """Coerce **s** to six.binary_type. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, binary_type): + return s + if isinstance(s, text_type): + return s.encode(encoding, errors) + raise TypeError("not expecting type '%s'" % type(s)) + + +def ensure_str(s, encoding="utf-8", errors="strict"): + """Coerce *s* to `str`. + + For Python 2: + - `unicode` -> encoded to `str` + - `str` -> `str` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + # Optimization: Fast return for the common case. + if type(s) is str: + return s + if PY2 and isinstance(s, text_type): + return s.encode(encoding, errors) + elif PY3 and isinstance(s, binary_type): + return s.decode(encoding, errors) + elif not isinstance(s, (text_type, binary_type)): + raise TypeError("not expecting type '%s'" % type(s)) + return s + + +def ensure_text(s, encoding="utf-8", errors="strict"): + """Coerce *s* to six.text_type. + + For Python 2: + - `unicode` -> `unicode` + - `str` -> `unicode` + + For Python 3: + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, binary_type): + return s.decode(encoding, errors) + elif isinstance(s, text_type): + return s + else: + raise TypeError("not expecting type '%s'" % type(s)) + + +def python_2_unicode_compatible(klass): + """ + A class decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if "__str__" not in klass.__dict__: + raise ValueError( + "@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % klass.__name__ + ) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode("utf-8") + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if ( + type(importer).__name__ == "_SixMetaPathImporter" + and importer.name == __name__ + ): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/newrelic/packages/urllib3/poolmanager.py b/newrelic/packages/urllib3/poolmanager.py index 28ec82f016..fb51bf7d96 100644 --- a/newrelic/packages/urllib3/poolmanager.py +++ b/newrelic/packages/urllib3/poolmanager.py @@ -1,33 +1,24 @@ -from __future__ import annotations +from __future__ import absolute_import +import collections import functools import logging -import typing -import warnings -from types import TracebackType -from urllib.parse import urljoin from ._collections import HTTPHeaderDict, RecentlyUsedContainer -from ._request_methods import RequestMethods -from .connection import ProxyConfig from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme from .exceptions import ( LocationValueError, MaxRetryError, ProxySchemeUnknown, + ProxySchemeUnsupported, URLSchemeUnknown, ) -from .response import BaseHTTPResponse -from .util.connection import _TYPE_SOCKET_OPTIONS +from .packages import six +from .packages.six.moves.urllib.parse import urljoin +from .request import RequestMethods from .util.proxy import connection_requires_http_tunnel from .util.retry import Retry -from .util.timeout import Timeout -from .util.url import Url, parse_url - -if typing.TYPE_CHECKING: - import ssl - - from typing_extensions import Self +from .util.url import parse_url __all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] @@ -39,62 +30,53 @@ "cert_file", "cert_reqs", "ca_certs", - "ca_cert_data", "ssl_version", - "ssl_minimum_version", - "ssl_maximum_version", "ca_cert_dir", "ssl_context", "key_password", "server_hostname", ) -# Default value for `blocksize` - a new parameter introduced to -# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 -_DEFAULT_BLOCKSIZE = 16384 +# All known keyword arguments that could be provided to the pool manager, its +# pools, or the underlying connections. This is used to construct a pool key. +_key_fields = ( + "key_scheme", # str + "key_host", # str + "key_port", # int + "key_timeout", # int or float or Timeout + "key_retries", # int or Retry + "key_strict", # bool + "key_block", # bool + "key_source_address", # str + "key_key_file", # str + "key_key_password", # str + "key_cert_file", # str + "key_cert_reqs", # str + "key_ca_certs", # str + "key_ssl_version", # str + "key_ca_cert_dir", # str + "key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext + "key_maxsize", # int + "key_headers", # dict + "key__proxy", # parsed proxy url + "key__proxy_headers", # dict + "key__proxy_config", # class + "key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples + "key__socks_options", # dict + "key_assert_hostname", # bool or string + "key_assert_fingerprint", # str + "key_server_hostname", # str +) + +#: The namedtuple class used to construct keys for the connection pool. +#: All custom key schemes should include the fields in this key at a minimum. +PoolKey = collections.namedtuple("PoolKey", _key_fields) -class PoolKey(typing.NamedTuple): - """ - All known keyword arguments that could be provided to the pool manager, its - pools, or the underlying connections. +_proxy_config_fields = ("ssl_context", "use_forwarding_for_https") +ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields) - All custom key schemes should include the fields in this key at a minimum. - """ - key_scheme: str - key_host: str - key_port: int | None - key_timeout: Timeout | float | int | None - key_retries: Retry | bool | int | None - key_block: bool | None - key_source_address: tuple[str, int] | None - key_key_file: str | None - key_key_password: str | None - key_cert_file: str | None - key_cert_reqs: str | None - key_ca_certs: str | None - key_ca_cert_data: str | bytes | None - key_ssl_version: int | str | None - key_ssl_minimum_version: ssl.TLSVersion | None - key_ssl_maximum_version: ssl.TLSVersion | None - key_ca_cert_dir: str | None - key_ssl_context: ssl.SSLContext | None - key_maxsize: int | None - key_headers: frozenset[tuple[str, str]] | None - key__proxy: Url | None - key__proxy_headers: frozenset[tuple[str, str]] | None - key__proxy_config: ProxyConfig | None - key_socket_options: _TYPE_SOCKET_OPTIONS | None - key__socks_options: frozenset[tuple[str, str]] | None - key_assert_hostname: bool | str | None - key_assert_fingerprint: str | None - key_server_hostname: str | None - key_blocksize: int | None - - -def _default_key_normalizer( - key_class: type[PoolKey], request_context: dict[str, typing.Any] -) -> PoolKey: +def _default_key_normalizer(key_class, request_context): """ Create a pool key out of a request context dictionary. @@ -140,10 +122,6 @@ def _default_key_normalizer( if field not in context: context[field] = None - # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context - if context.get("key_blocksize") is None: - context["key_blocksize"] = _DEFAULT_BLOCKSIZE - return key_class(**context) @@ -176,50 +154,23 @@ class PoolManager(RequestMethods): Additional parameters are used to create fresh :class:`urllib3.connectionpool.ConnectionPool` instances. - Example: - - .. code-block:: python - - import urllib3 - - http = urllib3.PoolManager(num_pools=2) + Example:: - resp1 = http.request("GET", "https://google.com/") - resp2 = http.request("GET", "https://google.com/mail") - resp3 = http.request("GET", "https://yahoo.com/") - - print(len(http.pools)) - # 2 + >>> manager = PoolManager(num_pools=2) + >>> r = manager.request('GET', 'http://google.com/') + >>> r = manager.request('GET', 'http://google.com/mail') + >>> r = manager.request('GET', 'http://yahoo.com/') + >>> len(manager.pools) + 2 """ - proxy: Url | None = None - proxy_config: ProxyConfig | None = None + proxy = None + proxy_config = None - def __init__( - self, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - **connection_pool_kw: typing.Any, - ) -> None: - super().__init__(headers) - # PoolManager handles redirects itself in PoolManager.urlopen(). - # It always passes redirect=False to the underlying connection pool to - # suppress per-pool redirect handling. If the user supplied a non-Retry - # value (int/bool/etc) for retries and we let the pool normalize it - # while redirect=False, the resulting Retry object would have redirect - # handling disabled, which can interfere with PoolManager's own - # redirect logic. Normalize here so redirects remain governed solely by - # PoolManager logic. - if "retries" in connection_pool_kw: - retries = connection_pool_kw["retries"] - if not isinstance(retries, Retry): - retries = Retry.from_int(retries) - connection_pool_kw = connection_pool_kw.copy() - connection_pool_kw["retries"] = retries + def __init__(self, num_pools=10, headers=None, **connection_pool_kw): + RequestMethods.__init__(self, headers) self.connection_pool_kw = connection_pool_kw - - self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] self.pools = RecentlyUsedContainer(num_pools) # Locally set the pool classes and keys so other PoolManagers can @@ -227,26 +178,15 @@ def __init__( self.pool_classes_by_scheme = pool_classes_by_scheme self.key_fn_by_scheme = key_fn_by_scheme.copy() - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> typing.Literal[False]: + def __exit__(self, exc_type, exc_val, exc_tb): self.clear() # Return False to re-raise any potential exceptions return False - def _new_pool( - self, - scheme: str, - host: str, - port: int, - request_context: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def _new_pool(self, scheme, host, port, request_context=None): """ Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and any additional pool keyword arguments. @@ -256,15 +196,10 @@ def _new_pool( connection pools handed out by :meth:`connection_from_url` and companion methods. It is intended to be overridden for customization. """ - pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + pool_cls = self.pool_classes_by_scheme[scheme] if request_context is None: request_context = self.connection_pool_kw.copy() - # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly - # set to 'None' in the request_context. - if request_context.get("blocksize") is None: - request_context["blocksize"] = _DEFAULT_BLOCKSIZE - # Although the context has everything necessary to create the pool, # this function has historically only used the scheme, host, and port # in the positional args. When an API change is acceptable these can @@ -278,7 +213,7 @@ def _new_pool( return pool_cls(host, port, **request_context) - def clear(self) -> None: + def clear(self): """ Empty our store of pools and direct them all to close. @@ -287,13 +222,7 @@ def clear(self) -> None: """ self.pools.clear() - def connection_from_host( - self, - host: str | None, - port: int | None = None, - scheme: str | None = "http", - pool_kwargs: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. @@ -316,23 +245,13 @@ def connection_from_host( return self.connection_from_context(request_context) - def connection_from_context( - self, request_context: dict[str, typing.Any] - ) -> HTTPConnectionPool: + def connection_from_context(self, request_context): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. ``request_context`` must at least contain the ``scheme`` key and its value must be a key in ``key_fn_by_scheme`` instance variable. """ - if "strict" in request_context: - warnings.warn( - "The 'strict' parameter is no longer needed on Python 3+. " - "This will raise an error in urllib3 v2.1.0.", - DeprecationWarning, - ) - request_context.pop("strict") - scheme = request_context["scheme"].lower() pool_key_constructor = self.key_fn_by_scheme.get(scheme) if not pool_key_constructor: @@ -341,9 +260,7 @@ def connection_from_context( return self.connection_from_pool_key(pool_key, request_context=request_context) - def connection_from_pool_key( - self, pool_key: PoolKey, request_context: dict[str, typing.Any] - ) -> HTTPConnectionPool: + def connection_from_pool_key(self, pool_key, request_context=None): """ Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. @@ -367,9 +284,7 @@ def connection_from_pool_key( return pool - def connection_from_url( - self, url: str, pool_kwargs: dict[str, typing.Any] | None = None - ) -> HTTPConnectionPool: + def connection_from_url(self, url, pool_kwargs=None): """ Similar to :func:`urllib3.connectionpool.connection_from_url`. @@ -385,9 +300,7 @@ def connection_from_url( u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs ) - def _merge_pool_kwargs( - self, override: dict[str, typing.Any] | None - ) -> dict[str, typing.Any]: + def _merge_pool_kwargs(self, override): """ Merge a dictionary of override values for self.connection_pool_kw. @@ -407,7 +320,7 @@ def _merge_pool_kwargs( base_pool_kwargs[key] = value return base_pool_kwargs - def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: + def _proxy_requires_url_absolute_form(self, parsed_url): """ Indicates if the proxy requires the complete destination URL in the request. Normally this is only needed when not using an HTTP CONNECT @@ -420,9 +333,24 @@ def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: self.proxy, self.proxy_config, parsed_url.scheme ) - def urlopen( # type: ignore[override] - self, method: str, url: str, redirect: bool = True, **kw: typing.Any - ) -> BaseHTTPResponse: + def _validate_proxy_scheme_url_selection(self, url_scheme): + """ + Validates that were not attempting to do TLS in TLS connections on + Python2 or with unsupported SSL implementations. + """ + if self.proxy is None or url_scheme != "https": + return + + if self.proxy.scheme != "https": + return + + if six.PY2 and not self.proxy_config.use_forwarding_for_https: + raise ProxySchemeUnsupported( + "Contacting HTTPS destinations through HTTPS proxies " + "'via CONNECT tunnels' is not supported in Python 2" + ) + + def urlopen(self, method, url, redirect=True, **kw): """ Same as :meth:`urllib3.HTTPConnectionPool.urlopen` with custom cross-host redirect logic and only sends the request-uri @@ -432,16 +360,7 @@ def urlopen( # type: ignore[override] :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. """ u = parse_url(url) - - if u.scheme is None: - warnings.warn( - "URLs without a scheme (ie 'https://') are deprecated and will raise an error " - "in a future version of urllib3. To avoid this DeprecationWarning ensure all URLs " - "start with 'https://' or 'http://'. Read more in this issue: " - "https://github.com/urllib3/urllib3/issues/2920", - category=DeprecationWarning, - stacklevel=2, - ) + self._validate_proxy_scheme_url_selection(u.scheme) conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) @@ -449,7 +368,7 @@ def urlopen( # type: ignore[override] kw["redirect"] = False if "headers" not in kw: - kw["headers"] = self.headers + kw["headers"] = self.headers.copy() if self._proxy_requires_url_absolute_form(u): response = conn.urlopen(method, url, **kw) @@ -470,7 +389,7 @@ def urlopen( # type: ignore[override] kw["body"] = None kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() - retries = kw.get("retries", response.retries) + retries = kw.get("retries") if not isinstance(retries, Retry): retries = Retry.from_int(retries, redirect=redirect) @@ -480,11 +399,10 @@ def urlopen( # type: ignore[override] if retries.remove_headers_on_redirect and not conn.is_same_host( redirect_location ): - new_headers = kw["headers"].copy() - for header in kw["headers"]: + headers = list(six.iterkeys(kw["headers"])) + for header in headers: if header.lower() in retries.remove_headers_on_redirect: - new_headers.pop(header, None) - kw["headers"] = new_headers + kw["headers"].pop(header, None) try: retries = retries.increment(method, url, response=response, _pool=conn) @@ -530,51 +448,37 @@ class ProxyManager(PoolManager): private. IP address, target hostname, SNI, and port are always visible to an HTTPS proxy even when this flag is disabled. - :param proxy_assert_hostname: - The hostname of the certificate to verify against. - - :param proxy_assert_fingerprint: - The fingerprint of the certificate to verify against. - Example: - - .. code-block:: python - - import urllib3 - - proxy = urllib3.ProxyManager("https://localhost:3128/") - - resp1 = proxy.request("GET", "https://google.com/") - resp2 = proxy.request("GET", "https://httpbin.org/") - - print(len(proxy.pools)) - # 1 - - resp3 = proxy.request("GET", "https://httpbin.org/") - resp4 = proxy.request("GET", "https://twitter.com/") - - print(len(proxy.pools)) - # 3 + >>> proxy = urllib3.ProxyManager('http://localhost:3128/') + >>> r1 = proxy.request('GET', 'http://google.com/') + >>> r2 = proxy.request('GET', 'http://httpbin.org/') + >>> len(proxy.pools) + 1 + >>> r3 = proxy.request('GET', 'https://httpbin.org/') + >>> r4 = proxy.request('GET', 'https://twitter.com/') + >>> len(proxy.pools) + 3 """ def __init__( self, - proxy_url: str, - num_pools: int = 10, - headers: typing.Mapping[str, str] | None = None, - proxy_headers: typing.Mapping[str, str] | None = None, - proxy_ssl_context: ssl.SSLContext | None = None, - use_forwarding_for_https: bool = False, - proxy_assert_hostname: None | str | typing.Literal[False] = None, - proxy_assert_fingerprint: str | None = None, - **connection_pool_kw: typing.Any, - ) -> None: + proxy_url, + num_pools=10, + headers=None, + proxy_headers=None, + proxy_ssl_context=None, + use_forwarding_for_https=False, + **connection_pool_kw + ): + if isinstance(proxy_url, HTTPConnectionPool): - str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" - else: - str_proxy_url = proxy_url - proxy = parse_url(str_proxy_url) + proxy_url = "%s://%s:%i" % ( + proxy_url.scheme, + proxy_url.host, + proxy_url.port, + ) + proxy = parse_url(proxy_url) if proxy.scheme not in ("http", "https"): raise ProxySchemeUnknown(proxy.scheme) @@ -586,38 +490,25 @@ def __init__( self.proxy = proxy self.proxy_headers = proxy_headers or {} self.proxy_ssl_context = proxy_ssl_context - self.proxy_config = ProxyConfig( - proxy_ssl_context, - use_forwarding_for_https, - proxy_assert_hostname, - proxy_assert_fingerprint, - ) + self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https) connection_pool_kw["_proxy"] = self.proxy connection_pool_kw["_proxy_headers"] = self.proxy_headers connection_pool_kw["_proxy_config"] = self.proxy_config - super().__init__(num_pools, headers, **connection_pool_kw) + super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw) - def connection_from_host( - self, - host: str | None, - port: int | None = None, - scheme: str | None = "http", - pool_kwargs: dict[str, typing.Any] | None = None, - ) -> HTTPConnectionPool: + def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None): if scheme == "https": - return super().connection_from_host( + return super(ProxyManager, self).connection_from_host( host, port, scheme, pool_kwargs=pool_kwargs ) - return super().connection_from_host( - self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + return super(ProxyManager, self).connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs ) - def _set_proxy_headers( - self, url: str, headers: typing.Mapping[str, str] | None = None - ) -> typing.Mapping[str, str]: + def _set_proxy_headers(self, url, headers=None): """ Sets headers needed by proxies: specifically, the Accept and Host headers. Only sets headers not provided by the user. @@ -632,9 +523,7 @@ def _set_proxy_headers( headers_.update(headers) return headers_ - def urlopen( # type: ignore[override] - self, method: str, url: str, redirect: bool = True, **kw: typing.Any - ) -> BaseHTTPResponse: + def urlopen(self, method, url, redirect=True, **kw): "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." u = parse_url(url) if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): @@ -644,8 +533,8 @@ def urlopen( # type: ignore[override] headers = kw.get("headers", self.headers) kw["headers"] = self._set_proxy_headers(url, headers) - return super().urlopen(method, url, redirect=redirect, **kw) + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) -def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: +def proxy_from_url(url, **kw): return ProxyManager(proxy_url=url, **kw) diff --git a/newrelic/packages/urllib3/py.typed b/newrelic/packages/urllib3/py.typed deleted file mode 100644 index 5f3ea3d919..0000000000 --- a/newrelic/packages/urllib3/py.typed +++ /dev/null @@ -1,2 +0,0 @@ -# Instruct type checkers to look for inline type annotations in this package. -# See PEP 561. diff --git a/newrelic/packages/urllib3/_request_methods.py b/newrelic/packages/urllib3/request.py similarity index 50% rename from newrelic/packages/urllib3/_request_methods.py rename to newrelic/packages/urllib3/request.py index 297c271bf4..3b4cf99922 100644 --- a/newrelic/packages/urllib3/_request_methods.py +++ b/newrelic/packages/urllib3/request.py @@ -1,23 +1,15 @@ -from __future__ import annotations +from __future__ import absolute_import -import json as _json -import typing -from urllib.parse import urlencode +import sys -from ._base_connection import _TYPE_BODY -from ._collections import HTTPHeaderDict -from .filepost import _TYPE_FIELDS, encode_multipart_formdata -from .response import BaseHTTPResponse +from .filepost import encode_multipart_formdata +from .packages import six +from .packages.six.moves.urllib.parse import urlencode __all__ = ["RequestMethods"] -_TYPE_ENCODE_URL_FIELDS = typing.Union[ - typing.Sequence[tuple[str, typing.Union[str, bytes]]], - typing.Mapping[str, typing.Union[str, bytes]], -] - -class RequestMethods: +class RequestMethods(object): """ Convenience mixin for classes who implement a :meth:`urlopen` method, such as :class:`urllib3.HTTPConnectionPool` and @@ -48,34 +40,25 @@ class RequestMethods: _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} - def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None: + def __init__(self, headers=None): self.headers = headers or {} def urlopen( self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - headers: typing.Mapping[str, str] | None = None, - encode_multipart: bool = True, - multipart_boundary: str | None = None, - **kw: typing.Any, - ) -> BaseHTTPResponse: # Abstract + method, + url, + body=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **kw + ): # Abstract raise NotImplementedError( "Classes extending RequestMethods must implement " "their own ``urlopen`` method." ) - def request( - self, - method: str, - url: str, - body: _TYPE_BODY | None = None, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - json: typing.Any | None = None, - **urlopen_kw: typing.Any, - ) -> BaseHTTPResponse: + def request(self, method, url, fields=None, headers=None, **urlopen_kw): """ Make a request using :meth:`urlopen` with the appropriate encoding of ``fields`` based on the ``method`` used. @@ -85,95 +68,29 @@ def request( option to drop down to more specific methods when necessary, such as :meth:`request_encode_url`, :meth:`request_encode_body`, or even the lowest level :meth:`urlopen`. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param body: - Data to send in the request body, either :class:`str`, :class:`bytes`, - an iterable of :class:`str`/:class:`bytes`, or a file-like object. - - :param fields: - Data to encode and send in the URL or request body, depending on ``method``. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param json: - Data to encode and send as JSON with UTF-encoded in the request body. - The ``"Content-Type"`` header will be set to ``"application/json"`` - unless specified otherwise. """ method = method.upper() - if json is not None and body is not None: - raise TypeError( - "request got values for both 'body' and 'json' parameters which are mutually exclusive" - ) - - if json is not None: - if headers is None: - headers = self.headers - - if not ("content-type" in map(str.lower, headers.keys())): - headers = HTTPHeaderDict(headers) - headers["Content-Type"] = "application/json" - - body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( - "utf-8" - ) - - if body is not None: - urlopen_kw["body"] = body + urlopen_kw["request_url"] = url if method in self._encode_url_methods: return self.request_encode_url( - method, - url, - fields=fields, # type: ignore[arg-type] - headers=headers, - **urlopen_kw, + method, url, fields=fields, headers=headers, **urlopen_kw ) else: return self.request_encode_body( method, url, fields=fields, headers=headers, **urlopen_kw ) - def request_encode_url( - self, - method: str, - url: str, - fields: _TYPE_ENCODE_URL_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - **urlopen_kw: str, - ) -> BaseHTTPResponse: + def request_encode_url(self, method, url, fields=None, headers=None, **urlopen_kw): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the url. This is useful for request methods like GET, HEAD, DELETE, etc. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param fields: - Data to encode and send in the URL. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. """ if headers is None: headers = self.headers - extra_kw: dict[str, typing.Any] = {"headers": headers} + extra_kw = {"headers": headers} extra_kw.update(urlopen_kw) if fields: @@ -183,14 +100,14 @@ def request_encode_url( def request_encode_body( self, - method: str, - url: str, - fields: _TYPE_FIELDS | None = None, - headers: typing.Mapping[str, str] | None = None, - encode_multipart: bool = True, - multipart_boundary: str | None = None, - **urlopen_kw: str, - ) -> BaseHTTPResponse: + method, + url, + fields=None, + headers=None, + encode_multipart=True, + multipart_boundary=None, + **urlopen_kw + ): """ Make a request using :meth:`urlopen` with the ``fields`` encoded in the body. This is useful for request methods like POST, PUT, PATCH, etc. @@ -225,34 +142,11 @@ def request_encode_body( be overwritten because it depends on the dynamic random boundary string which is used to compose the body of the request. The random boundary string can be explicitly set with the ``multipart_boundary`` parameter. - - :param method: - HTTP request method (such as GET, POST, PUT, etc.) - - :param url: - The URL to perform the request on. - - :param fields: - Data to encode and send in the request body. - - :param headers: - Dictionary of custom headers to send, such as User-Agent, - If-None-Match, etc. If None, pool headers are used. If provided, - these headers completely replace any pool-specific headers. - - :param encode_multipart: - If True, encode the ``fields`` using the multipart/form-data MIME - format. - - :param multipart_boundary: - If not specified, then a random boundary will be generated using - :func:`urllib3.filepost.choose_boundary`. """ if headers is None: headers = self.headers - extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} - body: bytes | str + extra_kw = {"headers": {}} if fields: if "body" in urlopen_kw: @@ -266,13 +160,32 @@ def request_encode_body( ) else: body, content_type = ( - urlencode(fields), # type: ignore[arg-type] + urlencode(fields), "application/x-www-form-urlencoded", ) extra_kw["body"] = body - extra_kw["headers"].setdefault("Content-Type", content_type) + extra_kw["headers"] = {"Content-Type": content_type} + extra_kw["headers"].update(headers) extra_kw.update(urlopen_kw) return self.urlopen(method, url, **extra_kw) + + +if not six.PY2: + + class RequestModule(sys.modules[__name__].__class__): + def __call__(self, *args, **kwargs): + """ + If user tries to call this module directly urllib3 v2.x style raise an error to the user + suggesting they may need urllib3 v2 + """ + raise TypeError( + "'module' object is not callable\n" + "urllib3.request() method is not supported in this release, " + "upgrade to urllib3 v2 to use it\n" + "see https://urllib3.readthedocs.io/en/stable/v2-migration-guide.html" + ) + + sys.modules[__name__].__class__ = RequestModule diff --git a/newrelic/packages/urllib3/response.py b/newrelic/packages/urllib3/response.py index ff6d1f4911..0bd13d40b8 100644 --- a/newrelic/packages/urllib3/response.py +++ b/newrelic/packages/urllib3/response.py @@ -1,38 +1,28 @@ -from __future__ import annotations +from __future__ import absolute_import -import collections import io -import json as _json import logging -import socket import sys -import typing import warnings import zlib from contextlib import contextmanager -from http.client import HTTPMessage as _HttplibHTTPMessage -from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import error as SocketError from socket import timeout as SocketTimeout -if typing.TYPE_CHECKING: - from ._base_connection import BaseHTTPConnection - try: try: - import brotlicffi as brotli # type: ignore[import-not-found] + import brotlicffi as brotli except ImportError: - import brotli # type: ignore[import-not-found] + import brotli except ImportError: brotli = None from . import util -from ._base_connection import _TYPE_BODY from ._collections import HTTPHeaderDict -from .connection import BaseSSLError, HTTPConnection, HTTPException +from .connection import BaseSSLError, HTTPException from .exceptions import ( BodyNotHttplibCompatible, DecodeError, - DependencyWarning, HTTPError, IncompleteRead, InvalidChunkLength, @@ -42,262 +32,101 @@ ResponseNotChunked, SSLError, ) +from .packages import six from .util.response import is_fp_closed, is_response_to_head -from .util.retry import Retry - -if typing.TYPE_CHECKING: - from .connectionpool import HTTPConnectionPool log = logging.getLogger(__name__) -class ContentDecoder: - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - raise NotImplementedError() - - @property - def has_unconsumed_tail(self) -> bool: - raise NotImplementedError() - - def flush(self) -> bytes: - raise NotImplementedError() - - -class DeflateDecoder(ContentDecoder): - def __init__(self) -> None: +class DeflateDecoder(object): + def __init__(self): self._first_try = True - self._first_try_data = b"" - self._unfed_data = b"" + self._data = b"" self._obj = zlib.decompressobj() - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - data = self._unfed_data + data - self._unfed_data = b"" - if not data and not self._obj.unconsumed_tail: + def __getattr__(self, name): + return getattr(self._obj, name) + + def decompress(self, data): + if not data: return data - original_max_length = max_length - if original_max_length < 0: - max_length = 0 - elif original_max_length == 0: - # We should not pass 0 to the zlib decompressor because 0 is - # the default value that will make zlib decompress without a - # length limit. - # Data should be stored for subsequent calls. - self._unfed_data = data - return b"" - # Subsequent calls always reuse `self._obj`. zlib requires - # passing the unconsumed tail if decompression is to continue. if not self._first_try: - return self._obj.decompress( - self._obj.unconsumed_tail + data, max_length=max_length - ) + return self._obj.decompress(data) - # First call tries with RFC 1950 ZLIB format. - self._first_try_data += data + self._data += data try: - decompressed = self._obj.decompress(data, max_length=max_length) + decompressed = self._obj.decompress(data) if decompressed: self._first_try = False - self._first_try_data = b"" + self._data = None return decompressed - # On failure, it falls back to RFC 1951 DEFLATE format. except zlib.error: self._first_try = False self._obj = zlib.decompressobj(-zlib.MAX_WBITS) try: - return self.decompress( - self._first_try_data, max_length=original_max_length - ) + return self.decompress(self._data) finally: - self._first_try_data = b"" + self._data = None - @property - def has_unconsumed_tail(self) -> bool: - return bool(self._unfed_data) or ( - bool(self._obj.unconsumed_tail) and not self._first_try - ) - def flush(self) -> bytes: - return self._obj.flush() +class GzipDecoderState(object): - -class GzipDecoderState: FIRST_MEMBER = 0 OTHER_MEMBERS = 1 SWALLOW_DATA = 2 -class GzipDecoder(ContentDecoder): - def __init__(self) -> None: +class GzipDecoder(object): + def __init__(self): self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) self._state = GzipDecoderState.FIRST_MEMBER - self._unconsumed_tail = b"" - - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - ret = bytearray() - if self._state == GzipDecoderState.SWALLOW_DATA: - return bytes(ret) - if max_length == 0: - # We should not pass 0 to the zlib decompressor because 0 is - # the default value that will make zlib decompress without a - # length limit. - # Data should be stored for subsequent calls. - self._unconsumed_tail += data - return b"" + def __getattr__(self, name): + return getattr(self._obj, name) - # zlib requires passing the unconsumed tail to the subsequent - # call if decompression is to continue. - data = self._unconsumed_tail + data - if not data and self._obj.eof: + def decompress(self, data): + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: return bytes(ret) - while True: try: - ret += self._obj.decompress( - data, max_length=max(max_length - len(ret), 0) - ) + ret += self._obj.decompress(data) except zlib.error: previous_state = self._state # Ignore data after the first error self._state = GzipDecoderState.SWALLOW_DATA - self._unconsumed_tail = b"" if previous_state == GzipDecoderState.OTHER_MEMBERS: # Allow trailing garbage acceptable in other gzip clients return bytes(ret) raise - - self._unconsumed_tail = data = ( - self._obj.unconsumed_tail or self._obj.unused_data - ) - if max_length > 0 and len(ret) >= max_length: - break - + data = self._obj.unused_data if not data: return bytes(ret) - # When the end of a gzip member is reached, a new decompressor - # must be created for unused (possibly future) data. - if self._obj.eof: - self._state = GzipDecoderState.OTHER_MEMBERS - self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) - - return bytes(ret) - - @property - def has_unconsumed_tail(self) -> bool: - return bool(self._unconsumed_tail) - - def flush(self) -> bytes: - return self._obj.flush() + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) if brotli is not None: - class BrotliDecoder(ContentDecoder): + class BrotliDecoder(object): # Supports both 'brotlipy' and 'Brotli' packages # since they share an import name. The top branches # are for 'brotlipy' and bottom branches for 'Brotli' - def __init__(self) -> None: + def __init__(self): self._obj = brotli.Decompressor() if hasattr(self._obj, "decompress"): - setattr(self, "_decompress", self._obj.decompress) + self.decompress = self._obj.decompress else: - setattr(self, "_decompress", self._obj.process) - - # Requires Brotli >= 1.2.0 for `output_buffer_limit`. - def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: - raise NotImplementedError() - - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - try: - if max_length > 0: - return self._decompress(data, output_buffer_limit=max_length) - else: - return self._decompress(data) - except TypeError: - # Fallback for Brotli/brotlicffi/brotlipy versions without - # the `output_buffer_limit` parameter. - warnings.warn( - "Brotli >= 1.2.0 is required to prevent decompression bombs.", - DependencyWarning, - ) - return self._decompress(data) - - @property - def has_unconsumed_tail(self) -> bool: - try: - return not self._obj.can_accept_more_data() - except AttributeError: - return False + self.decompress = self._obj.process - def flush(self) -> bytes: + def flush(self): if hasattr(self._obj, "flush"): - return self._obj.flush() # type: ignore[no-any-return] - return b"" - - -try: - if sys.version_info >= (3, 14): - from compression import zstd - else: - from backports import zstd -except ImportError: - HAS_ZSTD = False -else: - HAS_ZSTD = True - - class ZstdDecoder(ContentDecoder): - def __init__(self) -> None: - self._obj = zstd.ZstdDecompressor() - - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - if not data and not self.has_unconsumed_tail: - return b"" - if self._obj.eof: - data = self._obj.unused_data + data - self._obj = zstd.ZstdDecompressor() - part = self._obj.decompress(data, max_length=max_length) - length = len(part) - data_parts = [part] - # Every loop iteration is supposed to read data from a separate frame. - # The loop breaks when: - # - enough data is read; - # - no more unused data is available; - # - end of the last read frame has not been reached (i.e., - # more data has to be fed). - while ( - self._obj.eof - and self._obj.unused_data - and (max_length < 0 or length < max_length) - ): - unused_data = self._obj.unused_data - if not self._obj.needs_input: - self._obj = zstd.ZstdDecompressor() - part = self._obj.decompress( - unused_data, - max_length=(max_length - length) if max_length > 0 else -1, - ) - if part_length := len(part): - data_parts.append(part) - length += part_length - elif self._obj.needs_input: - break - return b"".join(data_parts) - - @property - def has_unconsumed_tail(self) -> bool: - return not (self._obj.needs_input or self._obj.eof) or bool( - self._obj.unused_data - ) - - def flush(self) -> bytes: - if not self._obj.eof: - raise DecodeError("Zstandard data is incomplete") + return self._obj.flush() return b"" -class MultiDecoder(ContentDecoder): +class MultiDecoder(object): """ From RFC7231: If one or more encodings have been applied to a representation, the @@ -306,387 +135,32 @@ class MultiDecoder(ContentDecoder): they were applied. """ - # Maximum allowed number of chained HTTP encodings in the - # Content-Encoding header. - max_decode_links = 5 - - def __init__(self, modes: str) -> None: - encodings = [m.strip() for m in modes.split(",")] - if len(encodings) > self.max_decode_links: - raise DecodeError( - "Too many content encodings in the chain: " - f"{len(encodings)} > {self.max_decode_links}" - ) - self._decoders = [_get_decoder(e) for e in encodings] + def __init__(self, modes): + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] - def flush(self) -> bytes: + def flush(self): return self._decoders[0].flush() - def decompress(self, data: bytes, max_length: int = -1) -> bytes: - if max_length <= 0: - for d in reversed(self._decoders): - data = d.decompress(data) - return data - - ret = bytearray() - # Every while loop iteration goes through all decoders once. - # It exits when enough data is read or no more data can be read. - # It is possible that the while loop iteration does not produce - # any data because we retrieve up to `max_length` from every - # decoder, and the amount of bytes may be insufficient for the - # next decoder to produce enough/any output. - while True: - any_data = False - for d in reversed(self._decoders): - data = d.decompress(data, max_length=max_length - len(ret)) - if data: - any_data = True - # We should not break when no data is returned because - # next decoders may produce data even with empty input. - ret += data - if not any_data or len(ret) >= max_length: - return bytes(ret) - data = b"" - - @property - def has_unconsumed_tail(self) -> bool: - return any(d.has_unconsumed_tail for d in self._decoders) + def decompress(self, data): + for d in reversed(self._decoders): + data = d.decompress(data) + return data -def _get_decoder(mode: str) -> ContentDecoder: +def _get_decoder(mode): if "," in mode: return MultiDecoder(mode) - # According to RFC 9110 section 8.4.1.3, recipients should - # consider x-gzip equivalent to gzip - if mode in ("gzip", "x-gzip"): + if mode == "gzip": return GzipDecoder() if brotli is not None and mode == "br": return BrotliDecoder() - if HAS_ZSTD and mode == "zstd": - return ZstdDecoder() - return DeflateDecoder() -class BytesQueueBuffer: - """Memory-efficient bytes buffer - - To return decoded data in read() and still follow the BufferedIOBase API, we need a - buffer to always return the correct amount of bytes. - - This buffer should be filled using calls to put() - - Our maximum memory usage is determined by the sum of the size of: - - * self.buffer, which contains the full data - * the largest chunk that we will copy in get() - """ - - def __init__(self) -> None: - self.buffer: typing.Deque[bytes | memoryview[bytes]] = collections.deque() - self._size: int = 0 - - def __len__(self) -> int: - return self._size - - def put(self, data: bytes) -> None: - self.buffer.append(data) - self._size += len(data) - - def get(self, n: int) -> bytes: - if n == 0: - return b"" - elif not self.buffer: - raise RuntimeError("buffer is empty") - elif n < 0: - raise ValueError("n should be > 0") - - if len(self.buffer[0]) == n and isinstance(self.buffer[0], bytes): - self._size -= n - return self.buffer.popleft() - - fetched = 0 - ret = io.BytesIO() - while fetched < n: - remaining = n - fetched - chunk = self.buffer.popleft() - chunk_length = len(chunk) - if remaining < chunk_length: - chunk = memoryview(chunk) - left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] - ret.write(left_chunk) - self.buffer.appendleft(right_chunk) - self._size -= remaining - break - else: - ret.write(chunk) - self._size -= chunk_length - fetched += chunk_length - - if not self.buffer: - break - - return ret.getvalue() - - def get_all(self) -> bytes: - buffer = self.buffer - if not buffer: - assert self._size == 0 - return b"" - if len(buffer) == 1: - result = buffer.pop() - if isinstance(result, memoryview): - result = result.tobytes() - else: - ret = io.BytesIO() - ret.writelines(buffer.popleft() for _ in range(len(buffer))) - result = ret.getvalue() - self._size = 0 - return result - - -class BaseHTTPResponse(io.IOBase): - CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] - if brotli is not None: - CONTENT_DECODERS += ["br"] - if HAS_ZSTD: - CONTENT_DECODERS += ["zstd"] - REDIRECT_STATUSES = [301, 302, 303, 307, 308] - - DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) - if brotli is not None: - DECODER_ERROR_CLASSES += (brotli.error,) - - if HAS_ZSTD: - DECODER_ERROR_CLASSES += (zstd.ZstdError,) - - def __init__( - self, - *, - headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, - status: int, - version: int, - version_string: str, - reason: str | None, - decode_content: bool, - request_url: str | None, - retries: Retry | None = None, - ) -> None: - if isinstance(headers, HTTPHeaderDict): - self.headers = headers - else: - self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] - self.status = status - self.version = version - self.version_string = version_string - self.reason = reason - self.decode_content = decode_content - self._has_decoded_content = False - self._request_url: str | None = request_url - self.retries = retries - - self.chunked = False - tr_enc = self.headers.get("transfer-encoding", "").lower() - # Don't incur the penalty of creating a list and then discarding it - encodings = (enc.strip() for enc in tr_enc.split(",")) - if "chunked" in encodings: - self.chunked = True - - self._decoder: ContentDecoder | None = None - self.length_remaining: int | None - - def get_redirect_location(self) -> str | None | typing.Literal[False]: - """ - Should we redirect and where to? - - :returns: Truthy redirect location string if we got a redirect status - code and valid location. ``None`` if redirect status and no - location. ``False`` if not a redirect status code. - """ - if self.status in self.REDIRECT_STATUSES: - return self.headers.get("location") - return False - - @property - def data(self) -> bytes: - raise NotImplementedError() - - def json(self) -> typing.Any: - """ - Deserializes the body of the HTTP response as a Python object. - - The body of the HTTP response must be encoded using UTF-8, as per - `RFC 8529 Section 8.1 `_. - - To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to - your custom decoder instead. - - If the body of the HTTP response is not decodable to UTF-8, a - `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a - valid JSON document, a `json.JSONDecodeError` will be raised. - - Read more :ref:`here `. - - :returns: The body of the HTTP response as a Python object. - """ - data = self.data.decode("utf-8") - return _json.loads(data) - - @property - def url(self) -> str | None: - raise NotImplementedError() - - @url.setter - def url(self, url: str | None) -> None: - raise NotImplementedError() - - @property - def connection(self) -> BaseHTTPConnection | None: - raise NotImplementedError() - - @property - def retries(self) -> Retry | None: - return self._retries - - @retries.setter - def retries(self, retries: Retry | None) -> None: - # Override the request_url if retries has a redirect location. - if retries is not None and retries.history: - self.url = retries.history[-1].redirect_location - self._retries = retries - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Iterator[bytes]: - raise NotImplementedError() - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, - cache_content: bool = False, - ) -> bytes: - raise NotImplementedError() - - def read1( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> bytes: - raise NotImplementedError() - - def read_chunked( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> typing.Iterator[bytes]: - raise NotImplementedError() - - def release_conn(self) -> None: - raise NotImplementedError() - - def drain_conn(self) -> None: - raise NotImplementedError() - - def shutdown(self) -> None: - raise NotImplementedError() - - def close(self) -> None: - raise NotImplementedError() - - def _init_decoder(self) -> None: - """ - Set-up the _decoder attribute if necessary. - """ - # Note: content-encoding value should be case-insensitive, per RFC 7230 - # Section 3.2 - content_encoding = self.headers.get("content-encoding", "").lower() - if self._decoder is None: - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) - elif "," in content_encoding: - encodings = [ - e.strip() - for e in content_encoding.split(",") - if e.strip() in self.CONTENT_DECODERS - ] - if encodings: - self._decoder = _get_decoder(content_encoding) - - def _decode( - self, - data: bytes, - decode_content: bool | None, - flush_decoder: bool, - max_length: int | None = None, - ) -> bytes: - """ - Decode the data passed in and potentially flush the decoder. - """ - if not decode_content: - if self._has_decoded_content: - raise RuntimeError( - "Calling read(decode_content=False) is not supported after " - "read(decode_content=True) was called." - ) - return data - - if max_length is None or flush_decoder: - max_length = -1 - - try: - if self._decoder: - data = self._decoder.decompress(data, max_length=max_length) - self._has_decoded_content = True - except self.DECODER_ERROR_CLASSES as e: - content_encoding = self.headers.get("content-encoding", "").lower() - raise DecodeError( - "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, - e, - ) from e - if flush_decoder: - data += self._flush_decoder() - - return data - - def _flush_decoder(self) -> bytes: - """ - Flushes the decoder. Should only be called if the decoder is actually - being used. - """ - if self._decoder: - return self._decoder.decompress(b"") + self._decoder.flush() - return b"" - - # Compatibility methods for `io` module - def readinto(self, b: bytearray) -> int: - temp = self.read(len(b)) - if len(temp) == 0: - return 0 - else: - b[: len(temp)] = temp - return len(temp) - - # Methods used by dependent libraries - def getheaders(self) -> HTTPHeaderDict: - return self.headers - - def getheader(self, name: str, default: str | None = None) -> str | None: - return self.headers.get(name, default) - - # Compatibility method for http.cookiejar - def info(self) -> HTTPHeaderDict: - return self.headers - - def geturl(self) -> str | None: - return self.url - - -class HTTPResponse(BaseHTTPResponse): +class HTTPResponse(io.IOBase): """ HTTP Response container. @@ -719,111 +193,126 @@ class is also compatible with the Python standard library's :mod:`io` value of Content-Length header, if present. Otherwise, raise error. """ + CONTENT_DECODERS = ["gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + def __init__( self, - body: _TYPE_BODY = "", - headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, - status: int = 0, - version: int = 0, - version_string: str = "HTTP/?", - reason: str | None = None, - preload_content: bool = True, - decode_content: bool = True, - original_response: _HttplibHTTPResponse | None = None, - pool: HTTPConnectionPool | None = None, - connection: HTTPConnection | None = None, - msg: _HttplibHTTPMessage | None = None, - retries: Retry | None = None, - enforce_content_length: bool = True, - request_method: str | None = None, - request_url: str | None = None, - auto_close: bool = True, - sock_shutdown: typing.Callable[[int], None] | None = None, - ) -> None: - super().__init__( - headers=headers, - status=status, - version=version, - version_string=version_string, - reason=reason, - decode_content=decode_content, - request_url=request_url, - retries=retries, - ) + body="", + headers=None, + status=0, + version=0, + reason=None, + strict=0, + preload_content=True, + decode_content=True, + original_response=None, + pool=None, + connection=None, + msg=None, + retries=None, + enforce_content_length=False, + request_method=None, + request_url=None, + auto_close=True, + ): + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) + self.status = status + self.version = version + self.reason = reason + self.strict = strict + self.decode_content = decode_content + self.retries = retries self.enforce_content_length = enforce_content_length self.auto_close = auto_close + self._decoder = None self._body = None - self._fp: _HttplibHTTPResponse | None = None + self._fp = None self._original_response = original_response self._fp_bytes_read = 0 self.msg = msg + self._request_url = request_url - if body and isinstance(body, (str, bytes)): + if body and isinstance(body, (six.string_types, bytes)): self._body = body self._pool = pool self._connection = connection if hasattr(body, "read"): - self._fp = body # type: ignore[assignment] - self._sock_shutdown = sock_shutdown + self._fp = body # Are we using the chunked-style of transfer encoding? - self.chunk_left: int | None = None + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True # Determine length of response self.length_remaining = self._init_length(request_method) - # Used to return the correct amount of bytes for partial read()s - self._decoded_buffer = BytesQueueBuffer() - # If requested, preload the body. if preload_content and not self._body: self._body = self.read(decode_content=decode_content) - def release_conn(self) -> None: + def get_redirect_location(self): + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + + return False + + def release_conn(self): if not self._pool or not self._connection: - return None + return self._pool._put_conn(self._connection) self._connection = None - def drain_conn(self) -> None: + def drain_conn(self): """ Read and discard any remaining HTTP response data in the response connection. Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. """ try: - self.read( - # Do not spend resources decoding the content unless - # decoding has already been initiated. - decode_content=self._has_decoded_content, - ) - except (HTTPError, OSError, BaseSSLError, HTTPException): + self.read() + except (HTTPError, SocketError, BaseSSLError, HTTPException): pass @property - def data(self) -> bytes: + def data(self): # For backwards-compat with earlier urllib3 0.4 and earlier. if self._body: - return self._body # type: ignore[return-value] + return self._body if self._fp: return self.read(cache_content=True) - return None # type: ignore[return-value] - @property - def connection(self) -> HTTPConnection | None: + def connection(self): return self._connection - def isclosed(self) -> bool: + def isclosed(self): return is_fp_closed(self._fp) - def tell(self) -> int: + def tell(self): """ Obtain the number of bytes pulled over the wire so far. May differ from the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` @@ -831,14 +320,13 @@ def tell(self) -> int: """ return self._fp_bytes_read - def _init_length(self, request_method: str | None) -> int | None: + def _init_length(self, request_method): """ Set initial length value for Response content if available. """ - length: int | None - content_length: str | None = self.headers.get("content-length") + length = self.headers.get("content-length") - if content_length is not None: + if length is not None: if self.chunked: # This Response will fail with an IncompleteRead if it can't be # received as chunked. This method falls back to attempt reading @@ -858,11 +346,11 @@ def _init_length(self, request_method: str | None) -> int | None: # (e.g. Content-Length: 42, 42). This line ensures the values # are all valid ints and that as long as the `set` length is 1, # all values are the same. Otherwise, the header is invalid. - lengths = {int(val) for val in content_length.split(",")} + lengths = set([int(val) for val in length.split(",")]) if len(lengths) > 1: raise InvalidHeader( "Content-Length contained multiple " - "unmatching values (%s)" % content_length + "unmatching values (%s)" % length ) length = lengths.pop() except ValueError: @@ -871,9 +359,6 @@ def _init_length(self, request_method: str | None) -> int | None: if length < 0: length = None - else: # if content_length is None - length = None - # Convert status to int for comparison # In some cases, httplib returns a status of "_UNKNOWN" try: @@ -887,8 +372,64 @@ def _init_length(self, request_method: str | None) -> int | None: return length + def _init_decoder(self): + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if len(encodings): + self._decoder = _get_decoder(content_encoding) + + DECODER_ERROR_CLASSES = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + def _decode(self, data, decode_content, flush_decoder): + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self): + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + buf = self._decoder.decompress(b"") + return buf + self._decoder.flush() + + return b"" + @contextmanager - def _error_catcher(self) -> typing.Generator[None]: + def _error_catcher(self): """ Catch low-level python exceptions, instead re-raising urllib3 variants, so that low-level exceptions are not leaked in the @@ -902,32 +443,22 @@ def _error_catcher(self) -> typing.Generator[None]: try: yield - except SocketTimeout as e: + except SocketTimeout: # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but # there is yet no clean way to get at it from this context. - raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + raise ReadTimeoutError(self._pool, None, "Read timed out.") except BaseSSLError as e: # FIXME: Is there a better way to differentiate between SSLErrors? if "read operation timed out" not in str(e): # SSL errors related to framing/MAC get wrapped and reraised here - raise SSLError(e) from e - - raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + raise SSLError(e) - except IncompleteRead as e: - if ( - e.expected is not None - and e.partial is not None - and e.expected == -e.partial - ): - arg = "Response may not contain content." - else: - arg = f"Connection broken: {e!r}" - raise ProtocolError(arg, e) from e + raise ReadTimeoutError(self._pool, None, "Read timed out.") - except (HTTPException, OSError) as e: - raise ProtocolError(f"Connection broken: {e!r}", e) from e + except (HTTPException, SocketError) as e: + # This includes IncompleteRead. + raise ProtocolError("Connection broken: %r" % e, e) # If no exception is thrown, we should avoid cleaning up # unnecessarily. @@ -953,12 +484,7 @@ def _error_catcher(self) -> typing.Generator[None]: if self._original_response and self._original_response.isclosed(): self.release_conn() - def _fp_read( - self, - amt: int | None = None, - *, - read1: bool = False, - ) -> bytes: + def _fp_read(self, amt): """ Read a response with the thought that reading the number of bytes larger than can fit in a 32-bit int at a time via SSL in some @@ -967,23 +493,21 @@ def _fp_read( happen. The known cases: - * CPython < 3.9.7 because of a bug + * 3.8 <= CPython < 3.9.7 because of a bug https://github.com/urllib3/urllib3/issues/2513#issuecomment-1152559900. * urllib3 injected with pyOpenSSL-backed SSL-support. * CPython < 3.10 only when `amt` does not fit 32-bit int. """ assert self._fp - c_int_max = 2**31 - 1 + c_int_max = 2 ** 31 - 1 if ( - (amt and amt > c_int_max) - or ( - amt is None - and self.length_remaining - and self.length_remaining > c_int_max + ( + (amt and amt > c_int_max) + or (self.length_remaining and self.length_remaining > c_int_max) ) - ) and (util.IS_PYOPENSSL or sys.version_info < (3, 10)): - if read1: - return self._fp.read1(c_int_max) + and not util.IS_SECURETRANSPORT + and (util.IS_PYOPENSSL or sys.version_info < (3, 10)) + ): buffer = io.BytesIO() # Besides `max_chunk_amt` being a maximum chunk size, it # affects memory overhead of reading a response by this @@ -991,7 +515,7 @@ def _fp_read( # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum # chunk size that does not lead to an overflow error, but # 256 MiB is a compromise. - max_chunk_amt = 2**28 + max_chunk_amt = 2 ** 28 while amt is None or amt != 0: if amt is not None: chunk_amt = min(amt, max_chunk_amt) @@ -1004,70 +528,11 @@ def _fp_read( buffer.write(data) del data # to reduce peak memory usage by `max_chunk_amt`. return buffer.getvalue() - elif read1: - return self._fp.read1(amt) if amt is not None else self._fp.read1() else: # StringIO doesn't like amt=None return self._fp.read(amt) if amt is not None else self._fp.read() - def _raw_read( - self, - amt: int | None = None, - *, - read1: bool = False, - ) -> bytes: - """ - Reads `amt` of bytes from the socket. - """ - if self._fp is None: - return None # type: ignore[return-value] - - fp_closed = getattr(self._fp, "closed", False) - - with self._error_catcher(): - data = self._fp_read(amt, read1=read1) if not fp_closed else b"" - if amt is not None and amt != 0 and not data: - # Platform-specific: Buggy versions of Python. - # Close the connection when no data is returned - # - # This is redundant to what httplib/http.client _should_ - # already do. However, versions of python released before - # December 15, 2012 (http://bugs.python.org/issue16298) do - # not properly close the connection in all cases. There is - # no harm in redundantly calling close. - self._fp.close() - if ( - self.enforce_content_length - and self.length_remaining is not None - and self.length_remaining != 0 - ): - # This is an edge case that httplib failed to cover due - # to concerns of backward compatibility. We're - # addressing it here to make sure IncompleteRead is - # raised during streaming, so all calls with incorrect - # Content-Length are caught. - raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - elif read1 and ( - (amt != 0 and not data) or self.length_remaining == len(data) - ): - # All data has been read, but `self._fp.read1` in - # CPython 3.12 and older doesn't always close - # `http.client.HTTPResponse`, so we close it here. - # See https://github.com/python/cpython/issues/113199 - self._fp.close() - - if data: - self._fp_bytes_read += len(data) - if self.length_remaining is not None: - self.length_remaining -= len(data) - return data - - def read( - self, - amt: int | None = None, - decode_content: bool | None = None, - cache_content: bool = False, - ) -> bytes: + def read(self, amt=None, decode_content=None, cache_content=False): """ Similar to :meth:`http.client.HTTPResponse.read`, but with two additional parameters: ``decode_content`` and ``cache_content``. @@ -1092,145 +557,54 @@ def read( if decode_content is None: decode_content = self.decode_content - if amt and amt < 0: - # Negative numbers and `None` should be treated the same. - amt = None - elif amt is not None: - cache_content = False - - if self._decoder and self._decoder.has_unconsumed_tail: - decoded_data = self._decode( - b"", - decode_content, - flush_decoder=False, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - if len(self._decoded_buffer) >= amt: - return self._decoded_buffer.get(amt) + if self._fp is None: + return - data = self._raw_read(amt) + flush_decoder = False + fp_closed = getattr(self._fp, "closed", False) - flush_decoder = amt is None or (amt != 0 and not data) + with self._error_catcher(): + data = self._fp_read(amt) if not fp_closed else b"" + if amt is None: + flush_decoder = True + else: + cache_content = False + if ( + amt != 0 and not data + ): # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + flush_decoder = True + if self.enforce_content_length and self.length_remaining not in ( + 0, + None, + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) - if ( - not data - and len(self._decoded_buffer) == 0 - and not (self._decoder and self._decoder.has_unconsumed_tail) - ): - return data + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) - if amt is None: data = self._decode(data, decode_content, flush_decoder) + if cache_content: self._body = data - else: - # do not waste memory on buffer when not decoding - if not decode_content: - if self._has_decoded_content: - raise RuntimeError( - "Calling read(decode_content=False) is not supported after " - "read(decode_content=True) was called." - ) - return data - - decoded_data = self._decode( - data, - decode_content, - flush_decoder, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - - while len(self._decoded_buffer) < amt and data: - # TODO make sure to initially read enough data to get past the headers - # For example, the GZ file header takes 10 bytes, we don't want to read - # it one byte at a time - data = self._raw_read(amt) - decoded_data = self._decode( - data, - decode_content, - flush_decoder, - max_length=amt - len(self._decoded_buffer), - ) - self._decoded_buffer.put(decoded_data) - data = self._decoded_buffer.get(amt) return data - def read1( - self, - amt: int | None = None, - decode_content: bool | None = None, - ) -> bytes: - """ - Similar to ``http.client.HTTPResponse.read1`` and documented - in :meth:`io.BufferedReader.read1`, but with an additional parameter: - ``decode_content``. - - :param amt: - How much of the content to read. - - :param decode_content: - If True, will attempt to decode the body based on the - 'content-encoding' header. - """ - if decode_content is None: - decode_content = self.decode_content - if amt and amt < 0: - # Negative numbers and `None` should be treated the same. - amt = None - # try and respond without going to the network - if self._has_decoded_content: - if not decode_content: - raise RuntimeError( - "Calling read1(decode_content=False) is not supported after " - "read1(decode_content=True) was called." - ) - if ( - self._decoder - and self._decoder.has_unconsumed_tail - and (amt is None or len(self._decoded_buffer) < amt) - ): - decoded_data = self._decode( - b"", - decode_content, - flush_decoder=False, - max_length=( - amt - len(self._decoded_buffer) if amt is not None else None - ), - ) - self._decoded_buffer.put(decoded_data) - if len(self._decoded_buffer) > 0: - if amt is None: - return self._decoded_buffer.get_all() - return self._decoded_buffer.get(amt) - if amt == 0: - return b"" - - # FIXME, this method's type doesn't say returning None is possible - data = self._raw_read(amt, read1=True) - if not decode_content or data is None: - return data - - self._init_decoder() - while True: - flush_decoder = not data - decoded_data = self._decode( - data, decode_content, flush_decoder, max_length=amt - ) - self._decoded_buffer.put(decoded_data) - if decoded_data or flush_decoder: - break - data = self._raw_read(8192, read1=True) - - if amt is None: - return self._decoded_buffer.get_all() - return self._decoded_buffer.get(amt) - - def stream( - self, amt: int | None = 2**16, decode_content: bool | None = None - ) -> typing.Generator[bytes]: + def stream(self, amt=2 ** 16, decode_content=None): """ A generator wrapper for the read() method. A call will block until ``amt`` bytes have been read from the connection or until the @@ -1247,35 +621,73 @@ def stream( 'content-encoding' header. """ if self.chunked and self.supports_chunked_reads(): - yield from self.read_chunked(amt, decode_content=decode_content) + for line in self.read_chunked(amt, decode_content=decode_content): + yield line else: - while ( - not is_fp_closed(self._fp) - or len(self._decoded_buffer) > 0 - or (self._decoder and self._decoder.has_unconsumed_tail) - ): + while not is_fp_closed(self._fp): data = self.read(amt=amt, decode_content=decode_content) if data: yield data - # Overrides from io.IOBase - def readable(self) -> bool: - return True + @classmethod + def from_httplib(ResponseCls, r, **response_kw): + """ + Given an :class:`http.client.HTTPResponse` instance ``r``, return a + corresponding :class:`urllib3.response.HTTPResponse` object. - def shutdown(self) -> None: - if not self._sock_shutdown: - raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") - if self._connection is None: - raise RuntimeError( - "Cannot shutdown as connection has already been released to the pool" - ) - self._sock_shutdown(socket.SHUT_RD) + Remaining parameters are passed to the HTTPResponse constructor, along + with ``original_response=r``. + """ + headers = r.msg + + if not isinstance(headers, HTTPHeaderDict): + if six.PY2: + # Python 2.7 + headers = HTTPHeaderDict.from_httplib(headers) + else: + headers = HTTPHeaderDict(headers.items()) + + # HTTPResponse objects in Python 3 don't have a .strict attribute + strict = getattr(r, "strict", 0) + resp = ResponseCls( + body=r, + headers=headers, + status=r.status, + version=r.version, + reason=r.reason, + strict=strict, + original_response=r, + **response_kw + ) + return resp + + # Backwards-compatibility methods for http.client.HTTPResponse + def getheaders(self): + warnings.warn( + "HTTPResponse.getheaders() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead access HTTPResponse.headers directly.", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers - def close(self) -> None: - self._sock_shutdown = None + def getheader(self, name, default=None): + warnings.warn( + "HTTPResponse.getheader() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers.get(name, default) + + # Backwards compatibility for http.cookiejar + def info(self): + return self.headers - if not self.closed and self._fp: + # Overrides from io.IOBase + def close(self): + if not self.closed: self._fp.close() if self._connection: @@ -1285,9 +697,9 @@ def close(self) -> None: io.IOBase.close(self) @property - def closed(self) -> bool: + def closed(self): if not self.auto_close: - return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + return io.IOBase.closed.__get__(self) elif self._fp is None: return True elif hasattr(self._fp, "isclosed"): @@ -1297,18 +709,18 @@ def closed(self) -> bool: else: return True - def fileno(self) -> int: + def fileno(self): if self._fp is None: - raise OSError("HTTPResponse has no file to get a fileno from") + raise IOError("HTTPResponse has no file to get a fileno from") elif hasattr(self._fp, "fileno"): return self._fp.fileno() else: - raise OSError( + raise IOError( "The file-like object this HTTPResponse is wrapped " "around has no file descriptor" ) - def flush(self) -> None: + def flush(self): if ( self._fp is not None and hasattr(self._fp, "flush") @@ -1316,7 +728,20 @@ def flush(self) -> None: ): return self._fp.flush() - def supports_chunked_reads(self) -> bool: + def readable(self): + # This method is required for `io` module compatibility. + return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + def supports_chunked_reads(self): """ Checks if the underlying file-like object looks like a :class:`http.client.HTTPResponse` object. We do this by testing for @@ -1325,49 +750,43 @@ def supports_chunked_reads(self) -> bool: """ return hasattr(self._fp, "fp") - def _update_chunk_length(self) -> None: + def _update_chunk_length(self): # First, we'll figure out length of a chunk and then # we'll try to read it from socket. if self.chunk_left is not None: - return None - line = self._fp.fp.readline() # type: ignore[union-attr] + return + line = self._fp.fp.readline() line = line.split(b";", 1)[0] try: self.chunk_left = int(line, 16) except ValueError: + # Invalid chunked protocol response, abort. self.close() - if line: - # Invalid chunked protocol response, abort. - raise InvalidChunkLength(self, line) from None - else: - # Truncated at start of next chunk - raise ProtocolError("Response ended prematurely") from None + raise InvalidChunkLength(self, line) - def _handle_chunk(self, amt: int | None) -> bytes: + def _handle_chunk(self, amt): returned_chunk = None if amt is None: - chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + chunk = self._fp._safe_read(self.chunk_left) returned_chunk = chunk - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None - elif self.chunk_left is not None and amt < self.chunk_left: - value = self._fp._safe_read(amt) # type: ignore[union-attr] + elif amt < self.chunk_left: + value = self._fp._safe_read(amt) self.chunk_left = self.chunk_left - amt returned_chunk = value elif amt == self.chunk_left: - value = self._fp._safe_read(amt) # type: ignore[union-attr] - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + value = self._fp._safe_read(amt) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None returned_chunk = value else: # amt > self.chunk_left - returned_chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] - self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + returned_chunk = self._fp._safe_read(self.chunk_left) + self._fp._safe_read(2) # Toss the CRLF at the end of the chunk. self.chunk_left = None - return returned_chunk # type: ignore[no-any-return] + return returned_chunk - def read_chunked( - self, amt: int | None = None, decode_content: bool | None = None - ) -> typing.Generator[bytes]: + def read_chunked(self, amt=None, decode_content=None): """ Similar to :meth:`HTTPResponse.read`, but with an additional parameter: ``decode_content``. @@ -1398,32 +817,20 @@ def read_chunked( # Don't bother reading the body of a HEAD request. if self._original_response and is_response_to_head(self._original_response): self._original_response.close() - return None + return # If a response is already read and closed # then return immediately. - if self._fp.fp is None: # type: ignore[union-attr] - return None - - if amt and amt < 0: - # Negative numbers and `None` should be treated the same, - # but httplib handles only `None` correctly. - amt = None + if self._fp.fp is None: + return while True: - # First, check if any data is left in the decoder's buffer. - if self._decoder and self._decoder.has_unconsumed_tail: - chunk = b"" - else: - self._update_chunk_length() - if self.chunk_left == 0: - break - chunk = self._handle_chunk(amt) + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) decoded = self._decode( - chunk, - decode_content=decode_content, - flush_decoder=False, - max_length=amt, + chunk, decode_content=decode_content, flush_decoder=False ) if decoded: yield decoded @@ -1437,7 +844,7 @@ def read_chunked( yield decoded # Chunk content ends with \r\n: discard it. - while self._fp is not None: + while True: line = self._fp.fp.readline() if not line: # Some sites may not end with '\r\n'. @@ -1449,29 +856,27 @@ def read_chunked( if self._original_response: self._original_response.close() - @property - def url(self) -> str | None: + def geturl(self): """ Returns the URL that was the source of this response. If the request that generated this response redirected, this method will return the final redirect location. """ - return self._request_url - - @url.setter - def url(self, url: str | None) -> None: - self._request_url = url + if self.retries is not None and len(self.retries.history): + return self.retries.history[-1].redirect_location + else: + return self._request_url - def __iter__(self) -> typing.Iterator[bytes]: - buffer: list[bytes] = [] + def __iter__(self): + buffer = [] for chunk in self.stream(decode_content=True): if b"\n" in chunk: - chunks = chunk.split(b"\n") - yield b"".join(buffer) + chunks[0] + b"\n" - for x in chunks[1:-1]: + chunk = chunk.split(b"\n") + yield b"".join(buffer) + chunk[0] + b"\n" + for x in chunk[1:-1]: yield x + b"\n" - if chunks[-1]: - buffer = [chunks[-1]] + if chunk[-1]: + buffer = [chunk[-1]] else: buffer = [] else: diff --git a/newrelic/packages/urllib3/util/__init__.py b/newrelic/packages/urllib3/util/__init__.py index 534126033c..4547fc522b 100644 --- a/newrelic/packages/urllib3/util/__init__.py +++ b/newrelic/packages/urllib3/util/__init__.py @@ -1,39 +1,46 @@ -# For backwards compatibility, provide imports that used to be here. -from __future__ import annotations +from __future__ import absolute_import +# For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers from .response import is_fp_closed from .retry import Retry from .ssl_ import ( ALPN_PROTOCOLS, + HAS_SNI, IS_PYOPENSSL, + IS_SECURETRANSPORT, + PROTOCOL_TLS, SSLContext, assert_fingerprint, - create_urllib3_context, resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, ) -from .timeout import Timeout -from .url import Url, parse_url +from .timeout import Timeout, current_time +from .url import Url, get_host, parse_url, split_first from .wait import wait_for_read, wait_for_write __all__ = ( + "HAS_SNI", "IS_PYOPENSSL", + "IS_SECURETRANSPORT", "SSLContext", + "PROTOCOL_TLS", "ALPN_PROTOCOLS", "Retry", "Timeout", "Url", "assert_fingerprint", - "create_urllib3_context", + "current_time", "is_connection_dropped", "is_fp_closed", + "get_host", "parse_url", "make_headers", "resolve_cert_reqs", "resolve_ssl_version", + "split_first", "ssl_wrap_socket", "wait_for_read", "wait_for_write", diff --git a/newrelic/packages/urllib3/util/connection.py b/newrelic/packages/urllib3/util/connection.py index f92519ee91..6af1138f26 100644 --- a/newrelic/packages/urllib3/util/connection.py +++ b/newrelic/packages/urllib3/util/connection.py @@ -1,23 +1,33 @@ -from __future__ import annotations +from __future__ import absolute_import import socket -import typing +from ..contrib import _appengine_environ from ..exceptions import LocationParseError -from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from ..packages import six +from .wait import NoWayToWaitForSocketError, wait_for_read -_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] -if typing.TYPE_CHECKING: - from .._base_connection import BaseHTTPConnection - - -def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific +def is_connection_dropped(conn): # Platform-specific """ Returns True if the connection is dropped and should be closed. - :param conn: :class:`urllib3.connection.HTTPConnection` object. + + :param conn: + :class:`http.client.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. """ - return not conn.is_connected + sock = getattr(conn, "sock", False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return True + try: + # Returns True if readable, which here means it's been dropped + return wait_for_read(sock, timeout=0.0) + except NoWayToWaitForSocketError: # Platform-specific: AppEngine + return False # This function is copied from socket.py in the Python 2.7 standard @@ -25,11 +35,11 @@ def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specifi # One additional modification is that we avoid binding to IPv6 servers # discovered in DNS if the system doesn't have IPv6 functionality. def create_connection( - address: tuple[str, int], - timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - source_address: tuple[str, int] | None = None, - socket_options: _TYPE_SOCKET_OPTIONS | None = None, -) -> socket.socket: + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, + socket_options=None, +): """Connect to *address* and return the socket object. Convenience function. Connect to *address* (a 2-tuple ``(host, @@ -55,7 +65,9 @@ def create_connection( try: host.encode("idna") except UnicodeError: - raise LocationParseError(f"'{host}', label empty or too long") from None + return six.raise_from( + LocationParseError(u"'%s', label empty or too long" % host), None + ) for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res @@ -66,33 +78,26 @@ def create_connection( # If provided, set socket level options before connecting. _set_socket_options(sock, socket_options) - if timeout is not _DEFAULT_TIMEOUT: + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: sock.settimeout(timeout) if source_address: sock.bind(source_address) sock.connect(sa) - # Break explicitly a reference cycle - err = None return sock - except OSError as _: - err = _ + except socket.error as e: + err = e if sock is not None: sock.close() + sock = None if err is not None: - try: - raise err - finally: - # Break explicitly a reference cycle - err = None - else: - raise OSError("getaddrinfo returns an empty list") + raise err + raise socket.error("getaddrinfo returns an empty list") -def _set_socket_options( - sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None -) -> None: + +def _set_socket_options(sock, options): if options is None: return @@ -100,7 +105,7 @@ def _set_socket_options( sock.setsockopt(*opt) -def allowed_gai_family() -> socket.AddressFamily: +def allowed_gai_family(): """This function is designed to work in the context of getaddrinfo, where family=socket.AF_UNSPEC is the default and will perform a DNS search for both IPv6 and IPv4 records.""" @@ -111,11 +116,18 @@ def allowed_gai_family() -> socket.AddressFamily: return family -def _has_ipv6(host: str) -> bool: +def _has_ipv6(host): """Returns True if the system can bind an IPv6 address.""" sock = None has_ipv6 = False + # App Engine doesn't support IPV6 sockets and actually has a quota on the + # number of sockets that can be used, so just early out here instead of + # creating a socket needlessly. + # See https://github.com/urllib3/urllib3/issues/1446 + if _appengine_environ.is_appengine_sandbox(): + return False + if socket.has_ipv6: # has_ipv6 returns true if cPython was compiled with IPv6 support. # It does not tell us if the system has IPv6 support enabled. To diff --git a/newrelic/packages/urllib3/util/proxy.py b/newrelic/packages/urllib3/util/proxy.py index 908fc6621d..2199cc7b7f 100644 --- a/newrelic/packages/urllib3/util/proxy.py +++ b/newrelic/packages/urllib3/util/proxy.py @@ -1,18 +1,9 @@ -from __future__ import annotations - -import typing - -from .url import Url - -if typing.TYPE_CHECKING: - from ..connection import ProxyConfig +from .ssl_ import create_urllib3_context, resolve_cert_reqs, resolve_ssl_version def connection_requires_http_tunnel( - proxy_url: Url | None = None, - proxy_config: ProxyConfig | None = None, - destination_scheme: str | None = None, -) -> bool: + proxy_url=None, proxy_config=None, destination_scheme=None +): """ Returns True if the connection requires an HTTP CONNECT through the proxy. @@ -41,3 +32,26 @@ def connection_requires_http_tunnel( # Otherwise always use a tunnel. return True + + +def create_proxy_ssl_context( + ssl_version, cert_reqs, ca_certs=None, ca_cert_dir=None, ca_cert_data=None +): + """ + Generates a default proxy ssl context if one hasn't been provided by the + user. + """ + ssl_context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and hasattr(ssl_context, "load_default_certs") + ): + ssl_context.load_default_certs() + + return ssl_context diff --git a/newrelic/packages/urllib3/util/queue.py b/newrelic/packages/urllib3/util/queue.py new file mode 100644 index 0000000000..41784104ee --- /dev/null +++ b/newrelic/packages/urllib3/util/queue.py @@ -0,0 +1,22 @@ +import collections + +from ..packages import six +from ..packages.six.moves import queue + +if six.PY2: + # Queue is imported for side effects on MS Windows. See issue #229. + import Queue as _unused_module_Queue # noqa: F401 + + +class LifoQueue(queue.Queue): + def _init(self, _): + self.queue = collections.deque() + + def _qsize(self, len=len): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() diff --git a/newrelic/packages/urllib3/util/request.py b/newrelic/packages/urllib3/util/request.py index 6c2372ba7e..b574b081e9 100644 --- a/newrelic/packages/urllib3/util/request.py +++ b/newrelic/packages/urllib3/util/request.py @@ -1,16 +1,9 @@ -from __future__ import annotations +from __future__ import absolute_import -import io -import sys -import typing from base64 import b64encode -from enum import Enum from ..exceptions import UnrewindableBodyError -from .util import to_bytes - -if typing.TYPE_CHECKING: - from typing import Final +from ..packages.six import b, integer_types # Pass as a value within ``headers`` to skip # emitting some HTTP headers that are added automatically. @@ -22,49 +15,25 @@ ACCEPT_ENCODING = "gzip,deflate" try: try: - import brotlicffi as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + import brotlicffi as _unused_module_brotli # noqa: F401 except ImportError: - import brotli as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + import brotli as _unused_module_brotli # noqa: F401 except ImportError: pass else: ACCEPT_ENCODING += ",br" -try: - if sys.version_info >= (3, 14): - from compression import zstd as _unused_module_zstd # noqa: F401 - else: - from backports import zstd as _unused_module_zstd # noqa: F401 -except ImportError: - pass -else: - ACCEPT_ENCODING += ",zstd" - - -class _TYPE_FAILEDTELL(Enum): - token = 0 - - -_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token - -_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] - -# When sending a request with these methods we aren't expecting -# a body so don't need to set an explicit 'Content-Length: 0' -# The reason we do this in the negative instead of tracking methods -# which 'should' have a body is because unknown methods should be -# treated as if they were 'POST' which *does* expect a body. -_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} +_FAILEDTELL = object() def make_headers( - keep_alive: bool | None = None, - accept_encoding: bool | list[str] | str | None = None, - user_agent: str | None = None, - basic_auth: str | None = None, - proxy_basic_auth: str | None = None, - disable_cache: bool | None = None, -) -> dict[str, str]: + keep_alive=None, + accept_encoding=None, + user_agent=None, + basic_auth=None, + proxy_basic_auth=None, + disable_cache=None, +): """ Shortcuts for generating request headers. @@ -73,11 +42,7 @@ def make_headers( :param accept_encoding: Can be a boolean, list, or string. - ``True`` translates to 'gzip,deflate'. If the dependencies for - Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or - Zstandard (the ``backports.zstd`` package for Python before 3.14) - algorithms are installed, then their encodings are - included in the string ('br' and 'zstd', respectively). + ``True`` translates to 'gzip,deflate'. List will get joined by comma. String will be used as provided. @@ -96,18 +61,14 @@ def make_headers( :param disable_cache: If ``True``, adds 'cache-control: no-cache' header. - Example: - - .. code-block:: python + Example:: - import urllib3 - - print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) - # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} - print(urllib3.util.make_headers(accept_encoding=True)) - # {'accept-encoding': 'gzip,deflate'} + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} """ - headers: dict[str, str] = {} + headers = {} if accept_encoding: if isinstance(accept_encoding, str): pass @@ -124,14 +85,12 @@ def make_headers( headers["connection"] = "keep-alive" if basic_auth: - headers["authorization"] = ( - f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" - ) + headers["authorization"] = "Basic " + b64encode(b(basic_auth)).decode("utf-8") if proxy_basic_auth: - headers["proxy-authorization"] = ( - f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" - ) + headers["proxy-authorization"] = "Basic " + b64encode( + b(proxy_basic_auth) + ).decode("utf-8") if disable_cache: headers["cache-control"] = "no-cache" @@ -139,9 +98,7 @@ def make_headers( return headers -def set_file_position( - body: typing.Any, pos: _TYPE_BODY_POSITION | None -) -> _TYPE_BODY_POSITION | None: +def set_file_position(body, pos): """ If a position is provided, move file to that point. Otherwise, we'll attempt to record a position for future use. @@ -151,7 +108,7 @@ def set_file_position( elif getattr(body, "tell", None) is not None: try: pos = body.tell() - except OSError: + except (IOError, OSError): # This differentiates from None, allowing us to catch # a failed `tell()` later when trying to rewind the body. pos = _FAILEDTELL @@ -159,7 +116,7 @@ def set_file_position( return pos -def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: +def rewind_body(body, body_pos): """ Attempt to rewind body to a certain position. Primarily used for request redirects and retries. @@ -171,13 +128,13 @@ def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) - Position to seek to in file. """ body_seek = getattr(body, "seek", None) - if body_seek is not None and isinstance(body_pos, int): + if body_seek is not None and isinstance(body_pos, integer_types): try: body_seek(body_pos) - except OSError as e: + except (IOError, OSError): raise UnrewindableBodyError( "An error occurred when rewinding request body for redirect/retry." - ) from e + ) elif body_pos is _FAILEDTELL: raise UnrewindableBodyError( "Unable to record file position for rewinding " @@ -185,79 +142,5 @@ def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) - ) else: raise ValueError( - f"body_pos must be of type integer, instead it was {type(body_pos)}." + "body_pos must be of type integer, instead it was %s." % type(body_pos) ) - - -class ChunksAndContentLength(typing.NamedTuple): - chunks: typing.Iterable[bytes] | None - content_length: int | None - - -def body_to_chunks( - body: typing.Any | None, method: str, blocksize: int -) -> ChunksAndContentLength: - """Takes the HTTP request method, body, and blocksize and - transforms them into an iterable of chunks to pass to - socket.sendall() and an optional 'Content-Length' header. - - A 'Content-Length' of 'None' indicates the length of the body - can't be determined so should use 'Transfer-Encoding: chunked' - for framing instead. - """ - - chunks: typing.Iterable[bytes] | None - content_length: int | None - - # No body, we need to make a recommendation on 'Content-Length' - # based on whether that request method is expected to have - # a body or not. - if body is None: - chunks = None - if method.upper() not in _METHODS_NOT_EXPECTING_BODY: - content_length = 0 - else: - content_length = None - - # Bytes or strings become bytes - elif isinstance(body, (str, bytes)): - chunks = (to_bytes(body),) - content_length = len(chunks[0]) - - # File-like object, TODO: use seek() and tell() for length? - elif hasattr(body, "read"): - - def chunk_readable() -> typing.Iterable[bytes]: - encode = isinstance(body, io.TextIOBase) - while True: - datablock = body.read(blocksize) - if not datablock: - break - if encode: - datablock = datablock.encode("utf-8") - yield datablock - - chunks = chunk_readable() - content_length = None - - # Otherwise we need to start checking via duck-typing. - else: - try: - # Check if the body implements the buffer API. - mv = memoryview(body) - except TypeError: - try: - # Check if the body is an iterable - chunks = iter(body) - content_length = None - except TypeError: - raise TypeError( - f"'body' must be a bytes-like object, file-like " - f"object, or iterable. Instead was {body!r}" - ) from None - else: - # Since it implements the buffer API can be passed directly to socket.sendall() - chunks = (body,) - content_length = mv.nbytes - - return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/newrelic/packages/urllib3/util/response.py b/newrelic/packages/urllib3/util/response.py index 0f4578696f..5ea609cced 100644 --- a/newrelic/packages/urllib3/util/response.py +++ b/newrelic/packages/urllib3/util/response.py @@ -1,12 +1,12 @@ -from __future__ import annotations +from __future__ import absolute_import -import http.client as httplib from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect from ..exceptions import HeaderParsingError +from ..packages.six.moves import http_client as httplib -def is_fp_closed(obj: object) -> bool: +def is_fp_closed(obj): """ Checks whether a given file-like object is closed. @@ -17,27 +17,27 @@ def is_fp_closed(obj: object) -> bool: try: # Check `isclosed()` first, in case Python3 doesn't set `closed`. # GH Issue #928 - return obj.isclosed() # type: ignore[no-any-return, attr-defined] + return obj.isclosed() except AttributeError: pass try: # Check via the official file-like-object way. - return obj.closed # type: ignore[no-any-return, attr-defined] + return obj.closed except AttributeError: pass try: # Check if the object is a container for another file-like object that # gets released on exhaustion (e.g. HTTPResponse). - return obj.fp is None # type: ignore[attr-defined] + return obj.fp is None except AttributeError: pass raise ValueError("Unable to determine whether fp is closed.") -def assert_header_parsing(headers: httplib.HTTPMessage) -> None: +def assert_header_parsing(headers): """ Asserts whether all headers have been successfully parsed. Extracts encountered errors from the result of parsing headers. @@ -53,49 +53,55 @@ def assert_header_parsing(headers: httplib.HTTPMessage) -> None: # This will fail silently if we pass in the wrong kind of parameter. # To make debugging easier add an explicit check. if not isinstance(headers, httplib.HTTPMessage): - raise TypeError(f"expected httplib.Message, got {type(headers)}.") + raise TypeError("expected httplib.Message, got {0}.".format(type(headers))) - unparsed_data = None + defects = getattr(headers, "defects", None) + get_payload = getattr(headers, "get_payload", None) - # get_payload is actually email.message.Message.get_payload; - # we're only interested in the result if it's not a multipart message - if not headers.is_multipart(): - payload = headers.get_payload() - - if isinstance(payload, (bytes, str)): - unparsed_data = payload - - # httplib is assuming a response body is available - # when parsing headers even when httplib only sends - # header data to parse_headers() This results in - # defects on multipart responses in particular. - # See: https://github.com/urllib3/urllib3/issues/800 - - # So we ignore the following defects: - # - StartBoundaryNotFoundDefect: - # The claimed start boundary was never found. - # - MultipartInvariantViolationDefect: - # A message claimed to be a multipart but no subparts were found. - defects = [ - defect - for defect in headers.defects - if not isinstance( - defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) - ) - ] + unparsed_data = None + if get_payload: + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + if defects: + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] if defects or unparsed_data: raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) -def is_response_to_head(response: httplib.HTTPResponse) -> bool: +def is_response_to_head(response): """ Checks whether the request of a response has been a HEAD-request. + Handles the quirks of AppEngine. :param http.client.HTTPResponse response: Response to check if the originating request used 'HEAD' as a method. """ # FIXME: Can we do this somehow without accessing private httplib _method? - method_str = response._method # type: str # type: ignore[attr-defined] - return method_str.upper() == "HEAD" + method = response._method + if isinstance(method, int): # Platform-specific: Appengine + return method == 3 + return method.upper() == "HEAD" diff --git a/newrelic/packages/urllib3/util/retry.py b/newrelic/packages/urllib3/util/retry.py index b21b4b64eb..9a1e90d0b2 100644 --- a/newrelic/packages/urllib3/util/retry.py +++ b/newrelic/packages/urllib3/util/retry.py @@ -1,13 +1,12 @@ -from __future__ import annotations +from __future__ import absolute_import import email import logging -import random import re import time -import typing +import warnings +from collections import namedtuple from itertools import takewhile -from types import TracebackType from ..exceptions import ( ConnectTimeoutError, @@ -18,51 +17,97 @@ ReadTimeoutError, ResponseError, ) -from .util import reraise - -if typing.TYPE_CHECKING: - from typing_extensions import Self - - from ..connectionpool import ConnectionPool - from ..response import BaseHTTPResponse +from ..packages import six log = logging.getLogger(__name__) # Data structure for representing the metadata of requests that result in a retry. -class RequestHistory(typing.NamedTuple): - method: str | None - url: str | None - error: Exception | None - status: int | None - redirect_location: str | None +RequestHistory = namedtuple( + "RequestHistory", ["method", "url", "error", "status", "redirect_location"] +) + + +# TODO: In v2 we can remove this sentinel and metaclass with deprecated options. +_Default = object() + + +class _RetryMeta(type): + @property + def DEFAULT_METHOD_WHITELIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + return cls.DEFAULT_ALLOWED_METHODS + + @DEFAULT_METHOD_WHITELIST.setter + def DEFAULT_METHOD_WHITELIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_METHOD_WHITELIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_ALLOWED_METHODS' instead", + DeprecationWarning, + ) + cls.DEFAULT_ALLOWED_METHODS = value + + @property + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + return cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + + @DEFAULT_REDIRECT_HEADERS_BLACKLIST.setter + def DEFAULT_REDIRECT_HEADERS_BLACKLIST(cls, value): + warnings.warn( + "Using 'Retry.DEFAULT_REDIRECT_HEADERS_BLACKLIST' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_REMOVE_HEADERS_ON_REDIRECT' instead", + DeprecationWarning, + ) + cls.DEFAULT_REMOVE_HEADERS_ON_REDIRECT = value + + @property + def BACKOFF_MAX(cls): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + return cls.DEFAULT_BACKOFF_MAX + + @BACKOFF_MAX.setter + def BACKOFF_MAX(cls, value): + warnings.warn( + "Using 'Retry.BACKOFF_MAX' is deprecated and " + "will be removed in v2.0. Use 'Retry.DEFAULT_BACKOFF_MAX' instead", + DeprecationWarning, + ) + cls.DEFAULT_BACKOFF_MAX = value -class Retry: +@six.add_metaclass(_RetryMeta) +class Retry(object): """Retry configuration. Each retry attempt will create a new Retry object with updated values, so they can be safely reused. - Retries can be defined as a default for a pool: - - .. code-block:: python + Retries can be defined as a default for a pool:: retries = Retry(connect=5, read=2, redirect=5) http = PoolManager(retries=retries) - response = http.request("GET", "https://example.com/") + response = http.request('GET', 'http://example.com/') - Or per-request (which overrides the default for the pool): + Or per-request (which overrides the default for the pool):: - .. code-block:: python + response = http.request('GET', 'http://example.com/', retries=Retry(10)) - response = http.request("GET", "https://example.com/", retries=Retry(10)) + Retries can be disabled by passing ``False``:: - Retries can be disabled by passing ``False``: - - .. code-block:: python - - response = http.request("GET", "https://example.com/", retries=False) + response = http.request('GET', 'http://example.com/', retries=False) Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless retries are disabled, in which case the causing exception will be raised. @@ -124,16 +169,21 @@ class Retry: If ``total`` is not set, it's a good idea to set this to 0 to account for unexpected edge cases and avoid infinite retry loops. - :param Collection allowed_methods: + :param iterable allowed_methods: Set of uppercased HTTP method verbs that we should retry on. By default, we only retry on methods which are considered to be idempotent (multiple requests with the same parameters end with the same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. - Set to a ``None`` value to retry on any verb. + Set to a ``False`` value to retry on any verb. + + .. warning:: + + Previously this parameter was named ``method_whitelist``, that + usage is deprecated in v1.26.0 and will be removed in v2.0. - :param Collection status_forcelist: + :param iterable status_forcelist: A set of integer HTTP status codes that we should force a retry on. A retry is initiated if the request method is in ``allowed_methods`` and the response status code is in ``status_forcelist``. @@ -145,17 +195,13 @@ class Retry: (most errors are resolved immediately by a second try without a delay). urllib3 will sleep for:: - {backoff factor} * (2 ** ({number of previous retries})) + {backoff factor} * (2 ** ({number of total retries} - 1)) - seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep + for [0.0s, 0.2s, 0.4s, ...] between retries. It will never be longer + than :attr:`Retry.DEFAULT_BACKOFF_MAX`. - random.uniform(0, {backoff jitter}) - - seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will - sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever - be longer than `backoff_max`. - - By default, backoff is disabled (factor set to 0). + By default, backoff is disabled (set to 0). :param bool raise_on_redirect: Whether, if the number of redirects is exhausted, to raise a MaxRetryError, or to return a response with a @@ -174,15 +220,10 @@ class Retry: Whether to respect Retry-After header on status codes defined as :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. - :param Collection remove_headers_on_redirect: + :param iterable remove_headers_on_redirect: Sequence of headers to remove from the request when a response indicating a redirect is returned before firing off the redirected request. - - :param int retry_after_max: Number of seconds to allow as the maximum for - Retry-After headers. Defaults to :attr:`Retry.DEFAULT_RETRY_AFTER_MAX`. - Any Retry-After headers larger than this value will be limited to this - value. """ #: Default methods to be used for ``allowed_methods`` @@ -198,38 +239,48 @@ class Retry: ["Cookie", "Authorization", "Proxy-Authorization"] ) - #: Default maximum backoff time. + #: Maximum backoff time. DEFAULT_BACKOFF_MAX = 120 - # This is undocumented in the RFC. Setting to 6 hours matches other popular libraries. - #: Default maximum allowed value for Retry-After headers in seconds - DEFAULT_RETRY_AFTER_MAX: typing.Final[int] = 21600 - - # Backward compatibility; assigned outside of the class. - DEFAULT: typing.ClassVar[Retry] - def __init__( self, - total: bool | int | None = 10, - connect: int | None = None, - read: int | None = None, - redirect: bool | int | None = None, - status: int | None = None, - other: int | None = None, - allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, - status_forcelist: typing.Collection[int] | None = None, - backoff_factor: float = 0, - backoff_max: float = DEFAULT_BACKOFF_MAX, - raise_on_redirect: bool = True, - raise_on_status: bool = True, - history: tuple[RequestHistory, ...] | None = None, - respect_retry_after_header: bool = True, - remove_headers_on_redirect: typing.Collection[ - str - ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, - backoff_jitter: float = 0.0, - retry_after_max: int = DEFAULT_RETRY_AFTER_MAX, - ) -> None: + total=10, + connect=None, + read=None, + redirect=None, + status=None, + other=None, + allowed_methods=_Default, + status_forcelist=None, + backoff_factor=0, + raise_on_redirect=True, + raise_on_status=True, + history=None, + respect_retry_after_header=True, + remove_headers_on_redirect=_Default, + # TODO: Deprecated, remove in v2.0 + method_whitelist=_Default, + ): + + if method_whitelist is not _Default: + if allowed_methods is not _Default: + raise ValueError( + "Using both 'allowed_methods' and " + "'method_whitelist' together is not allowed. " + "Instead only use 'allowed_methods'" + ) + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + stacklevel=2, + ) + allowed_methods = method_whitelist + if allowed_methods is _Default: + allowed_methods = self.DEFAULT_ALLOWED_METHODS + if remove_headers_on_redirect is _Default: + remove_headers_on_redirect = self.DEFAULT_REMOVE_HEADERS_ON_REDIRECT + self.total = total self.connect = connect self.read = read @@ -244,18 +295,15 @@ def __init__( self.status_forcelist = status_forcelist or set() self.allowed_methods = allowed_methods self.backoff_factor = backoff_factor - self.backoff_max = backoff_max - self.retry_after_max = retry_after_max self.raise_on_redirect = raise_on_redirect self.raise_on_status = raise_on_status - self.history = history or () + self.history = history or tuple() self.respect_retry_after_header = respect_retry_after_header self.remove_headers_on_redirect = frozenset( - h.lower() for h in remove_headers_on_redirect + [h.lower() for h in remove_headers_on_redirect] ) - self.backoff_jitter = backoff_jitter - def new(self, **kw: typing.Any) -> Self: + def new(self, **kw): params = dict( total=self.total, connect=self.connect, @@ -263,29 +311,36 @@ def new(self, **kw: typing.Any) -> Self: redirect=self.redirect, status=self.status, other=self.other, - allowed_methods=self.allowed_methods, status_forcelist=self.status_forcelist, backoff_factor=self.backoff_factor, - backoff_max=self.backoff_max, - retry_after_max=self.retry_after_max, raise_on_redirect=self.raise_on_redirect, raise_on_status=self.raise_on_status, history=self.history, remove_headers_on_redirect=self.remove_headers_on_redirect, respect_retry_after_header=self.respect_retry_after_header, - backoff_jitter=self.backoff_jitter, ) + # TODO: If already given in **kw we use what's given to us + # If not given we need to figure out what to pass. We decide + # based on whether our class has the 'method_whitelist' property + # and if so we pass the deprecated 'method_whitelist' otherwise + # we use 'allowed_methods'. Remove in v2.0 + if "method_whitelist" not in kw and "allowed_methods" not in kw: + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + params["method_whitelist"] = self.allowed_methods + else: + params["allowed_methods"] = self.allowed_methods + params.update(kw) - return type(self)(**params) # type: ignore[arg-type] + return type(self)(**params) @classmethod - def from_int( - cls, - retries: Retry | bool | int | None, - redirect: bool | int | None = True, - default: Retry | bool | int | None = None, - ) -> Retry: + def from_int(cls, retries, redirect=True, default=None): """Backwards-compatibility for the old retries format.""" if retries is None: retries = default if default is not None else cls.DEFAULT @@ -298,7 +353,7 @@ def from_int( log.debug("Converted retries value: %r -> %r", retries, new_retries) return new_retries - def get_backoff_time(self) -> float: + def get_backoff_time(self): """Formula for computing the current backoff :rtype: float @@ -313,32 +368,32 @@ def get_backoff_time(self) -> float: return 0 backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) - if self.backoff_jitter != 0.0: - backoff_value += random.random() * self.backoff_jitter - return float(max(0, min(self.backoff_max, backoff_value))) + return min(self.DEFAULT_BACKOFF_MAX, backoff_value) - def parse_retry_after(self, retry_after: str) -> float: - seconds: float + def parse_retry_after(self, retry_after): # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 if re.match(r"^\s*[0-9]+\s*$", retry_after): seconds = int(retry_after) else: retry_date_tuple = email.utils.parsedate_tz(retry_after) if retry_date_tuple is None: - raise InvalidHeader(f"Invalid Retry-After header: {retry_after}") + raise InvalidHeader("Invalid Retry-After header: %s" % retry_after) + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] retry_date = email.utils.mktime_tz(retry_date_tuple) seconds = retry_date - time.time() - seconds = max(seconds, 0) - - # Check the seconds do not exceed the specified maximum - if seconds > self.retry_after_max: - seconds = self.retry_after_max + if seconds < 0: + seconds = 0 return seconds - def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + def get_retry_after(self, response): """Get the value of Retry-After in seconds.""" retry_after = response.headers.get("Retry-After") @@ -348,7 +403,7 @@ def get_retry_after(self, response: BaseHTTPResponse) -> float | None: return self.parse_retry_after(retry_after) - def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: + def sleep_for_retry(self, response=None): retry_after = self.get_retry_after(response) if retry_after: time.sleep(retry_after) @@ -356,13 +411,13 @@ def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: return False - def _sleep_backoff(self) -> None: + def _sleep_backoff(self): backoff = self.get_backoff_time() if backoff <= 0: return time.sleep(backoff) - def sleep(self, response: BaseHTTPResponse | None = None) -> None: + def sleep(self, response=None): """Sleep between retry attempts. This method will respect a server's ``Retry-After`` response header @@ -378,7 +433,7 @@ def sleep(self, response: BaseHTTPResponse | None = None) -> None: self._sleep_backoff() - def _is_connection_error(self, err: Exception) -> bool: + def _is_connection_error(self, err): """Errors when we're fairly sure that the server did not receive the request, so it should be safe to retry. """ @@ -386,23 +441,33 @@ def _is_connection_error(self, err: Exception) -> bool: err = err.original_error return isinstance(err, ConnectTimeoutError) - def _is_read_error(self, err: Exception) -> bool: + def _is_read_error(self, err): """Errors that occur after the request has been started, so we should assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) - def _is_method_retryable(self, method: str) -> bool: + def _is_method_retryable(self, method): """Checks if a given HTTP method should be retried upon, depending if it is included in the allowed_methods """ - if self.allowed_methods and method.upper() not in self.allowed_methods: + # TODO: For now favor if the Retry implementation sets its own method_whitelist + # property outside of our constructor to avoid breaking custom implementations. + if "method_whitelist" in self.__dict__: + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + allowed_methods = self.method_whitelist + else: + allowed_methods = self.allowed_methods + + if allowed_methods and method.upper() not in allowed_methods: return False return True - def is_retry( - self, method: str, status_code: int, has_retry_after: bool = False - ) -> bool: + def is_retry(self, method, status_code, has_retry_after=False): """Is this method/status code retryable? (Based on allowlists and control variables such as the number of total retries to allow, whether to respect the Retry-After header, whether this header is present, and @@ -415,27 +480,24 @@ def is_retry( if self.status_forcelist and status_code in self.status_forcelist: return True - return bool( + return ( self.total and self.respect_retry_after_header and has_retry_after and (status_code in self.RETRY_AFTER_STATUS_CODES) ) - def is_exhausted(self) -> bool: + def is_exhausted(self): """Are we out of retries?""" - retry_counts = [ - x - for x in ( - self.total, - self.connect, - self.read, - self.redirect, - self.status, - self.other, - ) - if x - ] + retry_counts = ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + retry_counts = list(filter(None, retry_counts)) if not retry_counts: return False @@ -443,18 +505,18 @@ def is_exhausted(self) -> bool: def increment( self, - method: str | None = None, - url: str | None = None, - response: BaseHTTPResponse | None = None, - error: Exception | None = None, - _pool: ConnectionPool | None = None, - _stacktrace: TracebackType | None = None, - ) -> Self: + method=None, + url=None, + response=None, + error=None, + _pool=None, + _stacktrace=None, + ): """Return a new Retry object with incremented retry counters. :param response: A response object, or None, if the server did not return a response. - :type response: :class:`~urllib3.response.BaseHTTPResponse` + :type response: :class:`~urllib3.response.HTTPResponse` :param Exception error: An error encountered during the request, or None if the response was received successfully. @@ -462,7 +524,7 @@ def increment( """ if self.total is False and error: # Disabled, indicate to re-raise the error. - raise reraise(type(error), error, _stacktrace) + raise six.reraise(type(error), error, _stacktrace) total = self.total if total is not None: @@ -480,14 +542,14 @@ def increment( if error and self._is_connection_error(error): # Connect retry? if connect is False: - raise reraise(type(error), error, _stacktrace) + raise six.reraise(type(error), error, _stacktrace) elif connect is not None: connect -= 1 elif error and self._is_read_error(error): # Read retry? - if read is False or method is None or not self._is_method_retryable(method): - raise reraise(type(error), error, _stacktrace) + if read is False or not self._is_method_retryable(method): + raise six.reraise(type(error), error, _stacktrace) elif read is not None: read -= 1 @@ -501,9 +563,7 @@ def increment( if redirect is not None: redirect -= 1 cause = "too many redirects" - response_redirect_location = response.get_redirect_location() - if response_redirect_location: - redirect_location = response_redirect_location + redirect_location = response.get_redirect_location() status = response.status else: @@ -531,18 +591,31 @@ def increment( ) if new_retry.is_exhausted(): - reason = error or ResponseError(cause) - raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + raise MaxRetryError(_pool, url, error or ResponseError(cause)) log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) return new_retry - def __repr__(self) -> str: + def __repr__(self): return ( - f"{type(self).__name__}(total={self.total}, connect={self.connect}, " - f"read={self.read}, redirect={self.redirect}, status={self.status})" - ) + "{cls.__name__}(total={self.total}, connect={self.connect}, " + "read={self.read}, redirect={self.redirect}, status={self.status})" + ).format(cls=type(self), self=self) + + def __getattr__(self, item): + if item == "method_whitelist": + # TODO: Remove this deprecated alias in v2.0 + warnings.warn( + "Using 'method_whitelist' with Retry is deprecated and " + "will be removed in v2.0. Use 'allowed_methods' instead", + DeprecationWarning, + ) + return self.allowed_methods + try: + return getattr(super(Retry, self), item) + except AttributeError: + return getattr(Retry, item) # For backwards compatibility (equivalent to pre-v1.9): diff --git a/newrelic/packages/urllib3/util/ssl_.py b/newrelic/packages/urllib3/util/ssl_.py index 56fe9093ad..8f867812a5 100644 --- a/newrelic/packages/urllib3/util/ssl_.py +++ b/newrelic/packages/urllib3/util/ssl_.py @@ -1,155 +1,185 @@ -from __future__ import annotations +from __future__ import absolute_import -import hashlib import hmac import os -import socket import sys -import typing import warnings -from binascii import unhexlify - -from ..exceptions import ProxySchemeUnsupported, SSLError -from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE +from binascii import hexlify, unhexlify +from hashlib import md5, sha1, sha256 + +from ..exceptions import ( + InsecurePlatformWarning, + ProxySchemeUnsupported, + SNIMissingWarning, + SSLError, +) +from ..packages import six +from .url import BRACELESS_IPV6_ADDRZ_RE, IPV4_RE SSLContext = None SSLTransport = None -HAS_NEVER_CHECK_COMMON_NAME = False +HAS_SNI = False IS_PYOPENSSL = False +IS_SECURETRANSPORT = False ALPN_PROTOCOLS = ["http/1.1"] -_TYPE_VERSION_INFO = tuple[int, int, int, str, int] - # Maps the length of a digest to a possible hash function producing this digest -HASHFUNC_MAP = { - length: getattr(hashlib, algorithm, None) - for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) -} - - -def _is_bpo_43522_fixed( - implementation_name: str, - version_info: _TYPE_VERSION_INFO, - pypy_version_info: _TYPE_VERSION_INFO | None, -) -> bool: - """Return True for CPython 3.9.3+ or 3.10+ and PyPy 7.3.8+ where - setting SSLContext.hostname_checks_common_name to False works. - - Outside of CPython and PyPy we don't know which implementations work - or not so we conservatively use our hostname matching as we know that works - on all implementations. - - https://github.com/urllib3/urllib3/issues/2192#issuecomment-821832963 - https://foss.heptapod.net/pypy/pypy/-/issues/3539 +HASHFUNC_MAP = {32: md5, 40: sha1, 64: sha256} + + +def _const_compare_digest_backport(a, b): """ - if implementation_name == "pypy": - # https://foss.heptapod.net/pypy/pypy/-/issues/3129 - return pypy_version_info >= (7, 3, 8) # type: ignore[operator] - elif implementation_name == "cpython": - major_minor = version_info[:2] - micro = version_info[2] - return (major_minor == (3, 9) and micro >= 3) or major_minor >= (3, 10) - else: # Defensive: - return False - - -def _is_has_never_check_common_name_reliable( - openssl_version: str, - openssl_version_number: int, - implementation_name: str, - version_info: _TYPE_VERSION_INFO, - pypy_version_info: _TYPE_VERSION_INFO | None, -) -> bool: - # As of May 2023, all released versions of LibreSSL fail to reject certificates with - # only common names, see https://github.com/urllib3/urllib3/pull/3024 - is_openssl = openssl_version.startswith("OpenSSL ") - # Before fixing OpenSSL issue #14579, the SSL_new() API was not copying hostflags - # like X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, which tripped up CPython. - # https://github.com/openssl/openssl/issues/14579 - # This was released in OpenSSL 1.1.1l+ (>=0x101010cf) - is_openssl_issue_14579_fixed = openssl_version_number >= 0x101010CF - - return is_openssl and ( - is_openssl_issue_14579_fixed - or _is_bpo_43522_fixed(implementation_name, version_info, pypy_version_info) - ) + Compare two digests of equal length in constant time. + + The digests must be of type str/bytes. + Returns True if the digests match, and False otherwise. + """ + result = abs(len(a) - len(b)) + for left, right in zip(bytearray(a), bytearray(b)): + result |= left ^ right + return result == 0 -if typing.TYPE_CHECKING: - from ssl import VerifyMode - from typing import TypedDict +_const_compare_digest = getattr(hmac, "compare_digest", _const_compare_digest_backport) - from .ssltransport import SSLTransport as SSLTransportType +try: # Test for SSL features + import ssl + from ssl import CERT_REQUIRED, wrap_socket +except ImportError: + pass - class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): - subjectAltName: tuple[tuple[str, str], ...] - subject: tuple[tuple[tuple[str, str], ...], ...] - serialNumber: str +try: + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass +try: + from .ssltransport import SSLTransport +except ImportError: + pass -# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' -_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} -try: # Do we have ssl at all? - import ssl - from ssl import ( # type: ignore[assignment] - CERT_REQUIRED, - HAS_NEVER_CHECK_COMMON_NAME, - OP_NO_COMPRESSION, - OP_NO_TICKET, - OPENSSL_VERSION, - OPENSSL_VERSION_NUMBER, - PROTOCOL_TLS, - PROTOCOL_TLS_CLIENT, - VERIFY_X509_STRICT, - OP_NO_SSLv2, - OP_NO_SSLv3, - SSLContext, - TLSVersion, - ) +try: # Platform-specific: Python 3.6 + from ssl import PROTOCOL_TLS PROTOCOL_SSLv23 = PROTOCOL_TLS +except ImportError: + try: + from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS - # Needed for Python 3.9 which does not define this - VERIFY_X509_PARTIAL_CHAIN = getattr(ssl, "VERIFY_X509_PARTIAL_CHAIN", 0x80000) - - # Setting SSLContext.hostname_checks_common_name = False didn't work before CPython - # 3.9.3, and 3.10 (but OK on PyPy) or OpenSSL 1.1.1l+ - if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( - OPENSSL_VERSION, - OPENSSL_VERSION_NUMBER, - sys.implementation.name, - sys.version_info, - sys.pypy_version_info if sys.implementation.name == "pypy" else None, # type: ignore[attr-defined] - ): # Defensive: for Python < 3.9.3 - HAS_NEVER_CHECK_COMMON_NAME = False - - # Need to be careful here in case old TLS versions get - # removed in future 'ssl' module implementations. - for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): - try: - _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( - TLSVersion, attr - ) - except AttributeError: # Defensive: - continue + PROTOCOL_SSLv23 = PROTOCOL_TLS + except ImportError: + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 - from .ssltransport import SSLTransport # type: ignore[assignment] +try: + from ssl import PROTOCOL_TLS_CLIENT except ImportError: - OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment, misc] - OP_NO_TICKET = 0x4000 # type: ignore[assignment, misc] - OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment, misc] - OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment, misc] - PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment, misc] - PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment, misc] - VERIFY_X509_PARTIAL_CHAIN = 0x80000 - VERIFY_X509_STRICT = 0x20 # type: ignore[assignment, misc] + PROTOCOL_TLS_CLIENT = PROTOCOL_TLS -_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] +try: + from ssl import OP_NO_COMPRESSION, OP_NO_SSLv2, OP_NO_SSLv3 +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 -def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: +try: # OP_NO_TICKET was added in Python 3.6 + from ssl import OP_NO_TICKET +except ImportError: + OP_NO_TICKET = 0x4000 + + +# A secure default. +# Sources for more information on TLS ciphers: +# +# - https://wiki.mozilla.org/Security/Server_Side_TLS +# - https://www.ssllabs.com/projects/best-practices/index.html +# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ +# +# The general intent is: +# - prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE), +# - prefer ECDHE over DHE for better performance, +# - prefer any AES-GCM and ChaCha20 over any AES-CBC for better performance and +# security, +# - prefer AES-GCM over ChaCha20 because hardware-accelerated AES is common, +# - disable NULL authentication, MD5 MACs, DSS, and other +# insecure ciphers for security reasons. +# - NOTE: TLS 1.3 cipher suites are managed through a different interface +# not exposed by CPython (yet!) and are enabled by default if they're available. +DEFAULT_CIPHERS = ":".join( + [ + "ECDHE+AESGCM", + "ECDHE+CHACHA20", + "DHE+AESGCM", + "DHE+CHACHA20", + "ECDH+AESGCM", + "DH+AESGCM", + "ECDH+AES", + "DH+AES", + "RSA+AESGCM", + "RSA+AES", + "!aNULL", + "!eNULL", + "!MD5", + "!DSS", + ] +) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + + class SSLContext(object): # Platform-specific: Python 2 + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, cafile=None, capath=None, cadata=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") + + if cadata is not None: + raise SSLError("CA data not supported in older Pythons") + + def set_ciphers(self, cipher_suite): + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None, server_side=False): + warnings.warn( + "A true SSLContext object is not available. This prevents " + "urllib3 from configuring SSL appropriately and may cause " + "certain SSL connections to fail. You can upgrade to a newer " + "version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + InsecurePlatformWarning, + ) + kwargs = { + "keyfile": self.keyfile, + "certfile": self.certfile, + "ca_certs": self.ca_certs, + "cert_reqs": self.verify_mode, + "ssl_version": self.protocol, + "server_side": server_side, + } + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + + +def assert_fingerprint(cert, fingerprint): """ Checks if given fingerprint matches the supplied certificate. @@ -159,31 +189,26 @@ def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: Fingerprint as string of hexdigits, can be interspersed by colons. """ - if cert is None: - raise SSLError("No certificate for the peer.") - fingerprint = fingerprint.replace(":", "").lower() digest_length = len(fingerprint) - if digest_length not in HASHFUNC_MAP: - raise SSLError(f"Fingerprint of invalid length: {fingerprint}") hashfunc = HASHFUNC_MAP.get(digest_length) - if hashfunc is None: - raise SSLError( - f"Hash function implementation unavailable for fingerprint length: {digest_length}" - ) + if not hashfunc: + raise SSLError("Fingerprint of invalid length: {0}".format(fingerprint)) # We need encode() here for py32; works on py2 and p33. fingerprint_bytes = unhexlify(fingerprint.encode()) cert_digest = hashfunc(cert).digest() - if not hmac.compare_digest(cert_digest, fingerprint_bytes): + if not _const_compare_digest(cert_digest, fingerprint_bytes): raise SSLError( - f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + 'Fingerprints did not match. Expected "{0}", got "{1}".'.format( + fingerprint, hexlify(cert_digest) + ) ) -def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: +def resolve_cert_reqs(candidate): """ Resolves the argument to a numeric constant, which can be passed to the wrap_socket function/method from the ssl module. @@ -201,12 +226,12 @@ def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: res = getattr(ssl, candidate, None) if res is None: res = getattr(ssl, "CERT_" + candidate) - return res # type: ignore[no-any-return] + return res - return candidate # type: ignore[return-value] + return candidate -def resolve_ssl_version(candidate: None | int | str) -> int: +def resolve_ssl_version(candidate): """ like resolve_cert_reqs """ @@ -217,34 +242,35 @@ def resolve_ssl_version(candidate: None | int | str) -> int: res = getattr(ssl, candidate, None) if res is None: res = getattr(ssl, "PROTOCOL_" + candidate) - return typing.cast(int, res) + return res return candidate def create_urllib3_context( - ssl_version: int | None = None, - cert_reqs: int | None = None, - options: int | None = None, - ciphers: str | None = None, - ssl_minimum_version: int | None = None, - ssl_maximum_version: int | None = None, - verify_flags: int | None = None, -) -> ssl.SSLContext: - """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + ssl_version=None, cert_reqs=None, options=None, ciphers=None +): + """All arguments have the same meaning as ``ssl_wrap_socket``. + + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: + + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers + + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). :param ssl_version: The desired protocol version to use. This will default to PROTOCOL_SSLv23 which will negotiate the highest protocol that both the server and your installation of OpenSSL support. - - This parameter is deprecated instead use 'ssl_minimum_version'. - :param ssl_minimum_version: - The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. - :param ssl_maximum_version: - The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. - Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the - default value. :param cert_reqs: Whether to require the certificate verification. This defaults to ``ssl.CERT_REQUIRED``. @@ -252,63 +278,18 @@ def create_urllib3_context( Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. :param ciphers: - Which cipher suites to allow the server to select. Defaults to either system configured - ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. - :param verify_flags: - The flags for certificate verification operations. These default to - ``ssl.VERIFY_X509_PARTIAL_CHAIN`` and ``ssl.VERIFY_X509_STRICT`` for Python 3.13+. + Which cipher suites to allow the server to select. :returns: Constructed SSLContext object with specified options :rtype: SSLContext """ - if SSLContext is None: - raise TypeError("Can't create an SSLContext object without an ssl module") - - # This means 'ssl_version' was specified as an exact value. - if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): - # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' - # to avoid conflicts. - if ssl_minimum_version is not None or ssl_maximum_version is not None: - raise ValueError( - "Can't specify both 'ssl_version' and either " - "'ssl_minimum_version' or 'ssl_maximum_version'" - ) + # PROTOCOL_TLS is deprecated in Python 3.10 + if not ssl_version or ssl_version == PROTOCOL_TLS: + ssl_version = PROTOCOL_TLS_CLIENT - # 'ssl_version' is deprecated and will be removed in the future. - else: - # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. - ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( - ssl_version, TLSVersion.MINIMUM_SUPPORTED - ) - ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( - ssl_version, TLSVersion.MAXIMUM_SUPPORTED - ) + context = SSLContext(ssl_version) - # This warning message is pushing users to use 'ssl_minimum_version' - # instead of both min/max. Best practice is to only set the minimum version and - # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' - warnings.warn( - "'ssl_version' option is deprecated and will be " - "removed in urllib3 v2.6.0. Instead use 'ssl_minimum_version'", - category=DeprecationWarning, - stacklevel=2, - ) - - # PROTOCOL_TLS is deprecated in Python 3.10 so we always use PROTOCOL_TLS_CLIENT - context = SSLContext(PROTOCOL_TLS_CLIENT) - - if ssl_minimum_version is not None: - context.minimum_version = ssl_minimum_version - else: # Python <3.10 defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here - context.minimum_version = TLSVersion.TLSv1_2 - - if ssl_maximum_version is not None: - context.maximum_version = ssl_maximum_version - - # Unless we're given ciphers defer to either system ciphers in - # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. - if ciphers: - context.set_ciphers(ciphers) + context.set_ciphers(ciphers or DEFAULT_CIPHERS) # Setting the default here, as we may have no ssl module on import cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs @@ -330,106 +311,65 @@ def create_urllib3_context( context.options |= options - if verify_flags is None: - verify_flags = 0 - # In Python 3.13+ ssl.create_default_context() sets VERIFY_X509_PARTIAL_CHAIN - # and VERIFY_X509_STRICT so we do the same - if sys.version_info >= (3, 13): - verify_flags |= VERIFY_X509_PARTIAL_CHAIN - verify_flags |= VERIFY_X509_STRICT - - context.verify_flags |= verify_flags - # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is # necessary for conditional client cert authentication with TLS 1.3. - # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using - # an SSLContext created by pyOpenSSL. - if getattr(context, "post_handshake_auth", None) is not None: + # The attribute is None for OpenSSL <= 1.1.0 or does not exist in older + # versions of Python. We only enable on Python 3.7.4+ or if certificate + # verification is enabled to work around Python issue #37428 + # See: https://bugs.python.org/issue37428 + if (cert_reqs == ssl.CERT_REQUIRED or sys.version_info >= (3, 7, 4)) and getattr( + context, "post_handshake_auth", None + ) is not None: context.post_handshake_auth = True + def disable_check_hostname(): + if ( + getattr(context, "check_hostname", None) is not None + ): # Platform-specific: Python 3.2 + # We do our own verification, including fingerprints and alternative + # hostnames. So disable it here + context.check_hostname = False + # The order of the below lines setting verify_mode and check_hostname # matter due to safe-guards SSLContext has to prevent an SSLContext with - # check_hostname=True, verify_mode=NONE/OPTIONAL. - # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own - # 'ssl.match_hostname()' implementation. - if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + # check_hostname=True, verify_mode=NONE/OPTIONAL. This is made even more + # complex because we don't know whether PROTOCOL_TLS_CLIENT will be used + # or not so we don't know the initial state of the freshly created SSLContext. + if cert_reqs == ssl.CERT_REQUIRED: context.verify_mode = cert_reqs - context.check_hostname = True + disable_check_hostname() else: - context.check_hostname = False + disable_check_hostname() context.verify_mode = cert_reqs - try: - context.hostname_checks_common_name = False - except AttributeError: # Defensive: for CPython < 3.9.3; for PyPy < 7.3.8 - pass - - if "SSLKEYLOGFILE" in os.environ: - sslkeylogfile = os.path.expandvars(os.environ.get("SSLKEYLOGFILE")) - else: - sslkeylogfile = None - if sslkeylogfile: - context.keylog_filename = sslkeylogfile + # Enable logging of TLS session keys via defacto standard environment variable + # 'SSLKEYLOGFILE', if the feature is available (Python 3.8+). Skip empty values. + if hasattr(context, "keylog_filename"): + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile return context -@typing.overload -def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = ..., - certfile: str | None = ..., - cert_reqs: int | None = ..., - ca_certs: str | None = ..., - server_hostname: str | None = ..., - ssl_version: int | None = ..., - ciphers: str | None = ..., - ssl_context: ssl.SSLContext | None = ..., - ca_cert_dir: str | None = ..., - key_password: str | None = ..., - ca_cert_data: None | str | bytes = ..., - tls_in_tls: typing.Literal[False] = ..., -) -> ssl.SSLSocket: ... - - -@typing.overload -def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = ..., - certfile: str | None = ..., - cert_reqs: int | None = ..., - ca_certs: str | None = ..., - server_hostname: str | None = ..., - ssl_version: int | None = ..., - ciphers: str | None = ..., - ssl_context: ssl.SSLContext | None = ..., - ca_cert_dir: str | None = ..., - key_password: str | None = ..., - ca_cert_data: None | str | bytes = ..., - tls_in_tls: bool = ..., -) -> ssl.SSLSocket | SSLTransportType: ... - - def ssl_wrap_socket( - sock: socket.socket, - keyfile: str | None = None, - certfile: str | None = None, - cert_reqs: int | None = None, - ca_certs: str | None = None, - server_hostname: str | None = None, - ssl_version: int | None = None, - ciphers: str | None = None, - ssl_context: ssl.SSLContext | None = None, - ca_cert_dir: str | None = None, - key_password: str | None = None, - ca_cert_data: None | str | bytes = None, - tls_in_tls: bool = False, -) -> ssl.SSLSocket | SSLTransportType: + sock, + keyfile=None, + certfile=None, + cert_reqs=None, + ca_certs=None, + server_hostname=None, + ssl_version=None, + ciphers=None, + ssl_context=None, + ca_cert_dir=None, + key_password=None, + ca_cert_data=None, + tls_in_tls=False, +): """ - All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and - ca_cert_dir have the same meaning as they do when using - :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, - :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`. + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. :param server_hostname: When SNI is supported, the expected hostname of the certificate @@ -452,18 +392,19 @@ def ssl_wrap_socket( """ context = ssl_context if context is None: - # Note: This branch of code and all the variables in it are only used in tests. - # We should consider deprecating and removing this code. + # Note: This branch of code and all the variables in it are no longer + # used by urllib3 itself. We should consider deprecating and removing + # this code. context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) if ca_certs or ca_cert_dir or ca_cert_data: try: context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) - except OSError as e: - raise SSLError(e) from e + except (IOError, OSError) as e: + raise SSLError(e) elif ssl_context is None and hasattr(context, "load_default_certs"): - # try to load OS default certs; works well on Windows. + # try to load OS default certs; works well on Windows (require Python3.4+) context.load_default_certs() # Attempt to detect if we get the goofy behavior of the @@ -478,28 +419,57 @@ def ssl_wrap_socket( else: context.load_cert_chain(certfile, keyfile, key_password) - context.set_alpn_protocols(ALPN_PROTOCOLS) + try: + if hasattr(context, "set_alpn_protocols"): + context.set_alpn_protocols(ALPN_PROTOCOLS) + except NotImplementedError: # Defensive: in CI, we always have set_alpn_protocols + pass + + # If we detect server_hostname is an IP address then the SNI + # extension should not be used according to RFC3546 Section 3.1 + use_sni_hostname = server_hostname and not is_ipaddress(server_hostname) + # SecureTransport uses server_hostname in certificate verification. + send_sni = (use_sni_hostname and HAS_SNI) or ( + IS_SECURETRANSPORT and server_hostname + ) + # Do not warn the user if server_hostname is an invalid SNI hostname. + if not HAS_SNI and use_sni_hostname: + warnings.warn( + "An HTTPS request has been made, but the SNI (Server Name " + "Indication) extension to TLS is not available on this platform. " + "This may cause the server to present an incorrect TLS " + "certificate, which can cause validation failures. You can upgrade to " + "a newer version of Python to solve this. For more information, see " + "https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html" + "#ssl-warnings", + SNIMissingWarning, + ) - ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + if send_sni: + ssl_sock = _ssl_wrap_socket_impl( + sock, context, tls_in_tls, server_hostname=server_hostname + ) + else: + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls) return ssl_sock -def is_ipaddress(hostname: str | bytes) -> bool: +def is_ipaddress(hostname): """Detects whether the hostname given is an IPv4 or IPv6 address. Also detects IPv6 addresses with Zone IDs. :param str hostname: Hostname to examine. :return: True if the hostname is an IP address, False otherwise. """ - if isinstance(hostname, bytes): + if not six.PY2 and isinstance(hostname, bytes): # IDN A-label bytes are ASCII compatible. hostname = hostname.decode("ascii") - return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + return bool(IPV4_RE.match(hostname) or BRACELESS_IPV6_ADDRZ_RE.match(hostname)) -def _is_key_file_encrypted(key_file: str) -> bool: +def _is_key_file_encrypted(key_file): """Detects if a key file is encrypted or not.""" - with open(key_file) as f: + with open(key_file, "r") as f: for line in f: # Look for Proc-Type: 4,ENCRYPTED if "ENCRYPTED" in line: @@ -508,12 +478,7 @@ def _is_key_file_encrypted(key_file: str) -> bool: return False -def _ssl_wrap_socket_impl( - sock: socket.socket, - ssl_context: ssl.SSLContext, - tls_in_tls: bool, - server_hostname: str | None = None, -) -> ssl.SSLSocket | SSLTransportType: +def _ssl_wrap_socket_impl(sock, ssl_context, tls_in_tls, server_hostname=None): if tls_in_tls: if not SSLTransport: # Import error, ssl is not available. @@ -524,4 +489,7 @@ def _ssl_wrap_socket_impl( SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) return SSLTransport(sock, ssl_context, server_hostname) - return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + if server_hostname: + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) + else: + return ssl_context.wrap_socket(sock) diff --git a/newrelic/packages/urllib3/util/ssl_match_hostname.py b/newrelic/packages/urllib3/util/ssl_match_hostname.py index 25d9100041..1dd950c489 100644 --- a/newrelic/packages/urllib3/util/ssl_match_hostname.py +++ b/newrelic/packages/urllib3/util/ssl_match_hostname.py @@ -1,18 +1,19 @@ -"""The match_hostname() function from Python 3.5, essential when using SSL.""" +"""The match_hostname() function from Python 3.3.3, essential when using SSL.""" # Note: This file is under the PSF license as the code comes from the python # stdlib. http://docs.python.org/3/license.html -# It is modified to remove commonName support. -from __future__ import annotations - -import ipaddress import re -import typing -from ipaddress import IPv4Address, IPv6Address +import sys -if typing.TYPE_CHECKING: - from .ssl_ import _TYPE_PEER_CERT_RET_DICT +# ipaddress has been backported to 2.6+ in pypi. If it is installed on the +# system, use it to handle IPAddress ServerAltnames (this was added in +# python-3.5) otherwise only do DNS matching. This allows +# util.ssl_match_hostname to continue to be used in Python 2.7. +try: + import ipaddress +except ImportError: + ipaddress = None __version__ = "3.5.0.1" @@ -21,9 +22,7 @@ class CertificateError(ValueError): pass -def _dnsname_match( - dn: typing.Any, hostname: str, max_wildcards: int = 1 -) -> typing.Match[str] | None | bool: +def _dnsname_match(dn, hostname, max_wildcards=1): """Matching according to RFC 6125, section 6.4.3 http://tools.ietf.org/html/rfc6125#section-6.4.3 @@ -50,7 +49,7 @@ def _dnsname_match( # speed up common case w/o wildcards if not wildcards: - return bool(dn.lower() == hostname.lower()) + return dn.lower() == hostname.lower() # RFC 6125, section 6.4.3, subitem 1. # The client SHOULD NOT attempt to match a presented identifier in which @@ -77,26 +76,26 @@ def _dnsname_match( return pat.match(hostname) -def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: +def _to_unicode(obj): + if isinstance(obj, str) and sys.version_info < (3,): + # ignored flake8 # F821 to support python 2.7 function + obj = unicode(obj, encoding="ascii", errors="strict") # noqa: F821 + return obj + + +def _ipaddress_match(ipname, host_ip): """Exact matching of IP addresses. - RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded - bytes of the IP address. An IP version 4 address is 4 octets, and an IP - version 6 address is 16 octets. [...] A reference identity of type IP-ID - matches if the address is identical to an iPAddress value of the - subjectAltName extension of the certificate." + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). """ # OpenSSL may add a trailing newline to a subjectAltName's IP address # Divergence from upstream: ipaddress can't handle byte str - ip = ipaddress.ip_address(ipname.rstrip()) - return bool(ip.packed == host_ip.packed) + ip = ipaddress.ip_address(_to_unicode(ipname).rstrip()) + return ip == host_ip -def match_hostname( - cert: _TYPE_PEER_CERT_RET_DICT | None, - hostname: str, - hostname_checks_common_name: bool = False, -) -> None: +def match_hostname(cert, hostname): """Verify that *cert* (in decoded format as returned by SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 rules are followed, but IP addresses are not accepted for *hostname*. @@ -112,22 +111,21 @@ def match_hostname( ) try: # Divergence from upstream: ipaddress can't handle byte str - # - # The ipaddress module shipped with Python < 3.9 does not support - # scoped IPv6 addresses so we unconditionally strip the Zone IDs for - # now. Once we drop support for Python 3.9 we can remove this branch. - if "%" in hostname: - host_ip = ipaddress.ip_address(hostname[: hostname.rfind("%")]) - else: - host_ip = ipaddress.ip_address(hostname) - - except ValueError: - # Not an IP address (common case) + host_ip = ipaddress.ip_address(_to_unicode(hostname)) + except (UnicodeError, ValueError): + # ValueError: Not an IP address (common case) + # UnicodeError: Divergence from upstream: Have to deal with ipaddress not taking + # byte strings. addresses should be all ascii, so we consider it not + # an ipaddress in this case host_ip = None + except AttributeError: + # Divergence from upstream: Make ipaddress library optional + if ipaddress is None: + host_ip = None + else: # Defensive + raise dnsnames = [] - san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) - key: str - value: str + san = cert.get("subjectAltName", ()) for key, value in san: if key == "DNS": if host_ip is None and _dnsname_match(value, hostname): @@ -137,23 +135,25 @@ def match_hostname( if host_ip is not None and _ipaddress_match(value, host_ip): return dnsnames.append(value) - - # We only check 'commonName' if it's enabled and we're not verifying - # an IP address. IP addresses aren't valid within 'commonName'. - if hostname_checks_common_name and host_ip is None and not dnsnames: + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName for sub in cert.get("subject", ()): for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. if key == "commonName": if _dnsname_match(value, hostname): return - dnsnames.append(value) # Defensive: for Python < 3.9.3 - + dnsnames.append(value) if len(dnsnames) > 1: raise CertificateError( "hostname %r " "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) ) elif len(dnsnames) == 1: - raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + raise CertificateError("hostname %r doesn't match %r" % (hostname, dnsnames[0])) else: - raise CertificateError("no appropriate subjectAltName fields were found") + raise CertificateError( + "no appropriate commonName or subjectAltName fields were found" + ) diff --git a/newrelic/packages/urllib3/util/ssltransport.py b/newrelic/packages/urllib3/util/ssltransport.py index 6d59bc3bce..4a7105d179 100644 --- a/newrelic/packages/urllib3/util/ssltransport.py +++ b/newrelic/packages/urllib3/util/ssltransport.py @@ -1,20 +1,9 @@ -from __future__ import annotations - import io import socket import ssl -import typing from ..exceptions import ProxySchemeUnsupported - -if typing.TYPE_CHECKING: - from typing_extensions import Self - - from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT - - -_WriteBuffer = typing.Union[bytearray, memoryview] -_ReturnValue = typing.TypeVar("_ReturnValue") +from ..packages import six SSL_BLOCKSIZE = 16384 @@ -31,7 +20,7 @@ class SSLTransport: """ @staticmethod - def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: + def _validate_ssl_context_for_tls_in_tls(ssl_context): """ Raises a ProxySchemeUnsupported if the provided ssl_context can't be used for TLS in TLS. @@ -41,18 +30,20 @@ def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: """ if not hasattr(ssl_context, "wrap_bio"): - raise ProxySchemeUnsupported( - "TLS in TLS requires SSLContext.wrap_bio() which isn't " - "available on non-native SSLContext" - ) + if six.PY2: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "supported on Python 2" + ) + else: + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) def __init__( - self, - socket: socket.socket, - ssl_context: ssl.SSLContext, - server_hostname: str | None = None, - suppress_ragged_eofs: bool = True, - ) -> None: + self, socket, ssl_context, server_hostname=None, suppress_ragged_eofs=True + ): """ Create an SSLTransport around socket using the provided ssl_context. """ @@ -69,36 +60,33 @@ def __init__( # Perform initial handshake. self._ssl_io_loop(self.sslobj.do_handshake) - def __enter__(self) -> Self: + def __enter__(self): return self - def __exit__(self, *_: typing.Any) -> None: + def __exit__(self, *_): self.close() - def fileno(self) -> int: + def fileno(self): return self.socket.fileno() - def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + def read(self, len=1024, buffer=None): return self._wrap_ssl_read(len, buffer) - def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + def recv(self, len=1024, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to recv") - return self._wrap_ssl_read(buflen) - - def recv_into( - self, - buffer: _WriteBuffer, - nbytes: int | None = None, - flags: int = 0, - ) -> None | int | bytes: + return self._wrap_ssl_read(len) + + def recv_into(self, buffer, nbytes=None, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to recv_into") - if nbytes is None: + if buffer and (nbytes is None): nbytes = len(buffer) + elif nbytes is None: + nbytes = 1024 return self.read(nbytes, buffer) - def sendall(self, data: bytes, flags: int = 0) -> None: + def sendall(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to sendall") count = 0 @@ -108,20 +96,15 @@ def sendall(self, data: bytes, flags: int = 0) -> None: v = self.send(byte_view[count:]) count += v - def send(self, data: bytes, flags: int = 0) -> int: + def send(self, data, flags=0): if flags != 0: raise ValueError("non-zero flags not allowed in calls to send") - return self._ssl_io_loop(self.sslobj.write, data) + response = self._ssl_io_loop(self.sslobj.write, data) + return response def makefile( - self, - mode: str, - buffering: int | None = None, - *, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + self, mode="r", buffering=None, encoding=None, errors=None, newline=None + ): """ Python's httpclient uses makefile and buffered io when reading HTTP messages and we need to support it. @@ -130,7 +113,7 @@ def makefile( changes to point to the socket directly. """ if not set(mode) <= {"r", "w", "b"}: - raise ValueError(f"invalid mode {mode!r} (only r, w, b allowed)") + raise ValueError("invalid mode %r (only r, w, b allowed)" % (mode,)) writing = "w" in mode reading = "r" in mode or not writing @@ -141,8 +124,8 @@ def makefile( rawmode += "r" if writing: rawmode += "w" - raw = socket.SocketIO(self, rawmode) # type: ignore[arg-type] - self.socket._io_refs += 1 # type: ignore[attr-defined] + raw = socket.SocketIO(self, rawmode) + self.socket._io_refs += 1 if buffering is None: buffering = -1 if buffering < 0: @@ -151,9 +134,8 @@ def makefile( if not binary: raise ValueError("unbuffered streams must be binary") return raw - buffer: typing.BinaryIO if reading and writing: - buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + buffer = io.BufferedRWPair(raw, raw, buffering) elif reading: buffer = io.BufferedReader(raw, buffering) else: @@ -162,51 +144,46 @@ def makefile( if binary: return buffer text = io.TextIOWrapper(buffer, encoding, errors, newline) - text.mode = mode # type: ignore[misc] + text.mode = mode return text - def unwrap(self) -> None: + def unwrap(self): self._ssl_io_loop(self.sslobj.unwrap) - def close(self) -> None: + def close(self): self.socket.close() - @typing.overload - def getpeercert( - self, binary_form: typing.Literal[False] = ... - ) -> _TYPE_PEER_CERT_RET_DICT | None: ... - - @typing.overload - def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... - - def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: - return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] + def getpeercert(self, binary_form=False): + return self.sslobj.getpeercert(binary_form) - def version(self) -> str | None: + def version(self): return self.sslobj.version() - def cipher(self) -> tuple[str, str, int] | None: + def cipher(self): return self.sslobj.cipher() - def selected_alpn_protocol(self) -> str | None: + def selected_alpn_protocol(self): return self.sslobj.selected_alpn_protocol() - def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + def selected_npn_protocol(self): + return self.sslobj.selected_npn_protocol() + + def shared_ciphers(self): return self.sslobj.shared_ciphers() - def compression(self) -> str | None: + def compression(self): return self.sslobj.compression() - def settimeout(self, value: float | None) -> None: + def settimeout(self, value): self.socket.settimeout(value) - def gettimeout(self) -> float | None: + def gettimeout(self): return self.socket.gettimeout() - def _decref_socketios(self) -> None: - self.socket._decref_socketios() # type: ignore[attr-defined] + def _decref_socketios(self): + self.socket._decref_socketios() - def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + def _wrap_ssl_read(self, len, buffer=None): try: return self._ssl_io_loop(self.sslobj.read, len, buffer) except ssl.SSLError as e: @@ -215,29 +192,7 @@ def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | byt else: raise - # func is sslobj.do_handshake or sslobj.unwrap - @typing.overload - def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... - - # func is sslobj.write, arg1 is data - @typing.overload - def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... - - # func is sslobj.read, arg1 is len, arg2 is buffer - @typing.overload - def _ssl_io_loop( - self, - func: typing.Callable[[int, bytearray | None], bytes], - arg1: int, - arg2: bytearray | None, - ) -> bytes: ... - - def _ssl_io_loop( - self, - func: typing.Callable[..., _ReturnValue], - arg1: None | bytes | int = None, - arg2: bytearray | None = None, - ) -> _ReturnValue: + def _ssl_io_loop(self, func, *args): """Performs an I/O loop between incoming/outgoing and the socket.""" should_loop = True ret = None @@ -245,12 +200,7 @@ def _ssl_io_loop( while should_loop: errno = None try: - if arg1 is None and arg2 is None: - ret = func() - elif arg2 is None: - ret = func(arg1) - else: - ret = func(arg1, arg2) + ret = func(*args) except ssl.SSLError as e: if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): # WANT_READ, and WANT_WRITE are expected, others are not. @@ -268,4 +218,4 @@ def _ssl_io_loop( self.incoming.write(buf) else: self.incoming.write_eof() - return typing.cast(_ReturnValue, ret) + return ret diff --git a/newrelic/packages/urllib3/util/timeout.py b/newrelic/packages/urllib3/util/timeout.py index 4bb1be11d9..78e18a6272 100644 --- a/newrelic/packages/urllib3/util/timeout.py +++ b/newrelic/packages/urllib3/util/timeout.py @@ -1,56 +1,44 @@ -from __future__ import annotations +from __future__ import absolute_import import time -import typing -from enum import Enum -from socket import getdefaulttimeout -from ..exceptions import TimeoutStateError - -if typing.TYPE_CHECKING: - from typing import Final +# The default socket timeout, used by httplib to indicate that no timeout was; specified by the user +from socket import _GLOBAL_DEFAULT_TIMEOUT, getdefaulttimeout +from ..exceptions import TimeoutStateError -class _TYPE_DEFAULT(Enum): - # This value should never be passed to socket.settimeout() so for safety we use a -1. - # socket.settimout() raises a ValueError for negative values. - token = -1 - +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() -_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token -_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] +# Use time.monotonic if available. +current_time = getattr(time, "monotonic", time.time) -class Timeout: +class Timeout(object): """Timeout configuration. Timeouts can be defined as a default for a pool: .. code-block:: python - import urllib3 - - timeout = urllib3.util.Timeout(connect=2.0, read=7.0) - - http = urllib3.PoolManager(timeout=timeout) - - resp = http.request("GET", "https://example.com/") - - print(resp.status) + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') Or per-request (which overrides the default for the pool): .. code-block:: python - response = http.request("GET", "https://example.com/", timeout=Timeout(10)) + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) Timeouts can be disabled by setting all the parameters to ``None``: .. code-block:: python no_timeout = Timeout(connect=None, read=None) - response = http.request("GET", "https://example.com/", timeout=no_timeout) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) :param total: @@ -101,34 +89,38 @@ class Timeout: the case; if a server streams one byte every fifteen seconds, a timeout of 20 seconds will not trigger, even though the request will take several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. """ #: A sentinel object representing the default timeout value - DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT - - def __init__( - self, - total: _TYPE_TIMEOUT = None, - connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, - ) -> None: + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): self._connect = self._validate_timeout(connect, "connect") self._read = self._validate_timeout(read, "read") self.total = self._validate_timeout(total, "total") - self._start_connect: float | None = None + self._start_connect = None - def __repr__(self) -> str: - return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + def __repr__(self): + return "%s(connect=%r, read=%r, total=%r)" % ( + type(self).__name__, + self._connect, + self._read, + self.total, + ) # __str__ provided for backwards compatibility __str__ = __repr__ - @staticmethod - def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: - return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + @classmethod + def resolve_default_timeout(cls, timeout): + return getdefaulttimeout() if timeout is cls.DEFAULT_TIMEOUT else timeout @classmethod - def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + def _validate_timeout(cls, value, name): """Check that a timeout attribute is valid. :param value: The timeout value to validate @@ -138,7 +130,10 @@ def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: :raises ValueError: If it is a numeric value less than or equal to zero, or the type is not an integer, float, or None. """ - if value is None or value is _DEFAULT_TIMEOUT: + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: return value if isinstance(value, bool): @@ -152,7 +147,7 @@ def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: raise ValueError( "Timeout value %s was %s, but it must be an " "int, float or None." % (name, value) - ) from None + ) try: if value <= 0: @@ -162,15 +157,16 @@ def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: "than or equal to 0." % (name, value) ) except TypeError: + # Python 3 raise ValueError( "Timeout value %s was %s, but it must be an " "int, float or None." % (name, value) - ) from None + ) return value @classmethod - def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: + def from_float(cls, timeout): """Create a new Timeout from a legacy timeout value. The timeout value used by httplib.py sets the same timeout on the @@ -179,13 +175,13 @@ def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: passed to this function. :param timeout: The legacy timeout value. - :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :type timeout: integer, float, sentinel default object, or None :return: Timeout object :rtype: :class:`Timeout` """ return Timeout(read=timeout, connect=timeout) - def clone(self) -> Timeout: + def clone(self): """Create a copy of the timeout object Timeout properties are stored per-pool but each request needs a fresh @@ -199,7 +195,7 @@ def clone(self) -> Timeout: # detect the user default. return Timeout(connect=self._connect, read=self._read, total=self.total) - def start_connect(self) -> float: + def start_connect(self): """Start the timeout clock, used during a connect() attempt :raises urllib3.exceptions.TimeoutStateError: if you attempt @@ -207,10 +203,10 @@ def start_connect(self) -> float: """ if self._start_connect is not None: raise TimeoutStateError("Timeout timer has already been started.") - self._start_connect = time.monotonic() + self._start_connect = current_time() return self._start_connect - def get_connect_duration(self) -> float: + def get_connect_duration(self): """Gets the time elapsed since the call to :meth:`start_connect`. :return: Elapsed time in seconds. @@ -222,10 +218,10 @@ def get_connect_duration(self) -> float: raise TimeoutStateError( "Can't get connect duration for timer that has not started." ) - return time.monotonic() - self._start_connect + return current_time() - self._start_connect @property - def connect_timeout(self) -> _TYPE_TIMEOUT: + def connect_timeout(self): """Get the value to use when setting a connection timeout. This will be a positive float or integer, the value None @@ -237,13 +233,13 @@ def connect_timeout(self) -> _TYPE_TIMEOUT: if self.total is None: return self._connect - if self._connect is None or self._connect is _DEFAULT_TIMEOUT: + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: return self.total - return min(self._connect, self.total) # type: ignore[type-var] + return min(self._connect, self.total) @property - def read_timeout(self) -> float | None: + def read_timeout(self): """Get the value for the read timeout. This assumes some time has elapsed in the connection timeout and @@ -255,21 +251,21 @@ def read_timeout(self) -> float | None: raised. :return: Value to use for the read timeout. - :rtype: int, float or None + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` has not yet been called on this object. """ if ( self.total is not None - and self.total is not _DEFAULT_TIMEOUT + and self.total is not self.DEFAULT_TIMEOUT and self._read is not None - and self._read is not _DEFAULT_TIMEOUT + and self._read is not self.DEFAULT_TIMEOUT ): # In case the connect timeout has not yet been established. if self._start_connect is None: return self._read return max(0, min(self.total - self.get_connect_duration(), self._read)) - elif self.total is not None and self.total is not _DEFAULT_TIMEOUT: + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: return max(0, self.total - self.get_connect_duration()) else: - return self.resolve_default_timeout(self._read) + return self._read diff --git a/newrelic/packages/urllib3/util/url.py b/newrelic/packages/urllib3/util/url.py index db057f17be..e5682d3be4 100644 --- a/newrelic/packages/urllib3/util/url.py +++ b/newrelic/packages/urllib3/util/url.py @@ -1,20 +1,22 @@ -from __future__ import annotations +from __future__ import absolute_import import re -import typing +from collections import namedtuple from ..exceptions import LocationParseError -from .util import to_str +from ..packages import six + +url_attrs = ["scheme", "auth", "host", "port", "path", "query", "fragment"] # We only want to normalize urls with an HTTP(S) scheme. # urllib3 infers URLs without a scheme (None) to be http. -_NORMALIZABLE_SCHEMES = ("http", "https", None) +NORMALIZABLE_SCHEMES = ("http", "https", None) # Almost all of these patterns were derived from the # 'rfc3986' module: https://github.com/python-hyper/rfc3986 -_PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") -_SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") -_URI_RE = re.compile( +PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +URI_RE = re.compile( r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" r"(?://([^\\/?#]*))?" r"([^?#]*)" @@ -23,10 +25,10 @@ re.UNICODE | re.DOTALL, ) -_IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" -_HEX_PAT = "[0-9A-Fa-f]{1,4}" -_LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT) -_subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT} +IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +HEX_PAT = "[0-9A-Fa-f]{1,4}" +LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=HEX_PAT, ipv4=IPV4_PAT) +_subs = {"hex": HEX_PAT, "ls32": LS32_PAT} _variations = [ # 6( h16 ":" ) ls32 "(?:%(hex)s:){6}%(ls32)s", @@ -48,78 +50,69 @@ "(?:(?:%(hex)s:){0,6}%(hex)s)?::", ] -_UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" -_IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" -_ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" -_IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]" -_REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" -_TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") +UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +ZONE_ID_PAT = "(?:%25|%)(?:[" + UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +IPV6_ADDRZ_PAT = r"\[" + IPV6_PAT + r"(?:" + ZONE_ID_PAT + r")?\]" +REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") -_IPV4_RE = re.compile("^" + _IPV4_PAT + "$") -_IPV6_RE = re.compile("^" + _IPV6_PAT + "$") -_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$") -_BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$") -_ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$") +IPV4_RE = re.compile("^" + IPV4_PAT + "$") +IPV6_RE = re.compile("^" + IPV6_PAT + "$") +IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT + "$") +BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + IPV6_ADDRZ_PAT[2:-2] + "$") +ZONE_ID_RE = re.compile("(" + ZONE_ID_PAT + r")\]$") _HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( - _REG_NAME_PAT, - _IPV4_PAT, - _IPV6_ADDRZ_PAT, + REG_NAME_PAT, + IPV4_PAT, + IPV6_ADDRZ_PAT, ) _HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) -_UNRESERVED_CHARS = set( +UNRESERVED_CHARS = set( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" ) -_SUB_DELIM_CHARS = set("!$&'()*+,;=") -_USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"} -_PATH_CHARS = _USERINFO_CHARS | {"@", "/"} -_QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"} - - -class Url( - typing.NamedTuple( - "Url", - [ - ("scheme", typing.Optional[str]), - ("auth", typing.Optional[str]), - ("host", typing.Optional[str]), - ("port", typing.Optional[int]), - ("path", typing.Optional[str]), - ("query", typing.Optional[str]), - ("fragment", typing.Optional[str]), - ], - ) -): +SUB_DELIM_CHARS = set("!$&'()*+,;=") +USERINFO_CHARS = UNRESERVED_CHARS | SUB_DELIM_CHARS | {":"} +PATH_CHARS = USERINFO_CHARS | {"@", "/"} +QUERY_CHARS = FRAGMENT_CHARS = PATH_CHARS | {"?"} + + +class Url(namedtuple("Url", url_attrs)): """ Data structure for representing an HTTP URL. Used as a return value for :func:`parse_url`. Both the scheme and host are normalized as they are both case-insensitive according to RFC 3986. """ - def __new__( # type: ignore[no-untyped-def] + __slots__ = () + + def __new__( cls, - scheme: str | None = None, - auth: str | None = None, - host: str | None = None, - port: int | None = None, - path: str | None = None, - query: str | None = None, - fragment: str | None = None, + scheme=None, + auth=None, + host=None, + port=None, + path=None, + query=None, + fragment=None, ): if path and not path.startswith("/"): path = "/" + path if scheme is not None: scheme = scheme.lower() - return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + return super(Url, cls).__new__( + cls, scheme, auth, host, port, path, query, fragment + ) @property - def hostname(self) -> str | None: + def hostname(self): """For backwards-compatibility with urlparse. We're nice like that.""" return self.host @property - def request_uri(self) -> str: + def request_uri(self): """Absolute path including the query string.""" uri = self.path or "/" @@ -129,37 +122,14 @@ def request_uri(self) -> str: return uri @property - def authority(self) -> str | None: - """ - Authority component as defined in RFC 3986 3.2. - This includes userinfo (auth), host and port. - - i.e. - userinfo@host:port - """ - userinfo = self.auth - netloc = self.netloc - if netloc is None or userinfo is None: - return netloc - else: - return f"{userinfo}@{netloc}" - - @property - def netloc(self) -> str | None: - """ - Network location including host and port. - - If you need the equivalent of urllib.parse's ``netloc``, - use the ``authority`` property instead. - """ - if self.host is None: - return None + def netloc(self): + """Network location including host and port""" if self.port: - return f"{self.host}:{self.port}" + return "%s:%d" % (self.host, self.port) return self.host @property - def url(self) -> str: + def url(self): """ Convert self into a url @@ -168,77 +138,88 @@ def url(self) -> str: :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls with a blank port will have : removed). - Example: - - .. code-block:: python - - import urllib3 + Example: :: - U = urllib3.util.parse_url("https://google.com/mail/") - - print(U.url) - # "https://google.com/mail/" - - print( urllib3.util.Url("https", "username:password", - "host.com", 80, "/path", "query", "fragment" - ).url - ) - # "https://username:password@host.com:80/path?query#fragment" + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' """ scheme, auth, host, port, path, query, fragment = self - url = "" + url = u"" # We use "is not None" we want things to happen with empty strings (or 0 port) if scheme is not None: - url += scheme + "://" + url += scheme + u"://" if auth is not None: - url += auth + "@" + url += auth + u"@" if host is not None: url += host if port is not None: - url += ":" + str(port) + url += u":" + str(port) if path is not None: url += path if query is not None: - url += "?" + query + url += u"?" + query if fragment is not None: - url += "#" + fragment + url += u"#" + fragment return url - def __str__(self) -> str: + def __str__(self): return self.url -@typing.overload -def _encode_invalid_chars( - component: str, allowed_chars: typing.Container[str] -) -> str: # Abstract - ... +def split_first(s, delims): + """ + .. deprecated:: 1.25 + + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example:: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d -@typing.overload -def _encode_invalid_chars( - component: None, allowed_chars: typing.Container[str] -) -> None: # Abstract - ... + if min_idx is None or min_idx < 0: + return s, "", None + return s[:min_idx], s[min_idx + 1 :], min_delim -def _encode_invalid_chars( - component: str | None, allowed_chars: typing.Container[str] -) -> str | None: + +def _encode_invalid_chars(component, allowed_chars, encoding="utf-8"): """Percent-encodes a URI component without reapplying onto an already percent-encoded component. """ if component is None: return component - component = to_str(component) + component = six.ensure_text(component) # Normalize existing percent-encoded bytes. # Try to see if the component we're encoding is already percent-encoded # so we can skip all '%' characters but still encode all others. - component, percent_encodings = _PERCENT_RE.subn( + component, percent_encodings = PERCENT_RE.subn( lambda match: match.group(0).upper(), component ) @@ -247,7 +228,7 @@ def _encode_invalid_chars( encoded_component = bytearray() for i in range(0, len(uri_bytes)): - # Will return a single character bytestring + # Will return a single character bytestring on both Python 2 & 3 byte = uri_bytes[i : i + 1] byte_ord = ord(byte) if (is_percent_encoded and byte == b"%") or ( @@ -257,10 +238,10 @@ def _encode_invalid_chars( continue encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) - return encoded_component.decode() + return encoded_component.decode(encoding) -def _remove_path_dot_segments(path: str) -> str: +def _remove_path_dot_segments(path): # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code segments = path.split("/") # Turn the path into a list of segments output = [] # Initialize the variable to use to store output @@ -270,7 +251,7 @@ def _remove_path_dot_segments(path: str) -> str: if segment == ".": continue # Anything other than '..', should be appended to the output - if segment != "..": + elif segment != "..": output.append(segment) # In this case segment == '..', if we can, we should pop the last # element @@ -290,23 +271,18 @@ def _remove_path_dot_segments(path: str) -> str: return "/".join(output) -@typing.overload -def _normalize_host(host: None, scheme: str | None) -> None: ... - - -@typing.overload -def _normalize_host(host: str, scheme: str | None) -> str: ... - - -def _normalize_host(host: str | None, scheme: str | None) -> str | None: +def _normalize_host(host, scheme): if host: - if scheme in _NORMALIZABLE_SCHEMES: - is_ipv6 = _IPV6_ADDRZ_RE.match(host) + if isinstance(host, six.binary_type): + host = six.ensure_str(host) + + if scheme in NORMALIZABLE_SCHEMES: + is_ipv6 = IPV6_ADDRZ_RE.match(host) if is_ipv6: # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID # separator as necessary to return a valid RFC 4007 scoped IP. - match = _ZONE_ID_RE.search(host) + match = ZONE_ID_RE.search(host) if match: start, end = match.span(1) zone_id = host[start:end] @@ -315,56 +291,46 @@ def _normalize_host(host: str | None, scheme: str | None) -> str | None: zone_id = zone_id[3:] else: zone_id = zone_id[1:] - zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS) - return f"{host[:start].lower()}%{zone_id}{host[end:]}" + zone_id = "%" + _encode_invalid_chars(zone_id, UNRESERVED_CHARS) + return host[:start].lower() + zone_id + host[end:] else: return host.lower() - elif not _IPV4_RE.match(host): - return to_str( - b".".join([_idna_encode(label) for label in host.split(".")]), - "ascii", + elif not IPV4_RE.match(host): + return six.ensure_str( + b".".join([_idna_encode(label) for label in host.split(".")]) ) return host -def _idna_encode(name: str) -> bytes: - if not name.isascii(): +def _idna_encode(name): + if name and any(ord(x) >= 128 for x in name): try: import idna except ImportError: - raise LocationParseError( - "Unable to parse URL without the 'idna' module" - ) from None - + six.raise_from( + LocationParseError("Unable to parse URL without the 'idna' module"), + None, + ) try: return idna.encode(name.lower(), strict=True, std3_rules=True) except idna.IDNAError: - raise LocationParseError( - f"Name '{name}' is not a valid IDNA label" - ) from None - + six.raise_from( + LocationParseError(u"Name '%s' is not a valid IDNA label" % name), None + ) return name.lower().encode("ascii") -def _encode_target(target: str) -> str: - """Percent-encodes a request target so that there are no invalid characters - - Pre-condition for this function is that 'target' must start with '/'. - If that is the case then _TARGET_RE will always produce a match. - """ - match = _TARGET_RE.match(target) - if not match: # Defensive: - raise LocationParseError(f"{target!r} is not a valid request URI") - - path, query = match.groups() - encoded_target = _encode_invalid_chars(path, _PATH_CHARS) +def _encode_target(target): + """Percent-encodes a request target so that there are no invalid characters""" + path, query = TARGET_RE.match(target).groups() + target = _encode_invalid_chars(path, PATH_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) if query is not None: - query = _encode_invalid_chars(query, _QUERY_CHARS) - encoded_target += "?" + query - return encoded_target + target += "?" + query + return target -def parse_url(url: str) -> Url: +def parse_url(url): """ Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is performed to parse incomplete urls. Fields not provided will be None. @@ -375,44 +341,28 @@ def parse_url(url: str) -> Url: :param str url: URL to parse into a :class:`.Url` namedtuple. - Partly backwards-compatible with :mod:`urllib.parse`. + Partly backwards-compatible with :mod:`urlparse`. - Example: + Example:: - .. code-block:: python - - import urllib3 - - print( urllib3.util.parse_url('http://google.com/mail/')) - # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) - - print( urllib3.util.parse_url('google.com:80')) - # Url(scheme=None, host='google.com', port=80, path=None, ...) - - print( urllib3.util.parse_url('/foo?bar')) - # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) """ if not url: # Empty return Url() source_url = url - if not _SCHEME_RE.search(url): + if not SCHEME_RE.search(url): url = "//" + url - scheme: str | None - authority: str | None - auth: str | None - host: str | None - port: str | None - port_int: int | None - path: str | None - query: str | None - fragment: str | None - try: - scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] - normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES + scheme, authority, path, query, fragment = URI_RE.match(url).groups() + normalize_uri = scheme is None or scheme.lower() in NORMALIZABLE_SCHEMES if scheme: scheme = scheme.lower() @@ -420,33 +370,31 @@ def parse_url(url: str) -> Url: if authority: auth, _, host_port = authority.rpartition("@") auth = auth or None - host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr] + host, port = _HOST_PORT_RE.match(host_port).groups() if auth and normalize_uri: - auth = _encode_invalid_chars(auth, _USERINFO_CHARS) + auth = _encode_invalid_chars(auth, USERINFO_CHARS) if port == "": port = None else: auth, host, port = None, None, None if port is not None: - port_int = int(port) - if not (0 <= port_int <= 65535): + port = int(port) + if not (0 <= port <= 65535): raise LocationParseError(url) - else: - port_int = None host = _normalize_host(host, scheme) if normalize_uri and path: path = _remove_path_dot_segments(path) - path = _encode_invalid_chars(path, _PATH_CHARS) + path = _encode_invalid_chars(path, PATH_CHARS) if normalize_uri and query: - query = _encode_invalid_chars(query, _QUERY_CHARS) + query = _encode_invalid_chars(query, QUERY_CHARS) if normalize_uri and fragment: - fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS) + fragment = _encode_invalid_chars(fragment, FRAGMENT_CHARS) - except (ValueError, AttributeError) as e: - raise LocationParseError(source_url) from e + except (ValueError, AttributeError): + return six.raise_from(LocationParseError(source_url), None) # For the sake of backwards compatibility we put empty # string values for path if there are any defined values @@ -458,12 +406,30 @@ def parse_url(url: str) -> Url: else: path = None + # Ensure that each part of the URL is a `str` for + # backwards compatibility. + if isinstance(url, six.text_type): + ensure_func = six.ensure_text + else: + ensure_func = six.ensure_str + + def ensure_type(x): + return x if x is None else ensure_func(x) + return Url( - scheme=scheme, - auth=auth, - host=host, - port=port_int, - path=path, - query=query, - fragment=fragment, + scheme=ensure_type(scheme), + auth=ensure_type(auth), + host=ensure_type(host), + port=port, + path=ensure_type(path), + query=ensure_type(query), + fragment=ensure_type(fragment), ) + + +def get_host(url): + """ + Deprecated. Use :func:`parse_url` instead. + """ + p = parse_url(url) + return p.scheme or "http", p.hostname, p.port diff --git a/newrelic/packages/urllib3/util/util.py b/newrelic/packages/urllib3/util/util.py deleted file mode 100644 index 35c77e4025..0000000000 --- a/newrelic/packages/urllib3/util/util.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -import typing -from types import TracebackType - - -def to_bytes( - x: str | bytes, encoding: str | None = None, errors: str | None = None -) -> bytes: - if isinstance(x, bytes): - return x - elif not isinstance(x, str): - raise TypeError(f"not expecting type {type(x).__name__}") - if encoding or errors: - return x.encode(encoding or "utf-8", errors=errors or "strict") - return x.encode() - - -def to_str( - x: str | bytes, encoding: str | None = None, errors: str | None = None -) -> str: - if isinstance(x, str): - return x - elif not isinstance(x, bytes): - raise TypeError(f"not expecting type {type(x).__name__}") - if encoding or errors: - return x.decode(encoding or "utf-8", errors=errors or "strict") - return x.decode() - - -def reraise( - tp: type[BaseException] | None, - value: BaseException, - tb: TracebackType | None = None, -) -> typing.NoReturn: - try: - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None # type: ignore[assignment] - tb = None diff --git a/newrelic/packages/urllib3/util/wait.py b/newrelic/packages/urllib3/util/wait.py index aeca0c7ad5..21b4590b3d 100644 --- a/newrelic/packages/urllib3/util/wait.py +++ b/newrelic/packages/urllib3/util/wait.py @@ -1,10 +1,18 @@ -from __future__ import annotations - +import errno import select -import socket +import sys from functools import partial -__all__ = ["wait_for_read", "wait_for_write"] +try: + from time import monotonic +except ImportError: + from time import time as monotonic + +__all__ = ["NoWayToWaitForSocketError", "wait_for_read", "wait_for_write"] + + +class NoWayToWaitForSocketError(Exception): + pass # How should we wait on sockets? @@ -29,13 +37,37 @@ # So: on Windows we use select(), and everywhere else we use poll(). We also # fall back to select() in case poll() is somehow broken or missing. - -def select_wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +if sys.version_info >= (3, 5): + # Modern Python, that retries syscalls by default + def _retry_on_intr(fn, timeout): + return fn(timeout) + +else: + # Old and broken Pythons. + def _retry_on_intr(fn, timeout): + if timeout is None: + deadline = float("inf") + else: + deadline = monotonic() + timeout + + while True: + try: + return fn(timeout) + # OSError for 3 <= pyver < 3.5, select.error for pyver <= 2.7 + except (OSError, select.error) as e: + # 'e.args[0]' incantation works for both OSError and select.error + if e.args[0] != errno.EINTR: + raise + else: + timeout = deadline - monotonic() + if timeout < 0: + timeout = 0 + if timeout == float("inf"): + timeout = None + continue + + +def select_wait_for_socket(sock, read=False, write=False, timeout=None): if not read and not write: raise RuntimeError("must specify at least one of read=True, write=True") rcheck = [] @@ -50,16 +82,11 @@ def select_wait_for_socket( # sockets for both conditions. (The stdlib selectors module does the same # thing.) fn = partial(select.select, rcheck, wcheck, wcheck) - rready, wready, xready = fn(timeout) + rready, wready, xready = _retry_on_intr(fn, timeout) return bool(rready or wready or xready) -def poll_wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +def poll_wait_for_socket(sock, read=False, write=False, timeout=None): if not read and not write: raise RuntimeError("must specify at least one of read=True, write=True") mask = 0 @@ -71,33 +98,32 @@ def poll_wait_for_socket( poll_obj.register(sock, mask) # For some reason, poll() takes timeout in milliseconds - def do_poll(t: float | None) -> list[tuple[int, int]]: + def do_poll(t): if t is not None: t *= 1000 return poll_obj.poll(t) - return bool(do_poll(timeout)) + return bool(_retry_on_intr(do_poll, timeout)) + + +def null_wait_for_socket(*args, **kwargs): + raise NoWayToWaitForSocketError("no select-equivalent available") -def _have_working_poll() -> bool: +def _have_working_poll(): # Apparently some systems have a select.poll that fails as soon as you try # to use it, either due to strange configuration or broken monkeypatching # from libraries like eventlet/greenlet. try: poll_obj = select.poll() - poll_obj.poll(0) + _retry_on_intr(poll_obj.poll, 0) except (AttributeError, OSError): return False else: return True -def wait_for_socket( - sock: socket.socket, - read: bool = False, - write: bool = False, - timeout: float | None = None, -) -> bool: +def wait_for_socket(*args, **kwargs): # We delay choosing which implementation to use until the first time we're # called. We could do it at import time, but then we might make the wrong # decision if someone goes wild with monkeypatching select.poll after @@ -107,17 +133,19 @@ def wait_for_socket( wait_for_socket = poll_wait_for_socket elif hasattr(select, "select"): wait_for_socket = select_wait_for_socket - return wait_for_socket(sock, read, write, timeout) + else: # Platform-specific: Appengine. + wait_for_socket = null_wait_for_socket + return wait_for_socket(*args, **kwargs) -def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: +def wait_for_read(sock, timeout=None): """Waits for reading to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ return wait_for_socket(sock, read=True, timeout=timeout) -def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool: +def wait_for_write(sock, timeout=None): """Waits for writing to be available on a given socket. Returns True if the socket is readable, or False if the timeout expired. """ diff --git a/newrelic/packages/wrapt/LICENSE b/newrelic/packages/wrapt/LICENSE index 643f6fe280..d49cae8439 100644 --- a/newrelic/packages/wrapt/LICENSE +++ b/newrelic/packages/wrapt/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013-2025, Graham Dumpleton +Copyright (c) 2013-2019, Graham Dumpleton All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/newrelic/packages/wrapt/__init__.py b/newrelic/packages/wrapt/__init__.py index fe7730c0b9..ed31a94313 100644 --- a/newrelic/packages/wrapt/__init__.py +++ b/newrelic/packages/wrapt/__init__.py @@ -1,64 +1,30 @@ -""" -Wrapt is a library for decorators, wrappers and monkey patching. -""" +__version_info__ = ('1', '16', '0') +__version__ = '.'.join(__version_info__) -__version_info__ = ("2", "0", "1") -__version__ = ".".join(__version_info__) +from .__wrapt__ import (ObjectProxy, CallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, PartialCallableObjectProxy) + +from .patches import (resolve_path, apply_patch, wrap_object, wrap_object_attribute, + function_wrapper, wrap_function_wrapper, patch_function_wrapper, + transient_function_wrapper) -from .__wrapt__ import ( - BaseObjectProxy, - BoundFunctionWrapper, - CallableObjectProxy, - FunctionWrapper, - PartialCallableObjectProxy, - partial, -) -from .decorators import AdapterFactory, adapter_factory, decorator, synchronized -from .importer import ( - discover_post_import_hooks, - notify_module_loaded, - register_post_import_hook, - when_imported, -) -from .patches import ( - apply_patch, - function_wrapper, - patch_function_wrapper, - resolve_path, - transient_function_wrapper, - wrap_function_wrapper, - wrap_object, - wrap_object_attribute, -) -from .proxies import AutoObjectProxy, LazyObjectProxy, ObjectProxy, lazy_import from .weakrefs import WeakFunctionProxy -__all__ = ( - "AutoObjectProxy", - "BaseObjectProxy", - "BoundFunctionWrapper", - "CallableObjectProxy", - "FunctionWrapper", - "LazyObjectProxy", - "ObjectProxy", - "PartialCallableObjectProxy", - "partial", - "AdapterFactory", - "adapter_factory", - "decorator", - "synchronized", - "discover_post_import_hooks", - "notify_module_loaded", - "register_post_import_hook", - "when_imported", - "apply_patch", - "function_wrapper", - "lazy_import", - "patch_function_wrapper", - "resolve_path", - "transient_function_wrapper", - "wrap_function_wrapper", - "wrap_object", - "wrap_object_attribute", - "WeakFunctionProxy", -) +from .decorators import (adapter_factory, AdapterFactory, decorator, + synchronized) + +from .importer import (register_post_import_hook, when_imported, + notify_module_loaded, discover_post_import_hooks) + +# Import of inspect.getcallargs() included for backward compatibility. An +# implementation of this was previously bundled and made available here for +# Python <2.7. Avoid using this in future. + +from inspect import getcallargs + +# Variant of inspect.formatargspec() included here for forward compatibility. +# This is being done because Python 3.11 dropped inspect.formatargspec() but +# code for handling signature changing decorators relied on it. Exposing the +# bundled implementation here in case any user of wrapt was also needing it. + +from .arguments import formatargspec diff --git a/newrelic/packages/wrapt/__init__.pyi b/newrelic/packages/wrapt/__init__.pyi deleted file mode 100644 index 2e33e00e7f..0000000000 --- a/newrelic/packages/wrapt/__init__.pyi +++ /dev/null @@ -1,319 +0,0 @@ -import sys - -if sys.version_info >= (3, 10): - from inspect import FullArgSpec - from types import ModuleType, TracebackType - from typing import ( - Any, - Callable, - Concatenate, - Generic, - ParamSpec, - Protocol, - TypeVar, - overload, - ) - - P = ParamSpec("P") - R = TypeVar("R", covariant=True) - - T = TypeVar("T", bound=Any) - - class Boolean(Protocol): - def __bool__(self) -> bool: ... - - # ObjectProxy - - class BaseObjectProxy(Generic[T]): - __wrapped__: T - def __init__(self, wrapped: T) -> None: ... - - class ObjectProxy(BaseObjectProxy[T]): - def __init__(self, wrapped: T) -> None: ... - - class AutoObjectProxy(BaseObjectProxy[T]): - def __init__(self, wrapped: T) -> None: ... - - # LazyObjectProxy - - class LazyObjectProxy(AutoObjectProxy[T]): - def __init__( - self, callback: Callable[[], T] | None, *, interface: Any = ... - ) -> None: ... - - @overload - def lazy_import(name: str) -> LazyObjectProxy[ModuleType]: ... - @overload - def lazy_import( - name: str, attribute: str, *, interface: Any = ... - ) -> LazyObjectProxy[Any]: ... - - # CallableObjectProxy - - class CallableObjectProxy(BaseObjectProxy[T]): - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - - # PartialCallableObjectProxy - - class PartialCallableObjectProxy: - def __init__( - self, func: Callable[..., Any], *args: Any, **kwargs: Any - ) -> None: ... - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - - def partial( - func: Callable[..., Any], /, *args: Any, **kwargs: Any - ) -> Callable[..., Any]: ... - - # WeakFunctionProxy - - class WeakFunctionProxy: - def __init__( - self, - wrapped: Callable[..., Any], - callback: Callable[..., Any] | None = None, - ) -> None: ... - def __call__(self, *args: Any, **kwargs: Any) -> Any: ... - - # FunctionWrapper - - WrappedFunction = Callable[P, R] - - GenericCallableWrapperFunction = Callable[ - [WrappedFunction[P, R], Any, tuple[Any, ...], dict[str, Any]], R - ] - - ClassMethodWrapperFunction = Callable[ - [type[Any], WrappedFunction[P, R], Any, tuple[Any, ...], dict[str, Any]], R - ] - - InstanceMethodWrapperFunction = Callable[ - [Any, WrappedFunction[P, R], Any, tuple[Any, ...], dict[str, Any]], R - ] - - WrapperFunction = ( - GenericCallableWrapperFunction[P, R] - | ClassMethodWrapperFunction[P, R] - | InstanceMethodWrapperFunction[P, R] - ) - - class _FunctionWrapperBase(ObjectProxy[WrappedFunction[P, R]]): - _self_instance: Any - _self_wrapper: WrapperFunction[P, R] - _self_enabled: bool | Boolean | Callable[[], bool] | None - _self_binding: str - _self_parent: Any - _self_owner: Any - - class BoundFunctionWrapper(_FunctionWrapperBase[P, R]): - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... - def __get__( - self, instance: Any, owner: type[Any] | None = None - ) -> "BoundFunctionWrapper[P, R]": ... - - class FunctionWrapper(_FunctionWrapperBase[P, R]): - def __init__( - self, - wrapped: WrappedFunction[P, R], - wrapper: WrapperFunction[P, R], - enabled: bool | Boolean | Callable[[], bool] | None = None, - ) -> None: ... - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: ... - def __get__( - self, instance: Any, owner: type[Any] | None = None - ) -> BoundFunctionWrapper[P, R]: ... - - # AdapterFactory/adapter_factory() - - class AdapterFactory(Protocol): - def __call__( - self, wrapped: Callable[..., Any] - ) -> str | FullArgSpec | Callable[..., Any]: ... - - def adapter_factory(wrapped: Callable[..., Any]) -> AdapterFactory: ... - - # decorator() - - class Descriptor(Protocol): - def __get__(self, instance: Any, owner: type[Any] | None = None) -> Any: ... - - class FunctionDecorator(Generic[P, R]): - def __call__( - self, - callable: ( - Callable[P, R] - | Callable[Concatenate[type[T], P], R] - | Callable[Concatenate[Any, P], R] - | Callable[[type[T]], R] - | Descriptor - ), - ) -> FunctionWrapper[P, R]: ... - - class PartialFunctionDecorator: - @overload - def __call__( - self, wrapper: GenericCallableWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - @overload - def __call__( - self, wrapper: ClassMethodWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - @overload - def __call__( - self, wrapper: InstanceMethodWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - - # ... Decorator applied to class type. - - @overload - def decorator(wrapper: type[T], /) -> FunctionDecorator[Any, Any]: ... - - # ... Decorator applied to function or method. - - @overload - def decorator( - wrapper: GenericCallableWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - @overload - def decorator( - wrapper: ClassMethodWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - @overload - def decorator( - wrapper: InstanceMethodWrapperFunction[P, R], / - ) -> FunctionDecorator[P, R]: ... - - # ... Positional arguments. - - @overload - def decorator( - *, - enabled: bool | Boolean | Callable[[], bool] | None = None, - adapter: str | FullArgSpec | AdapterFactory | Callable[..., Any] | None = None, - proxy: type[FunctionWrapper[Any, Any]] | None = None, - ) -> PartialFunctionDecorator: ... - - # function_wrapper() - - @overload - def function_wrapper(wrapper: type[Any]) -> FunctionDecorator[Any, Any]: ... - @overload - def function_wrapper( - wrapper: GenericCallableWrapperFunction[P, R], - ) -> FunctionDecorator[P, R]: ... - @overload - def function_wrapper( - wrapper: ClassMethodWrapperFunction[P, R], - ) -> FunctionDecorator[P, R]: ... - @overload - def function_wrapper( - wrapper: InstanceMethodWrapperFunction[P, R], - ) -> FunctionDecorator[P, R]: ... - # @overload - # def function_wrapper(wrapper: Any) -> FunctionDecorator[Any, Any]: ... # Don't use, breaks stuff. - - # wrap_function_wrapper() - - def wrap_function_wrapper( - target: ModuleType | type[Any] | Any | str, - name: str, - wrapper: WrapperFunction[P, R], - ) -> FunctionWrapper[P, R]: ... - - # patch_function_wrapper() - - class WrapperDecorator: - def __call__(self, wrapper: WrapperFunction[P, R]) -> FunctionWrapper[P, R]: ... - - def patch_function_wrapper( - target: ModuleType | type[Any] | Any | str, - name: str, - enabled: bool | Boolean | Callable[[], bool] | None = None, - ) -> WrapperDecorator: ... - - # transient_function_wrapper() - - class TransientDecorator: - def __call__( - self, wrapper: WrapperFunction[P, R] - ) -> FunctionDecorator[P, R]: ... - - def transient_function_wrapper( - target: ModuleType | type[Any] | Any | str, name: str - ) -> TransientDecorator: ... - - # resolve_path() - - def resolve_path( - target: ModuleType | type[Any] | Any | str, name: str - ) -> tuple[ModuleType | type[Any] | Any, str, Callable[..., Any]]: ... - - # apply_patch() - - def apply_patch( - parent: ModuleType | type[Any] | Any, - attribute: str, - replacement: Any, - ) -> None: ... - - # wrap_object() - - WrapperFactory = Callable[ - [Callable[..., Any], tuple[Any, ...], dict[str, Any]], type[ObjectProxy[Any]] - ] - - def wrap_object( - target: ModuleType | type[Any] | Any | str, - name: str, - factory: WrapperFactory | type[ObjectProxy[Any]], - args: tuple[Any, ...], - kwargs: dict[str, Any], - ) -> Any: ... - - # wrap_object_attribute() - - def wrap_object_attribute( - target: ModuleType | type[Any] | Any | str, - name: str, - factory: WrapperFactory | type[ObjectProxy[Any]], - args: tuple[Any, ...] = (), - kwargs: dict[str, Any] = {}, - ) -> Any: ... - - # register_post_import_hook() - - def register_post_import_hook( - hook: Callable[[ModuleType], Any] | str, name: str - ) -> None: ... - - # discover_post_import_hooks() - - def discover_post_import_hooks(group: str) -> None: ... - - # notify_module_loaded() - - def notify_module_loaded(module: ModuleType) -> None: ... - - # when_imported() - - class ImportHookDecorator: - def __call__(self, hook: Callable[[ModuleType], Any]) -> Callable[..., Any]: ... - - def when_imported(name: str) -> ImportHookDecorator: ... - - # synchronized() - - class SynchronizedObject: - def __call__(self, wrapped: Callable[P, R]) -> Callable[P, R]: ... - def __enter__(self) -> Any: ... - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, - ) -> bool | None: ... - - @overload - def synchronized(wrapped: Callable[P, R]) -> Callable[P, R]: ... - @overload - def synchronized(wrapped: Any) -> SynchronizedObject: ... diff --git a/newrelic/packages/wrapt/__wrapt__.py b/newrelic/packages/wrapt/__wrapt__.py index ac8f32bb92..9933b2c972 100644 --- a/newrelic/packages/wrapt/__wrapt__.py +++ b/newrelic/packages/wrapt/__wrapt__.py @@ -1,44 +1,14 @@ -"""This module is used to switch between C and Python implementations of the -wrappers. -""" - import os -from .wrappers import BoundFunctionWrapper, CallableObjectProxy, FunctionWrapper -from .wrappers import ObjectProxy as BaseObjectProxy -from .wrappers import PartialCallableObjectProxy, _FunctionWrapperBase - -# Try to use C extensions if not disabled. - -_using_c_extension = False - -_use_extensions = not os.environ.get("WRAPT_DISABLE_EXTENSIONS") - -if _use_extensions: - try: - from ._wrappers import ( # type: ignore[no-redef,import-not-found] - BoundFunctionWrapper, - CallableObjectProxy, - FunctionWrapper, - ) - from ._wrappers import ( - ObjectProxy as BaseObjectProxy, # type: ignore[no-redef,import-not-found] - ) - from ._wrappers import ( # type: ignore[no-redef,import-not-found] - PartialCallableObjectProxy, - _FunctionWrapperBase, - ) - - _using_c_extension = True - except ImportError: - # C extensions not available, using Python implementations - pass +from .wrappers import (ObjectProxy, CallableObjectProxy, + PartialCallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, _FunctionWrapperBase) +try: + if not os.environ.get('WRAPT_DISABLE_EXTENSIONS'): + from ._wrappers import (ObjectProxy, CallableObjectProxy, + PartialCallableObjectProxy, FunctionWrapper, + BoundFunctionWrapper, _FunctionWrapperBase) -def partial(*args, **kwargs): - """Create a callable object proxy with partial application of the given - arguments and keywords. This behaves the same as `functools.partial`, but - implemented using the `ObjectProxy` class to provide better support for - introspection. - """ - return PartialCallableObjectProxy(*args, **kwargs) +except ImportError: + pass diff --git a/newrelic/packages/wrapt/_wrappers.c b/newrelic/packages/wrapt/_wrappers.c index 8cd2f6c28f..e0e1b5bc65 100644 --- a/newrelic/packages/wrapt/_wrappers.c +++ b/newrelic/packages/wrapt/_wrappers.c @@ -10,38 +10,34 @@ /* ------------------------------------------------------------------------- */ -typedef struct -{ - PyObject_HEAD +typedef struct { + PyObject_HEAD - PyObject *dict; - PyObject *wrapped; - PyObject *weakreflist; + PyObject *dict; + PyObject *wrapped; + PyObject *weakreflist; } WraptObjectProxyObject; PyTypeObject WraptObjectProxy_Type; PyTypeObject WraptCallableObjectProxy_Type; -typedef struct -{ - WraptObjectProxyObject object_proxy; +typedef struct { + WraptObjectProxyObject object_proxy; - PyObject *args; - PyObject *kwargs; + PyObject *args; + PyObject *kwargs; } WraptPartialCallableObjectProxyObject; PyTypeObject WraptPartialCallableObjectProxy_Type; -typedef struct -{ - WraptObjectProxyObject object_proxy; - - PyObject *instance; - PyObject *wrapper; - PyObject *enabled; - PyObject *binding; - PyObject *parent; - PyObject *owner; +typedef struct { + WraptObjectProxyObject object_proxy; + + PyObject *instance; + PyObject *wrapper; + PyObject *enabled; + PyObject *binding; + PyObject *parent; } WraptFunctionWrapperObject; PyTypeObject WraptFunctionWrapperBase_Type; @@ -50,4048 +46,3195 @@ PyTypeObject WraptFunctionWrapper_Type; /* ------------------------------------------------------------------------- */ -static int raise_uninitialized_wrapper_error(WraptObjectProxyObject *object) +static PyObject *WraptObjectProxy_new(PyTypeObject *type, + PyObject *args, PyObject *kwds) { - // Before raising an exception we need to first check whether this is a lazy - // proxy object and attempt to intialize the wrapped object using the supplied - // callback if so. If the callback is not present then we can proceed to raise - // the exception, but if the callback is present and returns a value, we set - // that as the wrapped object and continue and return without raising an error. - - static PyObject *wrapped_str = NULL; - static PyObject *wrapped_factory_str = NULL; - static PyObject *wrapped_get_str = NULL; - - PyObject *callback = NULL; - PyObject *value = NULL; - - if (!wrapped_str) - { - wrapped_str = PyUnicode_InternFromString("__wrapped__"); - wrapped_factory_str = PyUnicode_InternFromString("__wrapped_factory__"); - wrapped_get_str = PyUnicode_InternFromString("__wrapped_get__"); - } - - // Note that we use existance of __wrapped_factory__ to gate whether we can - // attempt to initialize the wrapped object lazily, but it is __wrapped_get__ - // that we actually call to do the initialization. This is so that we can - // handle multithreading correctly by having __wrapped_get__ use a lock to - // protect against multiple threads trying to initialize the wrapped object - // at the same time. - - callback = PyObject_GenericGetAttr((PyObject *)object, wrapped_factory_str); - - if (callback) - { - if (callback != Py_None) - { - Py_DECREF(callback); - - callback = PyObject_GenericGetAttr((PyObject *)object, wrapped_get_str); - - if (!callback) - return -1; - - value = PyObject_CallObject(callback, NULL); - - Py_DECREF(callback); - - if (value) - { - // We use setattr so that special dunder methods will be properly set. - - if (PyObject_SetAttr((PyObject *)object, wrapped_str, value) == -1) - { - Py_DECREF(value); - return -1; - } - - Py_DECREF(value); - - return 0; - } - else - { - return -1; - } - } - else - { - Py_DECREF(callback); - } - } - else - PyErr_Clear(); - - // We need to reach into the wrapt.wrappers module to get the exception - // class because the exception we need to raise needs to inherit from both - // ValueError and AttributeError and we can't do that in C code using the - // built in exception classes, or at least not easily or safely. - - PyObject *wrapt_wrappers_module = NULL; - PyObject *wrapper_not_initialized_error = NULL; - - // Import the wrapt.wrappers module and get the exception class. - // We do this fresh each time to be safe with multiple sub-interpreters. - - wrapt_wrappers_module = PyImport_ImportModule("wrapt.wrappers"); - - if (!wrapt_wrappers_module) - { - // Fallback to ValueError if import fails. - - PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); - - return -1; - } + WraptObjectProxyObject *self; - wrapper_not_initialized_error = PyObject_GetAttrString( - wrapt_wrappers_module, "WrapperNotInitializedError"); + self = (WraptObjectProxyObject *)type->tp_alloc(type, 0); - if (!wrapper_not_initialized_error) - { - // Fallback to ValueError if attribute lookup fails. - - Py_DECREF(wrapt_wrappers_module); - - PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); - - return -1; - } - - // Raise the custom exception. - - PyErr_SetString(wrapper_not_initialized_error, - "wrapper has not been initialized"); - - // Clean up references. + if (!self) + return NULL; - Py_DECREF(wrapper_not_initialized_error); - Py_DECREF(wrapt_wrappers_module); + self->dict = PyDict_New(); + self->wrapped = NULL; + self->weakreflist = NULL; - return -1; + return (PyObject *)self; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_new(PyTypeObject *type, PyObject *args, - PyObject *kwds) +static int WraptObjectProxy_raw_init(WraptObjectProxyObject *self, + PyObject *wrapped) { - WraptObjectProxyObject *self; + static PyObject *module_str = NULL; + static PyObject *doc_str = NULL; - self = (WraptObjectProxyObject *)type->tp_alloc(type, 0); + PyObject *object = NULL; - if (!self) - return NULL; + Py_INCREF(wrapped); + Py_XDECREF(self->wrapped); + self->wrapped = wrapped; - self->dict = PyDict_New(); - self->wrapped = NULL; - self->weakreflist = NULL; + if (!module_str) { +#if PY_MAJOR_VERSION >= 3 + module_str = PyUnicode_InternFromString("__module__"); +#else + module_str = PyString_InternFromString("__module__"); +#endif + } - return (PyObject *)self; -} + if (!doc_str) { +#if PY_MAJOR_VERSION >= 3 + doc_str = PyUnicode_InternFromString("__doc__"); +#else + doc_str = PyString_InternFromString("__doc__"); +#endif + } -/* ------------------------------------------------------------------------- */ + object = PyObject_GetAttr(wrapped, module_str); -static int WraptObjectProxy_raw_init(WraptObjectProxyObject *self, - PyObject *wrapped) -{ - static PyObject *module_str = NULL; - static PyObject *doc_str = NULL; - static PyObject *wrapped_factory_str = NULL; - - PyObject *object = NULL; - - // If wrapped is Py_None and we have a __wrapped_factory__ attribute - // then we defer initialization of the wrapped object until it is first needed. - - if (!wrapped_factory_str) - { - wrapped_factory_str = PyUnicode_InternFromString("__wrapped_factory__"); - } - - if (wrapped == Py_None) - { - PyObject *callback = NULL; - - callback = PyObject_GenericGetAttr((PyObject *)self, wrapped_factory_str); - - if (callback) - { - if (callback != Py_None) - { - Py_DECREF(callback); - return 0; - } - else - { - Py_DECREF(callback); - } + if (object) { + if (PyDict_SetItem(self->dict, module_str, object) == -1) { + Py_DECREF(object); + return -1; + } + Py_DECREF(object); } else - PyErr_Clear(); - } - - Py_INCREF(wrapped); - Py_XDECREF(self->wrapped); - self->wrapped = wrapped; - - if (!module_str) - { - module_str = PyUnicode_InternFromString("__module__"); - } - - if (!doc_str) - { - doc_str = PyUnicode_InternFromString("__doc__"); - } - - object = PyObject_GetAttr(wrapped, module_str); - - if (object) - { - if (PyDict_SetItem(self->dict, module_str, object) == -1) - { - Py_DECREF(object); - return -1; - } - Py_DECREF(object); - } - else - PyErr_Clear(); + PyErr_Clear(); - object = PyObject_GetAttr(wrapped, doc_str); + object = PyObject_GetAttr(wrapped, doc_str); - if (object) - { - if (PyDict_SetItem(self->dict, doc_str, object) == -1) - { - Py_DECREF(object); - return -1; + if (object) { + if (PyDict_SetItem(self->dict, doc_str, object) == -1) { + Py_DECREF(object); + return -1; + } + Py_DECREF(object); } - Py_DECREF(object); - } - else - PyErr_Clear(); + else + PyErr_Clear(); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ -static int WraptObjectProxy_init(WraptObjectProxyObject *self, PyObject *args, - PyObject *kwds) +static int WraptObjectProxy_init(WraptObjectProxyObject *self, + PyObject *args, PyObject *kwds) { - PyObject *wrapped = NULL; + PyObject *wrapped = NULL; - char *const kwlist[] = {"wrapped", NULL}; + static char *kwlist[] = { "wrapped", NULL }; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:ObjectProxy", kwlist, - &wrapped)) - { - return -1; - } + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:ObjectProxy", + kwlist, &wrapped)) { + return -1; + } - return WraptObjectProxy_raw_init(self, wrapped); + return WraptObjectProxy_raw_init(self, wrapped); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_traverse(WraptObjectProxyObject *self, - visitproc visit, void *arg) + visitproc visit, void *arg) { - Py_VISIT(self->dict); - Py_VISIT(self->wrapped); + Py_VISIT(self->dict); + Py_VISIT(self->wrapped); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_clear(WraptObjectProxyObject *self) { - Py_CLEAR(self->dict); - Py_CLEAR(self->wrapped); + Py_CLEAR(self->dict); + Py_CLEAR(self->wrapped); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static void WraptObjectProxy_dealloc(WraptObjectProxyObject *self) { - PyObject_GC_UnTrack(self); + PyObject_GC_UnTrack(self); - if (self->weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *)self); + if (self->weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *)self); - WraptObjectProxy_clear(self); + WraptObjectProxy_clear(self); - Py_TYPE(self)->tp_free(self); + Py_TYPE(self)->tp_free(self); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_repr(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyUnicode_FromFormat("<%s at %p for %s at %p>", Py_TYPE(self)->tp_name, - self, Py_TYPE(self->wrapped)->tp_name, - self->wrapped); +#if PY_MAJOR_VERSION >= 3 + return PyUnicode_FromFormat("<%s at %p for %s at %p>", + Py_TYPE(self)->tp_name, self, + Py_TYPE(self->wrapped)->tp_name, self->wrapped); +#else + return PyString_FromFormat("<%s at %p for %s at %p>", + Py_TYPE(self)->tp_name, self, + Py_TYPE(self->wrapped)->tp_name, self->wrapped); +#endif } /* ------------------------------------------------------------------------- */ +#if PY_MAJOR_VERSION < 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 3) +typedef long Py_hash_t; +#endif + static Py_hash_t WraptObjectProxy_hash(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_Hash(self->wrapped); + return PyObject_Hash(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_str(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_Str(self->wrapped); + return PyObject_Str(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_add(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Add(o1, o2); + return PyNumber_Add(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_subtract(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - return PyNumber_Subtract(o1, o2); + return PyNumber_Subtract(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_multiply(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } + + return PyNumber_Multiply(o1, o2); +} + +/* ------------------------------------------------------------------------- */ + +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_divide(PyObject *o1, PyObject *o2) +{ + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o2 = ((WraptObjectProxyObject *)o2)->wrapped; + } - return PyNumber_Multiply(o1, o2); + return PyNumber_Divide(o1, o2); } +#endif /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_remainder(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Remainder(o1, o2); + return PyNumber_Remainder(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_divmod(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Divmod(o1, o2); + return PyNumber_Divmod(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_power(PyObject *o1, PyObject *o2, - PyObject *modulo) + PyObject *modulo) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Power(o1, o2, modulo); + return PyNumber_Power(o1, o2, modulo); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_negative(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Negative(self->wrapped); + return PyNumber_Negative(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_positive(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Positive(self->wrapped); + return PyNumber_Positive(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_absolute(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Absolute(self->wrapped); + return PyNumber_Absolute(self->wrapped); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_bool(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_IsTrue(self->wrapped); + return PyObject_IsTrue(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_invert(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Invert(self->wrapped); + return PyNumber_Invert(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_lshift(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Lshift(o1, o2); + return PyNumber_Lshift(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_rshift(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Rshift(o1, o2); + return PyNumber_Rshift(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_and(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_And(o1, o2); + return PyNumber_And(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_xor(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Xor(o1, o2); + return PyNumber_Xor(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_or(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_Or(o1, o2); + return PyNumber_Or(o1, o2); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_long(WraptObjectProxyObject *self) +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_int(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Long(self->wrapped); + return PyNumber_Int(self->wrapped); } +#endif /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_float(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_long(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyNumber_Float(self->wrapped); + return PyNumber_Long(self->wrapped); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_inplace_add(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_float(WraptObjectProxyObject *self) { - PyObject *object = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; - - if (PyObject_HasAttrString(self->wrapped, "__iadd__")) - { - object = PyNumber_InPlaceAdd(self->wrapped, other); + } - if (!object) - return NULL; + return PyNumber_Float(self->wrapped); +} - Py_DECREF(self->wrapped); - self->wrapped = object; +/* ------------------------------------------------------------------------- */ - Py_INCREF(self); - return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Add(self->wrapped, other); +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_oct(WraptObjectProxyObject *self) +{ + PyNumberMethods *nb; - if (!result) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; + } - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; + if ((nb = self->wrapped->ob_type->tp_as_number) == NULL || + nb->nb_oct == NULL) { + PyErr_SetString(PyExc_TypeError, + "oct() argument can't be converted to oct"); + return NULL; } - PyObject *proxy_args = PyTuple_Pack(1, result); + return (*nb->nb_oct)(self->wrapped); +} +#endif + +/* ------------------------------------------------------------------------- */ - Py_DECREF(result); +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_hex(WraptObjectProxyObject *self) +{ + PyNumberMethods *nb; - if (!proxy_args) - { - Py_DECREF(proxy_type); + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; } - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); + if ((nb = self->wrapped->ob_type->tp_as_number) == NULL || + nb->nb_hex == NULL) { + PyErr_SetString(PyExc_TypeError, + "hex() argument can't be converted to hex"); + return NULL; + } - return proxy_instance; - } + return (*nb->nb_hex)(self->wrapped); } +#endif /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_inplace_subtract(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_inplace_add(WraptObjectProxyObject *self, + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__isub__")) - { - object = PyNumber_InPlaceSubtract(self->wrapped, other); + object = PyNumber_InPlaceAdd(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Subtract(self->wrapped, other); +} - if (!result) - return NULL; +/* ------------------------------------------------------------------------- */ - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); +static PyObject *WraptObjectProxy_inplace_subtract( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; - if (!proxy_type) - { - Py_DECREF(proxy_type); + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; } - PyObject *proxy_args = PyTuple_Pack(1, result); + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } + object = PyNumber_InPlaceSubtract(self->wrapped, other); - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); + if (!object) + return NULL; - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); + Py_DECREF(self->wrapped); + self->wrapped = object; - return proxy_instance; - } + Py_INCREF(self); + return (PyObject *)self; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_inplace_multiply(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_inplace_multiply( + WraptObjectProxyObject *self, PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__imul__")) - { object = PyNumber_InPlaceMultiply(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Multiply(self->wrapped, other); +} - if (!result) - return NULL; +/* ------------------------------------------------------------------------- */ - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); +#if PY_MAJOR_VERSION < 3 +static PyObject *WraptObjectProxy_inplace_divide( + WraptObjectProxyObject *self, PyObject *other) +{ + PyObject *object = NULL; - if (!proxy_type) - { - Py_DECREF(proxy_type); + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; } - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } + object = PyNumber_InPlaceDivide(self->wrapped, other); - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); + if (!object) + return NULL; - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); + Py_DECREF(self->wrapped); + self->wrapped = object; - return proxy_instance; - } + Py_INCREF(self); + return (PyObject *)self; } +#endif /* ------------------------------------------------------------------------- */ -static PyObject * -WraptObjectProxy_inplace_remainder(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_inplace_remainder( + WraptObjectProxyObject *self, PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__imod__")) - { object = PyNumber_InPlaceRemainder(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Remainder(self->wrapped, other); +} - if (!result) - return NULL; +/* ------------------------------------------------------------------------- */ - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); +static PyObject *WraptObjectProxy_inplace_power(WraptObjectProxyObject *self, + PyObject *other, PyObject *modulo) +{ + PyObject *object = NULL; - if (!proxy_type) - { - Py_DECREF(proxy_type); + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; } - PyObject *proxy_args = PyTuple_Pack(1, result); + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } -} - -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_inplace_power(WraptObjectProxyObject *self, - PyObject *other, - PyObject *modulo) -{ - PyObject *object = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) - return NULL; - } - - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; - - if (PyObject_HasAttrString(self->wrapped, "__ipow__")) - { - object = PyNumber_InPlacePower(self->wrapped, other, modulo); + object = PyNumber_InPlacePower(self->wrapped, other, modulo); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Power(self->wrapped, other, modulo); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_inplace_lshift(WraptObjectProxyObject *self, - PyObject *other) + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__ilshift__")) - { object = PyNumber_InPlaceLshift(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Lshift(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_inplace_rshift(WraptObjectProxyObject *self, - PyObject *other) + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__irshift__")) - { object = PyNumber_InPlaceRshift(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Rshift(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_inplace_and(WraptObjectProxyObject *self, - PyObject *other) + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__iand__")) - { object = PyNumber_InPlaceAnd(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_And(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_inplace_xor(WraptObjectProxyObject *self, - PyObject *other) + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__ixor__")) - { object = PyNumber_InPlaceXor(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Xor(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_inplace_or(WraptObjectProxyObject *self, - PyObject *other) + PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__ior__")) - { object = PyNumber_InPlaceOr(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_Or(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_floor_divide(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_FloorDivide(o1, o2); + return PyNumber_FloorDivide(o1, o2); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_true_divide(PyObject *o1, PyObject *o2) { - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; + if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o1)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } + + o1 = ((WraptObjectProxyObject *)o1)->wrapped; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } + if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) { + if (!((WraptObjectProxyObject *)o2)->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; + o2 = ((WraptObjectProxyObject *)o2)->wrapped; } - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_TrueDivide(o1, o2); + return PyNumber_TrueDivide(o1, o2); } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptObjectProxy_inplace_floor_divide(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_inplace_floor_divide( + WraptObjectProxyObject *self, PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__ifloordiv__")) - { object = PyNumber_InPlaceFloorDivide(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_FloorDivide(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptObjectProxy_inplace_true_divide(WraptObjectProxyObject *self, - PyObject *other) +static PyObject *WraptObjectProxy_inplace_true_divide( + WraptObjectProxyObject *self, PyObject *other) { - PyObject *object = NULL; + PyObject *object = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; + if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) + other = ((WraptObjectProxyObject *)other)->wrapped; - if (PyObject_HasAttrString(self->wrapped, "__itruediv__")) - { object = PyNumber_InPlaceTrueDivide(self->wrapped, other); if (!object) - return NULL; + return NULL; Py_DECREF(self->wrapped); self->wrapped = object; Py_INCREF(self); return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_TrueDivide(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_index(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - return PyNumber_Index(self->wrapped); -} - -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_matrix_multiply(PyObject *o1, PyObject *o2) -{ - if (PyObject_IsInstance(o1, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o1)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o1) == -1) - return NULL; } - o1 = ((WraptObjectProxyObject *)o1)->wrapped; - } - - if (PyObject_IsInstance(o2, (PyObject *)&WraptObjectProxy_Type)) - { - if (!((WraptObjectProxyObject *)o2)->wrapped) - { - if (raise_uninitialized_wrapper_error((WraptObjectProxyObject *)o2) == -1) - return NULL; - } - - o2 = ((WraptObjectProxyObject *)o2)->wrapped; - } - - return PyNumber_MatrixMultiply(o1, o2); -} - -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_inplace_matrix_multiply( - WraptObjectProxyObject *self, PyObject *other) -{ - PyObject *object = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) - return NULL; - } - - if (PyObject_IsInstance(other, (PyObject *)&WraptObjectProxy_Type)) - other = ((WraptObjectProxyObject *)other)->wrapped; - - if (PyObject_HasAttrString(self->wrapped, "__imatmul__")) - { - object = PyNumber_InPlaceMatrixMultiply(self->wrapped, other); - - if (!object) - return NULL; - - Py_DECREF(self->wrapped); - self->wrapped = object; - - Py_INCREF(self); - return (PyObject *)self; - } - else - { - PyObject *result = PyNumber_MatrixMultiply(self->wrapped, other); - - if (!result) - return NULL; - - PyObject *proxy_type = PyObject_GetAttrString((PyObject *)self, "__object_proxy__"); - - if (!proxy_type) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_args = PyTuple_Pack(1, result); - - Py_DECREF(result); - - if (!proxy_args) - { - Py_DECREF(proxy_type); - return NULL; - } - - PyObject *proxy_instance = PyObject_Call(proxy_type, proxy_args, NULL); - - Py_DECREF(proxy_type); - Py_DECREF(proxy_args); - - return proxy_instance; - } + return PyNumber_Index(self->wrapped); } /* ------------------------------------------------------------------------- */ static Py_ssize_t WraptObjectProxy_length(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_Length(self->wrapped); + return PyObject_Length(self->wrapped); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_contains(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PySequence_Contains(self->wrapped, value); + return PySequence_Contains(self->wrapped, value); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_getitem(WraptObjectProxyObject *self, - PyObject *key) + PyObject *key) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetItem(self->wrapped, key); + return PyObject_GetItem(self->wrapped, key); } /* ------------------------------------------------------------------------- */ -static int WraptObjectProxy_setitem(WraptObjectProxyObject *self, PyObject *key, - PyObject *value) +static int WraptObjectProxy_setitem(WraptObjectProxyObject *self, + PyObject *key, PyObject* value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - if (value == NULL) - return PyObject_DelItem(self->wrapped, key); - else - return PyObject_SetItem(self->wrapped, key, value); + if (value == NULL) + return PyObject_DelItem(self->wrapped, key); + else + return PyObject_SetItem(self->wrapped, key, value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_self_setattr(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_self_setattr( + WraptObjectProxyObject *self, PyObject *args) { - PyObject *name = NULL; - PyObject *value = NULL; + PyObject *name = NULL; + PyObject *value = NULL; - if (!PyArg_ParseTuple(args, "UO:__self_setattr__", &name, &value)) - return NULL; +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTuple(args, "UO:__self_setattr__", &name, &value)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "SO:__self_setattr__", &name, &value)) + return NULL; +#endif - if (PyObject_GenericSetAttr((PyObject *)self, name, value) != 0) - { - return NULL; - } + if (PyObject_GenericSetAttr((PyObject *)self, name, value) != 0) { + return NULL; + } - Py_INCREF(Py_None); - return Py_None; + Py_INCREF(Py_None); + return Py_None; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_dir(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_dir( + WraptObjectProxyObject *self, PyObject *args) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_Dir(self->wrapped); + return PyObject_Dir(self->wrapped); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_enter(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_enter( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyObject *method = NULL; - PyObject *result = NULL; + PyObject *method = NULL; + PyObject *result = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - method = PyObject_GetAttrString(self->wrapped, "__enter__"); + method = PyObject_GetAttrString(self->wrapped, "__enter__"); - if (!method) - return NULL; + if (!method) + return NULL; - result = PyObject_Call(method, args, kwds); + result = PyObject_Call(method, args, kwds); - Py_DECREF(method); + Py_DECREF(method); - return result; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_exit(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_exit( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyObject *method = NULL; - PyObject *result = NULL; + PyObject *method = NULL; + PyObject *result = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - method = PyObject_GetAttrString(self->wrapped, "__exit__"); + method = PyObject_GetAttrString(self->wrapped, "__exit__"); - if (!method) - return NULL; + if (!method) + return NULL; - result = PyObject_Call(method, args, kwds); + result = PyObject_Call(method, args, kwds); - Py_DECREF(method); + Py_DECREF(method); - return result; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_aenter(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_copy( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyObject *method = NULL; - PyObject *result = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) - return NULL; - } + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __copy__()"); - method = PyObject_GetAttrString(self->wrapped, "__aenter__"); - - if (!method) return NULL; - - result = PyObject_Call(method, args, kwds); - - Py_DECREF(method); - - return result; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_aexit(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_deepcopy( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyObject *method = NULL; - PyObject *result = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) - return NULL; - } + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __deepcopy__()"); - method = PyObject_GetAttrString(self->wrapped, "__aexit__"); - - if (!method) return NULL; - - result = PyObject_Call(method, args, kwds); - - Py_DECREF(method); - - return result; -} - -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_copy(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) -{ - PyErr_SetString(PyExc_NotImplementedError, - "object proxy must define __copy__()"); - - return NULL; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_deepcopy(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_reduce( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyErr_SetString(PyExc_NotImplementedError, - "object proxy must define __deepcopy__()"); - - return NULL; -} + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __reduce_ex__()"); -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_reduce(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) -{ - PyErr_SetString(PyExc_NotImplementedError, - "object proxy must define __reduce__()"); - - return NULL; -} - -/* ------------------------------------------------------------------------- */ - -static PyObject *WraptObjectProxy_reduce_ex(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) -{ - PyErr_SetString(PyExc_NotImplementedError, - "object proxy must define __reduce_ex__()"); - - return NULL; + return NULL; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_bytes(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_reduce_ex( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) - return NULL; - } + PyErr_SetString(PyExc_NotImplementedError, + "object proxy must define __reduce_ex__()"); - return PyObject_Bytes(self->wrapped); + return NULL; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_format(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_bytes( + WraptObjectProxyObject *self, PyObject *args) { - PyObject *format_spec = NULL; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - if (!PyArg_ParseTuple(args, "|O:format", &format_spec)) - return NULL; + } - return PyObject_Format(self->wrapped, format_spec); + return PyObject_Bytes(self->wrapped); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_reversed(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_reversed( + WraptObjectProxyObject *self, PyObject *args) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_CallFunctionObjArgs((PyObject *)&PyReversed_Type, - self->wrapped, NULL); + return PyObject_CallFunctionObjArgs((PyObject *)&PyReversed_Type, + self->wrapped, NULL); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_round(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +#if PY_MAJOR_VERSION >= 3 +static PyObject *WraptObjectProxy_round( + WraptObjectProxyObject *self, PyObject *args) { - PyObject *ndigits = NULL; + PyObject *module = NULL; + PyObject *dict = NULL; + PyObject *round = NULL; - PyObject *module = NULL; - PyObject *round = NULL; - - PyObject *result = NULL; - - char *const kwlist[] = {"ndigits", NULL}; + PyObject *result = NULL; - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:ObjectProxy", kwlist, - &ndigits)) - { - return NULL; - } + module = PyImport_ImportModule("builtins"); - module = PyImport_ImportModule("builtins"); + if (!module) + return NULL; - if (!module) - return NULL; + dict = PyModule_GetDict(module); + round = PyDict_GetItemString(dict, "round"); - round = PyObject_GetAttrString(module, "round"); + if (!round) { + Py_DECREF(module); + return NULL; + } - if (!round) - { + Py_INCREF(round); Py_DECREF(module); - return NULL; - } - - Py_INCREF(round); - Py_DECREF(module); - result = PyObject_CallFunctionObjArgs(round, self->wrapped, ndigits, NULL); + result = PyObject_CallFunctionObjArgs(round, self->wrapped, NULL); - Py_DECREF(round); + Py_DECREF(round); - return result; + return result; } +#endif /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_complex(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_complex( + WraptObjectProxyObject *self, PyObject *args) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_CallFunctionObjArgs((PyObject *)&PyComplex_Type, - self->wrapped, NULL); + return PyObject_CallFunctionObjArgs((PyObject *)&PyComplex_Type, + self->wrapped, NULL); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_mro_entries(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptObjectProxy_mro_entries( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - PyObject *wrapped = NULL; - PyObject *mro_entries_method = NULL; - PyObject *result = NULL; - int is_type = 0; - - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - wrapped = self->wrapped; - - // Check if wrapped is a type (class). - - is_type = PyType_Check(wrapped); - - // If wrapped is not a type and has __mro_entries__, forward to it. - - if (!is_type) - { - mro_entries_method = PyObject_GetAttrString(wrapped, "__mro_entries__"); - - if (mro_entries_method) - { - // Call wrapped.__mro_entries__(bases). - - result = PyObject_Call(mro_entries_method, args, kwds); - - Py_DECREF(mro_entries_method); - - return result; } - else - { - PyErr_Clear(); - } - } - return Py_BuildValue("(O)", wrapped); + return Py_BuildValue("(O)", self->wrapped); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_name(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_name( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__name__"); + return PyObject_GetAttrString(self->wrapped, "__name__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_name(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_SetAttrString(self->wrapped, "__name__", value); + return PyObject_SetAttrString(self->wrapped, "__name__", value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_qualname(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_qualname( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__qualname__"); + return PyObject_GetAttrString(self->wrapped, "__qualname__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_qualname(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_SetAttrString(self->wrapped, "__qualname__", value); + return PyObject_SetAttrString(self->wrapped, "__qualname__", value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_module(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_module( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__module__"); + return PyObject_GetAttrString(self->wrapped, "__module__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_module(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - if (PyObject_SetAttrString(self->wrapped, "__module__", value) == -1) - return -1; + if (PyObject_SetAttrString(self->wrapped, "__module__", value) == -1) + return -1; - return PyDict_SetItemString(self->dict, "__module__", value); + return PyDict_SetItemString(self->dict, "__module__", value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_doc(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_doc( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__doc__"); + return PyObject_GetAttrString(self->wrapped, "__doc__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_doc(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - if (PyObject_SetAttrString(self->wrapped, "__doc__", value) == -1) - return -1; + if (PyObject_SetAttrString(self->wrapped, "__doc__", value) == -1) + return -1; - return PyDict_SetItemString(self->dict, "__doc__", value); + return PyDict_SetItemString(self->dict, "__doc__", value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_class(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_class( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__class__"); + return PyObject_GetAttrString(self->wrapped, "__class__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_class(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_SetAttrString(self->wrapped, "__class__", value); + return PyObject_SetAttrString(self->wrapped, "__class__", value); } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptObjectProxy_get_annotations(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_annotations( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttrString(self->wrapped, "__annotations__"); + return PyObject_GetAttrString(self->wrapped, "__annotations__"); } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_annotations(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_SetAttrString(self->wrapped, "__annotations__", value); + return PyObject_SetAttrString(self->wrapped, "__annotations__", value); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_get_wrapped(WraptObjectProxyObject *self) +static PyObject *WraptObjectProxy_get_wrapped( + WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - Py_INCREF(self->wrapped); - return self->wrapped; -} - -/* ------------------------------------------------------------------------- */ + } -static PyObject *WraptObjectProxy_get_object_proxy(WraptObjectProxyObject *self) -{ - Py_INCREF(&WraptObjectProxy_Type); - return (PyObject *)&WraptObjectProxy_Type; + Py_INCREF(self->wrapped); + return self->wrapped; } /* ------------------------------------------------------------------------- */ static int WraptObjectProxy_set_wrapped(WraptObjectProxyObject *self, - PyObject *value) + PyObject *value) { - static PyObject *fixups_str = NULL; - - PyObject *fixups = NULL; - - if (!value) - { - PyErr_SetString(PyExc_TypeError, "__wrapped__ must be an object"); - return -1; - } - - Py_INCREF(value); - Py_XDECREF(self->wrapped); - - self->wrapped = value; - - if (!fixups_str) - { - fixups_str = PyUnicode_InternFromString("__wrapped_setattr_fixups__"); - } - - fixups = PyObject_GetAttr((PyObject *)self, fixups_str); - - if (fixups) - { - PyObject *result = NULL; - - result = PyObject_CallObject(fixups, NULL); - Py_DECREF(fixups); + if (!value) { + PyErr_SetString(PyExc_TypeError, "__wrapped__ must be an object"); + return -1; + } - if (!result) - return -1; + Py_INCREF(value); + Py_XDECREF(self->wrapped); - Py_DECREF(result); - } - else - PyErr_Clear(); + self->wrapped = value; - return 0; + return 0; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_getattro(WraptObjectProxyObject *self, - PyObject *name) +static PyObject *WraptObjectProxy_getattro( + WraptObjectProxyObject *self, PyObject *name) { - PyObject *object = NULL; - PyObject *result = NULL; + PyObject *object = NULL; + PyObject *result = NULL; - static PyObject *getattr_str = NULL; + static PyObject *getattr_str = NULL; - object = PyObject_GenericGetAttr((PyObject *)self, name); + object = PyObject_GenericGetAttr((PyObject *)self, name); - if (object) - return object; + if (object) + return object; - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) - return NULL; + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return NULL; - PyErr_Clear(); + PyErr_Clear(); - if (!getattr_str) - { - getattr_str = PyUnicode_InternFromString("__getattr__"); - } + if (!getattr_str) { +#if PY_MAJOR_VERSION >= 3 + getattr_str = PyUnicode_InternFromString("__getattr__"); +#else + getattr_str = PyString_InternFromString("__getattr__"); +#endif + } - object = PyObject_GenericGetAttr((PyObject *)self, getattr_str); + object = PyObject_GenericGetAttr((PyObject *)self, getattr_str); - if (!object) - return NULL; + if (!object) + return NULL; - result = PyObject_CallFunctionObjArgs(object, name, NULL); + result = PyObject_CallFunctionObjArgs(object, name, NULL); - Py_DECREF(object); + Py_DECREF(object); - return result; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject *WraptObjectProxy_getattr(WraptObjectProxyObject *self, - PyObject *args) +static PyObject *WraptObjectProxy_getattr( + WraptObjectProxyObject *self, PyObject *args) { - PyObject *name = NULL; + PyObject *name = NULL; - if (!PyArg_ParseTuple(args, "U:__getattr__", &name)) - return NULL; +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTuple(args, "U:__getattr__", &name)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "S:__getattr__", &name)) + return NULL; +#endif - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetAttr(self->wrapped, name); + return PyObject_GetAttr(self->wrapped, name); } /* ------------------------------------------------------------------------- */ -static int WraptObjectProxy_setattro(WraptObjectProxyObject *self, - PyObject *name, PyObject *value) +static int WraptObjectProxy_setattro( + WraptObjectProxyObject *self, PyObject *name, PyObject *value) { - static PyObject *self_str = NULL; - static PyObject *startswith_str = NULL; + static PyObject *self_str = NULL; + static PyObject *wrapped_str = NULL; + static PyObject *startswith_str = NULL; - PyObject *match = NULL; + PyObject *match = NULL; + + if (!startswith_str) { +#if PY_MAJOR_VERSION >= 3 + startswith_str = PyUnicode_InternFromString("startswith"); +#else + startswith_str = PyString_InternFromString("startswith"); +#endif + } - if (!startswith_str) - { - startswith_str = PyUnicode_InternFromString("startswith"); - } + if (!self_str) { +#if PY_MAJOR_VERSION >= 3 + self_str = PyUnicode_InternFromString("_self_"); +#else + self_str = PyString_InternFromString("_self_"); +#endif + } - if (!self_str) - { - self_str = PyUnicode_InternFromString("_self_"); - } + match = PyObject_CallMethodObjArgs(name, startswith_str, self_str, NULL); - match = PyObject_CallMethodObjArgs(name, startswith_str, self_str, NULL); + if (match == Py_True) { + Py_DECREF(match); - if (match == Py_True) - { - Py_DECREF(match); + return PyObject_GenericSetAttr((PyObject *)self, name, value); + } + else if (!match) + PyErr_Clear(); - return PyObject_GenericSetAttr((PyObject *)self, name, value); - } - else if (!match) - PyErr_Clear(); + Py_XDECREF(match); - Py_XDECREF(match); + if (!wrapped_str) { +#if PY_MAJOR_VERSION >= 3 + wrapped_str = PyUnicode_InternFromString("__wrapped__"); +#else + wrapped_str = PyString_InternFromString("__wrapped__"); +#endif + } - if (PyObject_HasAttr((PyObject *)Py_TYPE(self), name)) - return PyObject_GenericSetAttr((PyObject *)self, name, value); + if (PyObject_HasAttr((PyObject *)Py_TYPE(self), name)) + return PyObject_GenericSetAttr((PyObject *)self, name, value); - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return -1; - } + } - return PyObject_SetAttr(self->wrapped, name, value); + return PyObject_SetAttr(self->wrapped, name, value); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_richcompare(WraptObjectProxyObject *self, - PyObject *other, int opcode) + PyObject *other, int opcode) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_RichCompare(self->wrapped, other, opcode); + return PyObject_RichCompare(self->wrapped, other, opcode); } /* ------------------------------------------------------------------------- */ static PyObject *WraptObjectProxy_iter(WraptObjectProxyObject *self) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_GetIter(self->wrapped); + return PyObject_GetIter(self->wrapped); } /* ------------------------------------------------------------------------- */ static PyNumberMethods WraptObjectProxy_as_number = { - (binaryfunc)WraptObjectProxy_add, /*nb_add*/ - (binaryfunc)WraptObjectProxy_subtract, /*nb_subtract*/ - (binaryfunc)WraptObjectProxy_multiply, /*nb_multiply*/ - (binaryfunc)WraptObjectProxy_remainder, /*nb_remainder*/ - (binaryfunc)WraptObjectProxy_divmod, /*nb_divmod*/ - (ternaryfunc)WraptObjectProxy_power, /*nb_power*/ - (unaryfunc)WraptObjectProxy_negative, /*nb_negative*/ - (unaryfunc)WraptObjectProxy_positive, /*nb_positive*/ - (unaryfunc)WraptObjectProxy_absolute, /*nb_absolute*/ - (inquiry)WraptObjectProxy_bool, /*nb_nonzero/nb_bool*/ - (unaryfunc)WraptObjectProxy_invert, /*nb_invert*/ - (binaryfunc)WraptObjectProxy_lshift, /*nb_lshift*/ - (binaryfunc)WraptObjectProxy_rshift, /*nb_rshift*/ - (binaryfunc)WraptObjectProxy_and, /*nb_and*/ - (binaryfunc)WraptObjectProxy_xor, /*nb_xor*/ - (binaryfunc)WraptObjectProxy_or, /*nb_or*/ - (unaryfunc)WraptObjectProxy_long, /*nb_int*/ - 0, /*nb_long/nb_reserved*/ - (unaryfunc)WraptObjectProxy_float, /*nb_float*/ - (binaryfunc)WraptObjectProxy_inplace_add, /*nb_inplace_add*/ - (binaryfunc)WraptObjectProxy_inplace_subtract, /*nb_inplace_subtract*/ - (binaryfunc)WraptObjectProxy_inplace_multiply, /*nb_inplace_multiply*/ + (binaryfunc)WraptObjectProxy_add, /*nb_add*/ + (binaryfunc)WraptObjectProxy_subtract, /*nb_subtract*/ + (binaryfunc)WraptObjectProxy_multiply, /*nb_multiply*/ +#if PY_MAJOR_VERSION < 3 + (binaryfunc)WraptObjectProxy_divide, /*nb_divide*/ +#endif + (binaryfunc)WraptObjectProxy_remainder, /*nb_remainder*/ + (binaryfunc)WraptObjectProxy_divmod, /*nb_divmod*/ + (ternaryfunc)WraptObjectProxy_power, /*nb_power*/ + (unaryfunc)WraptObjectProxy_negative, /*nb_negative*/ + (unaryfunc)WraptObjectProxy_positive, /*nb_positive*/ + (unaryfunc)WraptObjectProxy_absolute, /*nb_absolute*/ + (inquiry)WraptObjectProxy_bool, /*nb_nonzero/nb_bool*/ + (unaryfunc)WraptObjectProxy_invert, /*nb_invert*/ + (binaryfunc)WraptObjectProxy_lshift, /*nb_lshift*/ + (binaryfunc)WraptObjectProxy_rshift, /*nb_rshift*/ + (binaryfunc)WraptObjectProxy_and, /*nb_and*/ + (binaryfunc)WraptObjectProxy_xor, /*nb_xor*/ + (binaryfunc)WraptObjectProxy_or, /*nb_or*/ +#if PY_MAJOR_VERSION < 3 + 0, /*nb_coerce*/ +#endif +#if PY_MAJOR_VERSION < 3 + (unaryfunc)WraptObjectProxy_int, /*nb_int*/ + (unaryfunc)WraptObjectProxy_long, /*nb_long*/ +#else + (unaryfunc)WraptObjectProxy_long, /*nb_int*/ + 0, /*nb_long/nb_reserved*/ +#endif + (unaryfunc)WraptObjectProxy_float, /*nb_float*/ +#if PY_MAJOR_VERSION < 3 + (unaryfunc)WraptObjectProxy_oct, /*nb_oct*/ + (unaryfunc)WraptObjectProxy_hex, /*nb_hex*/ +#endif + (binaryfunc)WraptObjectProxy_inplace_add, /*nb_inplace_add*/ + (binaryfunc)WraptObjectProxy_inplace_subtract, /*nb_inplace_subtract*/ + (binaryfunc)WraptObjectProxy_inplace_multiply, /*nb_inplace_multiply*/ +#if PY_MAJOR_VERSION < 3 + (binaryfunc)WraptObjectProxy_inplace_divide, /*nb_inplace_divide*/ +#endif (binaryfunc)WraptObjectProxy_inplace_remainder, /*nb_inplace_remainder*/ - (ternaryfunc)WraptObjectProxy_inplace_power, /*nb_inplace_power*/ - (binaryfunc)WraptObjectProxy_inplace_lshift, /*nb_inplace_lshift*/ - (binaryfunc)WraptObjectProxy_inplace_rshift, /*nb_inplace_rshift*/ - (binaryfunc)WraptObjectProxy_inplace_and, /*nb_inplace_and*/ - (binaryfunc)WraptObjectProxy_inplace_xor, /*nb_inplace_xor*/ - (binaryfunc)WraptObjectProxy_inplace_or, /*nb_inplace_or*/ - (binaryfunc)WraptObjectProxy_floor_divide, /*nb_floor_divide*/ - (binaryfunc)WraptObjectProxy_true_divide, /*nb_true_divide*/ - (binaryfunc) - WraptObjectProxy_inplace_floor_divide, /*nb_inplace_floor_divide*/ - (binaryfunc)WraptObjectProxy_inplace_true_divide, /*nb_inplace_true_divide*/ - (unaryfunc)WraptObjectProxy_index, /*nb_index*/ - (binaryfunc)WraptObjectProxy_matrix_multiply, /*nb_matrix_multiply*/ - (binaryfunc)WraptObjectProxy_inplace_matrix_multiply, /*nb_inplace_matrix_multiply*/ + (ternaryfunc)WraptObjectProxy_inplace_power, /*nb_inplace_power*/ + (binaryfunc)WraptObjectProxy_inplace_lshift, /*nb_inplace_lshift*/ + (binaryfunc)WraptObjectProxy_inplace_rshift, /*nb_inplace_rshift*/ + (binaryfunc)WraptObjectProxy_inplace_and, /*nb_inplace_and*/ + (binaryfunc)WraptObjectProxy_inplace_xor, /*nb_inplace_xor*/ + (binaryfunc)WraptObjectProxy_inplace_or, /*nb_inplace_or*/ + (binaryfunc)WraptObjectProxy_floor_divide, /*nb_floor_divide*/ + (binaryfunc)WraptObjectProxy_true_divide, /*nb_true_divide*/ + (binaryfunc)WraptObjectProxy_inplace_floor_divide, /*nb_inplace_floor_divide*/ + (binaryfunc)WraptObjectProxy_inplace_true_divide, /*nb_inplace_true_divide*/ + (unaryfunc)WraptObjectProxy_index, /*nb_index*/ }; static PySequenceMethods WraptObjectProxy_as_sequence = { - (lenfunc)WraptObjectProxy_length, /*sq_length*/ - 0, /*sq_concat*/ - 0, /*sq_repeat*/ - 0, /*sq_item*/ - 0, /*sq_slice*/ - 0, /*sq_ass_item*/ - 0, /*sq_ass_slice*/ + (lenfunc)WraptObjectProxy_length, /*sq_length*/ + 0, /*sq_concat*/ + 0, /*sq_repeat*/ + 0, /*sq_item*/ + 0, /*sq_slice*/ + 0, /*sq_ass_item*/ + 0, /*sq_ass_slice*/ (objobjproc)WraptObjectProxy_contains, /* sq_contains */ }; static PyMappingMethods WraptObjectProxy_as_mapping = { - (lenfunc)WraptObjectProxy_length, /*mp_length*/ - (binaryfunc)WraptObjectProxy_getitem, /*mp_subscript*/ + (lenfunc)WraptObjectProxy_length, /*mp_length*/ + (binaryfunc)WraptObjectProxy_getitem, /*mp_subscript*/ (objobjargproc)WraptObjectProxy_setitem, /*mp_ass_subscript*/ }; static PyMethodDef WraptObjectProxy_methods[] = { - {"__self_setattr__", (PyCFunction)WraptObjectProxy_self_setattr, - METH_VARARGS, 0}, - {"__dir__", (PyCFunction)WraptObjectProxy_dir, METH_NOARGS, 0}, - {"__enter__", (PyCFunction)WraptObjectProxy_enter, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__exit__", (PyCFunction)WraptObjectProxy_exit, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__aenter__", (PyCFunction)WraptObjectProxy_aenter, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__aexit__", (PyCFunction)WraptObjectProxy_aexit, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__copy__", (PyCFunction)WraptObjectProxy_copy, METH_NOARGS, 0}, - {"__deepcopy__", (PyCFunction)WraptObjectProxy_deepcopy, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__reduce__", (PyCFunction)WraptObjectProxy_reduce, METH_NOARGS, 0}, - {"__reduce_ex__", (PyCFunction)WraptObjectProxy_reduce_ex, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__getattr__", (PyCFunction)WraptObjectProxy_getattr, METH_VARARGS, 0}, - {"__bytes__", (PyCFunction)WraptObjectProxy_bytes, METH_NOARGS, 0}, - {"__format__", (PyCFunction)WraptObjectProxy_format, METH_VARARGS, 0}, - {"__reversed__", (PyCFunction)WraptObjectProxy_reversed, METH_NOARGS, 0}, - {"__round__", (PyCFunction)WraptObjectProxy_round, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__complex__", (PyCFunction)WraptObjectProxy_complex, METH_NOARGS, 0}, - {"__mro_entries__", (PyCFunction)WraptObjectProxy_mro_entries, - METH_VARARGS | METH_KEYWORDS, 0}, - {NULL, NULL}, + { "__self_setattr__", (PyCFunction)WraptObjectProxy_self_setattr, + METH_VARARGS , 0 }, + { "__dir__", (PyCFunction)WraptObjectProxy_dir, METH_NOARGS, 0 }, + { "__enter__", (PyCFunction)WraptObjectProxy_enter, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__exit__", (PyCFunction)WraptObjectProxy_exit, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__copy__", (PyCFunction)WraptObjectProxy_copy, + METH_NOARGS, 0 }, + { "__deepcopy__", (PyCFunction)WraptObjectProxy_deepcopy, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__reduce__", (PyCFunction)WraptObjectProxy_reduce, + METH_NOARGS, 0 }, + { "__reduce_ex__", (PyCFunction)WraptObjectProxy_reduce_ex, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__getattr__", (PyCFunction)WraptObjectProxy_getattr, + METH_VARARGS , 0 }, + { "__bytes__", (PyCFunction)WraptObjectProxy_bytes, METH_NOARGS, 0 }, + { "__reversed__", (PyCFunction)WraptObjectProxy_reversed, METH_NOARGS, 0 }, +#if PY_MAJOR_VERSION >= 3 + { "__round__", (PyCFunction)WraptObjectProxy_round, METH_NOARGS, 0 }, +#endif + { "__complex__", (PyCFunction)WraptObjectProxy_complex, METH_NOARGS, 0 }, +#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7) + { "__mro_entries__", (PyCFunction)WraptObjectProxy_mro_entries, + METH_VARARGS | METH_KEYWORDS, 0 }, +#endif + { NULL, NULL }, }; static PyGetSetDef WraptObjectProxy_getset[] = { - {"__name__", (getter)WraptObjectProxy_get_name, - (setter)WraptObjectProxy_set_name, 0}, - {"__qualname__", (getter)WraptObjectProxy_get_qualname, - (setter)WraptObjectProxy_set_qualname, 0}, - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {"__class__", (getter)WraptObjectProxy_get_class, - (setter)WraptObjectProxy_set_class, 0}, - {"__annotations__", (getter)WraptObjectProxy_get_annotations, - (setter)WraptObjectProxy_set_annotations, 0}, - {"__wrapped__", (getter)WraptObjectProxy_get_wrapped, - (setter)WraptObjectProxy_set_wrapped, 0}, - {"__object_proxy__", (getter)WraptObjectProxy_get_object_proxy, 0, 0}, - {NULL}, + { "__name__", (getter)WraptObjectProxy_get_name, + (setter)WraptObjectProxy_set_name, 0 }, + { "__qualname__", (getter)WraptObjectProxy_get_qualname, + (setter)WraptObjectProxy_set_qualname, 0 }, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { "__class__", (getter)WraptObjectProxy_get_class, + (setter)WraptObjectProxy_set_class, 0 }, + { "__annotations__", (getter)WraptObjectProxy_get_annotations, + (setter)WraptObjectProxy_set_annotations, 0 }, + { "__wrapped__", (getter)WraptObjectProxy_get_wrapped, + (setter)WraptObjectProxy_set_wrapped, 0 }, + { NULL }, }; PyTypeObject WraptObjectProxy_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "ObjectProxy", /*tp_name*/ - sizeof(WraptObjectProxyObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "ObjectProxy", /*tp_name*/ + sizeof(WraptObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)WraptObjectProxy_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_as_async*/ - (unaryfunc)WraptObjectProxy_repr, /*tp_repr*/ - &WraptObjectProxy_as_number, /*tp_as_number*/ - &WraptObjectProxy_as_sequence, /*tp_as_sequence*/ - &WraptObjectProxy_as_mapping, /*tp_as_mapping*/ - (hashfunc)WraptObjectProxy_hash, /*tp_hash*/ - 0, /*tp_call*/ - (unaryfunc)WraptObjectProxy_str, /*tp_str*/ - (getattrofunc)WraptObjectProxy_getattro, /*tp_getattro*/ - (setattrofunc)WraptObjectProxy_setattro, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - 0, /*tp_doc*/ - (traverseproc)WraptObjectProxy_traverse, /*tp_traverse*/ - (inquiry)WraptObjectProxy_clear, /*tp_clear*/ - (richcmpfunc)WraptObjectProxy_richcompare, /*tp_richcompare*/ - offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /* (getiterfunc)WraptObjectProxy_iter, */ /*tp_iter*/ - 0, /*tp_iternext*/ - WraptObjectProxy_methods, /*tp_methods*/ - 0, /*tp_members*/ - WraptObjectProxy_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - offsetof(WraptObjectProxyObject, dict), /*tp_dictoffset*/ - (initproc)WraptObjectProxy_init, /*tp_init*/ - PyType_GenericAlloc, /*tp_alloc*/ - WraptObjectProxy_new, /*tp_new*/ - PyObject_GC_Del, /*tp_free*/ - 0, /*tp_is_gc*/ + (destructor)WraptObjectProxy_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + (unaryfunc)WraptObjectProxy_repr, /*tp_repr*/ + &WraptObjectProxy_as_number, /*tp_as_number*/ + &WraptObjectProxy_as_sequence, /*tp_as_sequence*/ + &WraptObjectProxy_as_mapping, /*tp_as_mapping*/ + (hashfunc)WraptObjectProxy_hash, /*tp_hash*/ + 0, /*tp_call*/ + (unaryfunc)WraptObjectProxy_str, /*tp_str*/ + (getattrofunc)WraptObjectProxy_getattro, /*tp_getattro*/ + (setattrofunc)WraptObjectProxy_setattro, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptObjectProxy_traverse, /*tp_traverse*/ + (inquiry)WraptObjectProxy_clear, /*tp_clear*/ + (richcmpfunc)WraptObjectProxy_richcompare, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + (getiterfunc)WraptObjectProxy_iter, /*tp_iter*/ + 0, /*tp_iternext*/ + WraptObjectProxy_methods, /*tp_methods*/ + 0, /*tp_members*/ + WraptObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + offsetof(WraptObjectProxyObject, dict), /*tp_dictoffset*/ + (initproc)WraptObjectProxy_init, /*tp_init*/ + PyType_GenericAlloc, /*tp_alloc*/ + WraptObjectProxy_new, /*tp_new*/ + PyObject_GC_Del, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ -static PyObject *WraptCallableObjectProxy_call(WraptObjectProxyObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptCallableObjectProxy_call( + WraptObjectProxyObject *self, PyObject *args, PyObject *kwds) { - if (!self->wrapped) - { - if (raise_uninitialized_wrapper_error(self) == -1) + if (!self->wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - return PyObject_Call(self->wrapped, args, kwds); + return PyObject_Call(self->wrapped, args, kwds); } /* ------------------------------------------------------------------------- */; static PyGetSetDef WraptCallableObjectProxy_getset[] = { - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {NULL}, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, }; PyTypeObject WraptCallableObjectProxy_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "CallableObjectProxy", /*tp_name*/ - sizeof(WraptObjectProxyObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "CallableObjectProxy", /*tp_name*/ + sizeof(WraptObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - (ternaryfunc)WraptCallableObjectProxy_call, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptCallableObjectProxy_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - 0, /*tp_members*/ - WraptCallableObjectProxy_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - (initproc)WraptObjectProxy_init, /*tp_init*/ - 0, /*tp_alloc*/ - 0, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptCallableObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptObjectProxy_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ static PyObject *WraptPartialCallableObjectProxy_new(PyTypeObject *type, - PyObject *args, - PyObject *kwds) + PyObject *args, PyObject *kwds) { - WraptPartialCallableObjectProxyObject *self; + WraptPartialCallableObjectProxyObject *self; - self = (WraptPartialCallableObjectProxyObject *)WraptObjectProxy_new( - type, args, kwds); + self = (WraptPartialCallableObjectProxyObject *)WraptObjectProxy_new(type, + args, kwds); - if (!self) - return NULL; + if (!self) + return NULL; - self->args = NULL; - self->kwargs = NULL; + self->args = NULL; + self->kwargs = NULL; - return (PyObject *)self; + return (PyObject *)self; } /* ------------------------------------------------------------------------- */ static int WraptPartialCallableObjectProxy_raw_init( - WraptPartialCallableObjectProxyObject *self, PyObject *wrapped, - PyObject *args, PyObject *kwargs) + WraptPartialCallableObjectProxyObject *self, + PyObject *wrapped, PyObject *args, PyObject *kwargs) { - int result = 0; + int result = 0; - result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, wrapped); + result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, + wrapped); - if (result == 0) - { - Py_INCREF(args); - Py_XDECREF(self->args); - self->args = args; + if (result == 0) { + Py_INCREF(args); + Py_XDECREF(self->args); + self->args = args; - Py_XINCREF(kwargs); - Py_XDECREF(self->kwargs); - self->kwargs = kwargs; - } + Py_XINCREF(kwargs); + Py_XDECREF(self->kwargs); + self->kwargs = kwargs; + } - return result; + return result; } /* ------------------------------------------------------------------------- */ static int WraptPartialCallableObjectProxy_init( - WraptPartialCallableObjectProxyObject *self, PyObject *args, - PyObject *kwds) + WraptPartialCallableObjectProxyObject *self, PyObject *args, + PyObject *kwds) { - PyObject *wrapped = NULL; - PyObject *fnargs = NULL; + PyObject *wrapped = NULL; + PyObject *fnargs = NULL; - int result = 0; + int result = 0; - if (!PyObject_Length(args)) - { - PyErr_SetString(PyExc_TypeError, "__init__ of partial needs an argument"); - return -1; - } + if (!PyObject_Length(args)) { + PyErr_SetString(PyExc_TypeError, + "__init__ of partial needs an argument"); + return -1; + } - if (PyObject_Length(args) < 1) - { - PyErr_SetString(PyExc_TypeError, - "partial type takes at least one argument"); - return -1; - } + if (PyObject_Length(args) < 1) { + PyErr_SetString(PyExc_TypeError, + "partial type takes at least one argument"); + return -1; + } - wrapped = PyTuple_GetItem(args, 0); + wrapped = PyTuple_GetItem(args, 0); - if (!PyCallable_Check(wrapped)) - { - PyErr_SetString(PyExc_TypeError, "the first argument must be callable"); - return -1; - } + if (!PyCallable_Check(wrapped)) { + PyErr_SetString(PyExc_TypeError, + "the first argument must be callable"); + return -1; + } - fnargs = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); + fnargs = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); - if (!fnargs) - return -1; + if (!fnargs) + return -1; - result = - WraptPartialCallableObjectProxy_raw_init(self, wrapped, fnargs, kwds); + result = WraptPartialCallableObjectProxy_raw_init(self, wrapped, + fnargs, kwds); - Py_DECREF(fnargs); + Py_DECREF(fnargs); - return result; + return result; } /* ------------------------------------------------------------------------- */ static int WraptPartialCallableObjectProxy_traverse( - WraptPartialCallableObjectProxyObject *self, visitproc visit, void *arg) + WraptPartialCallableObjectProxyObject *self, + visitproc visit, void *arg) { - WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); + WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); - Py_VISIT(self->args); - Py_VISIT(self->kwargs); + Py_VISIT(self->args); + Py_VISIT(self->kwargs); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static int WraptPartialCallableObjectProxy_clear( - WraptPartialCallableObjectProxyObject *self) + WraptPartialCallableObjectProxyObject *self) { - WraptObjectProxy_clear((WraptObjectProxyObject *)self); + WraptObjectProxy_clear((WraptObjectProxyObject *)self); - Py_CLEAR(self->args); - Py_CLEAR(self->kwargs); + Py_CLEAR(self->args); + Py_CLEAR(self->kwargs); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static void WraptPartialCallableObjectProxy_dealloc( - WraptPartialCallableObjectProxyObject *self) + WraptPartialCallableObjectProxyObject *self) { - PyObject_GC_UnTrack(self); + PyObject_GC_UnTrack(self); - WraptPartialCallableObjectProxy_clear(self); + WraptPartialCallableObjectProxy_clear(self); - WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); + WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); } /* ------------------------------------------------------------------------- */ static PyObject *WraptPartialCallableObjectProxy_call( - WraptPartialCallableObjectProxyObject *self, PyObject *args, - PyObject *kwds) + WraptPartialCallableObjectProxyObject *self, PyObject *args, + PyObject *kwds) { - PyObject *fnargs = NULL; - PyObject *fnkwargs = NULL; + PyObject *fnargs = NULL; + PyObject *fnkwargs = NULL; - PyObject *result = NULL; + PyObject *result = NULL; - long i; - long offset; + long i; + long offset; - if (!self->object_proxy.wrapped) - { - if (raise_uninitialized_wrapper_error(&self->object_proxy) == -1) + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - fnargs = PyTuple_New(PyTuple_Size(self->args) + PyTuple_Size(args)); + fnargs = PyTuple_New(PyTuple_Size(self->args)+PyTuple_Size(args)); - for (i = 0; i < PyTuple_Size(self->args); i++) - { - PyObject *item; - item = PyTuple_GetItem(self->args, i); - Py_INCREF(item); - PyTuple_SetItem(fnargs, i, item); - } + for (i=0; iargs); i++) { + PyObject *item; + item = PyTuple_GetItem(self->args, i); + Py_INCREF(item); + PyTuple_SetItem(fnargs, i, item); + } - offset = PyTuple_Size(self->args); + offset = PyTuple_Size(self->args); - for (i = 0; i < PyTuple_Size(args); i++) - { - PyObject *item; - item = PyTuple_GetItem(args, i); - Py_INCREF(item); - PyTuple_SetItem(fnargs, offset + i, item); - } + for (i=0; ikwargs && PyDict_Update(fnkwargs, self->kwargs) == -1) - { - Py_DECREF(fnargs); - Py_DECREF(fnkwargs); - return NULL; - } + if (self->kwargs && PyDict_Update(fnkwargs, self->kwargs) == -1) { + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); + return NULL; + } - if (kwds && PyDict_Update(fnkwargs, kwds) == -1) - { - Py_DECREF(fnargs); - Py_DECREF(fnkwargs); - return NULL; - } + if (kwds && PyDict_Update(fnkwargs, kwds) == -1) { + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); + return NULL; + } - result = PyObject_Call(self->object_proxy.wrapped, fnargs, fnkwargs); + result = PyObject_Call(self->object_proxy.wrapped, + fnargs, fnkwargs); - Py_DECREF(fnargs); - Py_DECREF(fnkwargs); + Py_DECREF(fnargs); + Py_DECREF(fnkwargs); - return result; + return result; } /* ------------------------------------------------------------------------- */; static PyGetSetDef WraptPartialCallableObjectProxy_getset[] = { - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {NULL}, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, }; PyTypeObject WraptPartialCallableObjectProxy_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "PartialCallableObjectProxy", /*tp_name*/ - sizeof(WraptPartialCallableObjectProxyObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "PartialCallableObjectProxy", /*tp_name*/ + sizeof(WraptPartialCallableObjectProxyObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)WraptPartialCallableObjectProxy_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - (ternaryfunc)WraptPartialCallableObjectProxy_call, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - 0, /*tp_doc*/ - (traverseproc)WraptPartialCallableObjectProxy_traverse, /*tp_traverse*/ - (inquiry)WraptPartialCallableObjectProxy_clear, /*tp_clear*/ - 0, /*tp_richcompare*/ - offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - 0, /*tp_members*/ - WraptPartialCallableObjectProxy_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - (initproc)WraptPartialCallableObjectProxy_init, /*tp_init*/ - 0, /*tp_alloc*/ - WraptPartialCallableObjectProxy_new, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ + (destructor)WraptPartialCallableObjectProxy_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptPartialCallableObjectProxy_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptPartialCallableObjectProxy_traverse, /*tp_traverse*/ + (inquiry)WraptPartialCallableObjectProxy_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptPartialCallableObjectProxy_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptPartialCallableObjectProxy_init, /*tp_init*/ + 0, /*tp_alloc*/ + WraptPartialCallableObjectProxy_new, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ static PyObject *WraptFunctionWrapperBase_new(PyTypeObject *type, - PyObject *args, PyObject *kwds) + PyObject *args, PyObject *kwds) { - WraptFunctionWrapperObject *self; + WraptFunctionWrapperObject *self; - self = (WraptFunctionWrapperObject *)WraptObjectProxy_new(type, args, kwds); + self = (WraptFunctionWrapperObject *)WraptObjectProxy_new(type, + args, kwds); - if (!self) - return NULL; + if (!self) + return NULL; - self->instance = NULL; - self->wrapper = NULL; - self->enabled = NULL; - self->binding = NULL; - self->parent = NULL; - self->owner = NULL; + self->instance = NULL; + self->wrapper = NULL; + self->enabled = NULL; + self->binding = NULL; + self->parent = NULL; - return (PyObject *)self; + return (PyObject *)self; } /* ------------------------------------------------------------------------- */ -static int WraptFunctionWrapperBase_raw_init( - WraptFunctionWrapperObject *self, PyObject *wrapped, PyObject *instance, - PyObject *wrapper, PyObject *enabled, PyObject *binding, PyObject *parent, - PyObject *owner) +static int WraptFunctionWrapperBase_raw_init(WraptFunctionWrapperObject *self, + PyObject *wrapped, PyObject *instance, PyObject *wrapper, + PyObject *enabled, PyObject *binding, PyObject *parent) { - int result = 0; + int result = 0; - result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, wrapped); + result = WraptObjectProxy_raw_init((WraptObjectProxyObject *)self, + wrapped); - if (result == 0) - { - Py_INCREF(instance); - Py_XDECREF(self->instance); - self->instance = instance; + if (result == 0) { + Py_INCREF(instance); + Py_XDECREF(self->instance); + self->instance = instance; - Py_INCREF(wrapper); - Py_XDECREF(self->wrapper); - self->wrapper = wrapper; + Py_INCREF(wrapper); + Py_XDECREF(self->wrapper); + self->wrapper = wrapper; - Py_INCREF(enabled); - Py_XDECREF(self->enabled); - self->enabled = enabled; + Py_INCREF(enabled); + Py_XDECREF(self->enabled); + self->enabled = enabled; - Py_INCREF(binding); - Py_XDECREF(self->binding); - self->binding = binding; + Py_INCREF(binding); + Py_XDECREF(self->binding); + self->binding = binding; - Py_INCREF(parent); - Py_XDECREF(self->parent); - self->parent = parent; - - Py_INCREF(owner); - Py_XDECREF(self->owner); - self->owner = owner; - } + Py_INCREF(parent); + Py_XDECREF(self->parent); + self->parent = parent; + } - return result; + return result; } /* ------------------------------------------------------------------------- */ static int WraptFunctionWrapperBase_init(WraptFunctionWrapperObject *self, - PyObject *args, PyObject *kwds) + PyObject *args, PyObject *kwds) { - PyObject *wrapped = NULL; - PyObject *instance = NULL; - PyObject *wrapper = NULL; - PyObject *enabled = Py_None; - PyObject *binding = NULL; - PyObject *parent = Py_None; - PyObject *owner = Py_None; - - static PyObject *callable_str = NULL; - - char *const kwlist[] = {"wrapped", "instance", "wrapper", "enabled", - "binding", "parent", "owner", NULL}; - - if (!callable_str) - { - callable_str = PyUnicode_InternFromString("callable"); - } - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOO|OOOO:FunctionWrapperBase", - kwlist, &wrapped, &instance, &wrapper, - &enabled, &binding, &parent, &owner)) - { - return -1; - } - - if (!binding) - binding = callable_str; - - return WraptFunctionWrapperBase_raw_init(self, wrapped, instance, wrapper, - enabled, binding, parent, owner); + PyObject *wrapped = NULL; + PyObject *instance = NULL; + PyObject *wrapper = NULL; + PyObject *enabled = Py_None; + PyObject *binding = NULL; + PyObject *parent = Py_None; + + static PyObject *function_str = NULL; + + static char *kwlist[] = { "wrapped", "instance", "wrapper", + "enabled", "binding", "parent", NULL }; + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + if (!PyArg_ParseTupleAndKeywords(args, kwds, + "OOO|OOO:FunctionWrapperBase", kwlist, &wrapped, &instance, + &wrapper, &enabled, &binding, &parent)) { + return -1; + } + + if (!binding) + binding = function_str; + + return WraptFunctionWrapperBase_raw_init(self, wrapped, instance, wrapper, + enabled, binding, parent); } /* ------------------------------------------------------------------------- */ static int WraptFunctionWrapperBase_traverse(WraptFunctionWrapperObject *self, - visitproc visit, void *arg) + visitproc visit, void *arg) { - WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); + WraptObjectProxy_traverse((WraptObjectProxyObject *)self, visit, arg); - Py_VISIT(self->instance); - Py_VISIT(self->wrapper); - Py_VISIT(self->enabled); - Py_VISIT(self->binding); - Py_VISIT(self->parent); - Py_VISIT(self->owner); + Py_VISIT(self->instance); + Py_VISIT(self->wrapper); + Py_VISIT(self->enabled); + Py_VISIT(self->binding); + Py_VISIT(self->parent); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static int WraptFunctionWrapperBase_clear(WraptFunctionWrapperObject *self) { - WraptObjectProxy_clear((WraptObjectProxyObject *)self); + WraptObjectProxy_clear((WraptObjectProxyObject *)self); - Py_CLEAR(self->instance); - Py_CLEAR(self->wrapper); - Py_CLEAR(self->enabled); - Py_CLEAR(self->binding); - Py_CLEAR(self->parent); - Py_CLEAR(self->owner); + Py_CLEAR(self->instance); + Py_CLEAR(self->wrapper); + Py_CLEAR(self->enabled); + Py_CLEAR(self->binding); + Py_CLEAR(self->parent); - return 0; + return 0; } /* ------------------------------------------------------------------------- */ static void WraptFunctionWrapperBase_dealloc(WraptFunctionWrapperObject *self) { - PyObject_GC_UnTrack(self); + PyObject_GC_UnTrack(self); - WraptFunctionWrapperBase_clear(self); + WraptFunctionWrapperBase_clear(self); - WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); + WraptObjectProxy_dealloc((WraptObjectProxyObject *)self); } /* ------------------------------------------------------------------------- */ -static PyObject *WraptFunctionWrapperBase_call(WraptFunctionWrapperObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptFunctionWrapperBase_call( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) { - PyObject *param_kwds = NULL; + PyObject *param_kwds = NULL; - PyObject *result = NULL; + PyObject *result = NULL; - static PyObject *function_str = NULL; - static PyObject *callable_str = NULL; - static PyObject *classmethod_str = NULL; - static PyObject *instancemethod_str = NULL; + static PyObject *function_str = NULL; + static PyObject *classmethod_str = NULL; - if (!function_str) - { - function_str = PyUnicode_InternFromString("function"); - callable_str = PyUnicode_InternFromString("callable"); - classmethod_str = PyUnicode_InternFromString("classmethod"); - instancemethod_str = PyUnicode_InternFromString("instancemethod"); - } + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); + classmethod_str = PyUnicode_InternFromString("classmethod"); +#else + function_str = PyString_InternFromString("function"); + classmethod_str = PyString_InternFromString("classmethod"); +#endif + } - if (self->enabled != Py_None) - { - if (PyCallable_Check(self->enabled)) - { - PyObject *object = NULL; + if (self->enabled != Py_None) { + if (PyCallable_Check(self->enabled)) { + PyObject *object = NULL; - object = PyObject_CallFunctionObjArgs(self->enabled, NULL); + object = PyObject_CallFunctionObjArgs(self->enabled, NULL); - if (!object) - return NULL; + if (!object) + return NULL; - if (PyObject_Not(object)) - { - Py_DECREF(object); - return PyObject_Call(self->object_proxy.wrapped, args, kwds); - } - - Py_DECREF(object); - } - else if (PyObject_Not(self->enabled)) - { - return PyObject_Call(self->object_proxy.wrapped, args, kwds); - } - } - - if (!kwds) - { - param_kwds = PyDict_New(); - kwds = param_kwds; - } - - if ((self->instance == Py_None) && - (self->binding == function_str || - PyObject_RichCompareBool(self->binding, function_str, Py_EQ) == 1 || - self->binding == instancemethod_str || - PyObject_RichCompareBool(self->binding, instancemethod_str, Py_EQ) == - 1 || - self->binding == callable_str || - PyObject_RichCompareBool(self->binding, callable_str, Py_EQ) == 1 || - self->binding == classmethod_str || - PyObject_RichCompareBool(self->binding, classmethod_str, Py_EQ) == 1)) - { + if (PyObject_Not(object)) { + Py_DECREF(object); + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } - PyObject *instance = NULL; + Py_DECREF(object); + } + else if (PyObject_Not(self->enabled)) { + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + if ((self->instance == Py_None) && (self->binding == function_str || + PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1 || self->binding == classmethod_str || + PyObject_RichCompareBool(self->binding, classmethod_str, + Py_EQ) == 1)) { + + PyObject *instance = NULL; - instance = PyObject_GetAttrString(self->object_proxy.wrapped, "__self__"); + instance = PyObject_GetAttrString(self->object_proxy.wrapped, + "__self__"); - if (instance) - { - result = PyObject_CallFunctionObjArgs(self->wrapper, - self->object_proxy.wrapped, - instance, args, kwds, NULL); + if (instance) { + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, instance, args, kwds, NULL); - Py_XDECREF(param_kwds); + Py_XDECREF(param_kwds); - Py_DECREF(instance); + Py_DECREF(instance); - return result; + return result; + } + else + PyErr_Clear(); } - else - PyErr_Clear(); - } - result = - PyObject_CallFunctionObjArgs(self->wrapper, self->object_proxy.wrapped, - self->instance, args, kwds, NULL); + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, self->instance, args, kwds, NULL); - Py_XDECREF(param_kwds); + Py_XDECREF(param_kwds); - return result; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_descr_get(WraptFunctionWrapperObject *self, - PyObject *obj, PyObject *type) +static PyObject *WraptFunctionWrapperBase_descr_get( + WraptFunctionWrapperObject *self, PyObject *obj, PyObject *type) { - PyObject *bound_type = NULL; - PyObject *descriptor = NULL; - PyObject *result = NULL; - - static PyObject *bound_type_str = NULL; - static PyObject *function_str = NULL; - static PyObject *callable_str = NULL; - static PyObject *builtin_str = NULL; - static PyObject *class_str = NULL; - static PyObject *instancemethod_str = NULL; - - if (!bound_type_str) - { - bound_type_str = PyUnicode_InternFromString("__bound_function_wrapper__"); - } - - if (!function_str) - { - function_str = PyUnicode_InternFromString("function"); - callable_str = PyUnicode_InternFromString("callable"); - builtin_str = PyUnicode_InternFromString("builtin"); - class_str = PyUnicode_InternFromString("class"); - instancemethod_str = PyUnicode_InternFromString("instancemethod"); - } - - if (self->parent == Py_None) - { - if (self->binding == builtin_str || - PyObject_RichCompareBool(self->binding, builtin_str, Py_EQ) == 1) - { - Py_INCREF(self); - return (PyObject *)self; - } - - if (self->binding == class_str || - PyObject_RichCompareBool(self->binding, class_str, Py_EQ) == 1) - { - Py_INCREF(self); - return (PyObject *)self; - } - - if (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get == NULL) - { - Py_INCREF(self); - return (PyObject *)self; - } - - descriptor = (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get)( - self->object_proxy.wrapped, obj, type); - - if (!descriptor) - return NULL; + PyObject *bound_type = NULL; + PyObject *descriptor = NULL; + PyObject *result = NULL; - if (Py_TYPE(self) != &WraptFunctionWrapper_Type) - { - bound_type = PyObject_GenericGetAttr((PyObject *)self, bound_type_str); + static PyObject *bound_type_str = NULL; + static PyObject *function_str = NULL; - if (!bound_type) - PyErr_Clear(); + if (!bound_type_str) { +#if PY_MAJOR_VERSION >= 3 + bound_type_str = PyUnicode_InternFromString( + "__bound_function_wrapper__"); +#else + bound_type_str = PyString_InternFromString( + "__bound_function_wrapper__"); +#endif } - if (obj == NULL) - obj = Py_None; + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } - result = PyObject_CallFunctionObjArgs( - bound_type ? bound_type : (PyObject *)&WraptBoundFunctionWrapper_Type, - descriptor, obj, self->wrapper, self->enabled, self->binding, self, - type, NULL); + if (self->parent == Py_None) { +#if PY_MAJOR_VERSION < 3 + if (PyObject_IsInstance(self->object_proxy.wrapped, + (PyObject *)&PyClass_Type) || PyObject_IsInstance( + self->object_proxy.wrapped, (PyObject *)&PyType_Type)) { + Py_INCREF(self); + return (PyObject *)self; + } +#else + if (PyObject_IsInstance(self->object_proxy.wrapped, + (PyObject *)&PyType_Type)) { + Py_INCREF(self); + return (PyObject *)self; + } +#endif - Py_XDECREF(bound_type); - Py_DECREF(descriptor); + if (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get == NULL) { + PyErr_Format(PyExc_AttributeError, + "'%s' object has no attribute '__get__'", + Py_TYPE(self->object_proxy.wrapped)->tp_name); + return NULL; + } - return result; - } - - if (self->instance == Py_None && - (self->binding == function_str || - PyObject_RichCompareBool(self->binding, function_str, Py_EQ) == 1 || - self->binding == instancemethod_str || - PyObject_RichCompareBool(self->binding, instancemethod_str, Py_EQ) == - 1 || - self->binding == callable_str || - PyObject_RichCompareBool(self->binding, callable_str, Py_EQ) == 1)) - { + descriptor = (Py_TYPE(self->object_proxy.wrapped)->tp_descr_get)( + self->object_proxy.wrapped, obj, type); - PyObject *wrapped = NULL; + if (!descriptor) + return NULL; - static PyObject *wrapped_str = NULL; + if (Py_TYPE(self) != &WraptFunctionWrapper_Type) { + bound_type = PyObject_GenericGetAttr((PyObject *)self, + bound_type_str); - if (!wrapped_str) - { - wrapped_str = PyUnicode_InternFromString("__wrapped__"); - } + if (!bound_type) + PyErr_Clear(); + } - wrapped = PyObject_GetAttr(self->parent, wrapped_str); + if (obj == NULL) + obj = Py_None; - if (!wrapped) - return NULL; + result = PyObject_CallFunctionObjArgs(bound_type ? bound_type : + (PyObject *)&WraptBoundFunctionWrapper_Type, descriptor, + obj, self->wrapper, self->enabled, self->binding, + self, NULL); - if (Py_TYPE(wrapped)->tp_descr_get == NULL) - { - PyErr_Format(PyExc_AttributeError, - "'%s' object has no attribute '__get__'", - Py_TYPE(wrapped)->tp_name); - Py_DECREF(wrapped); - return NULL; + Py_XDECREF(bound_type); + Py_DECREF(descriptor); + + return result; } - descriptor = (Py_TYPE(wrapped)->tp_descr_get)(wrapped, obj, type); + if (self->instance == Py_None && (self->binding == function_str || + PyObject_RichCompareBool(self->binding, function_str, + Py_EQ) == 1)) { - Py_DECREF(wrapped); + PyObject *wrapped = NULL; - if (!descriptor) - return NULL; + static PyObject *wrapped_str = NULL; - if (Py_TYPE(self->parent) != &WraptFunctionWrapper_Type) - { - bound_type = - PyObject_GenericGetAttr((PyObject *)self->parent, bound_type_str); + if (!wrapped_str) { +#if PY_MAJOR_VERSION >= 3 + wrapped_str = PyUnicode_InternFromString("__wrapped__"); +#else + wrapped_str = PyString_InternFromString("__wrapped__"); +#endif + } - if (!bound_type) - PyErr_Clear(); - } + wrapped = PyObject_GetAttr(self->parent, wrapped_str); - if (obj == NULL) - obj = Py_None; + if (!wrapped) + return NULL; + + if (Py_TYPE(wrapped)->tp_descr_get == NULL) { + PyErr_Format(PyExc_AttributeError, + "'%s' object has no attribute '__get__'", + Py_TYPE(wrapped)->tp_name); + Py_DECREF(wrapped); + return NULL; + } - result = PyObject_CallFunctionObjArgs( - bound_type ? bound_type : (PyObject *)&WraptBoundFunctionWrapper_Type, - descriptor, obj, self->wrapper, self->enabled, self->binding, - self->parent, type, NULL); + descriptor = (Py_TYPE(wrapped)->tp_descr_get)(wrapped, obj, type); - Py_XDECREF(bound_type); - Py_DECREF(descriptor); + Py_DECREF(wrapped); - return result; - } + if (!descriptor) + return NULL; + + if (Py_TYPE(self->parent) != &WraptFunctionWrapper_Type) { + bound_type = PyObject_GenericGetAttr((PyObject *)self->parent, + bound_type_str); - Py_INCREF(self); - return (PyObject *)self; + if (!bound_type) + PyErr_Clear(); + } + + if (obj == NULL) + obj = Py_None; + + result = PyObject_CallFunctionObjArgs(bound_type ? bound_type : + (PyObject *)&WraptBoundFunctionWrapper_Type, descriptor, + obj, self->wrapper, self->enabled, self->binding, + self->parent, NULL); + + Py_XDECREF(bound_type); + Py_DECREF(descriptor); + + return result; + } + + Py_INCREF(self); + return (PyObject *)self; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_set_name(WraptFunctionWrapperObject *self, - PyObject *args, PyObject *kwds) +static PyObject *WraptFunctionWrapperBase_set_name( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) { - PyObject *method = NULL; - PyObject *result = NULL; + PyObject *method = NULL; + PyObject *result = NULL; - if (!self->object_proxy.wrapped) - { - if (raise_uninitialized_wrapper_error(&self->object_proxy) == -1) + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } + } - method = PyObject_GetAttrString(self->object_proxy.wrapped, "__set_name__"); + method = PyObject_GetAttrString(self->object_proxy.wrapped, + "__set_name__"); - if (!method) - { - PyErr_Clear(); - Py_INCREF(Py_None); - return Py_None; - } + if (!method) { + PyErr_Clear(); + Py_INCREF(Py_None); + return Py_None; + } - result = PyObject_Call(method, args, kwds); + result = PyObject_Call(method, args, kwds); - Py_DECREF(method); + Py_DECREF(method); - return result; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_instancecheck(WraptFunctionWrapperObject *self, - PyObject *instance) +static PyObject *WraptFunctionWrapperBase_instancecheck( + WraptFunctionWrapperObject *self, PyObject *instance) { - PyObject *result = NULL; + PyObject *result = NULL; - int check = 0; + int check = 0; - if (!self->object_proxy.wrapped) - { - if (raise_uninitialized_wrapper_error(&self->object_proxy) == -1) - return NULL; - } + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); + return NULL; + } - check = PyObject_IsInstance(instance, self->object_proxy.wrapped); + check = PyObject_IsInstance(instance, self->object_proxy.wrapped); - if (check < 0) - { - return NULL; - } + if (check < 0) { + return NULL; + } - result = check ? Py_True : Py_False; + result = check ? Py_True : Py_False; - Py_INCREF(result); - return result; + Py_INCREF(result); + return result; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_subclasscheck(WraptFunctionWrapperObject *self, - PyObject *args) +static PyObject *WraptFunctionWrapperBase_subclasscheck( + WraptFunctionWrapperObject *self, PyObject *args) { - PyObject *subclass = NULL; - PyObject *object = NULL; - PyObject *result = NULL; + PyObject *subclass = NULL; + PyObject *object = NULL; + PyObject *result = NULL; - int check = 0; + int check = 0; - if (!self->object_proxy.wrapped) - { - if (raise_uninitialized_wrapper_error(&self->object_proxy) == -1) + if (!self->object_proxy.wrapped) { + PyErr_SetString(PyExc_ValueError, "wrapper has not been initialized"); return NULL; - } - - if (!PyArg_ParseTuple(args, "O", &subclass)) - return NULL; - - object = PyObject_GetAttrString(subclass, "__wrapped__"); - - if (!object) - PyErr_Clear(); + } - check = PyObject_IsSubclass(object ? object : subclass, - self->object_proxy.wrapped); + if (!PyArg_ParseTuple(args, "O", &subclass)) + return NULL; - Py_XDECREF(object); + object = PyObject_GetAttrString(subclass, "__wrapped__"); - if (check == -1) - return NULL; + if (!object) + PyErr_Clear(); - result = check ? Py_True : Py_False; + check = PyObject_IsSubclass(object ? object: subclass, + self->object_proxy.wrapped); - Py_INCREF(result); + Py_XDECREF(object); - return result; -} + if (check == -1) + return NULL; -/* ------------------------------------------------------------------------- */ + result = check ? Py_True : Py_False; -static PyObject * -WraptFunctionWrapperBase_get_self_instance(WraptFunctionWrapperObject *self, - void *closure) -{ - if (!self->instance) - { - Py_INCREF(Py_None); - return Py_None; - } + Py_INCREF(result); - Py_INCREF(self->instance); - return self->instance; + return result; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_get_self_wrapper(WraptFunctionWrapperObject *self, - void *closure) +static PyObject *WraptFunctionWrapperBase_get_self_instance( + WraptFunctionWrapperObject *self, void *closure) { - if (!self->wrapper) - { - Py_INCREF(Py_None); - return Py_None; - } + if (!self->instance) { + Py_INCREF(Py_None); + return Py_None; + } - Py_INCREF(self->wrapper); - return self->wrapper; + Py_INCREF(self->instance); + return self->instance; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_get_self_enabled(WraptFunctionWrapperObject *self, - void *closure) +static PyObject *WraptFunctionWrapperBase_get_self_wrapper( + WraptFunctionWrapperObject *self, void *closure) { - if (!self->enabled) - { - Py_INCREF(Py_None); - return Py_None; - } + if (!self->wrapper) { + Py_INCREF(Py_None); + return Py_None; + } - Py_INCREF(self->enabled); - return self->enabled; + Py_INCREF(self->wrapper); + return self->wrapper; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_get_self_binding(WraptFunctionWrapperObject *self, - void *closure) +static PyObject *WraptFunctionWrapperBase_get_self_enabled( + WraptFunctionWrapperObject *self, void *closure) { - if (!self->binding) - { - Py_INCREF(Py_None); - return Py_None; - } + if (!self->enabled) { + Py_INCREF(Py_None); + return Py_None; + } - Py_INCREF(self->binding); - return self->binding; + Py_INCREF(self->enabled); + return self->enabled; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_get_self_parent(WraptFunctionWrapperObject *self, - void *closure) +static PyObject *WraptFunctionWrapperBase_get_self_binding( + WraptFunctionWrapperObject *self, void *closure) { - if (!self->parent) - { - Py_INCREF(Py_None); - return Py_None; - } + if (!self->binding) { + Py_INCREF(Py_None); + return Py_None; + } - Py_INCREF(self->parent); - return self->parent; + Py_INCREF(self->binding); + return self->binding; } /* ------------------------------------------------------------------------- */ -static PyObject * -WraptFunctionWrapperBase_get_self_owner(WraptFunctionWrapperObject *self, - void *closure) +static PyObject *WraptFunctionWrapperBase_get_self_parent( + WraptFunctionWrapperObject *self, void *closure) { - if (!self->owner) - { - Py_INCREF(Py_None); - return Py_None; - } + if (!self->parent) { + Py_INCREF(Py_None); + return Py_None; + } - Py_INCREF(self->owner); - return self->owner; + Py_INCREF(self->parent); + return self->parent; } /* ------------------------------------------------------------------------- */; static PyMethodDef WraptFunctionWrapperBase_methods[] = { - {"__set_name__", (PyCFunction)WraptFunctionWrapperBase_set_name, - METH_VARARGS | METH_KEYWORDS, 0}, - {"__instancecheck__", (PyCFunction)WraptFunctionWrapperBase_instancecheck, - METH_O, 0}, - {"__subclasscheck__", (PyCFunction)WraptFunctionWrapperBase_subclasscheck, - METH_VARARGS, 0}, - {NULL, NULL}, + { "__set_name__", (PyCFunction)WraptFunctionWrapperBase_set_name, + METH_VARARGS | METH_KEYWORDS, 0 }, + { "__instancecheck__", (PyCFunction)WraptFunctionWrapperBase_instancecheck, + METH_O, 0}, + { "__subclasscheck__", (PyCFunction)WraptFunctionWrapperBase_subclasscheck, + METH_VARARGS, 0 }, + { NULL, NULL }, }; /* ------------------------------------------------------------------------- */; static PyGetSetDef WraptFunctionWrapperBase_getset[] = { - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {"_self_instance", (getter)WraptFunctionWrapperBase_get_self_instance, NULL, - 0}, - {"_self_wrapper", (getter)WraptFunctionWrapperBase_get_self_wrapper, NULL, - 0}, - {"_self_enabled", (getter)WraptFunctionWrapperBase_get_self_enabled, NULL, - 0}, - {"_self_binding", (getter)WraptFunctionWrapperBase_get_self_binding, NULL, - 0}, - {"_self_parent", (getter)WraptFunctionWrapperBase_get_self_parent, NULL, 0}, - {"_self_owner", (getter)WraptFunctionWrapperBase_get_self_owner, NULL, 0}, - {NULL}, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { "_self_instance", (getter)WraptFunctionWrapperBase_get_self_instance, + NULL, 0 }, + { "_self_wrapper", (getter)WraptFunctionWrapperBase_get_self_wrapper, + NULL, 0 }, + { "_self_enabled", (getter)WraptFunctionWrapperBase_get_self_enabled, + NULL, 0 }, + { "_self_binding", (getter)WraptFunctionWrapperBase_get_self_binding, + NULL, 0 }, + { "_self_parent", (getter)WraptFunctionWrapperBase_get_self_parent, + NULL, 0 }, + { NULL }, }; PyTypeObject WraptFunctionWrapperBase_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "_FunctionWrapperBase", /*tp_name*/ - sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "_FunctionWrapperBase", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - (destructor)WraptFunctionWrapperBase_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - (ternaryfunc)WraptFunctionWrapperBase_call, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/ - 0, /*tp_doc*/ - (traverseproc)WraptFunctionWrapperBase_traverse, /*tp_traverse*/ - (inquiry)WraptFunctionWrapperBase_clear, /*tp_clear*/ - 0, /*tp_richcompare*/ - offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - WraptFunctionWrapperBase_methods, /*tp_methods*/ - 0, /*tp_members*/ - WraptFunctionWrapperBase_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - (descrgetfunc)WraptFunctionWrapperBase_descr_get, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - (initproc)WraptFunctionWrapperBase_init, /*tp_init*/ - 0, /*tp_alloc*/ - WraptFunctionWrapperBase_new, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ + (destructor)WraptFunctionWrapperBase_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptFunctionWrapperBase_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + (traverseproc)WraptFunctionWrapperBase_traverse, /*tp_traverse*/ + (inquiry)WraptFunctionWrapperBase_clear, /*tp_clear*/ + 0, /*tp_richcompare*/ + offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + WraptFunctionWrapperBase_methods, /*tp_methods*/ + 0, /*tp_members*/ + WraptFunctionWrapperBase_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + (descrgetfunc)WraptFunctionWrapperBase_descr_get, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptFunctionWrapperBase_init, /*tp_init*/ + 0, /*tp_alloc*/ + WraptFunctionWrapperBase_new, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ -static PyObject * -WraptBoundFunctionWrapper_call(WraptFunctionWrapperObject *self, PyObject *args, - PyObject *kwds) +static PyObject *WraptBoundFunctionWrapper_call( + WraptFunctionWrapperObject *self, PyObject *args, PyObject *kwds) { - PyObject *param_args = NULL; - PyObject *param_kwds = NULL; + PyObject *param_args = NULL; + PyObject *param_kwds = NULL; - PyObject *wrapped = NULL; - PyObject *instance = NULL; + PyObject *wrapped = NULL; + PyObject *instance = NULL; - PyObject *result = NULL; + PyObject *result = NULL; - static PyObject *function_str = NULL; - static PyObject *callable_str = NULL; + static PyObject *function_str = NULL; - if (self->enabled != Py_None) - { - if (PyCallable_Check(self->enabled)) - { - PyObject *object = NULL; + if (self->enabled != Py_None) { + if (PyCallable_Check(self->enabled)) { + PyObject *object = NULL; - object = PyObject_CallFunctionObjArgs(self->enabled, NULL); + object = PyObject_CallFunctionObjArgs(self->enabled, NULL); - if (!object) - return NULL; + if (!object) + return NULL; - if (PyObject_Not(object)) - { - Py_DECREF(object); - return PyObject_Call(self->object_proxy.wrapped, args, kwds); - } - - Py_DECREF(object); - } - else if (PyObject_Not(self->enabled)) - { - return PyObject_Call(self->object_proxy.wrapped, args, kwds); - } - } - - if (!function_str) - { - function_str = PyUnicode_InternFromString("function"); - callable_str = PyUnicode_InternFromString("callable"); - } - - /* - * We need to do things different depending on whether we are likely - * wrapping an instance method vs a static method or class method. - */ - - if (self->binding == function_str || - PyObject_RichCompareBool(self->binding, function_str, Py_EQ) == 1 || - self->binding == callable_str || - PyObject_RichCompareBool(self->binding, callable_str, Py_EQ) == 1) - { - - // if (self->instance == Py_None) { - // /* - // * This situation can occur where someone is calling the - // * instancemethod via the class type and passing the - // * instance as the first argument. We need to shift the args - // * before making the call to the wrapper and effectively - // * bind the instance to the wrapped function using a partial - // * so the wrapper doesn't see anything as being different. - // */ - - // if (PyTuple_Size(args) == 0) { - // PyErr_SetString(PyExc_TypeError, - // "missing 1 required positional argument"); - // return NULL; - // } - - // instance = PyTuple_GetItem(args, 0); - - // if (!instance) - // return NULL; - - // wrapped = PyObject_CallFunctionObjArgs( - // (PyObject *)&WraptPartialCallableObjectProxy_Type, - // self->object_proxy.wrapped, instance, NULL); - - // if (!wrapped) - // return NULL; - - // param_args = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); - - // if (!param_args) { - // Py_DECREF(wrapped); - // return NULL; - // } - - // args = param_args; - // } - - if (self->instance == Py_None && PyTuple_Size(args) != 0) - { - /* - * This situation can occur where someone is calling the - * instancemethod via the class type and passing the - * instance as the first argument. We need to shift the args - * before making the call to the wrapper and effectively - * bind the instance to the wrapped function using a partial - * so the wrapper doesn't see anything as being different. - */ - - instance = PyTuple_GetItem(args, 0); - - if (!instance) - return NULL; + if (PyObject_Not(object)) { + Py_DECREF(object); + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } - if (PyObject_IsInstance(instance, self->owner) == 1) - { - wrapped = PyObject_CallFunctionObjArgs( - (PyObject *)&WraptPartialCallableObjectProxy_Type, - self->object_proxy.wrapped, instance, NULL); + Py_DECREF(object); + } + else if (PyObject_Not(self->enabled)) { + return PyObject_Call(self->object_proxy.wrapped, args, kwds); + } + } - if (!wrapped) - return NULL; + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } - param_args = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); + /* + * We need to do things different depending on whether we are likely + * wrapping an instance method vs a static method or class method. + */ - if (!param_args) - { - Py_DECREF(wrapped); - return NULL; - } + if (self->binding == function_str || PyObject_RichCompareBool( + self->binding, function_str, Py_EQ) == 1) { - args = param_args; - } - else - { - instance = self->instance; - } - } - else - { - instance = self->instance; - } + if (self->instance == Py_None) { + /* + * This situation can occur where someone is calling the + * instancemethod via the class type and passing the + * instance as the first argument. We need to shift the args + * before making the call to the wrapper and effectively + * bind the instance to the wrapped function using a partial + * so the wrapper doesn't see anything as being different. + */ - if (!wrapped) - { - Py_INCREF(self->object_proxy.wrapped); - wrapped = self->object_proxy.wrapped; - } + if (PyTuple_Size(args) == 0) { + PyErr_SetString(PyExc_TypeError, + "missing 1 required positional argument"); + return NULL; + } - if (!kwds) - { - param_kwds = PyDict_New(); - kwds = param_kwds; - } + instance = PyTuple_GetItem(args, 0); - result = PyObject_CallFunctionObjArgs(self->wrapper, wrapped, instance, - args, kwds, NULL); + if (!instance) + return NULL; - Py_XDECREF(param_args); - Py_XDECREF(param_kwds); - Py_DECREF(wrapped); + wrapped = PyObject_CallFunctionObjArgs( + (PyObject *)&WraptPartialCallableObjectProxy_Type, + self->object_proxy.wrapped, instance, NULL); - return result; - } - else - { - /* - * As in this case we would be dealing with a classmethod or - * staticmethod, then _self_instance will only tell us whether - * when calling the classmethod or staticmethod they did it via - * an instance of the class it is bound to and not the case - * where done by the class type itself. We thus ignore - * _self_instance and use the __self__ attribute of the bound - * function instead. For a classmethod, this means instance will - * be the class type and for a staticmethod it will be None. - * This is probably the more useful thing we can pass through - * even though we loose knowledge of whether they were called on - * the instance vs the class type, as it reflects what they have - * available in the decoratored function. - */ - - instance = PyObject_GetAttrString(self->object_proxy.wrapped, "__self__"); - - if (!instance) - { - PyErr_Clear(); - Py_INCREF(Py_None); - instance = Py_None; - } - - if (!kwds) - { - param_kwds = PyDict_New(); - kwds = param_kwds; - } - - result = PyObject_CallFunctionObjArgs( - self->wrapper, self->object_proxy.wrapped, instance, args, kwds, NULL); + if (!wrapped) + return NULL; - Py_XDECREF(param_kwds); + param_args = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); - Py_DECREF(instance); + if (!param_args) { + Py_DECREF(wrapped); + return NULL; + } - return result; - } + args = param_args; + } + else + instance = self->instance; + + if (!wrapped) { + Py_INCREF(self->object_proxy.wrapped); + wrapped = self->object_proxy.wrapped; + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + result = PyObject_CallFunctionObjArgs(self->wrapper, wrapped, + instance, args, kwds, NULL); + + Py_XDECREF(param_args); + Py_XDECREF(param_kwds); + Py_DECREF(wrapped); + + return result; + } + else { + /* + * As in this case we would be dealing with a classmethod or + * staticmethod, then _self_instance will only tell us whether + * when calling the classmethod or staticmethod they did it via + * an instance of the class it is bound to and not the case + * where done by the class type itself. We thus ignore + * _self_instance and use the __self__ attribute of the bound + * function instead. For a classmethod, this means instance will + * be the class type and for a staticmethod it will be None. + * This is probably the more useful thing we can pass through + * even though we loose knowledge of whether they were called on + * the instance vs the class type, as it reflects what they have + * available in the decoratored function. + */ + + instance = PyObject_GetAttrString(self->object_proxy.wrapped, + "__self__"); + + if (!instance) { + PyErr_Clear(); + Py_INCREF(Py_None); + instance = Py_None; + } + + if (!kwds) { + param_kwds = PyDict_New(); + kwds = param_kwds; + } + + result = PyObject_CallFunctionObjArgs(self->wrapper, + self->object_proxy.wrapped, instance, args, kwds, NULL); + + Py_XDECREF(param_kwds); + + Py_DECREF(instance); + + return result; + } } /* ------------------------------------------------------------------------- */ static PyGetSetDef WraptBoundFunctionWrapper_getset[] = { - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {NULL}, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, }; PyTypeObject WraptBoundFunctionWrapper_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "BoundFunctionWrapper", /*tp_name*/ - sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "BoundFunctionWrapper", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - (ternaryfunc)WraptBoundFunctionWrapper_call, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + (ternaryfunc)WraptBoundFunctionWrapper_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - 0, /*tp_members*/ - WraptBoundFunctionWrapper_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - 0, /*tp_init*/ - 0, /*tp_alloc*/ - 0, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptBoundFunctionWrapper_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + 0, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ static int WraptFunctionWrapper_init(WraptFunctionWrapperObject *self, - PyObject *args, PyObject *kwds) + PyObject *args, PyObject *kwds) { - PyObject *wrapped = NULL; - PyObject *wrapper = NULL; - PyObject *enabled = Py_None; - PyObject *binding = NULL; - PyObject *instance = NULL; - - static PyObject *function_str = NULL; - static PyObject *classmethod_str = NULL; - static PyObject *staticmethod_str = NULL; - static PyObject *callable_str = NULL; - static PyObject *builtin_str = NULL; - static PyObject *class_str = NULL; - static PyObject *instancemethod_str = NULL; - - int result = 0; - - char *const kwlist[] = {"wrapped", "wrapper", "enabled", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O:FunctionWrapper", kwlist, - &wrapped, &wrapper, &enabled)) - { - return -1; - } - - if (!function_str) - { - function_str = PyUnicode_InternFromString("function"); - } - - if (!classmethod_str) - { - classmethod_str = PyUnicode_InternFromString("classmethod"); - } - - if (!staticmethod_str) - { - staticmethod_str = PyUnicode_InternFromString("staticmethod"); - } - - if (!callable_str) - { - callable_str = PyUnicode_InternFromString("callable"); - } - - if (!builtin_str) - { - builtin_str = PyUnicode_InternFromString("builtin"); - } - - if (!class_str) - { - class_str = PyUnicode_InternFromString("class"); - } - - if (!instancemethod_str) - { - instancemethod_str = PyUnicode_InternFromString("instancemethod"); - } - - if (PyObject_IsInstance(wrapped, - (PyObject *)&WraptFunctionWrapperBase_Type)) - { - binding = PyObject_GetAttrString(wrapped, "_self_binding"); - } - - if (!binding) - { - if (PyCFunction_Check(wrapped)) - { - binding = builtin_str; - } - else if (PyObject_IsInstance(wrapped, (PyObject *)&PyFunction_Type)) - { - binding = function_str; - } - else if (PyObject_IsInstance(wrapped, (PyObject *)&PyClassMethod_Type)) - { - binding = classmethod_str; - } - else if (PyObject_IsInstance(wrapped, (PyObject *)&PyType_Type)) - { - binding = class_str; - } - else if (PyObject_IsInstance(wrapped, (PyObject *)&PyStaticMethod_Type)) - { - binding = staticmethod_str; - } - else if ((instance = PyObject_GetAttrString(wrapped, "__self__")) != 0) - { - if (PyObject_IsInstance(instance, (PyObject *)&PyType_Type)) - { + PyObject *wrapped = NULL; + PyObject *wrapper = NULL; + PyObject *enabled = Py_None; + PyObject *binding = NULL; + PyObject *instance = NULL; + + static PyObject *classmethod_str = NULL; + static PyObject *staticmethod_str = NULL; + static PyObject *function_str = NULL; + + int result = 0; + + static char *kwlist[] = { "wrapped", "wrapper", "enabled", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O:FunctionWrapper", + kwlist, &wrapped, &wrapper, &enabled)) { + return -1; + } + + if (!classmethod_str) { +#if PY_MAJOR_VERSION >= 3 + classmethod_str = PyUnicode_InternFromString("classmethod"); +#else + classmethod_str = PyString_InternFromString("classmethod"); +#endif + } + + if (!staticmethod_str) { +#if PY_MAJOR_VERSION >= 3 + staticmethod_str = PyUnicode_InternFromString("staticmethod"); +#else + staticmethod_str = PyString_InternFromString("staticmethod"); +#endif + } + + if (!function_str) { +#if PY_MAJOR_VERSION >= 3 + function_str = PyUnicode_InternFromString("function"); +#else + function_str = PyString_InternFromString("function"); +#endif + } + + if (PyObject_IsInstance(wrapped, (PyObject *)&PyClassMethod_Type)) { binding = classmethod_str; - } - else if (PyObject_IsInstance(wrapped, (PyObject *)&PyMethod_Type)) - { - binding = instancemethod_str; - } - else - binding = callable_str; + } + else if (PyObject_IsInstance(wrapped, (PyObject *)&PyStaticMethod_Type)) { + binding = staticmethod_str; + } + else if ((instance = PyObject_GetAttrString(wrapped, "__self__")) != 0) { +#if PY_MAJOR_VERSION < 3 + if (PyObject_IsInstance(instance, (PyObject *)&PyClass_Type) || + PyObject_IsInstance(instance, (PyObject *)&PyType_Type)) { + binding = classmethod_str; + } +#else + if (PyObject_IsInstance(instance, (PyObject *)&PyType_Type)) { + binding = classmethod_str; + } +#endif + else + binding = function_str; - Py_DECREF(instance); + Py_DECREF(instance); } - else - { - PyErr_Clear(); + else { + PyErr_Clear(); - binding = callable_str; + binding = function_str; } - } - result = WraptFunctionWrapperBase_raw_init( - self, wrapped, Py_None, wrapper, enabled, binding, Py_None, Py_None); + result = WraptFunctionWrapperBase_raw_init(self, wrapped, Py_None, + wrapper, enabled, binding, Py_None); - return result; + return result; } /* ------------------------------------------------------------------------- */ static PyGetSetDef WraptFunctionWrapper_getset[] = { - {"__module__", (getter)WraptObjectProxy_get_module, - (setter)WraptObjectProxy_set_module, 0}, - {"__doc__", (getter)WraptObjectProxy_get_doc, - (setter)WraptObjectProxy_set_doc, 0}, - {NULL}, + { "__module__", (getter)WraptObjectProxy_get_module, + (setter)WraptObjectProxy_set_module, 0 }, + { "__doc__", (getter)WraptObjectProxy_get_doc, + (setter)WraptObjectProxy_set_doc, 0 }, + { NULL }, }; PyTypeObject WraptFunctionWrapper_Type = { - PyVarObject_HEAD_INIT(NULL, 0) "FunctionWrapper", /*tp_name*/ - sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ - 0, /*tp_itemsize*/ + PyVarObject_HEAD_INIT(NULL, 0) + "FunctionWrapper", /*tp_name*/ + sizeof(WraptFunctionWrapperObject), /*tp_basicsize*/ + 0, /*tp_itemsize*/ /* methods */ - 0, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash*/ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - 0, /*tp_doc*/ - 0, /*tp_traverse*/ - 0, /*tp_clear*/ - 0, /*tp_richcompare*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ +#if PY_MAJOR_VERSION < 3 + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_CHECKTYPES, /*tp_flags*/ +#else + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ +#endif + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ offsetof(WraptObjectProxyObject, weakreflist), /*tp_weaklistoffset*/ - 0, /*tp_iter*/ - 0, /*tp_iternext*/ - 0, /*tp_methods*/ - 0, /*tp_members*/ - WraptFunctionWrapper_getset, /*tp_getset*/ - 0, /*tp_base*/ - 0, /*tp_dict*/ - 0, /*tp_descr_get*/ - 0, /*tp_descr_set*/ - 0, /*tp_dictoffset*/ - (initproc)WraptFunctionWrapper_init, /*tp_init*/ - 0, /*tp_alloc*/ - 0, /*tp_new*/ - 0, /*tp_free*/ - 0, /*tp_is_gc*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + 0, /*tp_methods*/ + 0, /*tp_members*/ + WraptFunctionWrapper_getset, /*tp_getset*/ + 0, /*tp_base*/ + 0, /*tp_dict*/ + 0, /*tp_descr_get*/ + 0, /*tp_descr_set*/ + 0, /*tp_dictoffset*/ + (initproc)WraptFunctionWrapper_init, /*tp_init*/ + 0, /*tp_alloc*/ + 0, /*tp_new*/ + 0, /*tp_free*/ + 0, /*tp_is_gc*/ }; /* ------------------------------------------------------------------------- */ +#if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, - "_wrappers", /* m_name */ - NULL, /* m_doc */ - -1, /* m_size */ - NULL, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ + "_wrappers", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + NULL, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ }; +#endif -static PyObject *moduleinit(void) +static PyObject * +moduleinit(void) { - PyObject *module; + PyObject *module; - module = PyModule_Create(&moduledef); +#if PY_MAJOR_VERSION >= 3 + module = PyModule_Create(&moduledef); +#else + module = Py_InitModule3("_wrappers", NULL, NULL); +#endif - if (module == NULL) - return NULL; + if (module == NULL) + return NULL; - if (PyType_Ready(&WraptObjectProxy_Type) < 0) - return NULL; + if (PyType_Ready(&WraptObjectProxy_Type) < 0) + return NULL; - /* Ensure that inheritance relationships specified. */ + /* Ensure that inheritance relationships specified. */ - WraptCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; - WraptPartialCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; - WraptFunctionWrapperBase_Type.tp_base = &WraptObjectProxy_Type; - WraptBoundFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; - WraptFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; + WraptCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; + WraptPartialCallableObjectProxy_Type.tp_base = &WraptObjectProxy_Type; + WraptFunctionWrapperBase_Type.tp_base = &WraptObjectProxy_Type; + WraptBoundFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; + WraptFunctionWrapper_Type.tp_base = &WraptFunctionWrapperBase_Type; - if (PyType_Ready(&WraptCallableObjectProxy_Type) < 0) - return NULL; - if (PyType_Ready(&WraptPartialCallableObjectProxy_Type) < 0) - return NULL; - if (PyType_Ready(&WraptFunctionWrapperBase_Type) < 0) - return NULL; - if (PyType_Ready(&WraptBoundFunctionWrapper_Type) < 0) - return NULL; - if (PyType_Ready(&WraptFunctionWrapper_Type) < 0) - return NULL; + if (PyType_Ready(&WraptCallableObjectProxy_Type) < 0) + return NULL; + if (PyType_Ready(&WraptPartialCallableObjectProxy_Type) < 0) + return NULL; + if (PyType_Ready(&WraptFunctionWrapperBase_Type) < 0) + return NULL; + if (PyType_Ready(&WraptBoundFunctionWrapper_Type) < 0) + return NULL; + if (PyType_Ready(&WraptFunctionWrapper_Type) < 0) + return NULL; - Py_INCREF(&WraptObjectProxy_Type); - PyModule_AddObject(module, "ObjectProxy", (PyObject *)&WraptObjectProxy_Type); - Py_INCREF(&WraptCallableObjectProxy_Type); - PyModule_AddObject(module, "CallableObjectProxy", - (PyObject *)&WraptCallableObjectProxy_Type); - Py_INCREF(&WraptPartialCallableObjectProxy_Type); - PyModule_AddObject(module, "PartialCallableObjectProxy", - (PyObject *)&WraptPartialCallableObjectProxy_Type); - Py_INCREF(&WraptFunctionWrapper_Type); - PyModule_AddObject(module, "FunctionWrapper", - (PyObject *)&WraptFunctionWrapper_Type); - - Py_INCREF(&WraptFunctionWrapperBase_Type); - PyModule_AddObject(module, "_FunctionWrapperBase", - (PyObject *)&WraptFunctionWrapperBase_Type); - Py_INCREF(&WraptBoundFunctionWrapper_Type); - PyModule_AddObject(module, "BoundFunctionWrapper", - (PyObject *)&WraptBoundFunctionWrapper_Type); - -#ifdef Py_GIL_DISABLED - PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); -#endif + Py_INCREF(&WraptObjectProxy_Type); + PyModule_AddObject(module, "ObjectProxy", + (PyObject *)&WraptObjectProxy_Type); + Py_INCREF(&WraptCallableObjectProxy_Type); + PyModule_AddObject(module, "CallableObjectProxy", + (PyObject *)&WraptCallableObjectProxy_Type); + PyModule_AddObject(module, "PartialCallableObjectProxy", + (PyObject *)&WraptPartialCallableObjectProxy_Type); + Py_INCREF(&WraptFunctionWrapper_Type); + PyModule_AddObject(module, "FunctionWrapper", + (PyObject *)&WraptFunctionWrapper_Type); - return module; + Py_INCREF(&WraptFunctionWrapperBase_Type); + PyModule_AddObject(module, "_FunctionWrapperBase", + (PyObject *)&WraptFunctionWrapperBase_Type); + Py_INCREF(&WraptBoundFunctionWrapper_Type); + PyModule_AddObject(module, "BoundFunctionWrapper", + (PyObject *)&WraptBoundFunctionWrapper_Type); + + return module; } -PyMODINIT_FUNC PyInit__wrappers(void) { return moduleinit(); } +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC init_wrappers(void) +{ + moduleinit(); +} +#else +PyMODINIT_FUNC PyInit__wrappers(void) +{ + return moduleinit(); +} +#endif /* ------------------------------------------------------------------------- */ diff --git a/newrelic/packages/wrapt/arguments.py b/newrelic/packages/wrapt/arguments.py index 554f62cd28..032bc059e0 100644 --- a/newrelic/packages/wrapt/arguments.py +++ b/newrelic/packages/wrapt/arguments.py @@ -1,35 +1,16 @@ -"""The inspect.formatargspec() function was dropped in Python 3.11 but we need -it for when constructing signature changing decorators based on result of -inspect.getfullargspec(). The code here implements inspect.formatargspec() based -on Parameter and Signature from inspect module, which were added in Python 3.6. -Thanks to Cyril Jouve for the implementation. -""" - -from typing import Any, Callable, List, Mapping, Optional, Sequence, Tuple +# The inspect.formatargspec() function was dropped in Python 3.11 but we need +# need it for when constructing signature changing decorators based on result of +# inspect.getargspec() or inspect.getfullargspec(). The code here implements +# inspect.formatargspec() base on Parameter and Signature from inspect module, +# which were added in Python 3.6. Thanks to Cyril Jouve for the implementation. try: from inspect import Parameter, Signature except ImportError: - from inspect import formatargspec # type: ignore[attr-defined] + from inspect import formatargspec else: - - def formatargspec( - args: List[str], - varargs: Optional[str] = None, - varkw: Optional[str] = None, - defaults: Optional[Tuple[Any, ...]] = None, - kwonlyargs: Optional[Sequence[str]] = None, - kwonlydefaults: Optional[Mapping[str, Any]] = None, - annotations: Mapping[str, Any] = {}, - formatarg: Callable[[str], str] = str, - formatvarargs: Callable[[str], str] = lambda name: "*" + name, - formatvarkw: Callable[[str], str] = lambda name: "**" + name, - formatvalue: Callable[[Any], str] = lambda value: "=" + repr(value), - formatreturns: Callable[[Any], str] = lambda text: " -> " + text, - formatannotation: Callable[[Any], str] = lambda annot: " -> " + repr(annot), - ) -> str: - if kwonlyargs is None: - kwonlyargs = () + def formatargspec(args, varargs=None, varkw=None, defaults=None, + kwonlyargs=(), kwonlydefaults={}, annotations={}): if kwonlydefaults is None: kwonlydefaults = {} ndefaults = len(defaults) if defaults else 0 @@ -37,10 +18,9 @@ def formatargspec( Parameter( arg, Parameter.POSITIONAL_OR_KEYWORD, - default=defaults[i] if defaults and i >= 0 else Parameter.empty, + default=defaults[i] if i >= 0 else Parameter.empty, annotation=annotations.get(arg, Parameter.empty), - ) - for i, arg in enumerate(args, ndefaults - len(args)) + ) for i, arg in enumerate(args, ndefaults - len(args)) ] if varargs: parameters.append(Parameter(varargs, Parameter.VAR_POSITIONAL)) @@ -50,10 +30,9 @@ def formatargspec( Parameter.KEYWORD_ONLY, default=kwonlydefaults.get(kwonlyarg, Parameter.empty), annotation=annotations.get(kwonlyarg, Parameter.empty), - ) - for kwonlyarg in kwonlyargs + ) for kwonlyarg in kwonlyargs ) if varkw: parameters.append(Parameter(varkw, Parameter.VAR_KEYWORD)) - return_annotation = annotations.get("return", Signature.empty) - return str(Signature(parameters, return_annotation=return_annotation)) + return_annotation = annotations.get('return', Signature.empty) + return str(Signature(parameters, return_annotation=return_annotation)) \ No newline at end of file diff --git a/newrelic/packages/wrapt/decorators.py b/newrelic/packages/wrapt/decorators.py index 6f5cedd2a4..c80a4bb72e 100644 --- a/newrelic/packages/wrapt/decorators.py +++ b/newrelic/packages/wrapt/decorators.py @@ -4,18 +4,51 @@ """ import sys + +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = basestring, + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + +else: + string_types = str, + + import builtins + + exec_ = getattr(builtins, "exec") + del builtins + from functools import partial -from inspect import isclass, signature +from inspect import isclass from threading import Lock, RLock -from .__wrapt__ import BoundFunctionWrapper, CallableObjectProxy, FunctionWrapper from .arguments import formatargspec +try: + from inspect import signature +except ImportError: + pass + +from .__wrapt__ import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy, + CallableObjectProxy) + # Adapter wrapper for the wrapped function which will overlay certain # properties from the adapter function onto the wrapped function so that -# functions such as inspect.getfullargspec(), inspect.signature() and -# inspect.getsource() return the correct results one would expect. - +# functions such as inspect.getargspec(), inspect.getfullargspec(), +# inspect.signature() and inspect.getsource() return the correct results +# one would expect. class _AdapterFunctionCode(CallableObjectProxy): @@ -43,7 +76,6 @@ def co_kwonlyargcount(self): def co_varnames(self): return self._self_adapter_code.co_varnames - class _AdapterFunctionSurrogate(CallableObjectProxy): def __init__(self, wrapped, adapter): @@ -52,9 +84,8 @@ def __init__(self, wrapped, adapter): @property def __code__(self): - return _AdapterFunctionCode( - self.__wrapped__.__code__, self._self_adapter.__code__ - ) + return _AdapterFunctionCode(self.__wrapped__.__code__, + self._self_adapter.__code__) @property def __defaults__(self): @@ -66,36 +97,41 @@ def __kwdefaults__(self): @property def __signature__(self): - if "signature" not in globals(): + if 'signature' not in globals(): return self._self_adapter.__signature__ else: return signature(self._self_adapter) + if PY2: + func_code = __code__ + func_defaults = __defaults__ class _BoundAdapterWrapper(BoundFunctionWrapper): @property def __func__(self): - return _AdapterFunctionSurrogate( - self.__wrapped__.__func__, self._self_parent._self_adapter - ) + return _AdapterFunctionSurrogate(self.__wrapped__.__func__, + self._self_parent._self_adapter) @property def __signature__(self): - if "signature" not in globals(): + if 'signature' not in globals(): return self.__wrapped__.__signature__ else: return signature(self._self_parent._self_adapter) + if PY2: + im_func = __func__ class AdapterWrapper(FunctionWrapper): __bound_function_wrapper__ = _BoundAdapterWrapper def __init__(self, *args, **kwargs): - adapter = kwargs.pop("adapter") + adapter = kwargs.pop('adapter') super(AdapterWrapper, self).__init__(*args, **kwargs) - self._self_surrogate = _AdapterFunctionSurrogate(self.__wrapped__, adapter) + self._self_surrogate = _AdapterFunctionSurrogate( + self.__wrapped__, adapter) self._self_adapter = adapter @property @@ -110,25 +146,25 @@ def __defaults__(self): def __kwdefaults__(self): return self._self_surrogate.__kwdefaults__ + if PY2: + func_code = __code__ + func_defaults = __defaults__ + @property def __signature__(self): return self._self_surrogate.__signature__ - -class AdapterFactory: +class AdapterFactory(object): def __call__(self, wrapped): raise NotImplementedError() - class DelegatedAdapterFactory(AdapterFactory): def __init__(self, factory): super(DelegatedAdapterFactory, self).__init__() self.factory = factory - def __call__(self, wrapped): return self.factory(wrapped) - adapter_factory = DelegatedAdapterFactory # Decorator for creating other decorators. This decorator and the @@ -138,32 +174,29 @@ def __call__(self, wrapped): # function so the wrapper is effectively indistinguishable from the # original wrapped function. - -def decorator(wrapper=None, /, *, enabled=None, adapter=None, proxy=FunctionWrapper): - """ - The decorator should be supplied with a single positional argument - which is the `wrapper` function to be used to implement the - decorator. This may be preceded by a step whereby the keyword - arguments are supplied to customise the behaviour of the - decorator. The `adapter` argument is used to optionally denote a - separate function which is notionally used by an adapter - decorator. In that case parts of the function `__code__` and - `__defaults__` attributes are used from the adapter function - rather than those of the wrapped function. This allows for the - argument specification from `inspect.getfullargspec()` and similar - functions to be overridden with a prototype for a different - function than what was wrapped. The `enabled` argument provides a - way to enable/disable the use of the decorator. If the type of - `enabled` is a boolean, then it is evaluated immediately and the - wrapper not even applied if it is `False`. If not a boolean, it will - be evaluated when the wrapper is called for an unbound wrapper, - and when binding occurs for a bound wrapper. When being evaluated, - if `enabled` is callable it will be called to obtain the value to - be checked. If `False`, the wrapper will not be called and instead - the original wrapped function will be called directly instead. - The `proxy` argument provides a way of passing a custom version of - the `FunctionWrapper` class used in decorating the function. - """ +def decorator(wrapper=None, enabled=None, adapter=None, proxy=FunctionWrapper): + # The decorator should be supplied with a single positional argument + # which is the wrapper function to be used to implement the + # decorator. This may be preceded by a step whereby the keyword + # arguments are supplied to customise the behaviour of the + # decorator. The 'adapter' argument is used to optionally denote a + # separate function which is notionally used by an adapter + # decorator. In that case parts of the function '__code__' and + # '__defaults__' attributes are used from the adapter function + # rather than those of the wrapped function. This allows for the + # argument specification from inspect.getfullargspec() and similar + # functions to be overridden with a prototype for a different + # function than what was wrapped. The 'enabled' argument provides a + # way to enable/disable the use of the decorator. If the type of + # 'enabled' is a boolean, then it is evaluated immediately and the + # wrapper not even applied if it is False. If not a boolean, it will + # be evaluated when the wrapper is called for an unbound wrapper, + # and when binding occurs for a bound wrapper. When being evaluated, + # if 'enabled' is callable it will be called to obtain the value to + # be checked. If False, the wrapper will not be called and instead + # the original wrapped function will be called directly instead. + # The 'proxy' argument provides a way of passing a custom version of + # the FunctionWrapper class used in decorating the function. if wrapper is not None: # Helper function for creating wrapper of the appropriate @@ -187,14 +220,14 @@ def _build(wrapped, wrapper, enabled=None, adapter=None): annotations = {} - if not isinstance(adapter, str): + if not isinstance(adapter, string_types): if len(adapter) == 7: annotations = adapter[-1] adapter = adapter[:-1] adapter = formatargspec(*adapter) - exec(f"def adapter{adapter}: pass", ns, ns) - adapter = ns["adapter"] + exec_('def adapter{}: pass'.format(adapter), ns, ns) + adapter = ns['adapter'] # Override the annotations for the manufactured # adapter function so they match the original @@ -203,9 +236,8 @@ def _build(wrapped, wrapper, enabled=None, adapter=None): if annotations: adapter.__annotations__ = annotations - return AdapterWrapper( - wrapped=wrapped, wrapper=wrapper, enabled=enabled, adapter=adapter - ) + return AdapterWrapper(wrapped=wrapped, wrapper=wrapper, + enabled=enabled, adapter=adapter) return proxy(wrapped=wrapped, wrapper=wrapper, enabled=enabled) @@ -221,7 +253,7 @@ def _wrapper(wrapped, instance, args, kwargs): # to a class type. # # @decorator - # class mydecoratorclass: + # class mydecoratorclass(object): # def __init__(self, arg=None): # self.arg = arg # def __call__(self, wrapped, instance, args, kwargs): @@ -265,7 +297,8 @@ def _capture(target_wrapped): # Finally build the wrapper itself and return it. - return _build(target_wrapped, target_wrapper, _enabled, adapter) + return _build(target_wrapped, target_wrapper, + _enabled, adapter) return _capture @@ -295,7 +328,7 @@ def _capture(target_wrapped): # as the decorator wrapper function. # # @decorator - # class mydecoratorclass: + # class mydecoratorclass(object): # def __init__(self, arg=None): # self.arg = arg # def __call__(self, wrapped, instance, @@ -335,7 +368,7 @@ def _capture(target_wrapped): # In this case the decorator was applied to a class # method. # - # class myclass: + # class myclass(object): # @decorator # @classmethod # def decoratorclassmethod(cls, wrapped, @@ -360,7 +393,7 @@ def _capture(target_wrapped): # In this case the decorator was applied to an instance # method. # - # class myclass: + # class myclass(object): # @decorator # def decoratorclassmethod(self, wrapped, # instance, args, kwargs): @@ -399,8 +432,8 @@ def _capture(target_wrapped): # decorator again wrapped in a partial using the collected # arguments. - return partial(decorator, enabled=enabled, adapter=adapter, proxy=proxy) - + return partial(decorator, enabled=enabled, adapter=adapter, + proxy=proxy) # Decorator for implementing thread synchronization. It can be used as a # decorator, in which case the synchronization context is determined by @@ -412,27 +445,14 @@ def _capture(target_wrapped): # synchronization primitive without creating a separate lock against the # derived or supplied context. - def synchronized(wrapped): - """Depending on the nature of the `wrapped` object, will either return a - decorator which can be used to wrap a function or method, or a context - manager, both of which will act accordingly depending on how used, to - synchronize access to calling of the wrapped function, or the block of - code within the context manager. If it is an object which is a - synchronization primitive, such as a threading Lock, RLock, Semaphore, - Condition, or Event, then it is assumed that the object is to be used - directly as the synchronization primitive, otherwise a lock is created - automatically and attached to the wrapped object and used as the - synchronization primitive. - """ - # Determine if being passed an object which is a synchronization # primitive. We can't check by type for Lock, RLock, Semaphore etc, # as the means of creating them isn't the type. Therefore use the # existence of acquire() and release() methods. This is more # extensible anyway as it allows custom synchronization mechanisms. - if hasattr(wrapped, "acquire") and hasattr(wrapped, "release"): + if hasattr(wrapped, 'acquire') and hasattr(wrapped, 'release'): # We remember what the original lock is and then return a new # decorator which accesses and locks it. When returning the new # decorator we wrap it with an object proxy so we can override @@ -469,7 +489,7 @@ def __exit__(self, *args): def _synchronized_lock(context): # Attempt to retrieve the lock for the specific context. - lock = vars(context).get("_synchronized_lock", None) + lock = vars(context).get('_synchronized_lock', None) if lock is None: # There is no existing lock defined for the context we @@ -490,11 +510,11 @@ def _synchronized_lock(context): # at the same time and were competing to create the # meta lock. - lock = vars(context).get("_synchronized_lock", None) + lock = vars(context).get('_synchronized_lock', None) if lock is None: lock = RLock() - setattr(context, "_synchronized_lock", lock) + setattr(context, '_synchronized_lock', lock) return lock @@ -518,5 +538,4 @@ def __exit__(self, *args): return _FinalDecorator(wrapped=wrapped, wrapper=_synchronized_wrapper) - -synchronized._synchronized_meta_lock = Lock() # type: ignore[attr-defined] +synchronized._synchronized_meta_lock = Lock() diff --git a/newrelic/packages/wrapt/importer.py b/newrelic/packages/wrapt/importer.py index a1e0cb7c59..23fcbd2f63 100644 --- a/newrelic/packages/wrapt/importer.py +++ b/newrelic/packages/wrapt/importer.py @@ -3,13 +3,19 @@ """ -import importlib.metadata import sys import threading -from importlib.util import find_spec -from typing import Callable, Dict, List -from .__wrapt__ import BaseObjectProxy +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = basestring, + find_spec = None +else: + string_types = str, + from importlib.util import find_spec + +from .__wrapt__ import ObjectProxy # The dictionary registering any post import hooks to be triggered once # the target module has been imported. Once a module has been imported @@ -17,7 +23,7 @@ # module will be truncated but the list left in the dictionary. This # acts as a flag to indicate that the module had already been imported. -_post_import_hooks: Dict[str, List[Callable]] = {} +_post_import_hooks = {} _post_import_hooks_init = False _post_import_hooks_lock = threading.RLock() @@ -28,36 +34,22 @@ # proxy callback being registered which will defer loading of the # specified module containing the callback function until required. - def _create_import_hook_from_string(name): def import_hook(module): - module_name, function = name.split(":") - attrs = function.split(".") + module_name, function = name.split(':') + attrs = function.split('.') __import__(module_name) callback = sys.modules[module_name] for attr in attrs: callback = getattr(callback, attr) return callback(module) - return import_hook - def register_post_import_hook(hook, name): - """ - Register a post import hook for the target module `name`. The `hook` - function will be called once the module is imported and will be passed the - module as argument. If the module is already imported, the `hook` will be - called immediately. If you also want to defer loading of the module containing - the `hook` function until required, you can specify the `hook` as a string in - the form 'module:function'. This will result in a proxy hook function being - registered which will defer loading of the specified module containing the - callback function until required. - """ - # Create a deferred import hook if hook is a string name rather than # a callable function. - if isinstance(hook, str): + if isinstance(hook, string_types): hook = _create_import_hook_from_string(hook) with _post_import_hooks_lock: @@ -86,59 +78,34 @@ def register_post_import_hook(hook, name): if module is not None: hook(module) - # Register post import hooks defined as package entry points. - def _create_import_hook_from_entrypoint(entrypoint): def import_hook(module): - entrypoint_value = entrypoint.value.split(":") - module_name = entrypoint_value[0] - __import__(module_name) - callback = sys.modules[module_name] - - if len(entrypoint_value) > 1: - attrs = entrypoint_value[1].split(".") - for attr in attrs: - callback = getattr(callback, attr) + __import__(entrypoint.module_name) + callback = sys.modules[entrypoint.module_name] + for attr in entrypoint.attrs: + callback = getattr(callback, attr) return callback(module) - return import_hook - def discover_post_import_hooks(group): - """ - Discover and register post import hooks defined as package entry points - in the specified `group`. The group should be a string that matches the - entry point group name used in the package metadata. - """ - try: - # Python 3.10+ style with select parameter - entrypoints = importlib.metadata.entry_points(group=group) - except TypeError: - # Python 3.8-3.9 style that returns a dict - entrypoints = importlib.metadata.entry_points().get(group, ()) - - for entrypoint in entrypoints: - callback = entrypoint.load() # Use the loaded callback directly - register_post_import_hook(callback, entrypoint.name) + import pkg_resources + except ImportError: + return + for entrypoint in pkg_resources.iter_entry_points(group=group): + callback = _create_import_hook_from_entrypoint(entrypoint) + register_post_import_hook(callback, entrypoint.name) # Indicate that a module has been loaded. Any post import hooks which # were registered against the target module will be invoked. If an # exception is raised in any of the post import hooks, that will cause # the import of the target module to fail. - def notify_module_loaded(module): - """ - Notify that a `module` has been loaded and invoke any post import hooks - registered against the module. If the module is not registered, this - function does nothing. - """ - - name = getattr(module, "__name__", None) + name = getattr(module, '__name__', None) with _post_import_hooks_lock: hooks = _post_import_hooks.pop(name, ()) @@ -150,13 +117,11 @@ def notify_module_loaded(module): for hook in hooks: hook(module) - # A custom module import finder. This intercepts attempts to import # modules and watches out for attempts to import target modules of # interest. When a module of interest is imported, then any post import # hooks which are registered will be invoked. - class _ImportHookLoader: def load_module(self, fullname): @@ -165,18 +130,17 @@ def load_module(self, fullname): return module - -class _ImportHookChainedLoader(BaseObjectProxy): +class _ImportHookChainedLoader(ObjectProxy): def __init__(self, loader): super(_ImportHookChainedLoader, self).__init__(loader) if hasattr(loader, "load_module"): - self.__self_setattr__("load_module", self._self_load_module) + self.__self_setattr__('load_module', self._self_load_module) if hasattr(loader, "create_module"): - self.__self_setattr__("create_module", self._self_create_module) + self.__self_setattr__('create_module', self._self_create_module) if hasattr(loader, "exec_module"): - self.__self_setattr__("exec_module", self._self_exec_module) + self.__self_setattr__('exec_module', self._self_exec_module) def _self_set_loader(self, module): # Set module's loader to self.__wrapped__ unless it's already set to @@ -184,14 +148,13 @@ def _self_set_loader(self, module): # None, so handle None as well. The module may not support attribute # assignment, in which case we simply skip it. Note that we also deal # with __loader__ not existing at all. This is to future proof things - # due to proposal to remove the attribute as described in the GitHub + # due to proposal to remove the attribue as described in the GitHub # issue at https://github.com/python/cpython/issues/77458. Also prior # to Python 3.3, the __loader__ attribute was only set if a custom # module loader was used. It isn't clear whether the attribute still # existed in that case or was set to None. - class UNDEFINED: - pass + class UNDEFINED: pass if getattr(module, "__loader__", UNDEFINED) in (None, self): try: @@ -199,10 +162,8 @@ class UNDEFINED: except AttributeError: pass - if ( - getattr(module, "__spec__", None) is not None - and getattr(module.__spec__, "loader", None) is self - ): + if (getattr(module, "__spec__", None) is not None + and getattr(module.__spec__, "loader", None) is self): module.__spec__.loader = self.__wrapped__ def _self_load_module(self, fullname): @@ -223,7 +184,6 @@ def _self_exec_module(self, module): self.__wrapped__.exec_module(module) notify_module_loaded(module) - class ImportHookFinder: def __init__(self): @@ -253,18 +213,32 @@ def find_module(self, fullname, path=None): # Now call back into the import system again. try: - # For Python 3 we need to use find_spec().loader - # from the importlib.util module. It doesn't actually - # import the target module and only finds the - # loader. If a loader is found, we need to return - # our own loader which will then in turn call the - # real loader to import the module and invoke the - # post import hooks. + if not find_spec: + # For Python 2 we don't have much choice but to + # call back in to __import__(). This will + # actually cause the module to be imported. If no + # module could be found then ImportError will be + # raised. Otherwise we return a loader which + # returns the already loaded module and invokes + # the post import hooks. - loader = getattr(find_spec(fullname), "loader", None) + __import__(fullname) - if loader and not isinstance(loader, _ImportHookChainedLoader): - return _ImportHookChainedLoader(loader) + return _ImportHookLoader() + + else: + # For Python 3 we need to use find_spec().loader + # from the importlib.util module. It doesn't actually + # import the target module and only finds the + # loader. If a loader is found, we need to return + # our own loader which will then in turn call the + # real loader to import the module and invoke the + # post import hooks. + + loader = getattr(find_spec(fullname), "loader", None) + + if loader and not isinstance(loader, _ImportHookChainedLoader): + return _ImportHookChainedLoader(loader) finally: del self.in_progress[fullname] @@ -311,22 +285,11 @@ def find_spec(self, fullname, path=None, target=None): finally: del self.in_progress[fullname] - # Decorator for marking that a function should be called as a post # import hook when the target module is imported. - def when_imported(name): - """ - Returns a decorator that registers the decorated function as a post import - hook for the module specified by `name`. The function will be called once - the module with the specified name is imported, and will be passed the - module as argument. If the module is already imported, the function will - be called immediately. - """ - def register(hook): register_post_import_hook(hook, name) return hook - return register diff --git a/newrelic/packages/wrapt/patches.py b/newrelic/packages/wrapt/patches.py index f5f1fc3ca9..e22adf7ca8 100644 --- a/newrelic/packages/wrapt/patches.py +++ b/newrelic/packages/wrapt/patches.py @@ -1,29 +1,25 @@ import inspect import sys -from .__wrapt__ import FunctionWrapper +PY2 = sys.version_info[0] == 2 -# Helper functions for applying wrappers to existing functions. +if PY2: + string_types = basestring, +else: + string_types = str, +from .__wrapt__ import FunctionWrapper -def resolve_path(target, name): - """ - Resolves the dotted path supplied as `name` to an attribute on a target - object. The `target` can be a module, class, or instance of a class. If the - `target` argument is a string, it is assumed to be the name of a module, - which will be imported if necessary and then used as the target object. - Returns a tuple containing the parent object holding the attribute lookup - resolved to, the attribute name (path prefix removed if present), and the - original attribute value. - """ +# Helper functions for applying wrappers to existing functions. - if isinstance(target, str): - __import__(target) - target = sys.modules[target] +def resolve_path(module, name): + if isinstance(module, string_types): + __import__(module) + module = sys.modules[module] - parent = target + parent = module - path = name.split(".") + path = name.split('.') attribute = path[0] # We can't just always use getattr() because in doing @@ -57,46 +53,22 @@ def lookup_attribute(parent, attribute): return (parent, attribute, original) - def apply_patch(parent, attribute, replacement): - """ - Convenience function for applying a patch to an attribute. Currently this - maps to the standard setattr() function, but in the future may be extended - to support more complex patching strategies. - """ - setattr(parent, attribute, replacement) - -def wrap_object(target, name, factory, args=(), kwargs={}): - """ - Wraps an object which is the attribute of a target object with a wrapper - object created by the `factory` function. The `target` can be a module, - class, or instance of a class. In the special case of `target` being a - string, it is assumed to be the name of a module, with the module being - imported if necessary and then used as the target object. The `name` is a - string representing the dotted path to the attribute. The `factory` function - should accept the original object and may accept additional positional and - keyword arguments which will be set by unpacking input arguments using - `*args` and `**kwargs` calling conventions. The factory function should - return a new object that will replace the original object. - """ - - (parent, attribute, original) = resolve_path(target, name) +def wrap_object(module, name, factory, args=(), kwargs={}): + (parent, attribute, original) = resolve_path(module, name) wrapper = factory(original, *args, **kwargs) apply_patch(parent, attribute, wrapper) - return wrapper - # Function for applying a proxy object to an attribute of a class # instance. The wrapper works by defining an attribute of the same name # on the class which is a descriptor and which intercepts access to the # instance attribute. Note that this cannot be used on attributes which # are themselves defined by a property object. - -class AttributeWrapper: +class AttributeWrapper(object): def __init__(self, attribute, factory, args, kwargs): self.attribute = attribute @@ -114,47 +86,19 @@ def __set__(self, instance, value): def __delete__(self, instance): del instance.__dict__[self.attribute] - def wrap_object_attribute(module, name, factory, args=(), kwargs={}): - """ - Wraps an object which is the attribute of a class instance with a wrapper - object created by the `factory` function. It does this by patching the - class, not the instance, with a descriptor that intercepts access to the - instance attribute. The `module` can be a module, class, or instance of a - class. In the special case of `module` being a string, it is assumed to be - the name of a module, with the module being imported if necessary and then - used as the target object. The `name` is a string representing the dotted - path to the attribute. The `factory` function should accept the original - object and may accept additional positional and keyword arguments which will - be set by unpacking input arguments using `*args` and `**kwargs` calling - conventions. The factory function should return a new object that will - replace the original object. - """ - - path, attribute = name.rsplit(".", 1) + path, attribute = name.rsplit('.', 1) parent = resolve_path(module, path)[2] wrapper = AttributeWrapper(attribute, factory, args, kwargs) apply_patch(parent, attribute, wrapper) return wrapper - # Functions for creating a simple decorator using a FunctionWrapper, # plus short cut functions for applying wrappers to functions. These are # for use when doing monkey patching. For a more featured way of # creating decorators see the decorator decorator instead. - def function_wrapper(wrapper): - """ - Creates a decorator for wrapping a function with a `wrapper` function. - The decorator which is returned may also be applied to any other callable - objects such as lambda functions, methods, classmethods, and staticmethods, - or objects which implement the `__call__()` method. The `wrapper` function - should accept the `wrapped` function, `instance`, `args`, and `kwargs`, - arguments and return the result of calling the wrapped function or some - other appropriate value. - """ - def _wrapper(wrapped, instance, args, kwargs): target_wrapped = args[0] if instance is None: @@ -164,55 +108,17 @@ def _wrapper(wrapped, instance, args, kwargs): else: target_wrapper = wrapper.__get__(instance, type(instance)) return FunctionWrapper(target_wrapped, target_wrapper) - return FunctionWrapper(wrapper, _wrapper) +def wrap_function_wrapper(module, name, wrapper): + return wrap_object(module, name, FunctionWrapper, (wrapper,)) -def wrap_function_wrapper(target, name, wrapper): - """ - Wraps a function which is the attribute of a target object with a `wrapper` - function. The `target` can be a module, class, or instance of a class. In - the special case of `target` being a string, it is assumed to be the name - of a module, with the module being imported if necessary. The `name` is a - string representing the dotted path to the attribute. The `wrapper` function - should accept the `wrapped` function, `instance`, `args`, and `kwargs` - arguments, and would return the result of calling the wrapped attribute or - some other appropriate value. - """ - - return wrap_object(target, name, FunctionWrapper, (wrapper,)) - - -def patch_function_wrapper(target, name, enabled=None): - """ - Creates a decorator which can be applied to a wrapper function, where the - wrapper function will be used to wrap a function which is the attribute of - a target object. The `target` can be a module, class, or instance of a class. - In the special case of `target` being a string, it is assumed to be the name - of a module, with the module being imported if necessary. The `name` is a - string representing the dotted path to the attribute. The `enabled` - argument can be a boolean or a callable that returns a boolean. When a - callable is provided, it will be called each time the wrapper is invoked to - determine if the wrapper function should be executed or whether the wrapped - function should be called directly. If `enabled` is not provided, the - wrapper is enabled by default. - """ - +def patch_function_wrapper(module, name, enabled=None): def _wrapper(wrapper): - return wrap_object(target, name, FunctionWrapper, (wrapper, enabled)) - + return wrap_object(module, name, FunctionWrapper, (wrapper, enabled)) return _wrapper - -def transient_function_wrapper(target, name): - """Creates a decorator that patches a target function with a wrapper - function, but only for the duration of the call that the decorator was - applied to. The `target` can be a module, class, or instance of a class. - In the special case of `target` being a string, it is assumed to be the name - of a module, with the module being imported if necessary. The `name` is a - string representing the dotted path to the attribute. - """ - +def transient_function_wrapper(module, name): def _decorator(wrapper): def _wrapper(wrapped, instance, args, kwargs): target_wrapped = args[0] @@ -222,18 +128,14 @@ def _wrapper(wrapped, instance, args, kwargs): target_wrapper = wrapper.__get__(None, instance) else: target_wrapper = wrapper.__get__(instance, type(instance)) - def _execute(wrapped, instance, args, kwargs): - (parent, attribute, original) = resolve_path(target, name) + (parent, attribute, original) = resolve_path(module, name) replacement = FunctionWrapper(original, target_wrapper) setattr(parent, attribute, replacement) try: return wrapped(*args, **kwargs) finally: setattr(parent, attribute, original) - return FunctionWrapper(target_wrapped, _execute) - return FunctionWrapper(wrapper, _wrapper) - return _decorator diff --git a/newrelic/packages/wrapt/proxies.py b/newrelic/packages/wrapt/proxies.py deleted file mode 100644 index fbb6c9e39a..0000000000 --- a/newrelic/packages/wrapt/proxies.py +++ /dev/null @@ -1,351 +0,0 @@ -"""Variants of ObjectProxy for different use cases.""" - -from collections.abc import Callable -from types import ModuleType - -from .__wrapt__ import BaseObjectProxy -from .decorators import synchronized - -# Define ObjectProxy which for compatibility adds `__iter__()` support which -# has been removed from `BaseObjectProxy`. - - -class ObjectProxy(BaseObjectProxy): - """A generic object proxy which forwards special methods as needed. - For backwards compatibility this class adds support for `__iter__()`. If - you don't need backward compatibility for `__iter__()` support then it is - preferable to use `BaseObjectProxy` directly. If you want automatic - support for special dunder methods for callables, iterators, and async, - then use `AutoObjectProxy`.""" - - @property - def __object_proxy__(self): - return ObjectProxy - - def __new__(cls, *args, **kwargs): - return super().__new__(cls) - - def __iter__(self): - return iter(self.__wrapped__) - - -# Define variant of ObjectProxy which can automatically adjust to the wrapped -# object and add special dunder methods. - - -def __wrapper_call__(*args, **kwargs): - def _unpack_self(self, *args): - return self, args - - self, args = _unpack_self(*args) - - return self.__wrapped__(*args, **kwargs) - - -def __wrapper_iter__(self): - return iter(self.__wrapped__) - - -def __wrapper_next__(self): - return self.__wrapped__.__next__() - - -def __wrapper_aiter__(self): - return self.__wrapped__.__aiter__() - - -async def __wrapper_anext__(self): - return await self.__wrapped__.__anext__() - - -def __wrapper_length_hint__(self): - return self.__wrapped__.__length_hint__() - - -def __wrapper_await__(self): - return (yield from self.__wrapped__.__await__()) - - -def __wrapper_get__(self, instance, owner): - return self.__wrapped__.__get__(instance, owner) - - -def __wrapper_set__(self, instance, value): - return self.__wrapped__.__set__(instance, value) - - -def __wrapper_delete__(self, instance): - return self.__wrapped__.__delete__(instance) - - -def __wrapper_set_name__(self, owner, name): - return self.__wrapped__.__set_name__(owner, name) - - -class AutoObjectProxy(BaseObjectProxy): - """An object proxy which can automatically adjust to the wrapped object - and add special dunder methods as needed. Note that this creates a new - class for each instance, so it has much higher memory overhead than using - `BaseObjectProxy` directly. If you know what special dunder methods you need - then it is preferable to use `BaseObjectProxy` directly and add them to a - subclass as needed. If you only need `__iter__()` support for backwards - compatibility then use `ObjectProxy` instead. - """ - - def __new__(cls, wrapped): - """Injects special dunder methods into a dynamically created subclass - as needed based on the wrapped object. - """ - - namespace = {} - - wrapped_attrs = dir(wrapped) - class_attrs = set(dir(cls)) - - if callable(wrapped) and "__call__" not in class_attrs: - namespace["__call__"] = __wrapper_call__ - - if "__iter__" in wrapped_attrs and "__iter__" not in class_attrs: - namespace["__iter__"] = __wrapper_iter__ - - if "__next__" in wrapped_attrs and "__next__" not in class_attrs: - namespace["__next__"] = __wrapper_next__ - - if "__aiter__" in wrapped_attrs and "__aiter__" not in class_attrs: - namespace["__aiter__"] = __wrapper_aiter__ - - if "__anext__" in wrapped_attrs and "__anext__" not in class_attrs: - namespace["__anext__"] = __wrapper_anext__ - - if "__length_hint__" in wrapped_attrs and "__length_hint__" not in class_attrs: - namespace["__length_hint__"] = __wrapper_length_hint__ - - # Note that not providing compatibility with generator-based coroutines - # (PEP 342) here as they are removed in Python 3.11+ and were deprecated - # in 3.8. - - if "__await__" in wrapped_attrs and "__await__" not in class_attrs: - namespace["__await__"] = __wrapper_await__ - - if "__get__" in wrapped_attrs and "__get__" not in class_attrs: - namespace["__get__"] = __wrapper_get__ - - if "__set__" in wrapped_attrs and "__set__" not in class_attrs: - namespace["__set__"] = __wrapper_set__ - - if "__delete__" in wrapped_attrs and "__delete__" not in class_attrs: - namespace["__delete__"] = __wrapper_delete__ - - if "__set_name__" in wrapped_attrs and "__set_name__" not in class_attrs: - namespace["__set_name__"] = __wrapper_set_name__ - - name = cls.__name__ - - if cls is AutoObjectProxy: - name = BaseObjectProxy.__name__ - - return super(AutoObjectProxy, cls).__new__(type(name, (cls,), namespace)) - - def __wrapped_setattr_fixups__(self): - """Adjusts special dunder methods on the class as needed based on the - wrapped object, when `__wrapped__` is changed. - """ - - cls = type(self) - class_attrs = set(dir(cls)) - - if callable(self.__wrapped__): - if "__call__" not in class_attrs: - cls.__call__ = __wrapper_call__ - elif getattr(cls, "__call__", None) is __wrapper_call__: - delattr(cls, "__call__") - - if hasattr(self.__wrapped__, "__iter__"): - if "__iter__" not in class_attrs: - cls.__iter__ = __wrapper_iter__ - elif getattr(cls, "__iter__", None) is __wrapper_iter__: - delattr(cls, "__iter__") - - if hasattr(self.__wrapped__, "__next__"): - if "__next__" not in class_attrs: - cls.__next__ = __wrapper_next__ - elif getattr(cls, "__next__", None) is __wrapper_next__: - delattr(cls, "__next__") - - if hasattr(self.__wrapped__, "__aiter__"): - if "__aiter__" not in class_attrs: - cls.__aiter__ = __wrapper_aiter__ - elif getattr(cls, "__aiter__", None) is __wrapper_aiter__: - delattr(cls, "__aiter__") - - if hasattr(self.__wrapped__, "__anext__"): - if "__anext__" not in class_attrs: - cls.__anext__ = __wrapper_anext__ - elif getattr(cls, "__anext__", None) is __wrapper_anext__: - delattr(cls, "__anext__") - - if hasattr(self.__wrapped__, "__length_hint__"): - if "__length_hint__" not in class_attrs: - cls.__length_hint__ = __wrapper_length_hint__ - elif getattr(cls, "__length_hint__", None) is __wrapper_length_hint__: - delattr(cls, "__length_hint__") - - if hasattr(self.__wrapped__, "__await__"): - if "__await__" not in class_attrs: - cls.__await__ = __wrapper_await__ - elif getattr(cls, "__await__", None) is __wrapper_await__: - delattr(cls, "__await__") - - if hasattr(self.__wrapped__, "__get__"): - if "__get__" not in class_attrs: - cls.__get__ = __wrapper_get__ - elif getattr(cls, "__get__", None) is __wrapper_get__: - delattr(cls, "__get__") - - if hasattr(self.__wrapped__, "__set__"): - if "__set__" not in class_attrs: - cls.__set__ = __wrapper_set__ - elif getattr(cls, "__set__", None) is __wrapper_set__: - delattr(cls, "__set__") - - if hasattr(self.__wrapped__, "__delete__"): - if "__delete__" not in class_attrs: - cls.__delete__ = __wrapper_delete__ - elif getattr(cls, "__delete__", None) is __wrapper_delete__: - delattr(cls, "__delete__") - - if hasattr(self.__wrapped__, "__set_name__"): - if "__set_name__" not in class_attrs: - cls.__set_name__ = __wrapper_set_name__ - elif getattr(cls, "__set_name__", None) is __wrapper_set_name__: - delattr(cls, "__set_name__") - - -class LazyObjectProxy(AutoObjectProxy): - """An object proxy which can generate/create the wrapped object on demand - when it is first needed. - """ - - def __new__(cls, callback=None, *, interface=...): - """Injects special dunder methods into a dynamically created subclass - as needed based on the wrapped object. - """ - - if interface is ...: - interface = type(None) - - namespace = {} - - interface_attrs = dir(interface) - class_attrs = set(dir(cls)) - - if "__call__" in interface_attrs and "__call__" not in class_attrs: - namespace["__call__"] = __wrapper_call__ - - if "__iter__" in interface_attrs and "__iter__" not in class_attrs: - namespace["__iter__"] = __wrapper_iter__ - - if "__next__" in interface_attrs and "__next__" not in class_attrs: - namespace["__next__"] = __wrapper_next__ - - if "__aiter__" in interface_attrs and "__aiter__" not in class_attrs: - namespace["__aiter__"] = __wrapper_aiter__ - - if "__anext__" in interface_attrs and "__anext__" not in class_attrs: - namespace["__anext__"] = __wrapper_anext__ - - if ( - "__length_hint__" in interface_attrs - and "__length_hint__" not in class_attrs - ): - namespace["__length_hint__"] = __wrapper_length_hint__ - - # Note that not providing compatibility with generator-based coroutines - # (PEP 342) here as they are removed in Python 3.11+ and were deprecated - # in 3.8. - - if "__await__" in interface_attrs and "__await__" not in class_attrs: - namespace["__await__"] = __wrapper_await__ - - if "__get__" in interface_attrs and "__get__" not in class_attrs: - namespace["__get__"] = __wrapper_get__ - - if "__set__" in interface_attrs and "__set__" not in class_attrs: - namespace["__set__"] = __wrapper_set__ - - if "__delete__" in interface_attrs and "__delete__" not in class_attrs: - namespace["__delete__"] = __wrapper_delete__ - - if "__set_name__" in interface_attrs and "__set_name__" not in class_attrs: - namespace["__set_name__"] = __wrapper_set_name__ - - name = cls.__name__ - - return super(AutoObjectProxy, cls).__new__(type(name, (cls,), namespace)) - - def __init__(self, callback=None, *, interface=...): - """Initialize the object proxy with wrapped object as `None` but due - to presence of special `__wrapped_factory__` attribute addded first, - this will actually trigger the deferred creation of the wrapped object - when first needed. - """ - - if callback is not None: - self.__wrapped_factory__ = callback - - super().__init__(None) - - __wrapped_initialized__ = False - - def __wrapped_factory__(self): - return None - - def __wrapped_get__(self): - """Gets the wrapped object, creating it if necessary.""" - - # We synchronize on the class type, which will be unique to this instance - # since we inherit from `AutoObjectProxy` which creates a new class - # for each instance. If we synchronize on `self` or the method then - # we can end up in infinite recursion via `__getattr__()`. - - with synchronized(type(self)): - # We were called because `__wrapped__` was not set, but because of - # multiple threads we may find that it has been set by the time - # we get the lock. So check again now whether `__wrapped__` is set. - # If it is then just return it, otherwise call the factory to - # create it. - - if self.__wrapped_initialized__: - return self.__wrapped__ - - self.__wrapped__ = self.__wrapped_factory__() - - self.__wrapped_initialized__ = True - - return self.__wrapped__ - - -def lazy_import(name, attribute=None, *, interface=...): - """Lazily imports the module `name`, returning a `LazyObjectProxy` which - will import the module when it is first needed. When `name is a dotted name, - then the full dotted name is imported and the last module is taken as the - target. If `attribute` is provided then it is used to retrieve an attribute - from the module. - """ - - if attribute is not None: - if interface is ...: - interface = Callable - else: - if interface is ...: - interface = ModuleType - - def _import(): - module = __import__(name, fromlist=[""]) - - if attribute is not None: - return getattr(module, attribute) - - return module - - return LazyObjectProxy(_import, interface=interface) diff --git a/newrelic/packages/wrapt/py.typed b/newrelic/packages/wrapt/py.typed deleted file mode 100644 index b648ac9233..0000000000 --- a/newrelic/packages/wrapt/py.typed +++ /dev/null @@ -1 +0,0 @@ -partial diff --git a/newrelic/packages/wrapt/weakrefs.py b/newrelic/packages/wrapt/weakrefs.py index dc8e7eb2d3..f931b60d5f 100644 --- a/newrelic/packages/wrapt/weakrefs.py +++ b/newrelic/packages/wrapt/weakrefs.py @@ -1,7 +1,7 @@ import functools import weakref -from .__wrapt__ import BaseObjectProxy, _FunctionWrapperBase +from .__wrapt__ import ObjectProxy, _FunctionWrapperBase # A weak function proxy. This will work on instance methods, class # methods, static methods and regular functions. Special treatment is @@ -12,7 +12,6 @@ # and the original function. The function is then rebound at the point # of a call via the weak function proxy. - def _weak_function_proxy_callback(ref, proxy, callback): if proxy._self_expired: return @@ -26,25 +25,11 @@ def _weak_function_proxy_callback(ref, proxy, callback): if callback is not None: callback(proxy) +class WeakFunctionProxy(ObjectProxy): -class WeakFunctionProxy(BaseObjectProxy): - """A weak function proxy.""" - - __slots__ = ("_self_expired", "_self_instance") + __slots__ = ('_self_expired', '_self_instance') def __init__(self, wrapped, callback=None): - """Create a proxy to object which uses a weak reference. This is - similar to the `weakref.proxy` but is designed to work with functions - and methods. It will automatically rebind the function to the instance - when called if the function was originally a bound method. This is - necessary because bound methods are transient objects and applying a - weak reference to one will immediately result in it being destroyed - and the weakref callback called. The weak reference is therefore - applied to the instance the method is bound to and the original - function. The function is then rebound at the point of a call via the - weak function proxy. - """ - # We need to determine if the wrapped function is actually a # bound method. In the case of a bound method, we need to keep a # reference to the original unbound function and the instance. @@ -58,23 +43,22 @@ def __init__(self, wrapped, callback=None): # the callback here so as not to cause any odd reference cycles. _callback = callback and functools.partial( - _weak_function_proxy_callback, proxy=self, callback=callback - ) + _weak_function_proxy_callback, proxy=self, + callback=callback) self._self_expired = False if isinstance(wrapped, _FunctionWrapperBase): - self._self_instance = weakref.ref(wrapped._self_instance, _callback) + self._self_instance = weakref.ref(wrapped._self_instance, + _callback) if wrapped._self_parent is not None: super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped._self_parent, _callback) - ) + weakref.proxy(wrapped._self_parent, _callback)) else: super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped, _callback) - ) + weakref.proxy(wrapped, _callback)) return @@ -82,13 +66,13 @@ def __init__(self, wrapped, callback=None): self._self_instance = weakref.ref(wrapped.__self__, _callback) super(WeakFunctionProxy, self).__init__( - weakref.proxy(wrapped.__func__, _callback) - ) + weakref.proxy(wrapped.__func__, _callback)) except AttributeError: self._self_instance = None - super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback)) + super(WeakFunctionProxy, self).__init__( + weakref.proxy(wrapped, _callback)) def __call__(*args, **kwargs): def _unpack_self(self, *args): diff --git a/newrelic/packages/wrapt/wrappers.py b/newrelic/packages/wrapt/wrappers.py index 445d0b2c6e..dfc3440db4 100644 --- a/newrelic/packages/wrapt/wrappers.py +++ b/newrelic/packages/wrapt/wrappers.py @@ -1,24 +1,19 @@ -import inspect -import operator import sys +import operator +import inspect +PY2 = sys.version_info[0] == 2 + +if PY2: + string_types = basestring, +else: + string_types = str, def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" return meta("NewBase", bases, {}) - -class WrapperNotInitializedError(ValueError, AttributeError): - """ - Exception raised when a wrapper is accessed before it has been initialized. - To satisfy different situations where this could arise, we inherit from both - ValueError and AttributeError. - """ - - pass - - -class _ObjectProxyMethods: +class _ObjectProxyMethods(object): # We use properties to override the values of __module__ and # __doc__. If we add these in ObjectProxy, the derived class @@ -61,7 +56,6 @@ def __dict__(self): def __weakref__(self): return self.__wrapped__.__weakref__ - class _ObjectProxyMetaType(type): def __new__(cls, name, bases, dictionary): # Copy our special properties into the class so that they @@ -73,47 +67,19 @@ def __new__(cls, name, bases, dictionary): return type.__new__(cls, name, bases, dictionary) +class ObjectProxy(with_metaclass(_ObjectProxyMetaType)): -# NOTE: Although Python 3+ supports the newer metaclass=MetaClass syntax, -# we must continue using with_metaclass() for ObjectProxy. The newer syntax -# changes how __slots__ is handled during class creation, which would break -# the ability to set _self_* attributes on ObjectProxy instances. The -# with_metaclass() approach creates an intermediate base class that allows -# the necessary attribute flexibility while still applying the metaclass. - - -class ObjectProxy(with_metaclass(_ObjectProxyMetaType)): # type: ignore[misc] - - __slots__ = "__wrapped__" + __slots__ = '__wrapped__' def __init__(self, wrapped): - """Create an object proxy around the given object.""" - - if wrapped is None: - try: - callback = object.__getattribute__(self, "__wrapped_factory__") - except AttributeError: - callback = None - - if callback is not None: - # If wrapped is none and class has a __wrapped_factory__ - # method, then we don't set __wrapped__ yet and instead will - # defer creation of the wrapped object until it is first - # needed. - - pass - - else: - object.__setattr__(self, "__wrapped__", wrapped) - else: - object.__setattr__(self, "__wrapped__", wrapped) + object.__setattr__(self, '__wrapped__', wrapped) # Python 3.2+ has the __qualname__ attribute, but it does not # allow it to be overridden using a property and it must instead # be an actual string object instead. try: - object.__setattr__(self, "__qualname__", wrapped.__qualname__) + object.__setattr__(self, '__qualname__', wrapped.__qualname__) except AttributeError: pass @@ -121,14 +87,10 @@ def __init__(self, wrapped): # using a property and it must instead be set explicitly. try: - object.__setattr__(self, "__annotations__", wrapped.__annotations__) + object.__setattr__(self, '__annotations__', wrapped.__annotations__) except AttributeError: pass - @property - def __object_proxy__(self): - return ObjectProxy - def __self_setattr__(self, name, value): object.__setattr__(self, name, value) @@ -154,27 +116,26 @@ def __dir__(self): def __str__(self): return str(self.__wrapped__) - def __bytes__(self): - return bytes(self.__wrapped__) + if not PY2: + def __bytes__(self): + return bytes(self.__wrapped__) def __repr__(self): - return f"<{type(self).__name__} at 0x{id(self):x} for {type(self.__wrapped__).__name__} at 0x{id(self.__wrapped__):x}>" - - def __format__(self, format_spec): - return format(self.__wrapped__, format_spec) + return '<{} at 0x{:x} for {} at 0x{:x}>'.format( + type(self).__name__, id(self), + type(self.__wrapped__).__name__, + id(self.__wrapped__)) def __reversed__(self): return reversed(self.__wrapped__) - def __round__(self, ndigits=None): - return round(self.__wrapped__, ndigits) + if not PY2: + def __round__(self): + return round(self.__wrapped__) - def __mro_entries__(self, bases): - if not isinstance(self.__wrapped__, type) and hasattr( - self.__wrapped__, "__mro_entries__" - ): - return self.__wrapped__.__mro_entries__(bases) - return (self.__wrapped__,) + if sys.hexversion >= 0x03070000: + def __mro_entries__(self, bases): + return (self.__wrapped__,) def __lt__(self, other): return self.__wrapped__ < other @@ -204,41 +165,33 @@ def __bool__(self): return bool(self.__wrapped__) def __setattr__(self, name, value): - if name.startswith("_self_"): + if name.startswith('_self_'): object.__setattr__(self, name, value) - elif name == "__wrapped__": + elif name == '__wrapped__': object.__setattr__(self, name, value) - try: - object.__delattr__(self, "__qualname__") + object.__delattr__(self, '__qualname__') except AttributeError: pass try: - object.__setattr__(self, "__qualname__", value.__qualname__) + object.__setattr__(self, '__qualname__', value.__qualname__) except AttributeError: pass try: - object.__delattr__(self, "__annotations__") + object.__delattr__(self, '__annotations__') except AttributeError: pass try: - object.__setattr__(self, "__annotations__", value.__annotations__) + object.__setattr__(self, '__annotations__', value.__annotations__) except AttributeError: pass - __wrapped_setattr_fixups__ = getattr( - self, "__wrapped_setattr_fixups__", None - ) - - if __wrapped_setattr_fixups__ is not None: - __wrapped_setattr_fixups__() - - elif name == "__qualname__": + elif name == '__qualname__': setattr(self.__wrapped__, name, value) object.__setattr__(self, name, value) - elif name == "__annotations__": + elif name == '__annotations__': setattr(self.__wrapped__, name, value) object.__setattr__(self, name, value) @@ -249,37 +202,22 @@ def __setattr__(self, name, value): setattr(self.__wrapped__, name, value) def __getattr__(self, name): - # If we need to lookup `__wrapped__` then the `__init__()` method - # cannot have been called, or this is a lazy object proxy which is - # deferring creation of the wrapped object until it is first needed. - - if name == "__wrapped__": - # Note that we use existance of `__wrapped_factory__` to gate whether - # we can attempt to initialize the wrapped object lazily, but it is - # `__wrapped_get__` that we actually call to do the initialization. - # This is so that we can handle multithreading correctly by having - # `__wrapped_get__` use a lock to protect against multiple threads - # trying to initialize the wrapped object at the same time. + # If we are being to lookup '__wrapped__' then the + # '__init__()' method cannot have been called. - try: - object.__getattribute__(self, "__wrapped_factory__") - except AttributeError: - pass - else: - return object.__getattribute__(self, "__wrapped_get__")() - - raise WrapperNotInitializedError("wrapper has not been initialized") + if name == '__wrapped__': + raise ValueError('wrapper has not been initialised') return getattr(self.__wrapped__, name) def __delattr__(self, name): - if name.startswith("_self_"): + if name.startswith('_self_'): object.__delattr__(self, name) - elif name == "__wrapped__": - raise TypeError("__wrapped__ attribute cannot be deleted") + elif name == '__wrapped__': + raise TypeError('__wrapped__ must be an object') - elif name == "__qualname__": + elif name == '__qualname__': object.__delattr__(self, name) delattr(self.__wrapped__, name) @@ -298,6 +236,9 @@ def __sub__(self, other): def __mul__(self, other): return self.__wrapped__ * other + def __div__(self, other): + return operator.div(self.__wrapped__, other) + def __truediv__(self, other): return operator.truediv(self.__wrapped__, other) @@ -337,6 +278,9 @@ def __rsub__(self, other): def __rmul__(self, other): return other * self.__wrapped__ + def __rdiv__(self, other): + return operator.div(other, self.__wrapped__) + def __rtruediv__(self, other): return operator.truediv(other, self.__wrapped__) @@ -368,90 +312,56 @@ def __ror__(self, other): return other | self.__wrapped__ def __iadd__(self, other): - if hasattr(self.__wrapped__, "__iadd__"): - self.__wrapped__ += other - return self - else: - return self.__object_proxy__(self.__wrapped__ + other) + self.__wrapped__ += other + return self def __isub__(self, other): - if hasattr(self.__wrapped__, "__isub__"): - self.__wrapped__ -= other - return self - else: - return self.__object_proxy__(self.__wrapped__ - other) + self.__wrapped__ -= other + return self def __imul__(self, other): - if hasattr(self.__wrapped__, "__imul__"): - self.__wrapped__ *= other - return self - else: - return self.__object_proxy__(self.__wrapped__ * other) + self.__wrapped__ *= other + return self + + def __idiv__(self, other): + self.__wrapped__ = operator.idiv(self.__wrapped__, other) + return self def __itruediv__(self, other): - if hasattr(self.__wrapped__, "__itruediv__"): - self.__wrapped__ /= other - return self - else: - return self.__object_proxy__(self.__wrapped__ / other) + self.__wrapped__ = operator.itruediv(self.__wrapped__, other) + return self def __ifloordiv__(self, other): - if hasattr(self.__wrapped__, "__ifloordiv__"): - self.__wrapped__ //= other - return self - else: - return self.__object_proxy__(self.__wrapped__ // other) + self.__wrapped__ //= other + return self def __imod__(self, other): - if hasattr(self.__wrapped__, "__imod__"): - self.__wrapped__ %= other - return self - else: - return self.__object_proxy__(self.__wrapped__ % other) - + self.__wrapped__ %= other return self - def __ipow__(self, other): # type: ignore[misc] - if hasattr(self.__wrapped__, "__ipow__"): - self.__wrapped__ **= other - return self - else: - return self.__object_proxy__(self.__wrapped__**other) + def __ipow__(self, other): + self.__wrapped__ **= other + return self def __ilshift__(self, other): - if hasattr(self.__wrapped__, "__ilshift__"): - self.__wrapped__ <<= other - return self - else: - return self.__object_proxy__(self.__wrapped__ << other) + self.__wrapped__ <<= other + return self def __irshift__(self, other): - if hasattr(self.__wrapped__, "__irshift__"): - self.__wrapped__ >>= other - return self - else: - return self.__object_proxy__(self.__wrapped__ >> other) + self.__wrapped__ >>= other + return self def __iand__(self, other): - if hasattr(self.__wrapped__, "__iand__"): - self.__wrapped__ &= other - return self - else: - return self.__object_proxy__(self.__wrapped__ & other) + self.__wrapped__ &= other + return self def __ixor__(self, other): - if hasattr(self.__wrapped__, "__ixor__"): - self.__wrapped__ ^= other - return self - else: - return self.__object_proxy__(self.__wrapped__ ^ other) + self.__wrapped__ ^= other + return self def __ior__(self, other): - if hasattr(self.__wrapped__, "__ior__"): - self.__wrapped__ |= other - return self - else: - return self.__object_proxy__(self.__wrapped__ | other) + self.__wrapped__ |= other + return self def __neg__(self): return -self.__wrapped__ @@ -468,6 +378,9 @@ def __invert__(self): def __int__(self): return int(self.__wrapped__) + def __long__(self): + return long(self.__wrapped__) + def __float__(self): return float(self.__wrapped__) @@ -483,19 +396,6 @@ def __hex__(self): def __index__(self): return operator.index(self.__wrapped__) - def __matmul__(self, other): - return self.__wrapped__ @ other - - def __rmatmul__(self, other): - return other @ self.__wrapped__ - - def __imatmul__(self, other): - if hasattr(self.__wrapped__, "__imatmul__"): - self.__wrapped__ @= other - return self - else: - return self.__object_proxy__(self.__wrapped__ @ other) - def __len__(self): return len(self.__wrapped__) @@ -526,24 +426,22 @@ def __enter__(self): def __exit__(self, *args, **kwargs): return self.__wrapped__.__exit__(*args, **kwargs) - def __aenter__(self): - return self.__wrapped__.__aenter__() - - def __aexit__(self, *args, **kwargs): - return self.__wrapped__.__aexit__(*args, **kwargs) + def __iter__(self): + return iter(self.__wrapped__) def __copy__(self): - raise NotImplementedError("object proxy must define __copy__()") + raise NotImplementedError('object proxy must define __copy__()') def __deepcopy__(self, memo): - raise NotImplementedError("object proxy must define __deepcopy__()") + raise NotImplementedError('object proxy must define __deepcopy__()') def __reduce__(self): - raise NotImplementedError("object proxy must define __reduce__()") + raise NotImplementedError( + 'object proxy must define __reduce_ex__()') def __reduce_ex__(self, protocol): - raise NotImplementedError("object proxy must define __reduce_ex__()") - + raise NotImplementedError( + 'object proxy must define __reduce_ex__()') class CallableObjectProxy(ObjectProxy): @@ -555,31 +453,21 @@ def _unpack_self(self, *args): return self.__wrapped__(*args, **kwargs) - class PartialCallableObjectProxy(ObjectProxy): - """A callable object proxy that supports partial application of arguments - and keywords. - """ def __init__(*args, **kwargs): - """Create a callable object proxy with partial application of the given - arguments and keywords. This behaves the same as `functools.partial`, but - implemented using the `ObjectProxy` class to provide better support for - introspection. - """ - def _unpack_self(self, *args): return self, args self, args = _unpack_self(*args) if len(args) < 1: - raise TypeError("partial type takes at least one argument") + raise TypeError('partial type takes at least one argument') wrapped, args = args[0], args[1:] if not callable(wrapped): - raise TypeError("the first argument must be callable") + raise TypeError('the first argument must be callable') super(PartialCallableObjectProxy, self).__init__(wrapped) @@ -591,7 +479,7 @@ def _unpack_self(self, *args): return self, args self, args = _unpack_self(*args) - + _args = self._self_args + args _kwargs = dict(self._self_kwargs) @@ -599,112 +487,75 @@ def _unpack_self(self, *args): return self.__wrapped__(*_args, **_kwargs) - class _FunctionWrapperBase(ObjectProxy): - __slots__ = ( - "_self_instance", - "_self_wrapper", - "_self_enabled", - "_self_binding", - "_self_parent", - "_self_owner", - ) - - def __init__( - self, - wrapped, - instance, - wrapper, - enabled=None, - binding="callable", - parent=None, - owner=None, - ): + __slots__ = ('_self_instance', '_self_wrapper', '_self_enabled', + '_self_binding', '_self_parent') + + def __init__(self, wrapped, instance, wrapper, enabled=None, + binding='function', parent=None): super(_FunctionWrapperBase, self).__init__(wrapped) - object.__setattr__(self, "_self_instance", instance) - object.__setattr__(self, "_self_wrapper", wrapper) - object.__setattr__(self, "_self_enabled", enabled) - object.__setattr__(self, "_self_binding", binding) - object.__setattr__(self, "_self_parent", parent) - object.__setattr__(self, "_self_owner", owner) + object.__setattr__(self, '_self_instance', instance) + object.__setattr__(self, '_self_wrapper', wrapper) + object.__setattr__(self, '_self_enabled', enabled) + object.__setattr__(self, '_self_binding', binding) + object.__setattr__(self, '_self_parent', parent) def __get__(self, instance, owner): - # This method is actually doing double duty for both unbound and bound - # derived wrapper classes. It should possibly be broken up and the - # distinct functionality moved into the derived classes. Can't do that - # straight away due to some legacy code which is relying on it being - # here in this base class. + # This method is actually doing double duty for both unbound and + # bound derived wrapper classes. It should possibly be broken up + # and the distinct functionality moved into the derived classes. + # Can't do that straight away due to some legacy code which is + # relying on it being here in this base class. # - # The distinguishing attribute which determines whether we are being - # called in an unbound or bound wrapper is the parent attribute. If - # binding has never occurred, then the parent will be None. + # The distinguishing attribute which determines whether we are + # being called in an unbound or bound wrapper is the parent + # attribute. If binding has never occurred, then the parent will + # be None. # - # First therefore, is if we are called in an unbound wrapper. In this - # case we perform the binding. + # First therefore, is if we are called in an unbound wrapper. In + # this case we perform the binding. # - # We have two special cases to worry about here. These are where we are - # decorating a class or builtin function as neither provide a __get__() - # method to call. In this case we simply return self. + # We have one special case to worry about here. This is where we + # are decorating a nested class. In this case the wrapped class + # would not have a __get__() method to call. In that case we + # simply return self. # - # Note that we otherwise still do binding even if instance is None and - # accessing an unbound instance method from a class. This is because we - # need to be able to later detect that specific case as we will need to - # extract the instance from the first argument of those passed in. + # Note that we otherwise still do binding even if instance is + # None and accessing an unbound instance method from a class. + # This is because we need to be able to later detect that + # specific case as we will need to extract the instance from the + # first argument of those passed in. if self._self_parent is None: - # Technically can probably just check for existence of __get__ on - # the wrapped object, but this is more explicit. - - if self._self_binding == "builtin": - return self - - if self._self_binding == "class": - return self - - binder = getattr(self.__wrapped__, "__get__", None) + if not inspect.isclass(self.__wrapped__): + descriptor = self.__wrapped__.__get__(instance, owner) - if binder is None: - return self + return self.__bound_function_wrapper__(descriptor, instance, + self._self_wrapper, self._self_enabled, + self._self_binding, self) - descriptor = binder(instance, owner) - - return self.__bound_function_wrapper__( - descriptor, - instance, - self._self_wrapper, - self._self_enabled, - self._self_binding, - self, - owner, - ) + return self - # Now we have the case of binding occurring a second time on what was - # already a bound function. In this case we would usually return - # ourselves again. This mirrors what Python does. + # Now we have the case of binding occurring a second time on what + # was already a bound function. In this case we would usually + # return ourselves again. This mirrors what Python does. # - # The special case this time is where we were originally bound with an - # instance of None and we were likely an instance method. In that case - # we rebind against the original wrapped function from the parent again. + # The special case this time is where we were originally bound + # with an instance of None and we were likely an instance + # method. In that case we rebind against the original wrapped + # function from the parent again. - if self._self_instance is None and self._self_binding in ( - "function", - "instancemethod", - "callable", - ): - descriptor = self._self_parent.__wrapped__.__get__(instance, owner) + if self._self_instance is None and self._self_binding == 'function': + descriptor = self._self_parent.__wrapped__.__get__( + instance, owner) return self._self_parent.__bound_function_wrapper__( - descriptor, - instance, - self._self_wrapper, - self._self_enabled, - self._self_binding, - self._self_parent, - owner, - ) + descriptor, instance, self._self_wrapper, + self._self_enabled, self._self_binding, + self._self_parent) return self @@ -731,16 +582,12 @@ def _unpack_self(self, *args): # a function that was already bound to an instance. In that case # we want to extract the instance from the function and use it. - if self._self_binding in ( - "function", - "instancemethod", - "classmethod", - "callable", - ): + if self._self_binding in ('function', 'classmethod'): if self._self_instance is None: - instance = getattr(self.__wrapped__, "__self__", None) + instance = getattr(self.__wrapped__, '__self__', None) if instance is not None: - return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + return self._self_wrapper(self.__wrapped__, instance, + args, kwargs) # This is generally invoked when the wrapped function is being # called as a normal function and is not bound to a class as an @@ -748,7 +595,8 @@ def _unpack_self(self, *args): # wrapped function was a method, but this wrapper was in turn # wrapped using the staticmethod decorator. - return self._self_wrapper(self.__wrapped__, self._self_instance, args, kwargs) + return self._self_wrapper(self.__wrapped__, self._self_instance, + args, kwargs) def __set_name__(self, owner, name): # This is a special method use to supply information to @@ -777,7 +625,6 @@ def __subclasscheck__(self, subclass): else: return issubclass(subclass, self.__wrapped__) - class BoundFunctionWrapper(_FunctionWrapperBase): def __call__(*args, **kwargs): @@ -786,11 +633,11 @@ def _unpack_self(self, *args): self, args = _unpack_self(*args) - # If enabled has been specified, then evaluate it at this point and if - # the wrapper is not to be executed, then simply return the bound - # function rather than a bound wrapper for the bound function. When - # evaluating enabled, if it is callable we call it, otherwise we - # evaluate it as a boolean. + # If enabled has been specified, then evaluate it at this point + # and if the wrapper is not to be executed, then simply return + # the bound function rather than a bound wrapper for the bound + # function. When evaluating enabled, if it is callable we call + # it, otherwise we evaluate it as a boolean. if self._self_enabled is not None: if callable(self._self_enabled): @@ -799,39 +646,28 @@ def _unpack_self(self, *args): elif not self._self_enabled: return self.__wrapped__(*args, **kwargs) - # We need to do things different depending on whether we are likely - # wrapping an instance method vs a static method or class method. - - if self._self_binding == "function": - if self._self_instance is None and args: - instance, newargs = args[0], args[1:] - if isinstance(instance, self._self_owner): - wrapped = PartialCallableObjectProxy(self.__wrapped__, instance) - return self._self_wrapper(wrapped, instance, newargs, kwargs) + # We need to do things different depending on whether we are + # likely wrapping an instance method vs a static method or class + # method. - return self._self_wrapper( - self.__wrapped__, self._self_instance, args, kwargs - ) - - elif self._self_binding == "callable": + if self._self_binding == 'function': if self._self_instance is None: # This situation can occur where someone is calling the - # instancemethod via the class type and passing the instance as - # the first argument. We need to shift the args before making - # the call to the wrapper and effectively bind the instance to - # the wrapped function using a partial so the wrapper doesn't - # see anything as being different. + # instancemethod via the class type and passing the instance + # as the first argument. We need to shift the args before + # making the call to the wrapper and effectively bind the + # instance to the wrapped function using a partial so the + # wrapper doesn't see anything as being different. if not args: - raise TypeError("missing 1 required positional argument") + raise TypeError('missing 1 required positional argument') instance, args = args[0], args[1:] wrapped = PartialCallableObjectProxy(self.__wrapped__, instance) return self._self_wrapper(wrapped, instance, args, kwargs) - return self._self_wrapper( - self.__wrapped__, self._self_instance, args, kwargs - ) + return self._self_wrapper(self.__wrapped__, self._self_instance, + args, kwargs) else: # As in this case we would be dealing with a classmethod or @@ -847,32 +683,16 @@ def _unpack_self(self, *args): # class type, as it reflects what they have available in the # decoratored function. - instance = getattr(self.__wrapped__, "__self__", None) - - return self._self_wrapper(self.__wrapped__, instance, args, kwargs) + instance = getattr(self.__wrapped__, '__self__', None) + return self._self_wrapper(self.__wrapped__, instance, args, + kwargs) class FunctionWrapper(_FunctionWrapperBase): - """ - A wrapper for callable objects that can be used to apply decorators to - functions, methods, classmethods, and staticmethods, or any other callable. - It handles binding and unbinding of methods, and allows for the wrapper to - be enabled or disabled. - """ __bound_function_wrapper__ = BoundFunctionWrapper def __init__(self, wrapped, wrapper, enabled=None): - """ - Initialize the `FunctionWrapper` with the `wrapped` callable, the - `wrapper` function, and an optional `enabled` argument. The `enabled` - argument can be a boolean or a callable that returns a boolean. When a - callable is provided, it will be called each time the wrapper is - invoked to determine if the wrapper function should be executed or - whether the wrapped function should be called directly. If `enabled` - is not provided, the wrapper is enabled by default. - """ - # What it is we are wrapping here could be anything. We need to # try and detect specific cases though. In particular, we need # to detect when we are given something that is a method of a @@ -913,7 +733,7 @@ def __init__(self, wrapped, wrapper, enabled=None): # # 4. The wrapper is being applied when performing monkey # patching of an instance of a class. In this case binding will - # have been performed where the instance was not None. + # have been perfomed where the instance was not None. # # This case is a problem because we can no longer tell if the # method was a static method. @@ -939,42 +759,26 @@ def __init__(self, wrapped, wrapper, enabled=None): # or patch it in the __dict__ of the class type. # # So to get the best outcome we can, whenever we aren't sure what - # it is, we label it as a 'callable'. If it was already bound and + # it is, we label it as a 'function'. If it was already bound and # that is rebound later, we assume that it will be an instance - # method and try and cope with the possibility that the 'self' + # method and try an cope with the possibility that the 'self' # argument it being passed as an explicit argument and shuffle # the arguments around to extract 'self' for use as the instance. - binding = None - - if isinstance(wrapped, _FunctionWrapperBase): - binding = wrapped._self_binding - - if not binding: - if inspect.isbuiltin(wrapped): - binding = "builtin" + if isinstance(wrapped, classmethod): + binding = 'classmethod' - elif inspect.isfunction(wrapped): - binding = "function" - - elif inspect.isclass(wrapped): - binding = "class" - - elif isinstance(wrapped, classmethod): - binding = "classmethod" - - elif isinstance(wrapped, staticmethod): - binding = "staticmethod" - - elif hasattr(wrapped, "__self__"): - if inspect.isclass(wrapped.__self__): - binding = "classmethod" - elif inspect.ismethod(wrapped): - binding = "instancemethod" - else: - binding = "callable" + elif isinstance(wrapped, staticmethod): + binding = 'staticmethod' + elif hasattr(wrapped, '__self__'): + if inspect.isclass(wrapped.__self__): + binding = 'classmethod' else: - binding = "callable" + binding = 'function' + + else: + binding = 'function' - super(FunctionWrapper, self).__init__(wrapped, None, wrapper, enabled, binding) + super(FunctionWrapper, self).__init__(wrapped, None, wrapper, + enabled, binding) diff --git a/pyproject.toml b/pyproject.toml index 8b64b37772..2dbdb34837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,9 +28,10 @@ readme = "README.md" # "LICENSE", # "THIRD_PARTY_NOTICES.md", # ] -requires-python = ">=3.9" # python_requires is also located in setup.py +requires-python = ">=3.8" # python_requires is also located in setup.py classifiers = [ "Development Status :: 5 - Production/Stable", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -78,13 +79,14 @@ packages = [ "newrelic.packages", "newrelic.packages.isort", "newrelic.packages.isort.stdlibs", - "newrelic.packages.opentelemetry_proto", "newrelic.packages.urllib3", - "newrelic.packages.urllib3.contrib", - "newrelic.packages.urllib3.contrib.emscripten", - "newrelic.packages.urllib3.http2", "newrelic.packages.urllib3.util", + "newrelic.packages.urllib3.contrib", + "newrelic.packages.urllib3.contrib._securetransport", + "newrelic.packages.urllib3.packages", + "newrelic.packages.urllib3.packages.backports", "newrelic.packages.wrapt", + "newrelic.packages.opentelemetry_proto", "newrelic.samplers", ] @@ -103,6 +105,7 @@ git_describe_command = 'git describe --dirty --tags --long --match "*.*.*"' [tool.ruff] output-format = "grouped" line-length = 120 +target-version = "py38" force-exclude = true # Fixes issue with megalinter config preventing exclusion of files extend-exclude = [ "newrelic/packages/", @@ -197,6 +200,7 @@ ignore = [ "PT012", # pytest-raises-with-multiple-statements (too many to fix all at once) # Permanently disabled rules "PLC0415", # import-outside-top-level (intentionally used frequently) + "UP006", # non-pep585-annotation (not compatible with Python 3.8) "D203", # incorrect-blank-line-before-class "D213", # multi-line-summary-second-line "ARG001", # unused-argument diff --git a/setup.py b/setup.py index 45fff5674b..4cccb1e437 100644 --- a/setup.py +++ b/setup.py @@ -17,9 +17,11 @@ python_version = sys.version_info[:2] -if python_version < (3, 9): +if python_version >= (3, 8): + pass +else: error_msg = ( - "The New Relic Python agent only supports Python 3.9+. We recommend upgrading to a newer version of Python." + "The New Relic Python agent only supports Python 3.8+. We recommend upgrading to a newer version of Python." ) try: @@ -32,7 +34,6 @@ (3, 5): "5.24.0.153", (3, 6): "7.16.0.178", (3, 7): "10.17.0", - (3, 8): "11.2.0", } last_supported_version = last_supported_version_lookup.get(python_version, None) @@ -114,19 +115,20 @@ def build_extension(self, ext): "newrelic.packages", "newrelic.packages.isort", "newrelic.packages.isort.stdlibs", - "newrelic.packages.opentelemetry_proto", "newrelic.packages.urllib3", - "newrelic.packages.urllib3.contrib", - "newrelic.packages.urllib3.contrib.emscripten", - "newrelic.packages.urllib3.http2", "newrelic.packages.urllib3.util", + "newrelic.packages.urllib3.contrib", + "newrelic.packages.urllib3.contrib._securetransport", + "newrelic.packages.urllib3.packages", + "newrelic.packages.urllib3.packages.backports", "newrelic.packages.wrapt", + "newrelic.packages.opentelemetry_proto", "newrelic.samplers", ] kwargs.update( { - "python_requires": ">=3.9", # python_requires is also located in pyproject.toml + "python_requires": ">=3.8", # python_requires is also located in pyproject.toml "zip_safe": False, "packages": packages, "package_data": { diff --git a/tests/agent_unittests/test_package_version_utils.py b/tests/agent_unittests/test_package_version_utils.py index 4de504b052..8add829195 100644 --- a/tests/agent_unittests/test_package_version_utils.py +++ b/tests/agent_unittests/test_package_version_utils.py @@ -27,12 +27,17 @@ ) # Notes: -# importlib.metadata was a provisional addition to the std library in Python 3.8 and 3.9 +# importlib.metadata was a provisional addition to the std library in PY38 and PY39 # while pkg_resources was deprecated. -# importlib.metadata is no longer provisional in Python 3.10+. It added some attributes +# importlib.metadata is no longer provisional in PY310+. It added some attributes # such as distribution_packages and removed pkg_resources. +IS_PY38_PLUS = sys.version_info[:2] >= (3, 8) IS_PY310_PLUS = sys.version_info[:2] >= (3, 10) +SKIP_IF_NOT_IMPORTLIB_METADATA = pytest.mark.skipif(not IS_PY38_PLUS, reason="importlib.metadata is not supported.") +SKIP_IF_IMPORTLIB_METADATA = pytest.mark.skipif( + IS_PY38_PLUS, reason="importlib.metadata is preferred over pkg_resources." +) SKIP_IF_NOT_PY310_PLUS = pytest.mark.skipif(not IS_PY310_PLUS, reason="These features were added in 3.10+") @@ -96,6 +101,7 @@ def test_get_package_version_tuple(monkeypatch, attr, value, expected_value): assert version == expected_value +@SKIP_IF_NOT_IMPORTLIB_METADATA @validate_function_called("importlib.metadata", "version") def test_importlib_dot_metadata(): # Test for importlib.metadata from the standard library. @@ -103,6 +109,14 @@ def test_importlib_dot_metadata(): assert version not in NULL_VERSIONS, version +@SKIP_IF_IMPORTLIB_METADATA +@validate_function_called("importlib_metadata", "version") +def test_importlib_underscore_metadata(): + # Test for importlib_metadata, a backport library available on PyPI. + version = get_package_version("pytest") + assert version not in NULL_VERSIONS, version + + @SKIP_IF_NOT_PY310_PLUS @validate_function_called("importlib.metadata", "packages_distributions") def test_mapping_import_to_distribution_packages(): @@ -110,6 +124,15 @@ def test_mapping_import_to_distribution_packages(): assert version not in NULL_VERSIONS, version +@SKIP_IF_IMPORTLIB_METADATA +@validate_function_called("pkg_resources", "get_distribution") +def test_pkg_resources_metadata(monkeypatch): + # Prevent importlib_metadata from being used by these tests + monkeypatch.setitem(sys.modules, "importlib_metadata", None) + version = get_package_version("pytest") + assert version not in NULL_VERSIONS, version + + def _getattr_deprecation_warning(attr): if attr == "__version__": warnings.warn("Testing deprecation warnings.", DeprecationWarning, stacklevel=2) diff --git a/tests/datastore_psycopg/test_cursor.py b/tests/datastore_psycopg/test_cursor.py index f37f00710e..ef5eb939f9 100644 --- a/tests/datastore_psycopg/test_cursor.py +++ b/tests/datastore_psycopg/test_cursor.py @@ -98,7 +98,7 @@ async def _execute(connection, cursor, row_type, wrapper): # Consume inserted records to check that returning param functions records = [] while True: - records.append(await maybe_await(cursor.fetchone())) + records.append(cursor.fetchone()) if not cursor.nextset(): break assert len(records) == len(params) @@ -140,7 +140,7 @@ async def _exercise_db(connection, row_factory=None, use_cur_context=False, row_ try: cursor = connection.cursor(**kwargs) if use_cur_context: - if hasattr(cursor.__wrapped__, "__aenter__"): + if hasattr(cursor, "__aenter__"): async with cursor: await _execute(connection, cursor, row_type, wrapper) else: diff --git a/tests/datastore_psycopg/test_register.py b/tests/datastore_psycopg/test_register.py index dd605774f8..46ea9dfcb4 100644 --- a/tests/datastore_psycopg/test_register.py +++ b/tests/datastore_psycopg/test_register.py @@ -32,7 +32,7 @@ def test(): psycopg.types.json.set_json_loads(loads=lambda x: x, context=connection) psycopg.types.json.set_json_loads(loads=lambda x: x, context=cursor) - if hasattr(connection.__wrapped__, "__aenter__"): + if hasattr(connection, "__aenter__"): async def coro(): async with connection: @@ -69,7 +69,7 @@ async def test(): await maybe_await(cursor.execute(f"DROP TYPE if exists {type_name}")) - if hasattr(connection.__wrapped__, "__aenter__"): + if hasattr(connection, "__aenter__"): async def coro(): async with connection: diff --git a/tests/datastore_psycopg/test_rollback.py b/tests/datastore_psycopg/test_rollback.py index 41849fa8e3..2d652ee1ee 100644 --- a/tests/datastore_psycopg/test_rollback.py +++ b/tests/datastore_psycopg/test_rollback.py @@ -57,7 +57,7 @@ async def _exercise_db(connection): try: - if hasattr(connection.__wrapped__, "__aenter__"): + if hasattr(connection, "__aenter__"): async with connection: raise RuntimeError("error") else: diff --git a/tests/framework_starlette/test_application.py b/tests/framework_starlette/test_application.py index 2005e53c2c..cd5668fcb8 100644 --- a/tests/framework_starlette/test_application.py +++ b/tests/framework_starlette/test_application.py @@ -119,6 +119,7 @@ def test_exception_in_middleware(target_application, app_name): app = target_application[app_name] # Starlette >=0.15 and <0.17 raises an exception group instead of reraising the ValueError + # This only occurs on Python versions >=3.8 if (0, 15, 0) <= starlette_version < (0, 17, 0): from anyio._backends._asyncio import ExceptionGroup diff --git a/tests/framework_strawberry/_target_schema_async.py b/tests/framework_strawberry/_target_schema_async.py index e85ef8ae30..72234e79a6 100644 --- a/tests/framework_strawberry/_target_schema_async.py +++ b/tests/framework_strawberry/_target_schema_async.py @@ -14,6 +14,8 @@ from __future__ import annotations +from typing import List + import strawberry try: @@ -66,7 +68,7 @@ async def resolve_search(contains: str): class Query: library: Library = field(resolver=resolve_library) hello: str = field(resolver=resolve_hello) - search: list[Item] = field(resolver=resolve_search) + search: List[Item] = field(resolver=resolve_search) echo: str = field(resolver=resolve_echo) storage: Storage = field(resolver=resolve_storage) error: str | None = field(resolver=resolve_error) diff --git a/tests/framework_strawberry/_target_schema_sync.py b/tests/framework_strawberry/_target_schema_sync.py index 1504022af5..b4559763e1 100644 --- a/tests/framework_strawberry/_target_schema_sync.py +++ b/tests/framework_strawberry/_target_schema_sync.py @@ -14,7 +14,7 @@ from __future__ import annotations -from typing import Union +from typing import List, Union import strawberry @@ -56,12 +56,12 @@ class Magazine: class Library: id: int branch: str - magazine: list[Magazine] - book: list[Book] + magazine: List[Magazine] + book: List[Book] Item = Union[Book, Magazine] -Storage = list[str] +Storage = List[str] authors = [ @@ -138,7 +138,7 @@ def resolve_search(contains: str): class Query: library: Library = field(resolver=resolve_library) hello: str = field(resolver=resolve_hello) - search: list[Item] = field(resolver=resolve_search) + search: List[Item] = field(resolver=resolve_search) echo: str = field(resolver=resolve_echo) storage: Storage = field(resolver=resolve_storage) error: str | None = field(resolver=resolve_error) diff --git a/tests/mlmodel_sklearn/test_inference_events.py b/tests/mlmodel_sklearn/test_inference_events.py index 92b01727b9..d1fc0762b0 100644 --- a/tests/mlmodel_sklearn/test_inference_events.py +++ b/tests/mlmodel_sklearn/test_inference_events.py @@ -59,9 +59,9 @@ def _test(): _test() -label_type = "numeric" -true_label_value = "1.0" -false_label_value = "0.0" +label_type = "bool" if sys.version_info < (3, 8) else "numeric" +true_label_value = "True" if sys.version_info < (3, 8) else "1.0" +false_label_value = "False" if sys.version_info < (3, 8) else "0.0" pandas_df_bool_recorded_custom_events = [ ( {"type": "InferenceData"}, @@ -87,7 +87,7 @@ def test_pandas_df_bool_feature_event(): def _test(): import sklearn.tree - dtype_name = "boolean" + dtype_name = "bool" if sys.version_info < (3, 8) else "boolean" x_train = pd.DataFrame({"col1": [True, False], "col2": [True, False]}, dtype=dtype_name) y_train = pd.DataFrame({"label": [True, False]}, dtype=dtype_name) x_test = pd.DataFrame({"col1": [True], "col2": [True]}, dtype=dtype_name) diff --git a/tests/testing_support/certs/cert.pem b/tests/testing_support/certs/cert.pem index f56002fbb9..0bbbf3a170 100644 --- a/tests/testing_support/certs/cert.pem +++ b/tests/testing_support/certs/cert.pem @@ -1,87 +1,51 @@ This is not a secret key! This private key is used only for testing and is not functionally used in the agent. To generate a new key and certificate, use the following. -openssl req -noenc -newkey rsa:4096 -x509 -keyout cert.pem -out cert.pem -subj '/CN=localhost' -days 3650 -addext "subjectAltName = DNS.1:localhost,IP.1:127.0.0.1" - +openssl req -nodes -newkey rsa:2048 -x509 -keyout key.pem -out cert.pem -subj '/CN=localhost' -days 3650 -----BEGIN PRIVATE KEY----- -MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDk8E5vrgXW21uo -+cqOQlotxS5w3q7y7YyVTbYgbHF/SlIdu4HpY4ycEnAjKYrAiPD5bWPIZhK0wHL0 -DQ5SqKw+zRBV+xRpubpRZrpYVjTwR/X26PEnhUZfikLk5/1Y88WGV0UcLzPk6Prx -IAd6w/s2I3hrg/ZKR0p6NHXB/0dabY9OPdX2wdR6Xg5Rd/+RHyvgXnxUxK2DfDBv -kp15wYHaLTMmcxrDEMYa0mh27tuXwtf7NyWBieJ9RvHasgxCkzdoYRwYc4qMCBOO -F7W9Q4LL79rMyJ3NdNcvT9moJmz0W8wuLcC5aXFpxOG1aj1ipZkaVQ8aflVl7M1H -Ug/PCBtXiieSR+58BoQNhG/wP0Qm2HZp+PQ33vX1/s5iQCeBSRizdi4Xf4p+glWt -f1vWDo0SjWDIDUif+HTu4oC/2ka2PJW5tLy6c02pbtMPFAgUg0Ep8CL8kx4iUORc -OJtiGvA4eaZTC8rQkVXh2c0uS4645QBEmOoYSNcx3KgggTcxHMMo6aW8+F3dQt8d -7YYp3YMXzZ2QNnGXIP8shUJjdz34XLErI/L19wycHtIqa4Hbtxl48e5XXzTORIJ3 -icDmIyne6Q0P7a/mYzjfFsXFuK8R+ogK+PqHSHnCIUjNmUxdPQ92C9jaEJ1/HFyf -bHGXHLP4hc5MD/kJ+7icbDeQSHZm7wIDAQABAoICAA8IhqYHv+NpeR3h9T6dNc2+ -nnuT69oQ5kPhm/2KEXPh3f2M1A2O12tiPJHahv14oJZIbB57MWxEHOhQuSmNYO4o -yhNTTvZYV1dEDyWA164Vk524kylcs4/PhPACGd1O+KAHOAcPRHGaKOxPhZ42o1bd -QmmQ+0nKX6Yhrr/j8vwJqLjjD5tKBBla9sa7wgD2EowDuFdaqOgy7f1Nm+CkZ9H7 -WNoEAfRgNBoLygdRTQMsrMEW0HQuqTw/vd72BR8UCrXkdpNWdvkWCK6yeOEqPzsE -D5KV8+LLctvs+uZzS4FKS+CWaYrjVSq0Xnvqs4g4RpL3levP8uyj/aDazyXxqtXX -dNJgWPX9Jer90JIUY7lnpxe/W1gW5qU1Q8g9XaYACIGJlUQAYLcRjnFZcIIG9579 -LW4J3DCw3/D93QQTD4j4xKyhBoAPEkaHaKp5RXe7Yw9XfXD5zGMGEvAZcXCxHaV1 -Hq5hJYULmjgukKhYUYGjN8UPezrg/4Jd/wOLee9ItTdlD3ihAp2MyDriMeGH41MO -zb+m63iazoT6bT5aMDm1cFUoaVx+WVVQr8S47jQr8c6pyoj2CYzP6t/J6ac0+grW -agp2RZNyLx5aMfAJt9plPKtLvsq2lC4eFL1ZMw4XJMEwEewlcDYGuLirGlDiSsOM -VtIheTqQftdcvS6i6/K1AoIBAQD1vHZtH3aVzZpH6Cwa9+ZdghAmX8e4boDe8Ra9 -q04+I1DHZAYUsxuP+0d6G/yQA9ihGay8Y/GwQCW+Kpss3RESUma/xbufS1+3PNXM -sSyo/PC90zUkN+dXdsK+aGbxv/22XYp7m0ieftorf/sWRCsmjE2vMdqG6mwWDolE -IIknhE/huc6T+QPPNc1AvOOGpA+dk94YkfW8zDVrqdbJPEBNt0KyXT5XTwyI8kPO -J84nn4iQijfQDEZDxKQifuNyJpjTpytf3mmLs8NpBUzhWe7Fhh5kTV9gvtZdWOq2 -AWZqw8DGjKhDKWNmuANNyoiwErLIaInIybnh8nIl0wgKLLR9AoIBAQDugDyIT4WR -URtNNmy6koSCqHtYoIv5wGjJT+zUBJwdZiNLbcysFcp/GPBz196FuZ8AoICD1HAd -ZdcaZtzXyk8KVv2NXIinbwXzemQc1wJzoo4rg2pSl4pU5A527M8MYHf9AfcArjnO -TK1kqrfVrPJpVVeX4PfRTw1eD0CoGxoiPzp0C9Wd/fbI44rjDUi7h+rSzeoc+VO+ -uueTxJUpG/0F9ieS9KIyRBRq5ALFCJwkA+lTT45CXFK8FUI1pkwt48lnDOiM+7XI -4eejnzSKmfyBeTjoMK7UBEksX8E43q55X2h77ezlUOySDizUvdcnrSZD7xXPLeIJ -kdNUqrQymADbAoIBAQCt4gvSr57T5caz9x+ufZgutqgC32eNo/PgzawPzjXxVkAE -t0xuPUbVnTM4vrD6nx4c8PP/4qDU3K9YXwGqv0sjMdeu/5YB4+341T1cOEqn0UPw -rpE97ajvhQPMhEfD7Nz0vEAPsxOxw4VRnp/nY5k9D66wt5AwQ5T0Dpkm8fbbVY7I -5Re+MUh2yVVR59cAIPtDv6w6qp2+WKm8Y1Ou1cmStIineb9xPGhcR0GfkR8ZfpO9 -43AW8XiO34hdOHhs/87IhdP1ZIY+6pbtq2h5VY/ViU/cHbvN03wQVajP3THBfn7c -gA9YZuMFflQoKZaLMM/9a6uDvuqfbVVEWo2n1XZpAoIBAFqDgHWa+G32Ag6DoTAN -ewy7NFSmWXkndJ0yIAc22KivoqV1vj9w5bDmnhrYyjKmB5oNT7i4XvRJOiFi+F1N -AkJCUWfcvmAM2o1U3bm0P9Hy11HcRfWiXXVqN7ManFluIxt6K2uus3F/2C5kO/Bz -+mvPX7bcQjDFd6VC1J736islI+H2u9OCFq6W7JbO69N/+baXP0pPtWClPk3uRU2c -uaIRkWNMRGIfREBs2EA+zEM+2MYtYyf8Mcn/p2kE+9ROppjdZURcItliIq8ONLqF -Rjc88kPsde0w0zRsAsC6giy98MFXwpgk5iNoDcuPYKBGLkeJ7RT7rNVE6pcvUcQB -vBECggEBAKyRPnOASM6n2WagyvkzNYFOhcPR/XmcBoiJdVE+XIJXmhXk7/sctxNU -BrMlawTZZOyHSqnIQ47bO+M+6YF4q3avdsqJjSPuhgDSHkEr5kuzoMbHCro5xKQ/ -Gi/TkgO+Orf6s5q1SubLA5Oe2DHFX6GVBWMpk3iqFkViJ9Vowndnu8CdewW9UGmI -zoobm3k9yqk/f7WnM4mzEFm4LW1j2Ke21frpsqqL6BTDsJrcT/E+8mW4NLjjGuAT -du74BILJK4MbvZAR+nTj0bMQukaVVNvFgETfXnTyK/rzM7w3f98g141IddAQd48e -IF6rWffrLUSEs02xLX3zXw56FK2qLbo= +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYF0U/0zXikonW +Ez532avkDL1QbQ8Yz5ULMwZz8j+cLEhdw/4pQJ7Dox6KEZbsan1nZZqpcZWT0d39 +aY/AQ9udwZT+G4biRCQQHd3IaYdveDuq/MQXEiDmcs1CpCwyWjmfRnjCm2/c8wdW +JCBaRq1hG/hsZ5UKhJxIA9BUl3qPizr7qU/VwNK8+8QQ28EsnUrkLDPL2x1fkIzy +XR89d/NO97a02YV7WwEf8GH9gB0KLhIZzDDh/BM3olHc1BRlaqkATcuxPbWqKH53 +HqL5Wi9O8Pxe9OSBXbAOSlBhmRRUVFx4siRNV+Bkv3VOBCUtk2/5SQwJdxfkICbP +0YlrBPqFAgMBAAECggEBAKzbiJiy0xMIl9w4jqr+4+LMUhBo/T+iph5MVeggK8Q5 +JDZllwXW3GmxLbfStEEwOlqgy2SqKLYTlpmlfMmXPrHmbdILoQ2U5qhBy+0Khb2k +l06DXjT6Wnkd8pZRj81DoX6IuAcsogJEImVFBuBQU1cwMbw969p7FC0DZ/6TIgZ6 +KHvm5Z43uy/wcbHFa2PoMaVvyutKun1po3NG90FlVMJmQYiph2V4/kxcZo8wWXU2 +hE8cbL2g1pv60ZF3wZTWWhnSZTRB2uesW0opNmpwcqqQjQOczJ91y8B4BIjy1QTD +ICxUO9pEtOexNi3/JnzreByHxQt5g7wINbYxFk8MR40CgYEA751xj/B6jut3NMmr +bTLngjE1IAjv/8xEXKhfDAp6fgyU+ATbD8ysIllkch/k2Q0boiIB+XoQ/+KqB/pC +iGw6cGtY3ZF75u/NokHMhQVtHIu3CDbNYSCCtLekG3osXfZaaD4QJVHSrgBYkmez +Ty7my+Sub+uqizz4fK2DUmBpDyMCgYEA5t4HJRgCRQzz340ou+H63QoN5P1x5FlP +M8QpklclpU+dOJsbvmcHzbZJgvuZb6Vt18LuUYLWGeVRrpvUVCapJecklbnNF3wL +YIehBDIiJd2Qq8rbg+yKjNTElbaDWJP+RqPX8IGfvGr23Lsw34vDj/d7N9Ch6xm5 +XChbVCi6/jcCgYB0RhhnWrB+TfDIotwW307MNIitBOlBXaQGuoV02FjcdcqMF/8d +SZp2CJ7fam6ojN3N7Wa74uoA4cLUoDJM9QfeqZiz2/cd91v30qomGp357iphSAad +jSMgAsUVuFFzPypbz1ISagQr/2r7kGrIj9/bLRsgoGFfs7R4+9Hv1WzltQKBgEof +BKo7IBdtRisC1g4kSneHD9jyKgvHRK95DmPGiPafLfoLiofB6nZ4TPe5sZRvx2lb +U0pmODkOMABgVXZDB1F8+Xj8s0UT9U8jnGWNdvszPIx7T6j2W7FFamwqsdbRhPTH +C8BSzacfrGxHyTQsWjgxm6Ta3fFuS92zs0a84PRXAoGANEm47fczWIdWR0SmuEoL +gIGLBi8b2nKZWIeATNwTyWWvD/jEJJXIdjXYxYMK3iG0CCXIColvfoqzEKfNCkz4 +p5wl5yH6EOI+QVISNq7ovrtgLXpUUPPXA/FjYk74e5ITAd5Ute3nB32bX0jPQCGN +cXIVO2dC+mN517lRVQyF/GQ= -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIIFJTCCAw2gAwIBAgIUPbDuIqZQBhN2JA/3iL3+FydryBcwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTIxNzE3MTUxOFoXDTM1MTIx -NTE3MTUxOFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA5PBOb64F1ttbqPnKjkJaLcUucN6u8u2MlU22IGxxf0pS -HbuB6WOMnBJwIymKwIjw+W1jyGYStMBy9A0OUqisPs0QVfsUabm6UWa6WFY08Ef1 -9ujxJ4VGX4pC5Of9WPPFhldFHC8z5Oj68SAHesP7NiN4a4P2SkdKejR1wf9HWm2P -Tj3V9sHUel4OUXf/kR8r4F58VMStg3wwb5KdecGB2i0zJnMawxDGGtJodu7bl8LX -+zclgYnifUbx2rIMQpM3aGEcGHOKjAgTjhe1vUOCy+/azMidzXTXL0/ZqCZs9FvM -Li3AuWlxacThtWo9YqWZGlUPGn5VZezNR1IPzwgbV4onkkfufAaEDYRv8D9EJth2 -afj0N9719f7OYkAngUkYs3YuF3+KfoJVrX9b1g6NEo1gyA1In/h07uKAv9pGtjyV -ubS8unNNqW7TDxQIFINBKfAi/JMeIlDkXDibYhrwOHmmUwvK0JFV4dnNLkuOuOUA -RJjqGEjXMdyoIIE3MRzDKOmlvPhd3ULfHe2GKd2DF82dkDZxlyD/LIVCY3c9+Fyx -KyPy9fcMnB7SKmuB27cZePHuV180zkSCd4nA5iMp3ukND+2v5mM43xbFxbivEfqI -Cvj6h0h5wiFIzZlMXT0PdgvY2hCdfxxcn2xxlxyz+IXOTA/5Cfu4nGw3kEh2Zu8C -AwEAAaNvMG0wHQYDVR0OBBYEFH/Er2J8BaxQKdKlICmL6Ef3yG/KMB8GA1UdIwQY -MBaAFH/Er2J8BaxQKdKlICmL6Ef3yG/KMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R -BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQCTyP6lF+vI -0Vhdrbdh0X2XPYdfFc7X8lIxC8esZ9hrbjgEeTaRbGML+EKqKmUNTcOsJB4WYoEw -CNOMKGT0y4jVeKhowDfIgE8LvHalw1GF6Y4jTwfztW3Wu5DUiW/fjxyxs6OG3D7b -OYmBHfp/zGDtmpzKLipaZ+c5rsPPPW/3g+hptNzbZh32CYH1vMcGqZChDOa79hGL -K0Q1F67ge6rgkPcIS2Ppii5rNDWwGbii0tXkOI7L6rPhbn5a5jg1cbmxEr10+jtk -TOXWZ21f1KhuFOq+wojufoCBkkHsmMf+PfGvyrdlaj+N4n2TJbUCwMFvXHQ9ftir -mXaiM1N8oKx09jbnQxOEp6xH2qJoLaLHcDBklSals77IuLVgWpppGU5QbYj9j63P -4pzTyGsJtypSeR8U+CRl64CsE19X9ao1Szpflkmda5H22YVLg8sHAZ7y9lPWkHzQ -fgFzUdEvMVJ4OXoRsoeHZvBO2mBSTs6TwHq2Mk+uvAMg6CXRKBIFVkI9TBPC5yf+ -fEoXbJY/FtrILTWxr6FGXV0SZf5LWhhb4uPi9fmNSwuY+WhmVigjjJIZk8AzazjL -S3zu4Ljz4mZQguDTud5NujMBAFjgyJQN2cJ4/rA3e95iQ3WaHJU2APVqNvlq8/sN -yyQWFg0mZ1vsKZbl0vxPyn01KhzS58uPbw== +MIIDCTCCAfGgAwIBAgIUbytSUISOZeaoqVCzd/zLDhQgZ1owDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMDMwMzAxMzY1MloXDTMxMDMw +MTAxMzY1MlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA2BdFP9M14pKJ1hM+d9mr5Ay9UG0PGM+VCzMGc/I/nCxI +XcP+KUCew6MeihGW7Gp9Z2WaqXGVk9Hd/WmPwEPbncGU/huG4kQkEB3dyGmHb3g7 +qvzEFxIg5nLNQqQsMlo5n0Z4wptv3PMHViQgWkatYRv4bGeVCoScSAPQVJd6j4s6 ++6lP1cDSvPvEENvBLJ1K5Cwzy9sdX5CM8l0fPXfzTve2tNmFe1sBH/Bh/YAdCi4S +Gcww4fwTN6JR3NQUZWqpAE3LsT21qih+dx6i+VovTvD8XvTkgV2wDkpQYZkUVFRc +eLIkTVfgZL91TgQlLZNv+UkMCXcX5CAmz9GJawT6hQIDAQABo1MwUTAdBgNVHQ4E +FgQUiC/0q2fQCAYC01Opw5iDBfhLNPQwHwYDVR0jBBgwFoAUiC/0q2fQCAYC01Op +w5iDBfhLNPQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAaxSo +gJh6X/ywmT3BXcS15MlAXufMm3uybVbMZjPszZ+vIPF65oelAbSnw/+JHp77fF7F +Erv19MGY8IlMEeUf9agXRF6JNVJD7N3i3zE/2GXoer9UOHQqz5/WWs4F17FAmZW8 +YkzMA70GVa20RedIMreEUxxIyN2eUL8xLfs3E9DEYovOldKfC0Ie1BHFMBhp1tja +6Ag91xyPqP9Pw9ofgS0DoYq6m2ltDNXLoWep1yi1OTwiTvI+GD6JJhmWbCjK0ofA +IkJENYq5tKA6yvQ2Roi9o6oixDJP/SGQtUKPGGRoFcN9gqn+IVC2XmvxHzTOxUWr +/FMyhRqe1k81s3T2hg== -----END CERTIFICATE----- diff --git a/tox.ini b/tox.ini index 597dafb4af..f61d90a775 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ ; framework_aiohttp-aiohttp01: aiohttp<2 ; framework_aiohttp-aiohttp0202: aiohttp<2.3 ; 3. Python version required. Uses the standard tox definitions. (https://tox.readthedocs.io/en/latest/config.html#tox-environments) -; Examples: py39,py310,py311,py312,py313,py314,py314t,pypy311 +; Examples: py38,py39,py310,py311,py312,py313,py314,pypy311 ; 4. Library and version (Optional). Used when testing multiple versions of the library, and may be omitted when only testing a single version. ; Versions should be specified with 2 digits per version number, so <3 becomes 02 and <3.5 becomes 0304. latest and master are also acceptable versions. ; Examples: uvicorn03, CherryPy0302, uvicornlatest @@ -40,7 +40,7 @@ ; ; Full Examples: ; - memcached-datastore_bmemcached-py313-memcached030 -; - linux-agent_unittests-py314-with_extensions +; - linux-agent_unittests-py38-with_extensions ; - python-adapter_gevent-py39 [tox] @@ -51,156 +51,167 @@ uv_seed = true skip_missing_interpreters = false envlist = # Linux Core Agent Test Suite - {linux,linux_arm64}-agent_features-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-agent_features-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-agent_features-pypy311-without_extensions, - {linux,linux_arm64}-agent_streaming-{py39,py310,py311,py312,py313,py314}-protobuf06-{with,without}_extensions, + {linux,linux_arm64}-agent_streaming-{py38,py39,py310,py311,py312,py313,py314}-protobuf06-{with,without}_extensions, {linux,linux_arm64}-agent_streaming-py39-protobuf{03,0319,04,05}-{with,without}_extensions, - {linux,linux_arm64}-agent_unittests-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-agent_unittests-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-agent_unittests-pypy311-without_extensions, - {linux,linux_arm64}-cross_agent-{py39,py310,py311,py312,py313,py314}-{with,without}_extensions, + {linux,linux_arm64}-cross_agent-{py38,py39,py310,py311,py312,py313,py314}-{with,without}_extensions, {linux,linux_arm64}-cross_agent-pypy311-without_extensions, # Windows Core Agent Test Suite - {windows,windows_arm64}-agent_features-{py313,py314,py314t}-{with,without}_extensions, + {windows,windows_arm64}-agent_features-{py313,py314}-{with,without}_extensions, # Windows grpcio wheels don't appear to be installable for Arm64 despite being available windows-agent_streaming-{py313,py314}-protobuf06-{with,without}_extensions, - {windows,windows_arm64}-agent_unittests-{py313,py314,py314t}-{with,without}_extensions, - {windows,windows_arm64}-cross_agent-{py313,py314,py314t}-{with,without}_extensions, + {windows,windows_arm64}-agent_unittests-{py313,py314}-{with,without}_extensions, + {windows,windows_arm64}-cross_agent-{py313,py314}-{with,without}_extensions, # Integration Tests (only run on Linux) + cassandra-datastore_cassandradriver-py38-cassandra032903, cassandra-datastore_cassandradriver-{py39,py310,py311,py312}-cassandralatest, - elasticsearchserver07-datastore_elasticsearch-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch07, - elasticsearchserver08-datastore_elasticsearch-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-elasticsearch08, - firestore-datastore_firestore-{py39,py310,py311,py312,py313,py314,py314t}, - grpc-framework_grpc-{py39,py310,py311,py312,py313,py314,py314t}-grpclatest, + elasticsearchserver07-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,py314,pypy311}-elasticsearch07, + elasticsearchserver08-datastore_elasticsearch-{py38,py39,py310,py311,py312,py313,py314,pypy311}-elasticsearch08, + firestore-datastore_firestore-{py38,py39,py310,py311,py312,py313,py314}, + grpc-framework_grpc-{py39,py310,py311,py312,py313,py314}-grpclatest, kafka-messagebroker_confluentkafka-py39-confluentkafka{0108,0107,0106}, - kafka-messagebroker_confluentkafka-{py39,py310,py311,py312,py313}-confluentkafkalatest, + kafka-messagebroker_confluentkafka-{py38,py39,py310,py311,py312,py313}-confluentkafkalatest, ;; Package not ready for Python 3.14 (confluent-kafka wheels not released) - ; kafka-messagebroker_confluentkafka-{py314,py314t}-confluentkafkalatest, - kafka-messagebroker_kafkapython-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonlatest, - kafka-messagebroker_kafkapython-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kafkapythonnglatest, - memcached-datastore_aiomcache-{py39,py310,py311,py312,py313,py314,py314t}, - memcached-datastore_bmemcached-{py39,py310,py311,py312,py313,py314,py314t}, - memcached-datastore_memcache-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-memcached01, - memcached-datastore_pylibmc-{py39,py310,py311}, - memcached-datastore_pymemcache-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - mongodb8-datastore_motor-{py39,py310,py311,py312,py313,py314,py314t}-motorlatest, - mongodb3-datastore_pymongo-{py39,py310,py311,py312}-pymongo03, - mongodb8-datastore_pymongo-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-pymongo04, - mysql-datastore_aiomysql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - mssql-datastore_pymssql-pymssqllatest-{py39,py310,py311,py312,py313,py314,py314t}, - mysql-datastore_mysql-mysqllatest-{py39,py310,py311,py312,py313,py314,py314t}, - mysql-datastore_mysqldb-{py39,py310,py311,py312,py313,py314,py314t}, - mysql-datastore_pymysql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - oracledb-datastore_oracledb-{py39,py310,py311,py312,py313,py314,py314t}-oracledblatest, - oracledb-datastore_oracledb-{py39,py313,py314,py314t}-oracledb02, + ; kafka-messagebroker_confluentkafka-py314-confluentkafkalatest, + kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,py314,pypy311}-kafkapythonlatest, + kafka-messagebroker_kafkapython-{py38,py39,py310,py311,py312,py313,py314,pypy311}-kafkapythonnglatest, + memcached-datastore_aiomcache-{py38,py39,py310,py311,py312,py313,py314}, + memcached-datastore_bmemcached-{py38,py39,py310,py311,py312,py313,py314}, + memcached-datastore_memcache-{py38,py39,py310,py311,py312,py313,py314,pypy311}-memcached01, + memcached-datastore_pylibmc-{py38,py39,py310,py311}, + memcached-datastore_pymemcache-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + mongodb8-datastore_motor-{py38,py39,py310,py311,py312,py313,py314}-motorlatest, + mongodb3-datastore_pymongo-{py38,py39,py310,py311,py312}-pymongo03, + mongodb8-datastore_pymongo-{py38,py39,py310,py311,py312,py313,py314,pypy311}-pymongo04, + mysql-datastore_aiomysql-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + mssql-datastore_pymssql-pymssqllatest-{py39,py310,py311,py312,py313,py314}, + mssql-datastore_pymssql-pymssql020301-py38, + mysql-datastore_mysql-mysqllatest-{py38,py39,py310,py311,py312,py313,py314}, + mysql-datastore_mysqldb-{py38,py39,py310,py311,py312,py313,py314}, + mysql-datastore_pymysql-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + oracledb-datastore_oracledb-{py39,py310,py311,py312,py313,py314}-oracledblatest, + oracledb-datastore_oracledb-{py39,py313,py314}-oracledb02, oracledb-datastore_oracledb-{py39,py312}-oracledb01, - nginx-external_httpx-{py39,py310,py311,py312,py313,py314,py314t}, - postgres16-datastore_asyncpg-{py39,py310,py311,py312,py313,py314,py314t}, - postgres16-datastore_psycopg-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-psycopglatest, + nginx-external_httpx-{py38,py39,py310,py311,py312,py313,py314}, + postgres16-datastore_asyncpg-{py38,py39,py310,py311,py312,py313,py314}, + postgres16-datastore_psycopg-{py38,py39,py310,py311,py312,py313,py314,pypy311}-psycopglatest, postgres16-datastore_psycopg-py312-psycopg_{purepython,binary,compiled}0301, - postgres16-datastore_psycopg2-{py39,py310,py311,py312}-psycopg2latest, - postgres16-datastore_psycopg2cffi-{py39,py310,py311,py312}-psycopg2cffilatest, - postgres16-datastore_pyodbc-{py39,py310,py311,py312,py313,py314,py314t}-pyodbclatest, - postgres9-datastore_postgresql-{py39,py310,py311,py312,py313,py314,py314t}, - python-adapter_asgiref-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-asgireflatest, + postgres16-datastore_psycopg2-{py38,py39,py310,py311,py312}-psycopg2latest, + postgres16-datastore_psycopg2cffi-{py38,py39,py310,py311,py312}-psycopg2cffilatest, + postgres16-datastore_pyodbc-{py38,py39,py310,py311,py312,py313,py314}-pyodbclatest, + postgres9-datastore_postgresql-{py38,py39,py310,py311,py312,py313,py314}, + python-adapter_asgiref-{py38,py39,py310,py311,py312,py313,py314,pypy311}-asgireflatest, python-adapter_asgiref-py310-asgiref{0303,0304,0305,0306,0307}, - python-adapter_cheroot-{py39,py310,py311,py312,py313,py314,py314t}, - python-adapter_daphne-{py39,py310,py311,py312,py313,py314,py314t}-daphnelatest, - python-adapter_gevent-{py310,py311,py312,py313,py314,py314t}, - python-adapter_gunicorn-{py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, + python-adapter_cheroot-{py38,py39,py310,py311,py312,py313,py314}, + python-adapter_daphne-{py38,py39,py310,py311,py312,py313,py314}-daphnelatest, + python-adapter_gevent-{py38,py310,py311,py312,py313,py314}, + python-adapter_gunicorn-{py38,py39,py310,py311,py312,py313}-aiohttp03-gunicornlatest, ;; Package not ready for Python 3.14 (aiohttp's worker not updated) - ; python-adapter_gunicorn-{py314,py314t}-aiohttp03-gunicornlatest, - python-adapter_hypercorn-{py39,py310,py311,py312,py313,py314,py314t}-hypercornlatest, - python-adapter_mcp-{py310,py311,py312,py313,py314,py314t}, - python-adapter_uvicorn-{py39,py310,py311,py312,py313,py314,py314t}-uvicornlatest, - python-adapter_waitress-{py39,py310,py311,py312,py313,py314,py314t}-waitresslatest, - python-application_celery-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-celerylatest, + ; python-adapter_gunicorn-py314-aiohttp03-gunicornlatest, + python-adapter_hypercorn-{py310,py311,py312,py313,py314}-hypercornlatest, + python-adapter_hypercorn-{py38,py39}-hypercorn{0010,0011,0012,0013}, + python-adapter_mcp-{py310,py311,py312,py313,py314}, + python-adapter_uvicorn-{py39,py310,py311,py312,py313,py314}-uvicornlatest, + python-adapter_uvicorn-py38-uvicorn020, + python-adapter_waitress-{py38,py39,py310,py311,py312,py313,py314}-waitresslatest, + python-application_celery-{py38,py39,py310,py311,py312,py313,py314,pypy311}-celerylatest, python-application_celery-py311-celery{0504,0503,0502}, - python-component_djangorestframework-{py39,py310,py311,py312,py313,py314,py314t}-djangorestframeworklatest, - python-component_flask_rest-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-flaskrestxlatest, - python-component_graphqlserver-{py39,py310,py311,py312}, + python-component_djangorestframework-{py38,py39,py310,py311,py312,py313,py314}-djangorestframeworklatest, + python-component_flask_rest-{py38,py39,py310,py311,py312,py313,py314,pypy311}-flaskrestxlatest, + python-component_graphqlserver-{py38,py39,py310,py311,py312}, ;; Tests need to be updated to support newer graphql-server/sanic versions - ; python-component_graphqlserver-{py313,py314,py314t}, - python-component_tastypie-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-tastypielatest, - python-coroutines_asyncio-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-datastore_sqlite-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_aiobotocore-{py39,py310,py311,py312,py313}-aiobotocorelatest, + ; python-component_graphqlserver-{py313,py314}, + python-component_tastypie-{py38,py39,py310,py311,py312,py313,py314,pypy311}-tastypielatest, + python-coroutines_asyncio-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + python-datastore_sqlite-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + python-external_aiobotocore-{py38,py39,py310,py311,py312,py313}-aiobotocorelatest, ;; Package not ready for Python 3.14 (hangs when running) - ; python-external_aiobotocore-{py314,py314t}-aiobotocorelatest, - python-external_botocore-{py39,py310,py311,py312,py313,py314,py314t}-botocorelatest, + ; python-external_aiobotocore-py314-aiobotocorelatest, + python-external_botocore-{py38,py39,py310,py311,py312,py313,py314}-botocorelatest, python-external_botocore-{py311}-botocorelatest-langchain, python-external_botocore-py310-botocore0125, python-external_botocore-py311-botocore0128, - python-external_feedparser-{py39,py310,py311,py312,py313,py314,py314t}-feedparser06, - python-external_http-{py39,py310,py311,py312,py313,py314,py314t}, - python-external_httplib-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_httplib2-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + python-external_feedparser-{py38,py39,py310,py311,py312,py313,py314}-feedparser06, + python-external_http-{py38,py39,py310,py311,py312,py313,py314}, + python-external_httplib-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + python-external_httplib2-{py38,py39,py310,py311,py312,py313,py314,pypy311}, # pyzeebe requires grpcio which does not support pypy - python-external_pyzeebe-{py39,py310,py311,py312,py313,py314,py314t}, - python-external_requests-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-external_urllib3-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-urllib3latest, - python-external_urllib3-{py312,py313,py314,py314t,pypy311}-urllib30126, - python-framework_aiohttp-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-aiohttp03, - python-framework_ariadne-{py39,py310,py311,py312,py313,py314,py314t}-ariadnelatest, + python-external_pyzeebe-{py39,py310,py311,py312,py313,py314}, + python-external_requests-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + python-external_urllib3-{py38,py39,py310,py311,py312,py313,py314,pypy311}-urllib3latest, + python-external_urllib3-{py312,py313,py314,pypy311}-urllib30126, + python-framework_aiohttp-{py38,py39,py310,py311,py312,py313,py314,pypy311}-aiohttp03, + python-framework_ariadne-{py38,py39,py310,py311,py312,py313,py314}-ariadnelatest, python-framework_azurefunctions-{py39,py310,py311,py312}, - python-framework_bottle-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-bottle0012, - python-framework_cherrypy-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-CherryPylatest, - python-framework_django-{py39,py310,py311,py312,py313,py314,py314t}-Djangolatest, + python-framework_bottle-{py38,py39,py310,py311,py312,py313,py314,pypy311}-bottle0012, + python-framework_cherrypy-{py38,py39,py310,py311,py312,py313,py314,pypy311}-CherryPylatest, + python-framework_django-{py38,py39,py310,py311,py312,py313,py314}-Djangolatest, python-framework_django-py39-Django{0202,0300,0301,0302,0401}, - python-framework_falcon-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-falconlatest, - python-framework_falcon-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-falconmaster, - python-framework_fastapi-{py39,py310,py311,py312,py313,py314,py314t}, - python-framework_flask-{py39,py310,py311,py312,pypy311}-flask02, + python-framework_falcon-{py39,py310,py311,py312,py313,py314,pypy311}-falconlatest, + python-framework_falcon-py38-falcon0410, + python-framework_falcon-{py39,py310,py311,py312,py313,py314,pypy311}-falconmaster, + python-framework_fastapi-{py38,py39,py310,py311,py312,py313,py314}, + python-framework_flask-{py38,py39,py310,py311,py312,pypy311}-flask02, + ; python-framework_flask-py38-flaskmaster fails, even with Flask-Compress<1.16 and coverage==7.61 for py38 + python-framework_flask-py38-flasklatest, ; flaskmaster tests disabled until they can be fixed - python-framework_flask-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-flask{latest}, - python-framework_graphene-{py39,py310,py311,py312,py313,py314,py314t}-graphenelatest, - python-component_graphenedjango-{py39,py310,py311,py312,py313,py314,py314t}-graphenedjangolatest, - python-framework_graphql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphql03, - python-framework_graphql-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-graphqllatest, - python-framework_pyramid-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-Pyramidlatest, - python-framework_pyramid-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-cornicelatest, - python-framework_sanic-py311-sanic2406, - python-framework_sanic-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-saniclatest, + python-framework_flask-{py39,py310,py311,py312,py313,py314,pypy311}-flask{latest}, + python-framework_graphene-{py38,py39,py310,py311,py312,py313,py314}-graphenelatest, + python-component_graphenedjango-{py38,py39,py310,py311,py312,py313,py314}-graphenedjangolatest, + python-framework_graphql-{py38,py39,py310,py311,py312,py313,py314,pypy311}-graphql03, + python-framework_graphql-{py38,py39,py310,py311,py312,py313,py314,pypy311}-graphqllatest, + python-framework_pyramid-{py38,py39,py310,py311,py312,py313,py314,pypy311}-Pyramidlatest, + python-framework_pyramid-{py38,py39,py310,py311,py312,py313,py314,pypy311}-cornicelatest, + python-framework_sanic-py38-sanic2406, + python-framework_sanic-{py39,py310,py311,py312,py313,py314,pypy311}-saniclatest, + python-framework_sanic-py38-sanic2290, python-framework_starlette-{py310,pypy311}-starlette{0014,0015,0019,0028}, - python-framework_starlette-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-starlettelatest, - python-framework_strawberry-{py39,py310,py311,py312}-strawberry02352, - python-framework_strawberry-{py39,py310,py311,py312,py313,py314,py314t}-strawberrylatest, - python-framework_tornado-{py39,py310,py311,py312,py313,py314,py314t}-tornadolatest, - ; Remove `python-framework_tornado-{py314,py314t}-tornadomaster` temporarily + python-framework_starlette-{py38,py39,py310,py311,py312,py313,py314,pypy311}-starlettelatest, + python-framework_starlette-{py38}-starlette002001, + python-framework_strawberry-{py38,py39,py310,py311,py312}-strawberry02352, + python-framework_strawberry-{py38,py39,py310,py311,py312,py313,py314}-strawberrylatest, + python-framework_tornado-{py38,py39,py310,py311,py312,py313,py314}-tornadolatest, + ; Remove `python-framework_tornado-py314-tornadomaster` temporarily python-framework_tornado-{py310,py311,py312,py313}-tornadomaster, - python-logger_logging-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, - python-logger_loguru-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-logurulatest, - python-logger_structlog-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-structloglatest, - python-mlmodel_autogen-{py310,py311,py312,py313,py314,py314t,pypy311}-autogen061, - python-mlmodel_autogen-{py310,py311,py312,py313,py314,py314t,pypy311}-autogenlatest, + python-logger_logging-{py38,py39,py310,py311,py312,py313,py314,pypy311}, + python-logger_loguru-{py38,py39,py310,py311,py312,py313,py314,pypy311}-logurulatest, + python-logger_structlog-{py38,py39,py310,py311,py312,py313,py314,pypy311}-structloglatest, + python-mlmodel_autogen-{py310,py311,py312,py313,py314,pypy311}-autogen061, + python-mlmodel_autogen-{py310,py311,py312,py313,py314,pypy311}-autogenlatest, python-mlmodel_strands-{py310,py311,py312,py313}-strandslatest, - python-mlmodel_gemini-{py39,py310,py311,py312,py313,py314,py314t}, + python-mlmodel_gemini-{py39,py310,py311,py312,py313,py314}, python-mlmodel_langchain-{py310,py311,py312,py313}, ;; Package not ready for Python 3.14 (type annotations not updated) - ; python-mlmodel_langchain-{py314,py314t}, - python-mlmodel_openai-openai0-{py39,py310,py311,py312}, - python-mlmodel_openai-openailatest-{py39,py310,py311,py312,py313,py314,py314t}, - python-mlmodel_sklearn-{py39,py310,py311,py312,py313,py314,py314t}-scikitlearnlatest, - python-template_genshi-{py39,py310,py311,py312,py313,py314,py314t}-genshilatest, - python-template_jinja2-{py39,py310,py311,py312,py313,py314,py314t}-jinja2latest, - python-template_mako-{py39,py310,py311,py312,py313,py314,py314t}, - rabbitmq-messagebroker_pika-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-pikalatest, - rabbitmq-messagebroker_kombu-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-kombulatest, - rabbitmq-messagebroker_kombu-{py39,py310,pypy311}-kombu050204, - redis-datastore_redis-{py39,py310,py311,pypy311}-redis04, - redis-datastore_redis-{py39,py310,py311,py312,pypy311}-redis05, - redis-datastore_redis-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-redislatest, - rediscluster-datastore_rediscluster-{py312,py313,py314,py314t,pypy311}-redislatest, - valkey-datastore_valkey-{py39,py310,py311,py312,py313,py314,py314t,pypy311}-valkeylatest, - solr-datastore_pysolr-{py39,py310,py311,py312,py313,py314,py314t,pypy311}, + ; python-mlmodel_langchain-py314, + python-mlmodel_openai-openai0-{py38,py39,py310,py311,py312}, + python-mlmodel_openai-openailatest-{py38,py39,py310,py311,py312,py313,py314}, + python-mlmodel_sklearn-{py38,py39,py310,py311,py312,py313,py314}-scikitlearnlatest, + python-template_genshi-{py38,py39,py310,py311,py312,py313,py314}-genshilatest, + python-template_jinja2-{py38,py39,py310,py311,py312,py313,py314}-jinja2latest, + python-template_mako-{py38,py39,py310,py311,py312,py313,py314}, + rabbitmq-messagebroker_pika-{py38,py39,py310,py311,py312,py313,py314,pypy311}-pikalatest, + rabbitmq-messagebroker_kombu-{py38,py39,py310,py311,py312,py313,py314,pypy311}-kombulatest, + rabbitmq-messagebroker_kombu-{py38,py39,py310,pypy311}-kombu050204, + redis-datastore_redis-{py38,py39,py310,py311,pypy311}-redis04, + redis-datastore_redis-{py38,py39,py310,py311,py312,pypy311}-redis05, + redis-datastore_redis-{py38,py39,py310,py311,py312,py313,py314,pypy311}-redislatest, + rediscluster-datastore_rediscluster-{py312,py313,py314,pypy311}-redislatest, + valkey-datastore_valkey-{py38,py39,py310,py311,py312,py313,py314,pypy311}-valkeylatest, + solr-datastore_pysolr-{py38,py39,py310,py311,py312,py313,py314,pypy311}, [testenv] deps = # Base Dependencies - {py310,py311,py312,py313,py314,py314t,pypy311}: pytest==9.0.2 - py39: pytest==8.4.2 - WebTest==3.0.7 + {py39,py310,py311,py312,py313,py314,pypy311}: pytest==8.4.1 + py38: pytest==8.3.5 + {py39,py310,py311,py312,py313,py314,pypy311}: WebTest==3.0.6 + py38: WebTest==3.0.1 + py313,py314: legacy-cgi==2.6.1 # cgi was removed from the stdlib in 3.13, and is required for WebTest iniconfig coverage @@ -222,8 +233,14 @@ deps = adapter_gunicorn-gunicorn19: gunicorn<20 adapter_gunicorn-gunicornlatest: gunicorn adapter_hypercorn-hypercornlatest: hypercorn[h3]!=0.18 + adapter_hypercorn-hypercorn0013: hypercorn[h3]<0.14 + adapter_hypercorn-hypercorn0012: hypercorn[h3]<0.13 + adapter_hypercorn-hypercorn0011: hypercorn[h3]<0.12 + adapter_hypercorn-hypercorn0010: hypercorn[h3]<0.11 adapter_hypercorn: niquests adapter_mcp: fastmcp + adapter_uvicorn-uvicorn020: uvicorn<0.21 + adapter_uvicorn-uvicorn020: uvloop<0.20 adapter_uvicorn-uvicornlatest: uvicorn adapter_uvicorn: typing-extensions adapter_uvicorn: uvloop @@ -262,7 +279,7 @@ deps = component_tastypie-tastypielatest: django-tastypie component_tastypie-tastypielatest: django<4.1 component_tastypie-tastypielatest: asgiref<3.7.1 # asgiref==3.7.1 only suppport Python 3.10+ - coroutines_asyncio-{py39,py310,py311,py312,py313,py314,py314t}: uvloop + coroutines_asyncio-{py38,py39,py310,py311,py312,py313,py314}: uvloop cross_agent: requests datastore_asyncpg: asyncpg datastore_aiomcache: aiomcache @@ -271,6 +288,7 @@ deps = datastore_aiomysql: sqlalchemy<2 datastore_bmemcached: python-binary-memcached datastore_cassandradriver-cassandralatest: cassandra-driver + datastore_cassandradriver-cassandra032903: cassandra-driver<3.29.3 datastore_cassandradriver: twisted datastore_elasticsearch: requests datastore_elasticsearch: httpx @@ -301,6 +319,7 @@ deps = datastore_pymongo-pymongo03: pymongo<4.0 datastore_pymongo-pymongo04: pymongo<5.0 datastore_pymssql-pymssqllatest: pymssql + datastore_pymssql-pymssql020301: pymssql==2.3.1 datastore_pymysql: PyMySQL datastore_pymysql: cryptography datastore_pysolr: pysolr<4.0 @@ -349,6 +368,7 @@ deps = framework_django-Django0401: Django<4.2 framework_django-Djangolatest: Django framework_django-Djangomaster: https://github.com/django/django/archive/main.zip + framework_falcon-falcon0410: falcon<4.2 framework_falcon-falconlatest: falcon framework_falcon-falconmaster: https://github.com/falconry/falcon/archive/master.zip framework_fastapi: fastapi @@ -376,8 +396,12 @@ deps = framework_pyramid: routes framework_pyramid-cornicelatest: cornice framework_pyramid-Pyramidlatest: Pyramid + framework_sanic-sanic2290: sanic<22.9.1 framework_sanic-sanic2406: sanic<24.07 framework_sanic-saniclatest: sanic + ; This is the last version of tracerite that supports Python 3.8 + framework_sanic-sanic{2290,2406}: tracerite<1.1.2 + framework_sanic-sanic2290: websockets<11 ; For test_exception_in_middleware test, anyio is used: ; https://github.com/encode/starlette/pull/1157 ; but anyiolatest creates breaking changes to our tests @@ -387,6 +411,7 @@ deps = framework_starlette-starlette0014: starlette<0.15 framework_starlette-starlette0015: starlette<0.16 framework_starlette-starlette0019: starlette<0.20 + framework_starlette-starlette002001: starlette==0.20.1 framework_starlette-starlette0028: starlette<0.29 framework_starlette-starlettelatest: starlette<0.35 framework_strawberry: starlette