summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/aiohttp/.dist-info/METADATA8
-rw-r--r--contrib/python/aiohttp/README.rst4
-rw-r--r--contrib/python/aiohttp/aiohttp/__init__.py4
-rw-r--r--contrib/python/aiohttp/aiohttp/_http_writer.pyx7
-rw-r--r--contrib/python/aiohttp/aiohttp/client.py41
-rw-r--r--contrib/python/aiohttp/aiohttp/client_exceptions.py9
-rw-r--r--contrib/python/aiohttp/aiohttp/client_reqrep.py218
-rw-r--r--contrib/python/aiohttp/aiohttp/client_ws.py50
-rw-r--r--contrib/python/aiohttp/aiohttp/compression_utils.py22
-rw-r--r--contrib/python/aiohttp/aiohttp/connector.py459
-rw-r--r--contrib/python/aiohttp/aiohttp/helpers.py52
-rw-r--r--contrib/python/aiohttp/aiohttp/http_parser.py7
-rw-r--r--contrib/python/aiohttp/aiohttp/http_websocket.py60
-rw-r--r--contrib/python/aiohttp/aiohttp/locks.py41
-rw-r--r--contrib/python/aiohttp/aiohttp/payload.py30
-rw-r--r--contrib/python/aiohttp/aiohttp/resolver.py8
-rw-r--r--contrib/python/aiohttp/aiohttp/streams.py5
-rw-r--r--contrib/python/aiohttp/aiohttp/test_utils.py2
-rw-r--r--contrib/python/aiohttp/aiohttp/tracing.py4
-rw-r--r--contrib/python/aiohttp/aiohttp/typedefs.py13
-rw-r--r--contrib/python/aiohttp/aiohttp/web_app.py14
-rw-r--r--contrib/python/aiohttp/aiohttp/web_request.py12
-rw-r--r--contrib/python/aiohttp/aiohttp/web_response.py133
-rw-r--r--contrib/python/aiohttp/aiohttp/web_ws.py22
-rw-r--r--contrib/python/aiohttp/patches/06-pr9398-fix-trace-request-ctx-typing.patch32
-rw-r--r--contrib/python/aiohttp/patches/99-rep-get-running-loop.sh8
-rw-r--r--contrib/python/aiohttp/ya.make3
-rw-r--r--contrib/python/docker/.dist-info/METADATA4
-rw-r--r--contrib/python/docker/docker/__init__.py2
-rw-r--r--contrib/python/docker/docker/_version.py30
-rw-r--r--contrib/python/docker/docker/client.py64
-rw-r--r--contrib/python/docker/docker/constants.py2
-rw-r--r--contrib/python/docker/docker/context/api.py27
-rw-r--r--contrib/python/docker/docker/models/containers.py3
-rw-r--r--contrib/python/docker/docker/models/images.py4
-rw-r--r--contrib/python/docker/docker/types/services.py7
-rw-r--r--contrib/python/docker/ya.make2
-rw-r--r--contrib/python/pyOpenSSL/py3/.dist-info/METADATA97
-rw-r--r--contrib/python/pyOpenSSL/py3/OpenSSL/SSL.py182
-rw-r--r--contrib/python/pyOpenSSL/py3/OpenSSL/__init__.py1
-rw-r--r--contrib/python/pyOpenSSL/py3/OpenSSL/crypto.py408
-rw-r--r--contrib/python/pyOpenSSL/py3/OpenSSL/debug.py4
-rw-r--r--contrib/python/pyOpenSSL/py3/OpenSSL/version.py4
-rw-r--r--contrib/python/pyOpenSSL/py3/tests/conftest.py3
-rw-r--r--contrib/python/pyOpenSSL/py3/tests/memdbg.py5
-rw-r--r--contrib/python/pyOpenSSL/py3/tests/test_crypto.py579
-rw-r--r--contrib/python/pyOpenSSL/py3/tests/test_ssl.py231
-rw-r--r--contrib/python/pyOpenSSL/py3/ya.make2
48 files changed, 1555 insertions, 1374 deletions
diff --git a/contrib/python/aiohttp/.dist-info/METADATA b/contrib/python/aiohttp/.dist-info/METADATA
index 7ad8cef0fd3..72c03e1c7f4 100644
--- a/contrib/python/aiohttp/.dist-info/METADATA
+++ b/contrib/python/aiohttp/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: aiohttp
-Version: 3.10.6
+Version: 3.10.11
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Maintainer: aiohttp team <[email protected]>
@@ -39,7 +39,7 @@ Requires-Dist: attrs >=17.3.0
Requires-Dist: frozenlist >=1.1.1
Requires-Dist: multidict <7.0,>=4.5
Requires-Dist: yarl <2.0,>=1.12.0
-Requires-Dist: async-timeout <5.0,>=4.0 ; python_version < "3.11"
+Requires-Dist: async-timeout <6.0,>=4.0 ; python_version < "3.11"
Provides-Extra: speedups
Requires-Dist: brotlicffi ; (platform_python_implementation != "CPython") and extra == 'speedups'
Requires-Dist: Brotli ; (platform_python_implementation == "CPython") and extra == 'speedups'
@@ -64,6 +64,10 @@ Async http client/server framework
:target: https://codecov.io/gh/aio-libs/aiohttp
:alt: codecov.io status for master branch
+.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
+ :target: https://codspeed.io/aio-libs/aiohttp
+ :alt: Codspeed.io status for aiohttp
+
.. image:: https://badge.fury.io/py/aiohttp.svg
:target: https://pypi.org/project/aiohttp
:alt: Latest PyPI package version
diff --git a/contrib/python/aiohttp/README.rst b/contrib/python/aiohttp/README.rst
index 470ced9b29c..554627a42e7 100644
--- a/contrib/python/aiohttp/README.rst
+++ b/contrib/python/aiohttp/README.rst
@@ -17,6 +17,10 @@ Async http client/server framework
:target: https://codecov.io/gh/aio-libs/aiohttp
:alt: codecov.io status for master branch
+.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
+ :target: https://codspeed.io/aio-libs/aiohttp
+ :alt: Codspeed.io status for aiohttp
+
.. image:: https://badge.fury.io/py/aiohttp.svg
:target: https://pypi.org/project/aiohttp
:alt: Latest PyPI package version
diff --git a/contrib/python/aiohttp/aiohttp/__init__.py b/contrib/python/aiohttp/aiohttp/__init__.py
index 8830d340940..4fc7908843a 100644
--- a/contrib/python/aiohttp/aiohttp/__init__.py
+++ b/contrib/python/aiohttp/aiohttp/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "3.10.6"
+__version__ = "3.10.11"
from typing import TYPE_CHECKING, Tuple
@@ -8,6 +8,7 @@ from .client import (
ClientConnectionError,
ClientConnectionResetError,
ClientConnectorCertificateError,
+ ClientConnectorDNSError,
ClientConnectorError,
ClientConnectorSSLError,
ClientError,
@@ -127,6 +128,7 @@ __all__: Tuple[str, ...] = (
"ClientConnectionError",
"ClientConnectionResetError",
"ClientConnectorCertificateError",
+ "ClientConnectorDNSError",
"ClientConnectorError",
"ClientConnectorSSLError",
"ClientError",
diff --git a/contrib/python/aiohttp/aiohttp/_http_writer.pyx b/contrib/python/aiohttp/aiohttp/_http_writer.pyx
index eff85219586..d19c20d76cc 100644
--- a/contrib/python/aiohttp/aiohttp/_http_writer.pyx
+++ b/contrib/python/aiohttp/aiohttp/_http_writer.pyx
@@ -127,10 +127,6 @@ def _serialize_headers(str status_line, headers):
_init_writer(&writer)
- for key, val in headers.items():
- _safe_header(to_str(key))
- _safe_header(to_str(val))
-
try:
if _write_str(&writer, status_line) < 0:
raise
@@ -140,6 +136,9 @@ def _serialize_headers(str status_line, headers):
raise
for key, val in headers.items():
+ _safe_header(to_str(key))
+ _safe_header(to_str(val))
+
if _write_str(&writer, to_str(key)) < 0:
raise
if _write_byte(&writer, b':') < 0:
diff --git a/contrib/python/aiohttp/aiohttp/client.py b/contrib/python/aiohttp/aiohttp/client.py
index 19d27eca31a..9c7ca13fb7d 100644
--- a/contrib/python/aiohttp/aiohttp/client.py
+++ b/contrib/python/aiohttp/aiohttp/client.py
@@ -42,6 +42,7 @@ from .client_exceptions import (
ClientConnectionError,
ClientConnectionResetError,
ClientConnectorCertificateError,
+ ClientConnectorDNSError,
ClientConnectorError,
ClientConnectorSSLError,
ClientError,
@@ -88,7 +89,6 @@ from .helpers import (
DEBUG,
BasicAuth,
TimeoutHandle,
- ceil_timeout,
get_env_proxy_for_url,
method_must_be_empty_body,
sentinel,
@@ -105,6 +105,7 @@ __all__ = (
"ClientConnectionError",
"ClientConnectionResetError",
"ClientConnectorCertificateError",
+ "ClientConnectorDNSError",
"ClientConnectorError",
"ClientConnectorSSLError",
"ClientError",
@@ -208,7 +209,7 @@ class ClientTimeout:
# 5 Minute default read timeout
-DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60)
+DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30)
# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2
IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"})
@@ -354,7 +355,7 @@ class ClientSession:
cookie_jar = CookieJar(loop=loop)
self._cookie_jar = cookie_jar
- if cookies is not None:
+ if cookies:
self._cookie_jar.update_cookies(cookies)
self._connector = connector
@@ -444,7 +445,7 @@ class ClientSession:
if self._base_url is None:
return url
else:
- assert not url.is_absolute() and url.path.startswith("/")
+ assert not url.absolute and url.path.startswith("/")
return self._base_url.join(url)
async def _request(
@@ -477,7 +478,7 @@ class ClientSession:
ssl: Union[SSLContext, bool, Fingerprint] = True,
server_hostname: Optional[str] = None,
proxy_headers: Optional[LooseHeaders] = None,
- trace_request_ctx: Optional[Mapping[str, str]] = None,
+ trace_request_ctx: Optional[Mapping[str, Any]] = None,
read_bufsize: Optional[int] = None,
auto_decompress: Optional[bool] = None,
max_line_size: Optional[int] = None,
@@ -504,13 +505,12 @@ class ClientSession:
warnings.warn("Chunk size is deprecated #1615", DeprecationWarning)
redirects = 0
- history = []
+ history: List[ClientResponse] = []
version = self._version
params = params or {}
# Merge with default headers and transform to CIMultiDict
headers = self._prepare_headers(headers)
- proxy_headers = self._prepare_headers(proxy_headers)
try:
url = self._build_url(str_or_url)
@@ -526,7 +526,10 @@ class ClientSession:
for i in skip_auto_headers:
skip_headers.add(istr(i))
- if proxy is not None:
+ if proxy is None:
+ proxy_headers = None
+ else:
+ proxy_headers = self._prepare_headers(proxy_headers)
try:
proxy = URL(proxy)
except ValueError as e:
@@ -586,13 +589,18 @@ class ClientSession:
else InvalidUrlClientError
)
raise err_exc_cls(url)
- if auth and auth_from_url:
+ # If `auth` was passed for an already authenticated URL,
+ # disallow only if this is the initial URL; this is to avoid issues
+ # with sketchy redirects that are not the caller's responsibility
+ if not history and (auth and auth_from_url):
raise ValueError(
"Cannot combine AUTH argument with "
"credentials encoded in URL"
)
- if auth is None:
+ # Override the auth with the one from the URL only if we
+ # have no auth, or if we got an auth from a redirect URL
+ if auth is None or (history and auth_from_url is not None):
auth = auth_from_url
if auth is None:
auth = self._default_auth
@@ -652,13 +660,9 @@ class ClientSession:
# connection timeout
try:
- async with ceil_timeout(
- real_timeout.connect,
- ceil_threshold=real_timeout.ceil_threshold,
- ):
- conn = await self._connector.connect(
- req, traces=traces, timeout=real_timeout
- )
+ conn = await self._connector.connect(
+ req, traces=traces, timeout=real_timeout
+ )
except asyncio.TimeoutError as exc:
raise ConnectionTimeoutError(
f"Connection timeout to host {url}"
@@ -702,7 +706,8 @@ class ClientSession:
raise
raise ClientOSError(*exc.args) from exc
- self._cookie_jar.update_cookies(resp.cookies, resp.url)
+ if cookies := resp.cookies:
+ self._cookie_jar.update_cookies(cookies, resp.url)
# redirects
if resp.status in (301, 302, 303, 307, 308) and allow_redirects:
diff --git a/contrib/python/aiohttp/aiohttp/client_exceptions.py b/contrib/python/aiohttp/aiohttp/client_exceptions.py
index 94991c42477..2cf6cf88328 100644
--- a/contrib/python/aiohttp/aiohttp/client_exceptions.py
+++ b/contrib/python/aiohttp/aiohttp/client_exceptions.py
@@ -30,6 +30,7 @@ __all__ = (
"ClientConnectorError",
"ClientProxyConnectionError",
"ClientSSLError",
+ "ClientConnectorDNSError",
"ClientConnectorSSLError",
"ClientConnectorCertificateError",
"ConnectionTimeoutError",
@@ -206,6 +207,14 @@ class ClientConnectorError(ClientOSError):
__reduce__ = BaseException.__reduce__
+class ClientConnectorDNSError(ClientConnectorError):
+ """DNS resolution failed during client connection.
+
+ Raised in :class:`aiohttp.connector.TCPConnector` if
+ DNS resolution fails.
+ """
+
+
class ClientProxyConnectionError(ClientConnectorError):
"""Proxy connection error.
diff --git a/contrib/python/aiohttp/aiohttp/client_reqrep.py b/contrib/python/aiohttp/aiohttp/client_reqrep.py
index aa8f54e67b8..91605f0e83d 100644
--- a/contrib/python/aiohttp/aiohttp/client_reqrep.py
+++ b/contrib/python/aiohttp/aiohttp/client_reqrep.py
@@ -3,6 +3,7 @@ import codecs
import contextlib
import functools
import io
+import itertools
import re
import sys
import traceback
@@ -89,8 +90,11 @@ if TYPE_CHECKING:
_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")
-_YARL_SUPPORTS_EXTEND_QUERY = tuple(map(int, yarl_version.split(".")[:2])) >= (1, 11)
json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json")
+_YARL_SUPPORTS_HOST_SUBCOMPONENT = tuple(map(int, yarl_version.split(".")[:2])) >= (
+ 1,
+ 13,
+)
def _gen_default_accept_encoding() -> str:
@@ -241,6 +245,9 @@ class ClientRequest:
}
POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT}
ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE})
+ _HOST_STRINGS = frozenset(
+ map("".join, itertools.product(*zip("host".upper(), "host".lower())))
+ )
DEFAULT_HEADERS = {
hdrs.ACCEPT: "*/*",
@@ -289,29 +296,24 @@ class ClientRequest:
):
if loop is None:
loop = asyncio.get_event_loop()
-
- match = _CONTAINS_CONTROL_CHAR_RE.search(method)
- if match:
+ if match := _CONTAINS_CONTROL_CHAR_RE.search(method):
raise ValueError(
f"Method cannot contain non-token characters {method!r} "
- "(found at least {match.group()!r})"
+ f"(found at least {match.group()!r})"
)
-
- assert isinstance(url, URL), url
- assert isinstance(proxy, (URL, type(None))), proxy
+ # URL forbids subclasses, so a simple type check is enough.
+ assert type(url) is URL, url
+ if proxy is not None:
+ assert type(proxy) is URL, proxy
# FIXME: session is None in tests only, need to fix tests
# assert session is not None
- self._session = cast("ClientSession", session)
+ if TYPE_CHECKING:
+ assert session is not None
+ self._session = session
if params:
- if _YARL_SUPPORTS_EXTEND_QUERY:
- url = url.extend_query(params)
- else:
- q = MultiDict(url.query)
- url2 = url.with_query(params)
- q.extend(url2.query)
- url = url.with_query(q)
+ url = url.extend_query(params)
self.original_url = url
- self.url = url.with_fragment(None)
+ self.url = url.with_fragment(None) if url.raw_fragment else url
self.method = method.upper()
self.chunked = chunked
self.compress = compress
@@ -342,9 +344,7 @@ class ClientRequest:
if data is not None or self.method not in self.GET_METHODS:
self.update_transfer_encoding()
self.update_expect_continue(expect100)
- if traces is None:
- traces = []
- self._traces = traces
+ self._traces = [] if traces is None else traces
def __reset_writer(self, _: object = None) -> None:
self.__writer = None
@@ -354,17 +354,11 @@ class ClientRequest:
return self.__writer
@_writer.setter
- def _writer(self, writer: Optional["asyncio.Task[None]"]) -> None:
+ def _writer(self, writer: "asyncio.Task[None]") -> None:
if self.__writer is not None:
self.__writer.remove_done_callback(self.__reset_writer)
self.__writer = writer
- if writer is None:
- return
- if writer.done():
- # The writer is already done, so we can reset it immediately.
- self.__reset_writer()
- else:
- writer.add_done_callback(self.__reset_writer)
+ writer.add_done_callback(self.__reset_writer)
def is_ssl(self) -> bool:
return self.url.scheme in ("https", "wss")
@@ -377,7 +371,7 @@ class ClientRequest:
def connection_key(self) -> ConnectionKey:
proxy_headers = self.proxy_headers
if proxy_headers:
- h: Optional[int] = hash(tuple((k, v) for k, v in proxy_headers.items()))
+ h: Optional[int] = hash(tuple(proxy_headers.items()))
else:
h = None
return ConnectionKey(
@@ -412,9 +406,8 @@ class ClientRequest:
raise InvalidURL(url)
# basic auth info
- username, password = url.user, url.password
- if username or password:
- self.auth = helpers.BasicAuth(username or "", password or "")
+ if url.raw_user or url.raw_password:
+ self.auth = helpers.BasicAuth(url.user or "", url.password or "")
def update_version(self, version: Union[http.HttpVersion, str]) -> None:
"""Convert request version to two elements tuple.
@@ -435,26 +428,49 @@ class ClientRequest:
"""Update request headers."""
self.headers: CIMultiDict[str] = CIMultiDict()
- # add host
- netloc = cast(str, self.url.raw_host)
- if helpers.is_ipv6_address(netloc):
- netloc = f"[{netloc}]"
- # See https://github.com/aio-libs/aiohttp/issues/3636.
- netloc = netloc.rstrip(".")
- if self.url.port is not None and not self.url.is_default_port():
- netloc += ":" + str(self.url.port)
- self.headers[hdrs.HOST] = netloc
+ # Build the host header
+ if _YARL_SUPPORTS_HOST_SUBCOMPONENT:
+ host = self.url.host_subcomponent
+ # host_subcomponent is None when the URL is a relative URL.
+ # but we know we do not have a relative URL here.
+ assert host is not None
+ else:
+ host = cast(str, self.url.raw_host)
+ if helpers.is_ipv6_address(host):
+ host = f"[{host}]"
- if headers:
- if isinstance(headers, (dict, MultiDictProxy, MultiDict)):
- headers = headers.items()
+ if host[-1] == ".":
+ # Remove all trailing dots from the netloc as while
+ # they are valid FQDNs in DNS, TLS validation fails.
+ # See https://github.com/aio-libs/aiohttp/issues/3636.
+ # To avoid string manipulation we only call rstrip if
+ # the last character is a dot.
+ host = host.rstrip(".")
- for key, value in headers: # type: ignore[misc]
- # A special case for Host header
- if key.lower() == "host":
- self.headers[key] = value
- else:
- self.headers.add(key, value)
+ # If explicit port is not None, it means that the port was
+ # explicitly specified in the URL. In this case we check
+ # if its not the default port for the scheme and add it to
+ # the host header. We check explicit_port first because
+ # yarl caches explicit_port and its likely to already be
+ # in the cache and non-default port URLs are far less common.
+ explicit_port = self.url.explicit_port
+ if explicit_port is not None and not self.url.is_default_port():
+ host = f"{host}:{explicit_port}"
+
+ self.headers[hdrs.HOST] = host
+
+ if not headers:
+ return
+
+ if isinstance(headers, (dict, MultiDictProxy, MultiDict)):
+ headers = headers.items()
+
+ for key, value in headers: # type: ignore[misc]
+ # A special case for Host header
+ if key in self._HOST_STRINGS:
+ self.headers[key] = value
+ else:
+ self.headers.add(key, value)
def update_auto_headers(self, skip_auto_headers: Optional[Iterable[str]]) -> None:
if skip_auto_headers is not None:
@@ -592,7 +608,10 @@ class ClientRequest:
def update_expect_continue(self, expect: bool = False) -> None:
if expect:
self.headers[hdrs.EXPECT] = "100-continue"
- elif self.headers.get(hdrs.EXPECT, "").lower() == "100-continue":
+ elif (
+ hdrs.EXPECT in self.headers
+ and self.headers[hdrs.EXPECT].lower() == "100-continue"
+ ):
expect = True
if expect:
@@ -604,10 +623,16 @@ class ClientRequest:
proxy_auth: Optional[BasicAuth],
proxy_headers: Optional[LooseHeaders],
) -> None:
+ self.proxy = proxy
+ if proxy is None:
+ self.proxy_auth = None
+ self.proxy_headers = None
+ return
+
if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth):
raise ValueError("proxy_auth must be None or BasicAuth() tuple")
- self.proxy = proxy
self.proxy_auth = proxy_auth
+
if proxy_headers is not None and not isinstance(
proxy_headers, (MultiDict, MultiDictProxy)
):
@@ -619,10 +644,8 @@ class ClientRequest:
# keep alive not supported at all
return False
if self.version == HttpVersion10:
- if self.headers.get(hdrs.CONNECTION) == "keep-alive":
- return True
- else: # no headers means we close for Http 1.0
- return False
+ # no headers means we close for Http 1.0
+ return self.headers.get(hdrs.CONNECTION) == "keep-alive"
elif self.headers.get(hdrs.CONNECTION) == "close":
return False
@@ -683,17 +706,19 @@ class ClientRequest:
# - not CONNECT proxy must send absolute form URI
# - most common is origin form URI
if self.method == hdrs.METH_CONNECT:
- connect_host = self.url.raw_host
- assert connect_host is not None
- if helpers.is_ipv6_address(connect_host):
- connect_host = f"[{connect_host}]"
+ if _YARL_SUPPORTS_HOST_SUBCOMPONENT:
+ connect_host = self.url.host_subcomponent
+ assert connect_host is not None
+ else:
+ connect_host = self.url.raw_host
+ assert connect_host is not None
+ if helpers.is_ipv6_address(connect_host):
+ connect_host = f"[{connect_host}]"
path = f"{connect_host}:{self.url.port}"
elif self.proxy and not self.is_ssl():
path = str(self.url)
else:
- path = self.url.raw_path
- if self.url.raw_query_string:
- path += "?" + self.url.raw_query_string
+ path = self.url.raw_path_qs
protocol = conn.protocol
assert protocol is not None
@@ -745,6 +770,7 @@ class ClientRequest:
await writer.write_headers(status_line, self.headers)
coro = self.write_bytes(writer, conn)
+ task: Optional["asyncio.Task[None]"]
if sys.version_info >= (3, 12):
# Optimization for Python 3.12, try to write
# bytes immediately to avoid having to schedule
@@ -753,13 +779,17 @@ class ClientRequest:
else:
task = self.loop.create_task(coro)
- self._writer = task
+ if task.done():
+ task = None
+ else:
+ self._writer = task
+
response_class = self.response_class
assert response_class is not None
self.response = response_class(
self.method,
self.original_url,
- writer=self._writer,
+ writer=task,
continue100=self._continue,
timer=self._timer,
request_info=self.request_info,
@@ -770,9 +800,9 @@ class ClientRequest:
return self.response
async def close(self) -> None:
- if self._writer is not None:
+ if self.__writer is not None:
try:
- await self._writer
+ await self.__writer
except asyncio.CancelledError:
if (
sys.version_info >= (3, 11)
@@ -782,11 +812,11 @@ class ClientRequest:
raise
def terminate(self) -> None:
- if self._writer is not None:
+ if self.__writer is not None:
if not self.loop.is_closed():
- self._writer.cancel()
- self._writer.remove_done_callback(self.__reset_writer)
- self._writer = None
+ self.__writer.cancel()
+ self.__writer.remove_done_callback(self.__reset_writer)
+ self.__writer = None
async def _on_chunk_request_sent(self, method: str, url: URL, chunk: bytes) -> None:
for trace in self._traces:
@@ -799,6 +829,9 @@ class ClientRequest:
await trace.send_request_headers(method, url, headers)
+_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError("Connection closed")
+
+
class ClientResponse(HeadersMixin):
# Some of these attributes are None when created,
@@ -827,7 +860,7 @@ class ClientResponse(HeadersMixin):
method: str,
url: URL,
*,
- writer: "asyncio.Task[None]",
+ writer: "Optional[asyncio.Task[None]]",
continue100: Optional["asyncio.Future[bool]"],
timer: BaseTimerContext,
request_info: RequestInfo,
@@ -841,9 +874,10 @@ class ClientResponse(HeadersMixin):
self.cookies = SimpleCookie()
self._real_url = url
- self._url = url.with_fragment(None)
- self._body: Any = None
- self._writer: Optional[asyncio.Task[None]] = writer
+ self._url = url.with_fragment(None) if url.raw_fragment else url
+ self._body: Optional[bytes] = None
+ if writer is not None:
+ self._writer = writer
self._continue = continue100 # None by default
self._closed = True
self._history: Tuple[ClientResponse, ...] = ()
@@ -871,18 +905,24 @@ class ClientResponse(HeadersMixin):
@property
def _writer(self) -> Optional["asyncio.Task[None]"]:
+ """The writer task for streaming data.
+
+ _writer is only provided for backwards compatibility
+ for subclasses that may need to access it.
+ """
return self.__writer
@_writer.setter
def _writer(self, writer: Optional["asyncio.Task[None]"]) -> None:
+ """Set the writer task for streaming data."""
if self.__writer is not None:
self.__writer.remove_done_callback(self.__reset_writer)
self.__writer = writer
if writer is None:
return
if writer.done():
- # The writer is already done, so we can reset it immediately.
- self.__reset_writer()
+ # The writer is already done, so we can clear it immediately.
+ self.__writer = None
else:
writer.add_done_callback(self.__reset_writer)
@@ -1125,16 +1165,16 @@ class ClientResponse(HeadersMixin):
def _release_connection(self) -> None:
if self._connection is not None:
- if self._writer is None:
+ if self.__writer is None:
self._connection.release()
self._connection = None
else:
- self._writer.add_done_callback(lambda f: self._release_connection())
+ self.__writer.add_done_callback(lambda f: self._release_connection())
async def _wait_released(self) -> None:
- if self._writer is not None:
+ if self.__writer is not None:
try:
- await self._writer
+ await self.__writer
except asyncio.CancelledError:
if (
sys.version_info >= (3, 11)
@@ -1145,20 +1185,20 @@ class ClientResponse(HeadersMixin):
self._release_connection()
def _cleanup_writer(self) -> None:
- if self._writer is not None:
- self._writer.cancel()
+ if self.__writer is not None:
+ self.__writer.cancel()
self._session = None
def _notify_content(self) -> None:
content = self.content
if content and content.exception() is None:
- set_exception(content, ClientConnectionError("Connection closed"))
+ set_exception(content, _CONNECTION_CLOSED_EXCEPTION)
self._released = True
async def wait_for_close(self) -> None:
- if self._writer is not None:
+ if self.__writer is not None:
try:
- await self._writer
+ await self.__writer
except asyncio.CancelledError:
if (
sys.version_info >= (3, 11)
@@ -1186,7 +1226,7 @@ class ClientResponse(HeadersMixin):
protocol = self._connection and self._connection.protocol
if protocol is None or not protocol.upgraded:
await self._wait_released() # Underlying connection released
- return self._body # type: ignore[no-any-return]
+ return self._body
def get_encoding(self) -> str:
ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
@@ -1219,9 +1259,7 @@ class ClientResponse(HeadersMixin):
if encoding is None:
encoding = self.get_encoding()
- return self._body.decode( # type: ignore[no-any-return,union-attr]
- encoding, errors=errors
- )
+ return self._body.decode(encoding, errors=errors) # type: ignore[union-attr]
async def json(
self,
diff --git a/contrib/python/aiohttp/aiohttp/client_ws.py b/contrib/python/aiohttp/aiohttp/client_ws.py
index c6b5da5103b..96e2c59715c 100644
--- a/contrib/python/aiohttp/aiohttp/client_ws.py
+++ b/contrib/python/aiohttp/aiohttp/client_ws.py
@@ -249,10 +249,30 @@ class ClientWebSocketResponse:
self._reader.feed_data(WS_CLOSING_MESSAGE, 0)
await self._close_wait
- if not self._closed:
- self._set_closed()
+ if self._closed:
+ return False
+
+ self._set_closed()
+ try:
+ await self._writer.close(code, message)
+ except asyncio.CancelledError:
+ self._close_code = WSCloseCode.ABNORMAL_CLOSURE
+ self._response.close()
+ raise
+ except Exception as exc:
+ self._close_code = WSCloseCode.ABNORMAL_CLOSURE
+ self._exception = exc
+ self._response.close()
+ return True
+
+ if self._close_code:
+ self._response.close()
+ return True
+
+ while True:
try:
- await self._writer.close(code, message)
+ async with async_timeout.timeout(self._timeout):
+ msg = await self._reader.read()
except asyncio.CancelledError:
self._close_code = WSCloseCode.ABNORMAL_CLOSURE
self._response.close()
@@ -263,31 +283,11 @@ class ClientWebSocketResponse:
self._response.close()
return True
- if self._close_code:
+ if msg.type is WSMsgType.CLOSE:
+ self._close_code = msg.data
self._response.close()
return True
- while True:
- try:
- async with async_timeout.timeout(self._timeout):
- msg = await self._reader.read()
- except asyncio.CancelledError:
- self._close_code = WSCloseCode.ABNORMAL_CLOSURE
- self._response.close()
- raise
- except Exception as exc:
- self._close_code = WSCloseCode.ABNORMAL_CLOSURE
- self._exception = exc
- self._response.close()
- return True
-
- if msg.type is WSMsgType.CLOSE:
- self._close_code = msg.data
- self._response.close()
- return True
- else:
- return False
-
async def receive(self, timeout: Optional[float] = None) -> WSMessage:
receive_timeout = timeout or self._receive_timeout
diff --git a/contrib/python/aiohttp/aiohttp/compression_utils.py b/contrib/python/aiohttp/aiohttp/compression_utils.py
index ab4a2f1cc84..ebe8857f487 100644
--- a/contrib/python/aiohttp/aiohttp/compression_utils.py
+++ b/contrib/python/aiohttp/aiohttp/compression_utils.py
@@ -70,6 +70,14 @@ class ZLibCompressor(ZlibBaseHandler):
return self._compressor.compress(data)
async def compress(self, data: bytes) -> bytes:
+ """Compress the data and returned the compressed bytes.
+
+ Note that flush() must be called after the last call to compress()
+
+ If the data size is large than the max_sync_chunk_size, the compression
+ will be done in the executor. Otherwise, the compression will be done
+ in the event loop.
+ """
async with self._compress_lock:
# To ensure the stream is consistent in the event
# there are multiple writers, we need to lock
@@ -79,8 +87,8 @@ class ZLibCompressor(ZlibBaseHandler):
self._max_sync_chunk_size is not None
and len(data) > self._max_sync_chunk_size
):
- return await asyncio.get_event_loop().run_in_executor(
- self._executor, self.compress_sync, data
+ return await asyncio.get_running_loop().run_in_executor(
+ self._executor, self._compressor.compress, data
)
return self.compress_sync(data)
@@ -107,12 +115,18 @@ class ZLibDecompressor(ZlibBaseHandler):
return self._decompressor.decompress(data, max_length)
async def decompress(self, data: bytes, max_length: int = 0) -> bytes:
+ """Decompress the data and return the decompressed bytes.
+
+ If the data size is large than the max_sync_chunk_size, the decompression
+ will be done in the executor. Otherwise, the decompression will be done
+ in the event loop.
+ """
if (
self._max_sync_chunk_size is not None
and len(data) > self._max_sync_chunk_size
):
- return await asyncio.get_event_loop().run_in_executor(
- self._executor, self.decompress_sync, data, max_length
+ return await asyncio.get_running_loop().run_in_executor(
+ self._executor, self._decompressor.decompress, data, max_length
)
return self.decompress_sync(data, max_length)
diff --git a/contrib/python/aiohttp/aiohttp/connector.py b/contrib/python/aiohttp/aiohttp/connector.py
index 8288a0115b7..7d0160fa038 100644
--- a/contrib/python/aiohttp/aiohttp/connector.py
+++ b/contrib/python/aiohttp/aiohttp/connector.py
@@ -5,11 +5,10 @@ import socket
import sys
import traceback
import warnings
-from collections import defaultdict, deque
+from collections import OrderedDict, defaultdict
from contextlib import suppress
from http import HTTPStatus
-from http.cookies import SimpleCookie
-from itertools import cycle, islice
+from itertools import chain, cycle, islice
from time import monotonic
from types import TracebackType
from typing import (
@@ -39,6 +38,7 @@ from .abc import AbstractResolver, ResolveResult
from .client_exceptions import (
ClientConnectionError,
ClientConnectorCertificateError,
+ ClientConnectorDNSError,
ClientConnectorError,
ClientConnectorSSLError,
ClientHttpProxyError,
@@ -50,8 +50,14 @@ from .client_exceptions import (
)
from .client_proto import ResponseHandler
from .client_reqrep import ClientRequest, Fingerprint, _merge_ssl_params
-from .helpers import ceil_timeout, is_ip_address, noop, sentinel
-from .locks import EventResultOrError
+from .helpers import (
+ ceil_timeout,
+ is_ip_address,
+ noop,
+ sentinel,
+ set_exception,
+ set_result,
+)
from .resolver import DefaultResolver
try:
@@ -70,6 +76,14 @@ WS_SCHEMA_SET = frozenset({"ws", "wss"})
HTTP_AND_EMPTY_SCHEMA_SET = HTTP_SCHEMA_SET | EMPTY_SCHEMA_SET
HIGH_LEVEL_SCHEMA_SET = HTTP_AND_EMPTY_SCHEMA_SET | WS_SCHEMA_SET
+NEEDS_CLEANUP_CLOSED = (3, 13, 0) <= sys.version_info < (
+ 3,
+ 13,
+ 1,
+) or sys.version_info < (3, 12, 7)
+# Cleanup closed is no longer needed after https://github.com/python/cpython/pull/118960
+# which first appeared in Python 3.12.7 and 3.13.1
+
__all__ = ("BaseConnector", "TCPConnector", "UnixConnector", "NamedPipeConnector")
@@ -103,7 +117,6 @@ class _DeprecationWaiter:
class Connection:
_source_traceback = None
- _transport = None
def __init__(
self,
@@ -181,9 +194,7 @@ class Connection:
self._notify_release()
if self._protocol is not None:
- self._connector._release(
- self._key, self._protocol, should_close=self._protocol.should_close
- )
+ self._connector._release(self._key, self._protocol)
self._protocol = None
@property
@@ -194,8 +205,10 @@ class Connection:
class _TransportPlaceholder:
"""placeholder for BaseConnector.connect function"""
+ __slots__ = ()
+
def close(self) -> None:
- pass
+ """Close the placeholder transport."""
class BaseConnector:
@@ -260,18 +273,31 @@ class BaseConnector:
self._force_close = force_close
# {host_key: FIFO list of waiters}
- self._waiters = defaultdict(deque) # type: ignore[var-annotated]
+ # The FIFO is implemented with an OrderedDict with None keys because
+ # python does not have an ordered set.
+ self._waiters: DefaultDict[
+ ConnectionKey, OrderedDict[asyncio.Future[None], None]
+ ] = defaultdict(OrderedDict)
self._loop = loop
self._factory = functools.partial(ResponseHandler, loop=loop)
- self.cookies = SimpleCookie()
-
# start keep-alive connection cleanup task
self._cleanup_handle: Optional[asyncio.TimerHandle] = None
# start cleanup closed transports task
self._cleanup_closed_handle: Optional[asyncio.TimerHandle] = None
+
+ if enable_cleanup_closed and not NEEDS_CLEANUP_CLOSED:
+ warnings.warn(
+ "enable_cleanup_closed ignored because "
+ "https://github.com/python/cpython/pull/118960 is fixed "
+ f"in Python version {sys.version_info}",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ enable_cleanup_closed = False
+
self._cleanup_closed_disabled = not enable_cleanup_closed
self._cleanup_closed_transports: List[Optional[asyncio.Transport]] = []
self._cleanup_closed()
@@ -350,28 +376,22 @@ class BaseConnector:
# recreate it ever!
self._cleanup_handle = None
- now = self._loop.time()
+ now = monotonic()
timeout = self._keepalive_timeout
if self._conns:
connections = {}
deadline = now - timeout
for key, conns in self._conns.items():
- alive = []
+ alive: List[Tuple[ResponseHandler, float]] = []
for proto, use_time in conns:
- if proto.is_connected():
- if use_time - deadline < 0:
- transport = proto.transport
- proto.close()
- if key.is_ssl and not self._cleanup_closed_disabled:
- self._cleanup_closed_transports.append(transport)
- else:
- alive.append((proto, use_time))
- else:
- transport = proto.transport
- proto.close()
- if key.is_ssl and not self._cleanup_closed_disabled:
- self._cleanup_closed_transports.append(transport)
+ if proto.is_connected() and use_time - deadline >= 0:
+ alive.append((proto, use_time))
+ continue
+ transport = proto.transport
+ proto.close()
+ if not self._cleanup_closed_disabled and key.is_ssl:
+ self._cleanup_closed_transports.append(transport)
if alive:
connections[key] = alive
@@ -387,17 +407,6 @@ class BaseConnector:
timeout_ceil_threshold=self._timeout_ceil_threshold,
)
- def _drop_acquired_per_host(
- self, key: "ConnectionKey", val: ResponseHandler
- ) -> None:
- acquired_per_host = self._acquired_per_host
- if key not in acquired_per_host:
- return
- conns = acquired_per_host[key]
- conns.remove(val)
- if not conns:
- del self._acquired_per_host[key]
-
def _cleanup_closed(self) -> None:
"""Double confirmation for transport close.
@@ -458,6 +467,9 @@ class BaseConnector:
finally:
self._conns.clear()
self._acquired.clear()
+ for keyed_waiters in self._waiters.values():
+ for keyed_waiter in keyed_waiters:
+ keyed_waiter.cancel()
self._waiters.clear()
self._cleanup_handle = None
self._cleanup_closed_transports.clear()
@@ -480,139 +492,153 @@ class BaseConnector:
If it returns less than 1 means that there are no connections
available.
"""
- if self._limit:
- # total calc available connections
- available = self._limit - len(self._acquired)
+ # check total available connections
+ # If there are no limits, this will always return 1
+ total_remain = 1
- # check limit per host
- if (
- self._limit_per_host
- and available > 0
- and key in self._acquired_per_host
- ):
- acquired = self._acquired_per_host.get(key)
- assert acquired is not None
- available = self._limit_per_host - len(acquired)
+ if self._limit and (total_remain := self._limit - len(self._acquired)) <= 0:
+ return total_remain
- elif self._limit_per_host and key in self._acquired_per_host:
- # check limit per host
- acquired = self._acquired_per_host.get(key)
- assert acquired is not None
- available = self._limit_per_host - len(acquired)
- else:
- available = 1
+ # check limit per host
+ if host_remain := self._limit_per_host:
+ if acquired := self._acquired_per_host.get(key):
+ host_remain -= len(acquired)
+ if total_remain > host_remain:
+ return host_remain
- return available
+ return total_remain
async def connect(
self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout"
) -> Connection:
"""Get from pool or create new connection."""
key = req.connection_key
- available = self._available_connections(key)
-
- # Wait if there are no available connections or if there are/were
- # waiters (i.e. don't steal connection from a waiter about to wake up)
- if available <= 0 or key in self._waiters:
- fut = self._loop.create_future()
-
- # This connection will now count towards the limit.
- self._waiters[key].append(fut)
-
- if traces:
- for trace in traces:
- await trace.send_connection_queued_start()
-
- try:
- await fut
- except BaseException as e:
- if key in self._waiters:
- # remove a waiter even if it was cancelled, normally it's
- # removed when it's notified
- try:
- self._waiters[key].remove(fut)
- except ValueError: # fut may no longer be in list
- pass
-
- raise e
- finally:
- if key in self._waiters and not self._waiters[key]:
- del self._waiters[key]
+ if (conn := await self._get(key, traces)) is not None:
+ # If we do not have to wait and we can get a connection from the pool
+ # we can avoid the timeout ceil logic and directly return the connection
+ return conn
- if traces:
- for trace in traces:
- await trace.send_connection_queued_end()
+ async with ceil_timeout(timeout.connect, timeout.ceil_threshold):
+ if self._available_connections(key) <= 0:
+ await self._wait_for_available_connection(key, traces)
+ if (conn := await self._get(key, traces)) is not None:
+ return conn
- proto = self._get(key)
- if proto is None:
placeholder = cast(ResponseHandler, _TransportPlaceholder())
self._acquired.add(placeholder)
self._acquired_per_host[key].add(placeholder)
- if traces:
- for trace in traces:
- await trace.send_connection_create_start()
-
try:
+ # Traces are done inside the try block to ensure that the
+ # that the placeholder is still cleaned up if an exception
+ # is raised.
+ if traces:
+ for trace in traces:
+ await trace.send_connection_create_start()
proto = await self._create_connection(req, traces, timeout)
- if self._closed:
- proto.close()
- raise ClientConnectionError("Connector is closed.")
+ if traces:
+ for trace in traces:
+ await trace.send_connection_create_end()
except BaseException:
- if not self._closed:
- self._acquired.remove(placeholder)
- self._drop_acquired_per_host(key, placeholder)
- self._release_waiter()
+ self._release_acquired(key, placeholder)
raise
else:
- if not self._closed:
- self._acquired.remove(placeholder)
- self._drop_acquired_per_host(key, placeholder)
-
- if traces:
- for trace in traces:
- await trace.send_connection_create_end()
- else:
- if traces:
- # Acquire the connection to prevent race conditions with limits
- placeholder = cast(ResponseHandler, _TransportPlaceholder())
- self._acquired.add(placeholder)
- self._acquired_per_host[key].add(placeholder)
- for trace in traces:
- await trace.send_connection_reuseconn()
- self._acquired.remove(placeholder)
- self._drop_acquired_per_host(key, placeholder)
+ if self._closed:
+ proto.close()
+ raise ClientConnectionError("Connector is closed.")
+ # The connection was successfully created, drop the placeholder
+ # and add the real connection to the acquired set. There should
+ # be no awaits after the proto is added to the acquired set
+ # to ensure that the connection is not left in the acquired set
+ # on cancellation.
+ acquired_per_host = self._acquired_per_host[key]
+ self._acquired.remove(placeholder)
+ acquired_per_host.remove(placeholder)
self._acquired.add(proto)
- self._acquired_per_host[key].add(proto)
+ acquired_per_host.add(proto)
return Connection(self, key, proto, self._loop)
- def _get(self, key: "ConnectionKey") -> Optional[ResponseHandler]:
+ async def _wait_for_available_connection(
+ self, key: "ConnectionKey", traces: List["Trace"]
+ ) -> None:
+ """Wait for an available connection slot."""
+ # We loop here because there is a race between
+ # the connection limit check and the connection
+ # being acquired. If the connection is acquired
+ # between the check and the await statement, we
+ # need to loop again to check if the connection
+ # slot is still available.
+ attempts = 0
+ while True:
+ fut: asyncio.Future[None] = self._loop.create_future()
+ keyed_waiters = self._waiters[key]
+ keyed_waiters[fut] = None
+ if attempts:
+ # If we have waited before, we need to move the waiter
+ # to the front of the queue as otherwise we might get
+ # starved and hit the timeout.
+ keyed_waiters.move_to_end(fut, last=False)
+
+ try:
+ # Traces happen in the try block to ensure that the
+ # the waiter is still cleaned up if an exception is raised.
+ if traces:
+ for trace in traces:
+ await trace.send_connection_queued_start()
+ await fut
+ if traces:
+ for trace in traces:
+ await trace.send_connection_queued_end()
+ finally:
+ # pop the waiter from the queue if its still
+ # there and not already removed by _release_waiter
+ keyed_waiters.pop(fut, None)
+ if not self._waiters.get(key, True):
+ del self._waiters[key]
+
+ if self._available_connections(key) > 0:
+ break
+ attempts += 1
+
+ async def _get(
+ self, key: "ConnectionKey", traces: List["Trace"]
+ ) -> Optional[Connection]:
+ """Get next reusable connection for the key or None.
+
+ The connection will be marked as acquired.
+ """
try:
conns = self._conns[key]
except KeyError:
return None
- t1 = self._loop.time()
+ t1 = monotonic()
while conns:
proto, t0 = conns.pop()
- if proto.is_connected():
- if t1 - t0 > self._keepalive_timeout:
- transport = proto.transport
- proto.close()
- # only for SSL transports
- if key.is_ssl and not self._cleanup_closed_disabled:
- self._cleanup_closed_transports.append(transport)
- else:
- if not conns:
- # The very last connection was reclaimed: drop the key
- del self._conns[key]
- return proto
- else:
- transport = proto.transport
- proto.close()
- if key.is_ssl and not self._cleanup_closed_disabled:
- self._cleanup_closed_transports.append(transport)
+ # We will we reuse the connection if its connected and
+ # the keepalive timeout has not been exceeded
+ if proto.is_connected() and t1 - t0 <= self._keepalive_timeout:
+ if not conns:
+ # The very last connection was reclaimed: drop the key
+ del self._conns[key]
+ self._acquired.add(proto)
+ self._acquired_per_host[key].add(proto)
+ if traces:
+ for trace in traces:
+ try:
+ await trace.send_connection_reuseconn()
+ except BaseException:
+ self._release_acquired(key, proto)
+ raise
+ return Connection(self, key, proto, self._loop)
+
+ # Connection cannot be reused, close it
+ transport = proto.transport
+ proto.close()
+ # only for SSL transports
+ if not self._cleanup_closed_disabled and key.is_ssl:
+ self._cleanup_closed_transports.append(transport)
# No more connections: drop the key
del self._conns[key]
@@ -630,7 +656,7 @@ class BaseConnector:
# Having the dict keys ordered this avoids to iterate
# at the same order at each call.
- queues = list(self._waiters.keys())
+ queues = list(self._waiters)
random.shuffle(queues)
for key in queues:
@@ -639,25 +665,23 @@ class BaseConnector:
waiters = self._waiters[key]
while waiters:
- waiter = waiters.popleft()
+ waiter, _ = waiters.popitem(last=False)
if not waiter.done():
waiter.set_result(None)
return
def _release_acquired(self, key: "ConnectionKey", proto: ResponseHandler) -> None:
+ """Release acquired connection."""
if self._closed:
# acquired connection is already released on connector closing
return
- try:
- self._acquired.remove(proto)
- self._drop_acquired_per_host(key, proto)
- except KeyError: # pragma: no cover
- # this may be result of undetermenistic order of objects
- # finalization due garbage collection.
- pass
- else:
- self._release_waiter()
+ self._acquired.discard(proto)
+ if conns := self._acquired_per_host.get(key):
+ conns.discard(proto)
+ if not conns:
+ del self._acquired_per_host[key]
+ self._release_waiter()
def _release(
self,
@@ -681,20 +705,21 @@ class BaseConnector:
if key.is_ssl and not self._cleanup_closed_disabled:
self._cleanup_closed_transports.append(transport)
- else:
- conns = self._conns.get(key)
- if conns is None:
- conns = self._conns[key] = []
- conns.append((protocol, self._loop.time()))
+ return
- if self._cleanup_handle is None:
- self._cleanup_handle = helpers.weakref_handle(
- self,
- "_cleanup",
- self._keepalive_timeout,
- self._loop,
- timeout_ceil_threshold=self._timeout_ceil_threshold,
- )
+ conns = self._conns.get(key)
+ if conns is None:
+ conns = self._conns[key] = []
+ conns.append((protocol, monotonic()))
+
+ if self._cleanup_handle is None:
+ self._cleanup_handle = helpers.weakref_handle(
+ self,
+ "_cleanup",
+ self._keepalive_timeout,
+ self._loop,
+ timeout_ceil_threshold=self._timeout_ceil_threshold,
+ )
async def _create_connection(
self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout"
@@ -840,7 +865,9 @@ class TCPConnector(BaseConnector):
self._use_dns_cache = use_dns_cache
self._cached_hosts = _DNSCacheTable(ttl=ttl_dns_cache)
- self._throttle_dns_events: Dict[Tuple[str, int], EventResultOrError] = {}
+ self._throttle_dns_futures: Dict[
+ Tuple[str, int], Set["asyncio.Future[None]"]
+ ] = {}
self._family = family
self._local_addr_infos = aiohappyeyeballs.addr_to_addr_infos(local_addr)
self._happy_eyeballs_delay = happy_eyeballs_delay
@@ -849,8 +876,8 @@ class TCPConnector(BaseConnector):
def close(self) -> Awaitable[None]:
"""Close all ongoing DNS calls."""
- for ev in self._throttle_dns_events.values():
- ev.cancel()
+ for fut in chain.from_iterable(self._throttle_dns_futures.values()):
+ fut.cancel()
for t in self._resolve_host_tasks:
t.cancel()
@@ -918,22 +945,46 @@ class TCPConnector(BaseConnector):
await trace.send_dns_cache_hit(host)
return result
+ futures: Set["asyncio.Future[None]"]
#
# If multiple connectors are resolving the same host, we wait
# for the first one to resolve and then use the result for all of them.
- # We use a throttle event to ensure that we only resolve the host once
+ # We use a throttle to ensure that we only resolve the host once
# and then use the result for all the waiters.
#
+ if key in self._throttle_dns_futures:
+ # get futures early, before any await (#4014)
+ futures = self._throttle_dns_futures[key]
+ future: asyncio.Future[None] = self._loop.create_future()
+ futures.add(future)
+ if traces:
+ for trace in traces:
+ await trace.send_dns_cache_hit(host)
+ try:
+ await future
+ finally:
+ futures.discard(future)
+ return self._cached_hosts.next_addrs(key)
+
+ # update dict early, before any await (#4014)
+ self._throttle_dns_futures[key] = futures = set()
# In this case we need to create a task to ensure that we can shield
# the task from cancellation as cancelling this lookup should not cancel
# the underlying lookup or else the cancel event will get broadcast to
# all the waiters across all connections.
#
- resolved_host_task = asyncio.create_task(
- self._resolve_host_with_throttle(key, host, port, traces)
- )
- self._resolve_host_tasks.add(resolved_host_task)
- resolved_host_task.add_done_callback(self._resolve_host_tasks.discard)
+ coro = self._resolve_host_with_throttle(key, host, port, futures, traces)
+ loop = asyncio.get_running_loop()
+ if sys.version_info >= (3, 12):
+ # Optimization for Python 3.12, try to send immediately
+ resolved_host_task = asyncio.Task(coro, loop=loop, eager_start=True)
+ else:
+ resolved_host_task = loop.create_task(coro)
+
+ if not resolved_host_task.done():
+ self._resolve_host_tasks.add(resolved_host_task)
+ resolved_host_task.add_done_callback(self._resolve_host_tasks.discard)
+
try:
return await asyncio.shield(resolved_host_task)
except asyncio.CancelledError:
@@ -950,42 +1001,39 @@ class TCPConnector(BaseConnector):
key: Tuple[str, int],
host: str,
port: int,
+ futures: Set["asyncio.Future[None]"],
traces: Optional[Sequence["Trace"]],
) -> List[ResolveResult]:
- """Resolve host with a dns events throttle."""
- if key in self._throttle_dns_events:
- # get event early, before any await (#4014)
- event = self._throttle_dns_events[key]
+ """Resolve host and set result for all waiters.
+
+ This method must be run in a task and shielded from cancellation
+ to avoid cancelling the underlying lookup.
+ """
+ if traces:
+ for trace in traces:
+ await trace.send_dns_cache_miss(host)
+ try:
if traces:
for trace in traces:
- await trace.send_dns_cache_hit(host)
- await event.wait()
- else:
- # update dict early, before any await (#4014)
- self._throttle_dns_events[key] = EventResultOrError(self._loop)
+ await trace.send_dns_resolvehost_start(host)
+
+ addrs = await self._resolver.resolve(host, port, family=self._family)
if traces:
for trace in traces:
- await trace.send_dns_cache_miss(host)
- try:
-
- if traces:
- for trace in traces:
- await trace.send_dns_resolvehost_start(host)
-
- addrs = await self._resolver.resolve(host, port, family=self._family)
- if traces:
- for trace in traces:
- await trace.send_dns_resolvehost_end(host)
+ await trace.send_dns_resolvehost_end(host)
- self._cached_hosts.add(key, addrs)
- self._throttle_dns_events[key].set()
- except BaseException as e:
- # any DNS exception, independently of the implementation
- # is set for the waiters to raise the same exception.
- self._throttle_dns_events[key].set(exc=e)
- raise
- finally:
- self._throttle_dns_events.pop(key)
+ self._cached_hosts.add(key, addrs)
+ for fut in futures:
+ set_result(fut, None)
+ except BaseException as e:
+ # any DNS exception is set for the waiters to raise the same exception.
+ # This coro is always run in task that is shielded from cancellation so
+ # we should never be propagating cancellation here.
+ for fut in futures:
+ set_exception(fut, e)
+ raise
+ finally:
+ self._throttle_dns_futures.pop(key)
return self._cached_hosts.next_addrs(key)
@@ -1290,7 +1338,7 @@ class TCPConnector(BaseConnector):
raise
# in case of proxy it is not ClientProxyConnectionError
# it is problem of resolving proxy ip itself
- raise ClientConnectorError(req.connection_key, exc) from exc
+ raise ClientConnectorDNSError(req.connection_key, exc) from exc
last_exc: Optional[Exception] = None
addr_infos = self._convert_hosts_to_addr_infos(hosts)
@@ -1311,7 +1359,7 @@ class TCPConnector(BaseConnector):
req=req,
client_error=client_error,
)
- except ClientConnectorError as exc:
+ except (ClientConnectorError, asyncio.TimeoutError) as exc:
last_exc = exc
aiohappyeyeballs.pop_addr_infos_interleave(addr_infos, self._interleave)
continue
@@ -1412,7 +1460,6 @@ class TCPConnector(BaseConnector):
raise
else:
conn._protocol = None
- conn._transport = None
try:
if resp.status != 200:
message = resp.reason
diff --git a/contrib/python/aiohttp/aiohttp/helpers.py b/contrib/python/aiohttp/aiohttp/helpers.py
index 40705b16d71..6ee70786cfb 100644
--- a/contrib/python/aiohttp/aiohttp/helpers.py
+++ b/contrib/python/aiohttp/aiohttp/helpers.py
@@ -163,7 +163,9 @@ class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])):
"""Create BasicAuth from url."""
if not isinstance(url, URL):
raise TypeError("url should be yarl.URL instance")
- if url.user is None and url.password is None:
+ # Check raw_user and raw_password first as yarl is likely
+ # to already have these values parsed from the netloc in the cache.
+ if url.raw_user is None and url.raw_password is None:
return None
return cls(url.user or "", url.password or "", encoding=encoding)
@@ -174,11 +176,12 @@ class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])):
def strip_auth_from_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]:
- auth = BasicAuth.from_url(url)
- if auth is None:
+ """Remove user and password from URL if present and return BasicAuth object."""
+ # Check raw_user and raw_password first as yarl is likely
+ # to already have these values parsed from the netloc in the cache.
+ if url.raw_user is None and url.raw_password is None:
return url, None
- else:
- return url.with_user(None), auth
+ return url.with_user(None), BasicAuth(url.user or "", url.password or "")
def netrc_from_env() -> Optional[netrc.netrc]:
@@ -614,6 +617,8 @@ def calculate_timeout_when(
class TimeoutHandle:
"""Timeout handle"""
+ __slots__ = ("_timeout", "_loop", "_ceil_threshold", "_callbacks")
+
def __init__(
self,
loop: asyncio.AbstractEventLoop,
@@ -662,11 +667,17 @@ class TimeoutHandle:
class BaseTimerContext(ContextManager["BaseTimerContext"]):
+
+ __slots__ = ()
+
def assert_timeout(self) -> None:
"""Raise TimeoutError if timeout has been exceeded."""
class TimerNoop(BaseTimerContext):
+
+ __slots__ = ()
+
def __enter__(self) -> BaseTimerContext:
return self
@@ -682,10 +693,13 @@ class TimerNoop(BaseTimerContext):
class TimerContext(BaseTimerContext):
"""Low resolution timeout context manager"""
+ __slots__ = ("_loop", "_tasks", "_cancelled", "_cancelling")
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
self._loop = loop
self._tasks: List[asyncio.Task[Any]] = []
self._cancelled = False
+ self._cancelling = 0
def assert_timeout(self) -> None:
"""Raise TimeoutError if timer has already been cancelled."""
@@ -694,12 +708,17 @@ class TimerContext(BaseTimerContext):
def __enter__(self) -> BaseTimerContext:
task = asyncio.current_task(loop=self._loop)
-
if task is None:
raise RuntimeError(
"Timeout context manager should be used " "inside a task"
)
+ if sys.version_info >= (3, 11):
+ # Remember if the task was already cancelling
+ # so when we __exit__ we can decide if we should
+ # raise asyncio.TimeoutError or let the cancellation propagate
+ self._cancelling = task.cancelling()
+
if self._cancelled:
raise asyncio.TimeoutError from None
@@ -712,11 +731,22 @@ class TimerContext(BaseTimerContext):
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> Optional[bool]:
+ enter_task: Optional[asyncio.Task[Any]] = None
if self._tasks:
- self._tasks.pop()
+ enter_task = self._tasks.pop()
if exc_type is asyncio.CancelledError and self._cancelled:
- raise asyncio.TimeoutError from None
+ assert enter_task is not None
+ # The timeout was hit, and the task was cancelled
+ # so we need to uncancel the last task that entered the context manager
+ # since the cancellation should not leak out of the context manager
+ if sys.version_info >= (3, 11):
+ # If the task was already cancelling don't raise
+ # asyncio.TimeoutError and instead return None
+ # to allow the cancellation to propagate
+ if enter_task.uncancel() > self._cancelling:
+ return None
+ raise asyncio.TimeoutError from exc_val
return None
def timeout(self) -> None:
@@ -784,11 +814,7 @@ class HeadersMixin:
def content_length(self) -> Optional[int]:
"""The value of Content-Length HTTP header."""
content_length = self._headers.get(hdrs.CONTENT_LENGTH)
-
- if content_length is not None:
- return int(content_length)
- else:
- return None
+ return None if content_length is None else int(content_length)
def set_result(fut: "asyncio.Future[_T]", result: _T) -> None:
diff --git a/contrib/python/aiohttp/aiohttp/http_parser.py b/contrib/python/aiohttp/aiohttp/http_parser.py
index 686a2d02e28..9fc7e8a2c8b 100644
--- a/contrib/python/aiohttp/aiohttp/http_parser.py
+++ b/contrib/python/aiohttp/aiohttp/http_parser.py
@@ -845,6 +845,13 @@ class HttpPayloadParser:
i = chunk.find(CHUNK_EXT, 0, pos)
if i >= 0:
size_b = chunk[:i] # strip chunk-extensions
+ # Verify no LF in the chunk-extension
+ if b"\n" in (ext := chunk[i:pos]):
+ exc = BadHttpMessage(
+ f"Unexpected LF in chunk-extension: {ext!r}"
+ )
+ set_exception(self.payload, exc)
+ raise exc
else:
size_b = chunk[:pos]
diff --git a/contrib/python/aiohttp/aiohttp/http_websocket.py b/contrib/python/aiohttp/aiohttp/http_websocket.py
index fb00ebc7d35..0916530dbe8 100644
--- a/contrib/python/aiohttp/aiohttp/http_websocket.py
+++ b/contrib/python/aiohttp/aiohttp/http_websocket.py
@@ -133,8 +133,12 @@ class WSMessage(NamedTuple):
return loads(self.data)
-WS_CLOSED_MESSAGE = WSMessage(WSMsgType.CLOSED, None, None)
-WS_CLOSING_MESSAGE = WSMessage(WSMsgType.CLOSING, None, None)
+# Constructing the tuple directly to avoid the overhead of
+# the lambda and arg processing since NamedTuples are constructed
+# with a run time built lambda
+# https://github.com/python/cpython/blob/d83fcf8371f2f33c7797bc8f5423a8bca8c46e5c/Lib/collections/__init__.py#L441
+WS_CLOSED_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSED, None, None))
+WS_CLOSING_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSING, None, None))
class WebSocketError(Exception):
@@ -411,12 +415,14 @@ class WebSocketReader:
WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
) from exc
- self.queue.feed_data(WSMessage(WSMsgType.TEXT, text, ""), len(text))
+ # tuple.__new__ is used to avoid the overhead of the lambda
+ msg = tuple.__new__(WSMessage, (WSMsgType.TEXT, text, ""))
+ self.queue.feed_data(msg, len(payload_merged))
continue
- self.queue.feed_data(
- WSMessage(WSMsgType.BINARY, payload_merged, ""), len(payload_merged)
- )
+ # tuple.__new__ is used to avoid the overhead of the lambda
+ msg = tuple.__new__(WSMessage, (WSMsgType.BINARY, payload_merged, ""))
+ self.queue.feed_data(msg, len(payload_merged))
elif opcode == WSMsgType.CLOSE:
if len(payload) >= 2:
close_code = UNPACK_CLOSE_CODE(payload[:2])[0]
@@ -431,26 +437,26 @@ class WebSocketReader:
raise WebSocketError(
WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message"
) from exc
- msg = WSMessage(WSMsgType.CLOSE, close_code, close_message)
+ msg = tuple.__new__(
+ WSMessage, (WSMsgType.CLOSE, close_code, close_message)
+ )
elif payload:
raise WebSocketError(
WSCloseCode.PROTOCOL_ERROR,
f"Invalid close frame: {fin} {opcode} {payload!r}",
)
else:
- msg = WSMessage(WSMsgType.CLOSE, 0, "")
+ msg = tuple.__new__(WSMessage, (WSMsgType.CLOSE, 0, ""))
self.queue.feed_data(msg, 0)
elif opcode == WSMsgType.PING:
- self.queue.feed_data(
- WSMessage(WSMsgType.PING, payload, ""), len(payload)
- )
+ msg = tuple.__new__(WSMessage, (WSMsgType.PING, payload, ""))
+ self.queue.feed_data(msg, len(payload))
elif opcode == WSMsgType.PONG:
- self.queue.feed_data(
- WSMessage(WSMsgType.PONG, payload, ""), len(payload)
- )
+ msg = tuple.__new__(WSMessage, (WSMsgType.PONG, payload, ""))
+ self.queue.feed_data(msg, len(payload))
else:
raise WebSocketError(
@@ -669,13 +675,16 @@ class WebSocketWriter:
if msg_length < 126:
header = PACK_LEN1(first_byte, msg_length | mask_bit)
header_len = 2
- elif msg_length < (1 << 16):
+ elif msg_length < 65536:
header = PACK_LEN2(first_byte, 126 | mask_bit, msg_length)
header_len = 4
else:
header = PACK_LEN3(first_byte, 127 | mask_bit, msg_length)
header_len = 10
+ if self.transport.is_closing():
+ raise ClientConnectionResetError("Cannot write to closing transport")
+
# https://datatracker.ietf.org/doc/html/rfc6455#section-5.3
# If we are using a mask, we need to generate it randomly
# and apply it to the message before sending it. A mask is
@@ -687,17 +696,15 @@ class WebSocketWriter:
mask = PACK_RANDBITS(self.get_random_bits())
message = bytearray(message)
_websocket_mask(mask, message)
- self._write(header + mask + message)
- self._output_size += header_len + MASK_LEN + msg_length
-
+ self.transport.write(header + mask + message)
+ self._output_size += MASK_LEN
+ elif msg_length > MSG_SIZE:
+ self.transport.write(header)
+ self.transport.write(message)
else:
- if msg_length > MSG_SIZE:
- self._write(header)
- self._write(message)
- else:
- self._write(header + message)
+ self.transport.write(header + message)
- self._output_size += header_len + msg_length
+ self._output_size += header_len + msg_length
# It is safe to return control to the event loop when using compression
# after this point as we have already sent or buffered all the data.
@@ -718,11 +725,6 @@ class WebSocketWriter:
max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE,
)
- def _write(self, data: bytes) -> None:
- if self.transport is None or self.transport.is_closing():
- raise ClientConnectionResetError("Cannot write to closing transport")
- self.transport.write(data)
-
async def pong(self, message: Union[bytes, str] = b"") -> None:
"""Send pong message."""
if isinstance(message, str):
diff --git a/contrib/python/aiohttp/aiohttp/locks.py b/contrib/python/aiohttp/aiohttp/locks.py
deleted file mode 100644
index de2dc83d09d..00000000000
--- a/contrib/python/aiohttp/aiohttp/locks.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import asyncio
-import collections
-from typing import Any, Deque, Optional
-
-
-class EventResultOrError:
- """Event asyncio lock helper class.
-
- Wraps the Event asyncio lock allowing either to awake the
- locked Tasks without any error or raising an exception.
-
- thanks to @vorpalsmith for the simple design.
- """
-
- def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
- self._loop = loop
- self._exc: Optional[BaseException] = None
- self._event = asyncio.Event()
- self._waiters: Deque[asyncio.Future[Any]] = collections.deque()
-
- def set(self, exc: Optional[BaseException] = None) -> None:
- self._exc = exc
- self._event.set()
-
- async def wait(self) -> Any:
- waiter = self._loop.create_task(self._event.wait())
- self._waiters.append(waiter)
- try:
- val = await waiter
- finally:
- self._waiters.remove(waiter)
-
- if self._exc is not None:
- raise self._exc
-
- return val
-
- def cancel(self) -> None:
- """Cancel all waiters"""
- for waiter in self._waiters:
- waiter.cancel()
diff --git a/contrib/python/aiohttp/aiohttp/payload.py b/contrib/python/aiohttp/aiohttp/payload.py
index 27636977774..151f9dd497b 100644
--- a/contrib/python/aiohttp/aiohttp/payload.py
+++ b/contrib/python/aiohttp/aiohttp/payload.py
@@ -101,6 +101,7 @@ class PayloadRegistry:
self._first: List[_PayloadRegistryItem] = []
self._normal: List[_PayloadRegistryItem] = []
self._last: List[_PayloadRegistryItem] = []
+ self._normal_lookup: Dict[Any, PayloadType] = {}
def get(
self,
@@ -109,12 +110,20 @@ class PayloadRegistry:
_CHAIN: "Type[chain[_PayloadRegistryItem]]" = chain,
**kwargs: Any,
) -> "Payload":
+ if self._first:
+ for factory, type_ in self._first:
+ if isinstance(data, type_):
+ return factory(data, *args, **kwargs)
+ # Try the fast lookup first
+ if lookup_factory := self._normal_lookup.get(type(data)):
+ return lookup_factory(data, *args, **kwargs)
+ # Bail early if its already a Payload
if isinstance(data, Payload):
return data
- for factory, type in _CHAIN(self._first, self._normal, self._last):
- if isinstance(data, type):
+ # Fallback to the slower linear search
+ for factory, type_ in _CHAIN(self._normal, self._last):
+ if isinstance(data, type_):
return factory(data, *args, **kwargs)
-
raise LookupError()
def register(
@@ -124,6 +133,11 @@ class PayloadRegistry:
self._first.append((factory, type))
elif order is Order.normal:
self._normal.append((factory, type))
+ if isinstance(type, Iterable):
+ for t in type:
+ self._normal_lookup[t] = factory
+ else:
+ self._normal_lookup[type] = factory
elif order is Order.try_last:
self._last.append((factory, type))
else:
@@ -159,7 +173,8 @@ class Payload(ABC):
self._headers[hdrs.CONTENT_TYPE] = content_type
else:
self._headers[hdrs.CONTENT_TYPE] = self._default_content_type
- self._headers.update(headers or {})
+ if headers:
+ self._headers.update(headers)
@property
def size(self) -> Optional[int]:
@@ -228,9 +243,6 @@ class BytesPayload(Payload):
def __init__(
self, value: Union[bytes, bytearray, memoryview], *args: Any, **kwargs: Any
) -> None:
- if not isinstance(value, (bytes, bytearray, memoryview)):
- raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")
-
if "content_type" not in kwargs:
kwargs["content_type"] = "application/octet-stream"
@@ -238,8 +250,10 @@ class BytesPayload(Payload):
if isinstance(value, memoryview):
self._size = value.nbytes
- else:
+ elif isinstance(value, (bytes, bytearray)):
self._size = len(value)
+ else:
+ raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")
if self._size > TOO_LARGE_BYTES_BODY:
kwargs = {"source": self}
diff --git a/contrib/python/aiohttp/aiohttp/resolver.py b/contrib/python/aiohttp/aiohttp/resolver.py
index 06855fa13fd..5bcdb8ada43 100644
--- a/contrib/python/aiohttp/aiohttp/resolver.py
+++ b/contrib/python/aiohttp/aiohttp/resolver.py
@@ -111,7 +111,7 @@ class AsyncResolver(AbstractResolver):
)
except aiodns.error.DNSError as exc:
msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
- raise OSError(msg) from exc
+ raise OSError(None, msg) from exc
hosts: List[ResolveResult] = []
for node in resp.nodes:
address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr
@@ -145,7 +145,7 @@ class AsyncResolver(AbstractResolver):
)
if not hosts:
- raise OSError("DNS lookup failed")
+ raise OSError(None, "DNS lookup failed")
return hosts
@@ -161,7 +161,7 @@ class AsyncResolver(AbstractResolver):
resp = await self._resolver.query(host, qtype)
except aiodns.error.DNSError as exc:
msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
- raise OSError(msg) from exc
+ raise OSError(None, msg) from exc
hosts = []
for rr in resp:
@@ -177,7 +177,7 @@ class AsyncResolver(AbstractResolver):
)
if not hosts:
- raise OSError("DNS lookup failed")
+ raise OSError(None, "DNS lookup failed")
return hosts
diff --git a/contrib/python/aiohttp/aiohttp/streams.py b/contrib/python/aiohttp/aiohttp/streams.py
index c927cfbb1b3..030c6735947 100644
--- a/contrib/python/aiohttp/aiohttp/streams.py
+++ b/contrib/python/aiohttp/aiohttp/streams.py
@@ -245,9 +245,10 @@ class StreamReader(AsyncStreamReaderMixin):
if not data:
return
- self._size += len(data)
+ data_len = len(data)
+ self._size += data_len
self._buffer.append(data)
- self.total_bytes += len(data)
+ self.total_bytes += data_len
waiter = self._waiter
if waiter is not None:
diff --git a/contrib/python/aiohttp/aiohttp/test_utils.py b/contrib/python/aiohttp/aiohttp/test_utils.py
index 01496b6711a..850efcb8b65 100644
--- a/contrib/python/aiohttp/aiohttp/test_utils.py
+++ b/contrib/python/aiohttp/aiohttp/test_utils.py
@@ -148,7 +148,7 @@ class BaseTestServer(ABC):
assert self._root is not None
url = URL(path)
if not self.skip_url_asserts:
- assert not url.is_absolute()
+ assert not url.absolute
return self._root.join(url)
else:
return URL(str(self._root) + str(path))
diff --git a/contrib/python/aiohttp/aiohttp/tracing.py b/contrib/python/aiohttp/aiohttp/tracing.py
index a42fc0d2a44..067a132464e 100644
--- a/contrib/python/aiohttp/aiohttp/tracing.py
+++ b/contrib/python/aiohttp/aiohttp/tracing.py
@@ -1,5 +1,5 @@
from types import SimpleNamespace
-from typing import TYPE_CHECKING, Any, Awaitable, Mapping, Optional, Protocol, Type, TypeVar
+from typing import TYPE_CHECKING, Awaitable, Mapping, Optional, Protocol, Type, TypeVar
import attr
from aiosignal import Signal
@@ -64,7 +64,7 @@ class TraceConfig:
self._trace_config_ctx_factory = trace_config_ctx_factory
def trace_config_ctx(
- self, trace_request_ctx: Optional[Mapping[str, Any]] = None
+ self, trace_request_ctx: Optional[Mapping[str, str]] = None
) -> SimpleNamespace:
"""Return a new trace_config_ctx instance"""
return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx)
diff --git a/contrib/python/aiohttp/aiohttp/typedefs.py b/contrib/python/aiohttp/aiohttp/typedefs.py
index 2e285fa2561..cc8c0825b4e 100644
--- a/contrib/python/aiohttp/aiohttp/typedefs.py
+++ b/contrib/python/aiohttp/aiohttp/typedefs.py
@@ -8,23 +8,12 @@ from typing import (
Iterable,
Mapping,
Protocol,
- Sequence,
Tuple,
Union,
)
from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr
-from yarl import URL
-
-try:
- # Available in yarl>=1.10.0
- from yarl import Query as _Query
-except ImportError: # pragma: no cover
- SimpleQuery = Union[str, int, float] # pragma: no cover
- QueryVariable = Union[SimpleQuery, "Sequence[SimpleQuery]"] # pragma: no cover
- _Query = Union[ # type: ignore[misc] # pragma: no cover
- None, str, "Mapping[str, QueryVariable]", "Sequence[Tuple[str, QueryVariable]]"
- ]
+from yarl import URL, Query as _Query
Query = _Query
diff --git a/contrib/python/aiohttp/aiohttp/web_app.py b/contrib/python/aiohttp/aiohttp/web_app.py
index ab11981a8a5..628e098d2ea 100644
--- a/contrib/python/aiohttp/aiohttp/web_app.py
+++ b/contrib/python/aiohttp/aiohttp/web_app.py
@@ -54,6 +54,7 @@ from .web_urldispatcher import (
MaskDomain,
MatchedSubAppResource,
PrefixedSubAppResource,
+ SystemRoute,
UrlDispatcher,
)
@@ -79,7 +80,6 @@ _U = TypeVar("_U")
_Resource = TypeVar("_Resource", bound=AbstractResource)
-@lru_cache(None)
def _build_middlewares(
handler: Handler, apps: Tuple["Application", ...]
) -> Callable[[Request], Awaitable[StreamResponse]]:
@@ -90,6 +90,9 @@ def _build_middlewares(
return handler
+_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)
+
+
class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
ATTRS = frozenset(
[
@@ -544,8 +547,13 @@ class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
handler = match_info.handler
if self._run_middlewares:
- if not self._has_legacy_middlewares:
- handler = _build_middlewares(handler, match_info.apps)
+ # If its a SystemRoute, don't cache building the middlewares since
+ # they are constructed for every MatchInfoError as a new handler
+ # is made each time.
+ if not self._has_legacy_middlewares and not isinstance(
+ match_info.route, SystemRoute
+ ):
+ handler = _cached_build_middleware(handler, match_info.apps)
else:
for app in match_info.apps[::-1]:
for m, new_style in app._middlewares_handlers: # type: ignore[union-attr]
diff --git a/contrib/python/aiohttp/aiohttp/web_request.py b/contrib/python/aiohttp/aiohttp/web_request.py
index eca71e4413a..62a08ea248b 100644
--- a/contrib/python/aiohttp/aiohttp/web_request.py
+++ b/contrib/python/aiohttp/aiohttp/web_request.py
@@ -174,7 +174,7 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
self._version = message.version
self._cache: Dict[str, Any] = {}
url = message.url
- if url.is_absolute():
+ if url.absolute:
if scheme is not None:
url = url.with_scheme(scheme)
if host is not None:
@@ -431,6 +431,10 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
- overridden value by .clone(host=new_host) call.
- HOST HTTP header
- socket.getfqdn() value
+
+ For example, 'example.com' or 'localhost:8080'.
+
+ For historical reasons, the port number may be included.
"""
host = self._message.headers.get(hdrs.HOST)
if host is not None:
@@ -454,8 +458,10 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin):
@reify
def url(self) -> URL:
- url = URL.build(scheme=self.scheme, host=self.host)
- return url.join(self._rel_url)
+ """The full URL of the request."""
+ # authority is used here because it may include the port number
+ # and we want yarl to parse it correctly
+ return URL.build(scheme=self.scheme, authority=self.host).join(self._rel_url)
@reify
def path(self) -> str:
diff --git a/contrib/python/aiohttp/aiohttp/web_response.py b/contrib/python/aiohttp/aiohttp/web_response.py
index 956359718da..2db96222709 100644
--- a/contrib/python/aiohttp/aiohttp/web_response.py
+++ b/contrib/python/aiohttp/aiohttp/web_response.py
@@ -42,6 +42,7 @@ from .payload import Payload
from .typedefs import JSONEncoder, LooseHeaders
REASON_PHRASES = {http_status.value: http_status.phrase for http_status in HTTPStatus}
+LARGE_BODY_SIZE = 1024**2
__all__ = ("ContentCoding", "StreamResponse", "Response", "json_response")
@@ -395,25 +396,26 @@ class StreamResponse(BaseClass, HeadersMixin):
self._headers[CONTENT_TYPE] = ctype
async def _do_start_compression(self, coding: ContentCoding) -> None:
- if coding != ContentCoding.identity:
- assert self._payload_writer is not None
- self._headers[hdrs.CONTENT_ENCODING] = coding.value
- self._payload_writer.enable_compression(coding.value)
- # Compressed payload may have different content length,
- # remove the header
- self._headers.popall(hdrs.CONTENT_LENGTH, None)
+ if coding is ContentCoding.identity:
+ return
+ assert self._payload_writer is not None
+ self._headers[hdrs.CONTENT_ENCODING] = coding.value
+ self._payload_writer.enable_compression(coding.value)
+ # Compressed payload may have different content length,
+ # remove the header
+ self._headers.popall(hdrs.CONTENT_LENGTH, None)
async def _start_compression(self, request: "BaseRequest") -> None:
if self._compression_force:
await self._do_start_compression(self._compression_force)
- else:
- # Encoding comparisons should be case-insensitive
- # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1
- accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower()
- for value, coding in CONTENT_CODINGS.items():
- if value in accept_encoding:
- await self._do_start_compression(coding)
- return
+ return
+ # Encoding comparisons should be case-insensitive
+ # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1
+ accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower()
+ for value, coding in CONTENT_CODINGS.items():
+ if value in accept_encoding:
+ await self._do_start_compression(coding)
+ return
async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]:
if self._eof_sent:
@@ -446,9 +448,10 @@ class StreamResponse(BaseClass, HeadersMixin):
version = request.version
headers = self._headers
- for cookie in self._cookies.values():
- value = cookie.output(header="")[1:]
- headers.add(hdrs.SET_COOKIE, value)
+ if self._cookies:
+ for cookie in self._cookies.values():
+ value = cookie.output(header="")[1:]
+ headers.add(hdrs.SET_COOKIE, value)
if self._compression:
await self._start_compression(request)
@@ -464,7 +467,7 @@ class StreamResponse(BaseClass, HeadersMixin):
headers[hdrs.TRANSFER_ENCODING] = "chunked"
if hdrs.CONTENT_LENGTH in headers:
del headers[hdrs.CONTENT_LENGTH]
- elif self._length_check:
+ elif self._length_check: # Disabled for WebSockets
writer.length = self.content_length
if writer.length is None:
if version >= HttpVersion11:
@@ -485,7 +488,7 @@ class StreamResponse(BaseClass, HeadersMixin):
# https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-13
if hdrs.TRANSFER_ENCODING in headers:
del headers[hdrs.TRANSFER_ENCODING]
- elif self.content_length != 0:
+ elif (writer.length if self._length_check else self.content_length) != 0:
# https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5
headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream")
headers.setdefault(hdrs.DATE, rfc822_formatted_time())
@@ -496,9 +499,8 @@ class StreamResponse(BaseClass, HeadersMixin):
if keep_alive:
if version == HttpVersion10:
headers[hdrs.CONNECTION] = "keep-alive"
- else:
- if version == HttpVersion11:
- headers[hdrs.CONNECTION] = "close"
+ elif version == HttpVersion11:
+ headers[hdrs.CONNECTION] = "close"
async def _write_headers(self) -> None:
request = self._req
@@ -626,19 +628,17 @@ class Response(StreamResponse):
real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset
body = text.encode(charset)
text = None
- else:
- if hdrs.CONTENT_TYPE in real_headers:
- if content_type is not None or charset is not None:
- raise ValueError(
- "passing both Content-Type header and "
- "content_type or charset params "
- "is forbidden"
- )
- else:
- if content_type is not None:
- if charset is not None:
- content_type += "; charset=" + charset
- real_headers[hdrs.CONTENT_TYPE] = content_type
+ elif hdrs.CONTENT_TYPE in real_headers:
+ if content_type is not None or charset is not None:
+ raise ValueError(
+ "passing both Content-Type header and "
+ "content_type or charset params "
+ "is forbidden"
+ )
+ elif content_type is not None:
+ if charset is not None:
+ content_type += "; charset=" + charset
+ real_headers[hdrs.CONTENT_TYPE] = content_type
super().__init__(status=status, reason=reason, headers=real_headers)
@@ -707,7 +707,7 @@ class Response(StreamResponse):
return None
if hdrs.CONTENT_LENGTH in self._headers:
- return super().content_length
+ return int(self._headers[hdrs.CONTENT_LENGTH])
if self._compressed_body is not None:
# Return length of the compressed body
@@ -734,16 +734,13 @@ class Response(StreamResponse):
assert not data, f"data arg is not supported, got {data!r}"
assert self._req is not None
assert self._payload_writer is not None
- if body is not None:
- if self._must_be_empty_body:
- await super().write_eof()
- elif isinstance(self._body, Payload):
- await self._body.write(self._payload_writer)
- await super().write_eof()
- else:
- await super().write_eof(cast(bytes, body))
- else:
+ if body is None or self._must_be_empty_body:
+ await super().write_eof()
+ elif isinstance(self._body, Payload):
+ await self._body.write(self._payload_writer)
await super().write_eof()
+ else:
+ await super().write_eof(cast(bytes, body))
async def _start(self, request: "BaseRequest") -> AbstractStreamWriter:
if hdrs.CONTENT_LENGTH in self._headers:
@@ -766,30 +763,28 @@ class Response(StreamResponse):
async def _do_start_compression(self, coding: ContentCoding) -> None:
if self._chunked or isinstance(self._body, Payload):
return await super()._do_start_compression(coding)
-
- if coding != ContentCoding.identity:
- # Instead of using _payload_writer.enable_compression,
- # compress the whole body
- compressor = ZLibCompressor(
- encoding=str(coding.value),
- max_sync_chunk_size=self._zlib_executor_size,
- executor=self._zlib_executor,
- )
- assert self._body is not None
- if self._zlib_executor_size is None and len(self._body) > 1024 * 1024:
- warnings.warn(
- "Synchronous compression of large response bodies "
- f"({len(self._body)} bytes) might block the async event loop. "
- "Consider providing a custom value to zlib_executor_size/"
- "zlib_executor response properties or disabling compression on it."
- )
- self._compressed_body = (
- await compressor.compress(self._body) + compressor.flush()
+ if coding is ContentCoding.identity:
+ return
+ # Instead of using _payload_writer.enable_compression,
+ # compress the whole body
+ compressor = ZLibCompressor(
+ encoding=coding.value,
+ max_sync_chunk_size=self._zlib_executor_size,
+ executor=self._zlib_executor,
+ )
+ assert self._body is not None
+ if self._zlib_executor_size is None and len(self._body) > LARGE_BODY_SIZE:
+ warnings.warn(
+ "Synchronous compression of large response bodies "
+ f"({len(self._body)} bytes) might block the async event loop. "
+ "Consider providing a custom value to zlib_executor_size/"
+ "zlib_executor response properties or disabling compression on it."
)
- assert self._compressed_body is not None
-
- self._headers[hdrs.CONTENT_ENCODING] = coding.value
- self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body))
+ self._compressed_body = (
+ await compressor.compress(self._body) + compressor.flush()
+ )
+ self._headers[hdrs.CONTENT_ENCODING] = coding.value
+ self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body))
def json_response(
diff --git a/contrib/python/aiohttp/aiohttp/web_ws.py b/contrib/python/aiohttp/aiohttp/web_ws.py
index 382223097ea..0e8adef6f8d 100644
--- a/contrib/python/aiohttp/aiohttp/web_ws.py
+++ b/contrib/python/aiohttp/aiohttp/web_ws.py
@@ -379,14 +379,14 @@ class WebSocketResponse(StreamResponse):
raise RuntimeError("Call .prepare() first")
await self._writer.pong(message)
- async def send_str(self, data: str, compress: Optional[bool] = None) -> None:
+ async def send_str(self, data: str, compress: Optional[int] = None) -> None:
if self._writer is None:
raise RuntimeError("Call .prepare() first")
if not isinstance(data, str):
raise TypeError("data argument must be str (%r)" % type(data))
await self._writer.send(data, binary=False, compress=compress)
- async def send_bytes(self, data: bytes, compress: Optional[bool] = None) -> None:
+ async def send_bytes(self, data: bytes, compress: Optional[int] = None) -> None:
if self._writer is None:
raise RuntimeError("Call .prepare() first")
if not isinstance(data, (bytes, bytearray, memoryview)):
@@ -396,7 +396,7 @@ class WebSocketResponse(StreamResponse):
async def send_json(
self,
data: Any,
- compress: Optional[bool] = None,
+ compress: Optional[int] = None,
*,
dumps: JSONEncoder = json.dumps,
) -> None:
@@ -453,7 +453,11 @@ class WebSocketResponse(StreamResponse):
try:
async with async_timeout.timeout(self._timeout):
- msg = await reader.read()
+ while True:
+ msg = await reader.read()
+ if msg.type is WSMsgType.CLOSE:
+ self._set_code_close_transport(msg.data)
+ return True
except asyncio.CancelledError:
self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
raise
@@ -462,14 +466,6 @@ class WebSocketResponse(StreamResponse):
self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
return True
- if msg.type is WSMsgType.CLOSE:
- self._set_code_close_transport(msg.data)
- return True
-
- self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE)
- self._exception = asyncio.TimeoutError()
- return True
-
def _set_closing(self, code: WSCloseCode) -> None:
"""Set the close code and mark the connection as closing."""
self._closing = True
@@ -490,8 +486,6 @@ class WebSocketResponse(StreamResponse):
if self._reader is None:
raise RuntimeError("Call .prepare() first")
- loop = self._loop
- assert loop is not None
receive_timeout = timeout or self._receive_timeout
while True:
if self._waiting:
diff --git a/contrib/python/aiohttp/patches/06-pr9398-fix-trace-request-ctx-typing.patch b/contrib/python/aiohttp/patches/06-pr9398-fix-trace-request-ctx-typing.patch
deleted file mode 100644
index 55b5b76f4d6..00000000000
--- a/contrib/python/aiohttp/patches/06-pr9398-fix-trace-request-ctx-typing.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# back-port pull-req #9398
-#
---- contrib/python/aiohttp/aiohttp/client.py
-+++ contrib/python/aiohttp/aiohttp/client.py
-@@ -178,7 +178,7 @@ class _RequestOptions(TypedDict, total=False):
- ssl: Union[SSLContext, bool, Fingerprint]
- server_hostname: Union[str, None]
- proxy_headers: Union[LooseHeaders, None]
-- trace_request_ctx: Union[Mapping[str, str], None]
-+ trace_request_ctx: Union[Mapping[str, Any], None]
- read_bufsize: Union[int, None]
- auto_decompress: Union[bool, None]
- max_line_size: Union[int, None]
---- contrib/python/aiohttp/aiohttp/tracing.py
-+++ contrib/python/aiohttp/aiohttp/tracing.py
-@@ -1,5 +1,5 @@
- from types import SimpleNamespace
--from typing import TYPE_CHECKING, Awaitable, Mapping, Optional, Protocol, Type, TypeVar
-+from typing import TYPE_CHECKING, Any, Awaitable, Mapping, Optional, Protocol, Type, TypeVar
-
- import attr
- from aiosignal import Signal
-@@ -64,7 +64,7 @@ class TraceConfig:
- self._trace_config_ctx_factory = trace_config_ctx_factory
-
- def trace_config_ctx(
-- self, trace_request_ctx: Optional[Mapping[str, str]] = None
-+ self, trace_request_ctx: Optional[Mapping[str, Any]] = None
- ) -> SimpleNamespace:
- """Return a new trace_config_ctx instance"""
- return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx)
diff --git a/contrib/python/aiohttp/patches/99-rep-get-running-loop.sh b/contrib/python/aiohttp/patches/99-rep-get-running-loop.sh
index b97011e3c67..8565b28f2cf 100644
--- a/contrib/python/aiohttp/patches/99-rep-get-running-loop.sh
+++ b/contrib/python/aiohttp/patches/99-rep-get-running-loop.sh
@@ -1,4 +1,10 @@
# This patch may be dropped after python 3.13 upver
-find aiohttp -type f -exec sed --in-place 's|loop or asyncio.get_running_loop|loop or asyncio.get_event_loop|g' '{}' ';'
+if [ "$(uname)" = "Darwin" ]; then
+ SED=gsed
+else
+ SED=sed
+fi
+
+find aiohttp -type f -exec ${SED} --in-place 's|loop or asyncio.get_running_loop|loop or asyncio.get_event_loop|g' '{}' ';'
diff --git a/contrib/python/aiohttp/ya.make b/contrib/python/aiohttp/ya.make
index e714d0fd423..3767e000884 100644
--- a/contrib/python/aiohttp/ya.make
+++ b/contrib/python/aiohttp/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(3.10.6)
+VERSION(3.10.11)
LICENSE(Apache-2.0)
@@ -57,7 +57,6 @@ PY_SRCS(
aiohttp/http_parser.py
aiohttp/http_websocket.py
aiohttp/http_writer.py
- aiohttp/locks.py
aiohttp/log.py
aiohttp/multipart.py
aiohttp/payload.py
diff --git a/contrib/python/docker/.dist-info/METADATA b/contrib/python/docker/.dist-info/METADATA
index 90e41721a6f..d4e1c5135c2 100644
--- a/contrib/python/docker/.dist-info/METADATA
+++ b/contrib/python/docker/.dist-info/METADATA
@@ -1,6 +1,6 @@
-Metadata-Version: 2.3
+Metadata-Version: 2.4
Name: docker
-Version: 7.1.0
+Version: 7.2.0
Summary: A Python library for the Docker Engine API.
Project-URL: Changelog, https://docker-py.readthedocs.io/en/stable/change-log.html
Project-URL: Documentation, https://docker-py.readthedocs.io
diff --git a/contrib/python/docker/docker/__init__.py b/contrib/python/docker/docker/__init__.py
index fb7a5e921ad..4e06ac2ac69 100644
--- a/contrib/python/docker/docker/__init__.py
+++ b/contrib/python/docker/docker/__init__.py
@@ -1,5 +1,5 @@
from .api import APIClient
-from .client import DockerClient, from_env
+from .client import DockerClient, from_context, from_env
from .context import Context, ContextAPI
from .tls import TLSConfig
from .version import __version__
diff --git a/contrib/python/docker/docker/_version.py b/contrib/python/docker/docker/_version.py
index 32913e53d55..a8e096249dd 100644
--- a/contrib/python/docker/docker/_version.py
+++ b/contrib/python/docker/docker/_version.py
@@ -1,16 +1,24 @@
-# file generated by setuptools_scm
+# file generated by vcs-versioning
# don't change, don't track in version control
-TYPE_CHECKING = False
-if TYPE_CHECKING:
- from typing import Tuple, Union
- VERSION_TUPLE = Tuple[Union[int, str], ...]
-else:
- VERSION_TUPLE = object
+from __future__ import annotations
+
+__all__ = [
+ "__version__",
+ "__version_tuple__",
+ "version",
+ "version_tuple",
+ "__commit_id__",
+ "commit_id",
+]
version: str
__version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
+__version_tuple__: tuple[int | str, ...]
+version_tuple: tuple[int | str, ...]
+commit_id: str | None
+__commit_id__: str | None
+
+__version__ = version = '7.2.0'
+__version_tuple__ = version_tuple = (7, 2, 0)
-__version__ = version = '7.1.0'
-__version_tuple__ = version_tuple = (7, 1, 0)
+__commit_id__ = commit_id = None
diff --git a/contrib/python/docker/docker/client.py b/contrib/python/docker/docker/client.py
index 9012d24c9c1..821706387c1 100644
--- a/contrib/python/docker/docker/client.py
+++ b/contrib/python/docker/docker/client.py
@@ -1,5 +1,8 @@
+import os
+
from .api.client import APIClient
from .constants import DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS
+from .context import ContextAPI
from .models.configs import ConfigCollection
from .models.containers import ContainerCollection
from .models.images import ImageCollection
@@ -78,6 +81,10 @@ class DockerClient:
use_ssh_client (bool): If set to `True`, an ssh connection is
made via shelling out to the ssh client. Ensure the ssh
client is installed and configured on the host.
+ use_context (bool): If ``True`` (the default), fall back to the
+ current Docker CLI context (``~/.docker/config.json`` /
+ ``DOCKER_CONTEXT``) when ``DOCKER_HOST`` is not set. This
+ allows the client to talk to Docker Desktop out of the box.
Example:
@@ -91,12 +98,66 @@ class DockerClient:
max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE)
version = kwargs.pop('version', None)
use_ssh_client = kwargs.pop('use_ssh_client', False)
+ use_context = kwargs.pop('use_context', True)
+ environment = kwargs.get('environment') or os.environ
+
+ params = kwargs_from_env(**kwargs)
+ if use_context and 'base_url' not in params:
+ for k, v in ContextAPI.kwargs_from_context(
+ environment=environment).items():
+ params.setdefault(k, v)
+
+ return cls(
+ timeout=timeout,
+ max_pool_size=max_pool_size,
+ version=version,
+ use_ssh_client=use_ssh_client,
+ **params,
+ )
+
+ @classmethod
+ def from_context(cls, name=None, **kwargs):
+ """
+ Return a client configured from a Docker CLI context.
+
+ With no ``name``, resolves the current context the same way the
+ Docker CLI does: ``DOCKER_CONTEXT`` env var, then the
+ ``currentContext`` field in ``~/.docker/config.json``, falling back
+ to the built-in ``default`` context. On a machine with Docker
+ Desktop installed this typically resolves to ``desktop-linux``.
+
+ Args:
+ name (str): Name of the context to load. ``None`` (the default)
+ means "use the current context".
+ version (str): The version of the API to use. Set to ``auto`` to
+ automatically detect the server's version.
+ timeout (int): Default timeout for API calls, in seconds.
+ max_pool_size (int): The maximum number of connections to save in
+ the pool.
+ use_ssh_client (bool): If ``True``, shell out to the ssh client
+ for ssh:// contexts.
+
+ Example:
+
+ >>> import docker
+ >>> client = docker.DockerClient.from_context()
+ >>> # or, pick a specific context:
+ >>> client = docker.DockerClient.from_context('desktop-linux')
+ """
+ timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
+ max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE)
+ version = kwargs.pop('version', None)
+ use_ssh_client = kwargs.pop('use_ssh_client', False)
+
+ params = ContextAPI.kwargs_from_context(name=name)
+ params.update(kwargs)
+
return cls(
timeout=timeout,
max_pool_size=max_pool_size,
version=version,
use_ssh_client=use_ssh_client,
- **kwargs_from_env(**kwargs)
+ **params,
)
# Resources
@@ -220,3 +281,4 @@ class DockerClient:
from_env = DockerClient.from_env
+from_context = DockerClient.from_context
diff --git a/contrib/python/docker/docker/constants.py b/contrib/python/docker/docker/constants.py
index 3c527b47e3a..0e39dc2917c 100644
--- a/contrib/python/docker/docker/constants.py
+++ b/contrib/python/docker/docker/constants.py
@@ -2,7 +2,7 @@ import sys
from .version import __version__
-DEFAULT_DOCKER_API_VERSION = '1.44'
+DEFAULT_DOCKER_API_VERSION = '1.45'
MINIMUM_DOCKER_API_VERSION = '1.24'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8
diff --git a/contrib/python/docker/docker/context/api.py b/contrib/python/docker/docker/context/api.py
index 9ac4ff470af..c86f1c74d17 100644
--- a/contrib/python/docker/docker/context/api.py
+++ b/contrib/python/docker/docker/context/api.py
@@ -133,6 +133,33 @@ class ContextAPI:
return cls.get_context()
@classmethod
+ def kwargs_from_context(cls, name=None, environment=None):
+ """Build ``base_url`` / ``tls`` kwargs from a Docker CLI context.
+
+ Mirrors the Docker CLI: if ``name`` is not given, honours the
+ ``DOCKER_CONTEXT`` env var, then the ``currentContext`` field in
+ ``~/.docker/config.json``, defaulting to the built-in ``default``
+ context (local socket / named pipe). On a host with Docker Desktop
+ this resolves to the ``desktop-linux`` (or equivalent) context, so
+ client construction targets Docker Desktop out of the box.
+ """
+ if environment is None:
+ environment = os.environ
+ if name is None:
+ name = environment.get("DOCKER_CONTEXT")
+ ctx = cls.get_context(name)
+ if ctx is None:
+ return {}
+ host = ctx.Host
+ if not host:
+ return {}
+ params = {"base_url": host}
+ tls_cfg = ctx.TLSConfig
+ if tls_cfg is not None:
+ params["tls"] = tls_cfg
+ return params
+
+ @classmethod
def set_current_context(cls, name="default"):
ctx = cls.get_context(name)
if not ctx:
diff --git a/contrib/python/docker/docker/models/containers.py b/contrib/python/docker/docker/models/containers.py
index 4795523a158..9c9e92c90fd 100644
--- a/contrib/python/docker/docker/models/containers.py
+++ b/contrib/python/docker/docker/models/containers.py
@@ -181,7 +181,8 @@ class Container(Model):
user (str): User to execute command as. Default: root
detach (bool): If true, detach from the exec command.
Default: False
- stream (bool): Stream response data. Default: False
+ stream (bool): Stream response data. Ignored if ``detach`` is true.
+ Default: False
socket (bool): Return the connection socket to allow custom
read/write operations. Default: False
environment (dict or list): A dictionary or a list of strings in
diff --git a/contrib/python/docker/docker/models/images.py b/contrib/python/docker/docker/models/images.py
index 4f058d24d93..0e8cce3f822 100644
--- a/contrib/python/docker/docker/models/images.py
+++ b/contrib/python/docker/docker/models/images.py
@@ -407,8 +407,8 @@ class ImageCollection(Collection):
if match:
image_id = match.group(2)
images.append(image_id)
- if 'error' in chunk:
- raise ImageLoadError(chunk['error'])
+ if 'errorDetail' in chunk:
+ raise ImageLoadError(chunk['errorDetail']['message'])
return [self.get(i) for i in images]
diff --git a/contrib/python/docker/docker/types/services.py b/contrib/python/docker/docker/types/services.py
index 821115411c6..69c0c498ea4 100644
--- a/contrib/python/docker/docker/types/services.py
+++ b/contrib/python/docker/docker/types/services.py
@@ -242,6 +242,7 @@ class Mount(dict):
for the ``volume`` type.
driver_config (DriverConfig): Volume driver configuration. Only valid
for the ``volume`` type.
+ subpath (str): Path inside a volume to mount instead of the volume root.
tmpfs_size (int or string): The size for the tmpfs mount in bytes.
tmpfs_mode (int): The permission mode for the tmpfs mount.
"""
@@ -249,7 +250,7 @@ class Mount(dict):
def __init__(self, target, source, type='volume', read_only=False,
consistency=None, propagation=None, no_copy=False,
labels=None, driver_config=None, tmpfs_size=None,
- tmpfs_mode=None):
+ tmpfs_mode=None, subpath=None):
self['Target'] = target
self['Source'] = source
if type not in ('bind', 'volume', 'tmpfs', 'npipe'):
@@ -267,7 +268,7 @@ class Mount(dict):
self['BindOptions'] = {
'Propagation': propagation
}
- if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode]):
+ if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode, subpath]):
raise errors.InvalidArgument(
'Incompatible options have been provided for the bind '
'type mount.'
@@ -280,6 +281,8 @@ class Mount(dict):
volume_opts['Labels'] = labels
if driver_config:
volume_opts['DriverConfig'] = driver_config
+ if subpath:
+ volume_opts['Subpath'] = subpath
if volume_opts:
self['VolumeOptions'] = volume_opts
if any([propagation, tmpfs_size, tmpfs_mode]):
diff --git a/contrib/python/docker/ya.make b/contrib/python/docker/ya.make
index 5dbd794e305..bf30b2c36b0 100644
--- a/contrib/python/docker/ya.make
+++ b/contrib/python/docker/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(7.1.0)
+VERSION(7.2.0)
LICENSE(Apache-2.0)
diff --git a/contrib/python/pyOpenSSL/py3/.dist-info/METADATA b/contrib/python/pyOpenSSL/py3/.dist-info/METADATA
index e4139673e38..5691fc9e4ee 100644
--- a/contrib/python/pyOpenSSL/py3/.dist-info/METADATA
+++ b/contrib/python/pyOpenSSL/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: pyOpenSSL
-Version: 23.0.0
+Version: 23.3.0
Summary: Python wrapper module around the OpenSSL library
Home-page: https://pyopenssl.org/
Author: The pyOpenSSL developers
@@ -14,27 +14,27 @@ Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Networking
-Requires-Python: >=3.6
+Requires-Python: >=3.7
License-File: LICENSE
-Requires-Dist: cryptography (<40,>=38.0.0)
+Requires-Dist: cryptography <42,>=41.0.5
Provides-Extra: docs
-Requires-Dist: sphinx (!=5.2.0,!=5.2.0.post0) ; extra == 'docs'
+Requires-Dist: sphinx !=5.2.0,!=5.2.0.post0,!=7.2.5 ; extra == 'docs'
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
Provides-Extra: test
Requires-Dist: flaky ; extra == 'test'
Requires-Dist: pretend ; extra == 'test'
-Requires-Dist: pytest (>=3.0.1) ; extra == 'test'
+Requires-Dist: pytest >=3.0.1 ; extra == 'test'
========================================================
pyOpenSSL -- A Python wrapper around the OpenSSL library
@@ -87,6 +87,89 @@ You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get invol
Release Information
===================
+23.3.0 (2023-10-25)
+-------------------
+
+Backward-incompatible changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Dropped support for Python 3.6.
+- The minimum ``cryptography`` version is now 41.0.5.
+- Removed ``OpenSSL.crypto.loads_pkcs7`` and ``OpenSSL.crypto.loads_pkcs12`` which had been deprecated for 3 years.
+- Added ``OpenSSL.SSL.OP_LEGACY_SERVER_CONNECT`` to allow legacy insecure renegotiation between OpenSSL and unpatched servers.
+ `#1234 <https://github.com/pyca/pyopenssl/pull/1234>`_.
+
+Deprecations:
+^^^^^^^^^^^^^
+
+- Deprecated ``OpenSSL.crypto.PKCS12`` (which was intended to have been deprecated at the same time as ``OpenSSL.crypto.load_pkcs12``).
+- Deprecated ``OpenSSL.crypto.NetscapeSPKI``.
+- Deprecated ``OpenSSL.crypto.CRL``
+- Deprecated ``OpenSSL.crypto.Revoked``
+- Deprecated ``OpenSSL.crypto.load_crl`` and ``OpenSSL.crypto.dump_crl``
+- Deprecated ``OpenSSL.crypto.sign`` and ``OpenSSL.crypto.verify``
+- Deprecated ``OpenSSL.crypto.X509Extension``
+
+Changes:
+^^^^^^^^
+
+- Changed ``OpenSSL.crypto.X509Store.add_crl`` to also accept
+ ``cryptography``'s ``x509.CertificateRevocationList`` arguments in addition
+ to the now deprecated ``OpenSSL.crypto.CRL`` arguments.
+- Fixed ``test_set_default_verify_paths`` test so that it is skipped if no
+ network connection is available.
+
+23.2.0 (2023-05-30)
+-------------------
+
+Backward-incompatible changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Removed ``X509StoreFlags.NOTIFY_POLICY``.
+ `#1213 <https://github.com/pyca/pyopenssl/pull/1213>`_.
+
+Deprecations:
+^^^^^^^^^^^^^
+
+Changes:
+^^^^^^^^
+
+- ``cryptography`` maximum version has been increased to 41.0.x.
+- Invalid versions are now rejected in ``OpenSSL.crypto.X509Req.set_version``.
+- Added ``X509VerificationCodes`` to ``OpenSSL.SSL``.
+ `#1202 <https://github.com/pyca/pyopenssl/pull/1202>`_.
+
+23.1.1 (2023-03-28)
+-------------------
+
+Backward-incompatible changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Deprecations:
+^^^^^^^^^^^^^
+
+Changes:
+^^^^^^^^
+
+- Worked around an issue in OpenSSL 3.1.0 which caused `X509Extension.get_short_name` to raise an exception when no short name was known to OpenSSL.
+ `#1204 <https://github.com/pyca/pyopenssl/pull/1204>`_.
+
+23.1.0 (2023-03-24)
+-------------------
+
+Backward-incompatible changes:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Deprecations:
+^^^^^^^^^^^^^
+
+Changes:
+^^^^^^^^
+
+- ``cryptography`` maximum version has been increased to 40.0.x.
+- Add ``OpenSSL.SSL.Connection.DTLSv1_get_timeout`` and ``OpenSSL.SSL.Connection.DTLSv1_handle_timeout``
+ to support DTLS timeouts `#1180 <https://github.com/pyca/pyopenssl/pull/1180>`_.
+
23.0.0 (2023-01-01)
-------------------
@@ -111,7 +194,7 @@ Backward-incompatible changes:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Remove support for SSLv2 and SSLv3.
-- The minimum ``cryptography`` version is now 38.0.x (and we now pin releases
+- The minimum ``cryptography`` version is now 38.0.x (and we now pin releases
against ``cryptography`` major versions to prevent future breakage)
- The ``OpenSSL.crypto.X509StoreContextError`` exception has been refactored,
changing its internal attributes.
diff --git a/contrib/python/pyOpenSSL/py3/OpenSSL/SSL.py b/contrib/python/pyOpenSSL/py3/OpenSSL/SSL.py
index dfbd1094e9d..ae07442f5d1 100644
--- a/contrib/python/pyOpenSSL/py3/OpenSSL/SSL.py
+++ b/contrib/python/pyOpenSSL/py3/OpenSSL/SSL.py
@@ -1,5 +1,6 @@
import os
import socket
+import typing
from errno import errorcode
from functools import partial, wraps
from itertools import chain, count
@@ -8,18 +9,32 @@ from weakref import WeakValueDictionary
from OpenSSL._util import (
UNSPECIFIED as _UNSPECIFIED,
+)
+from OpenSSL._util import (
exception_from_error_queue as _exception_from_error_queue,
+)
+from OpenSSL._util import (
ffi as _ffi,
+)
+from OpenSSL._util import (
lib as _lib,
+)
+from OpenSSL._util import (
make_assert as _make_assert,
+)
+from OpenSSL._util import (
no_zero_allocator as _no_zero_allocator,
+)
+from OpenSSL._util import (
path_bytes as _path_bytes,
+)
+from OpenSSL._util import (
text_to_bytes_and_warn as _text_to_bytes_and_warn,
)
from OpenSSL.crypto import (
FILETYPE_PEM,
- PKey,
X509,
+ PKey,
X509Name,
X509Store,
_PassphraseHelper,
@@ -123,6 +138,7 @@ __all__ = [
"Session",
"Context",
"Connection",
+ "X509VerificationCodes",
]
@@ -216,6 +232,12 @@ try:
except AttributeError:
pass
+try:
+ OP_LEGACY_SERVER_CONNECT = _lib.SSL_OP_LEGACY_SERVER_CONNECT
+ __all__.append("OP_LEGACY_SERVER_CONNECT")
+except AttributeError:
+ pass
+
OP_ALL = _lib.SSL_OP_ALL
VERIFY_PEER = _lib.SSL_VERIFY_PEER
@@ -250,6 +272,113 @@ SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT
SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START
SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE
+
+class X509VerificationCodes:
+ """
+ Success and error codes for X509 verification, as returned by the
+ underlying ``X509_STORE_CTX_get_error()`` function and passed by pyOpenSSL
+ to verification callback functions.
+
+ See `OpenSSL Verification Errors
+ <https://www.openssl.org/docs/manmaster/man3/X509_verify_cert_error_string.html#ERROR-CODES>`_
+ for details.
+ """
+
+ OK = _lib.X509_V_OK
+ ERR_UNABLE_TO_GET_ISSUER_CERT = _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
+ ERR_UNABLE_TO_GET_CRL = _lib.X509_V_ERR_UNABLE_TO_GET_CRL
+ ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = (
+ _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE
+ )
+ ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = (
+ _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE
+ )
+ ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = (
+ _lib.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY
+ )
+ ERR_CERT_SIGNATURE_FAILURE = _lib.X509_V_ERR_CERT_SIGNATURE_FAILURE
+ ERR_CRL_SIGNATURE_FAILURE = _lib.X509_V_ERR_CRL_SIGNATURE_FAILURE
+ ERR_CERT_NOT_YET_VALID = _lib.X509_V_ERR_CERT_NOT_YET_VALID
+ ERR_CERT_HAS_EXPIRED = _lib.X509_V_ERR_CERT_HAS_EXPIRED
+ ERR_CRL_NOT_YET_VALID = _lib.X509_V_ERR_CRL_NOT_YET_VALID
+ ERR_CRL_HAS_EXPIRED = _lib.X509_V_ERR_CRL_HAS_EXPIRED
+ ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = (
+ _lib.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD
+ )
+ ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = (
+ _lib.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD
+ )
+ ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = (
+ _lib.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD
+ )
+ ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = (
+ _lib.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD
+ )
+ ERR_OUT_OF_MEM = _lib.X509_V_ERR_OUT_OF_MEM
+ ERR_DEPTH_ZERO_SELF_SIGNED_CERT = (
+ _lib.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
+ )
+ ERR_SELF_SIGNED_CERT_IN_CHAIN = _lib.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN
+ ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = (
+ _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY
+ )
+ ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = (
+ _lib.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
+ )
+ ERR_CERT_CHAIN_TOO_LONG = _lib.X509_V_ERR_CERT_CHAIN_TOO_LONG
+ ERR_CERT_REVOKED = _lib.X509_V_ERR_CERT_REVOKED
+ ERR_INVALID_CA = _lib.X509_V_ERR_INVALID_CA
+ ERR_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PATH_LENGTH_EXCEEDED
+ ERR_INVALID_PURPOSE = _lib.X509_V_ERR_INVALID_PURPOSE
+ ERR_CERT_UNTRUSTED = _lib.X509_V_ERR_CERT_UNTRUSTED
+ ERR_CERT_REJECTED = _lib.X509_V_ERR_CERT_REJECTED
+ ERR_SUBJECT_ISSUER_MISMATCH = _lib.X509_V_ERR_SUBJECT_ISSUER_MISMATCH
+ ERR_AKID_SKID_MISMATCH = _lib.X509_V_ERR_AKID_SKID_MISMATCH
+ ERR_AKID_ISSUER_SERIAL_MISMATCH = (
+ _lib.X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH
+ )
+ ERR_KEYUSAGE_NO_CERTSIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CERTSIGN
+ ERR_UNABLE_TO_GET_CRL_ISSUER = _lib.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER
+ ERR_UNHANDLED_CRITICAL_EXTENSION = (
+ _lib.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION
+ )
+ ERR_KEYUSAGE_NO_CRL_SIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN
+ ERR_UNHANDLED_CRITICAL_CRL_EXTENSION = (
+ _lib.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION
+ )
+ ERR_INVALID_NON_CA = _lib.X509_V_ERR_INVALID_NON_CA
+ ERR_PROXY_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED
+ ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE = (
+ _lib.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE
+ )
+ ERR_PROXY_CERTIFICATES_NOT_ALLOWED = (
+ _lib.X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED
+ )
+ ERR_INVALID_EXTENSION = _lib.X509_V_ERR_INVALID_EXTENSION
+ ERR_INVALID_POLICY_EXTENSION = _lib.X509_V_ERR_INVALID_POLICY_EXTENSION
+ ERR_NO_EXPLICIT_POLICY = _lib.X509_V_ERR_NO_EXPLICIT_POLICY
+ ERR_DIFFERENT_CRL_SCOPE = _lib.X509_V_ERR_DIFFERENT_CRL_SCOPE
+ ERR_UNSUPPORTED_EXTENSION_FEATURE = (
+ _lib.X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE
+ )
+ ERR_UNNESTED_RESOURCE = _lib.X509_V_ERR_UNNESTED_RESOURCE
+ ERR_PERMITTED_VIOLATION = _lib.X509_V_ERR_PERMITTED_VIOLATION
+ ERR_EXCLUDED_VIOLATION = _lib.X509_V_ERR_EXCLUDED_VIOLATION
+ ERR_SUBTREE_MINMAX = _lib.X509_V_ERR_SUBTREE_MINMAX
+ ERR_UNSUPPORTED_CONSTRAINT_TYPE = (
+ _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE
+ )
+ ERR_UNSUPPORTED_CONSTRAINT_SYNTAX = (
+ _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX
+ )
+ ERR_UNSUPPORTED_NAME_SYNTAX = _lib.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX
+ ERR_CRL_PATH_VALIDATION_ERROR = _lib.X509_V_ERR_CRL_PATH_VALIDATION_ERROR
+ ERR_HOSTNAME_MISMATCH = _lib.X509_V_ERR_HOSTNAME_MISMATCH
+ ERR_EMAIL_MISMATCH = _lib.X509_V_ERR_EMAIL_MISMATCH
+ ERR_IP_ADDRESS_MISMATCH = _lib.X509_V_ERR_IP_ADDRESS_MISMATCH
+ ERR_APPLICATION_VERIFICATION = _lib.X509_V_ERR_APPLICATION_VERIFICATION
+
+
# Taken from https://golang.org/src/crypto/x509/root_linux.go
_CERTIFICATE_FILE_LOCATIONS = [
"/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc.
@@ -689,7 +818,7 @@ class Context:
not be used.
"""
- _methods = {
+ _methods: typing.ClassVar[typing.Dict] = {
SSLv23_METHOD: (_lib.TLS_method, None),
TLSv1_METHOD: (_lib.TLS_method, TLS1_VERSION),
TLSv1_1_METHOD: (_lib.TLS_method, TLS1_1_VERSION),
@@ -847,9 +976,9 @@ class Context:
the ``[email protected]`` `Homebrew <https://brew.sh>`_ formula installed
in the default location.
* Windows will not work.
- * manylinux1 cryptography wheels will work on most common Linux
+ * manylinux cryptography wheels will work on most common Linux
distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the
- manylinux1 wheel and attempts to load roots via a fallback path.
+ manylinux wheel and attempts to load roots via a fallback path.
:return: None
"""
@@ -874,13 +1003,13 @@ class Context:
default_dir = _ffi.string(_lib.X509_get_default_cert_dir())
default_file = _ffi.string(_lib.X509_get_default_cert_file())
# Now we check to see if the default_dir and default_file are set
- # to the exact values we use in our manylinux1 builds. If they are
+ # to the exact values we use in our manylinux builds. If they are
# then we know to load the fallbacks
if (
default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR
and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE
):
- # This is manylinux1, let's load our fallback paths
+ # This is manylinux, let's load our fallback paths
self._fallback_default_verify_paths(
_CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS
)
@@ -899,7 +1028,7 @@ class Context:
def _fallback_default_verify_paths(self, file_path, dir_path):
"""
Default verify paths are based on the compiled version of OpenSSL.
- However, when pyca/cryptography is compiled as a manylinux1 wheel
+ However, when pyca/cryptography is compiled as a manylinux wheel
that compiled location can potentially be wrong. So, like Go, we
will try a predefined set of paths and attempt to load roots
from there.
@@ -1259,8 +1388,8 @@ class Context:
for ca_name in certificate_authorities:
if not isinstance(ca_name, X509Name):
raise TypeError(
- "client CAs must be X509Name objects, not %s "
- "objects" % (type(ca_name).__name__,)
+ "client CAs must be X509Name objects, not {} "
+ "objects".format(type(ca_name).__name__)
)
copy = _lib.X509_NAME_dup(ca_name._name)
_openssl_assert(copy != _ffi.NULL)
@@ -1663,8 +1792,7 @@ class Connection:
"""
if self._socket is None:
raise AttributeError(
- "'%s' object has no attribute '%s'"
- % (self.__class__.__name__, name)
+ f"'{self.__class__.__name__}' object has no attribute '{name}'"
)
else:
return getattr(self._socket, name)
@@ -1916,7 +2044,6 @@ class Connection:
buf = _text_to_bytes_and_warn("buf", buf)
with _ffi.from_buffer(buf) as data:
-
left_to_send = len(buf)
total_sent = 0
@@ -2160,6 +2287,37 @@ class Connection:
if result < 0:
self._raise_ssl_error(self._ssl, result)
+ def DTLSv1_get_timeout(self):
+ """
+ Determine when the DTLS SSL object next needs to perform internal
+ processing due to the passage of time.
+
+ When the returned number of seconds have passed, the
+ :meth:`DTLSv1_handle_timeout` method needs to be called.
+
+ :return: The time left in seconds before the next timeout or `None`
+ if no timeout is currently active.
+ """
+ ptv_sec = _ffi.new("time_t *")
+ ptv_usec = _ffi.new("long *")
+ if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec):
+ return ptv_sec[0] + (ptv_usec[0] / 1000000)
+ else:
+ return None
+
+ def DTLSv1_handle_timeout(self):
+ """
+ Handles any timeout events which have become pending on a DTLS SSL
+ object.
+
+ :return: `True` if there was a pending timeout, `False` otherwise.
+ """
+ result = _lib.DTLSv1_handle_timeout(self._ssl)
+ if result < 0:
+ self._raise_ssl_error(self._ssl, result)
+ else:
+ return bool(result)
+
def bio_shutdown(self):
"""
If the Connection was created with a memory BIO, this method can be
diff --git a/contrib/python/pyOpenSSL/py3/OpenSSL/__init__.py b/contrib/python/pyOpenSSL/py3/OpenSSL/__init__.py
index 0af3acdb8fd..2442ee6f054 100644
--- a/contrib/python/pyOpenSSL/py3/OpenSSL/__init__.py
+++ b/contrib/python/pyOpenSSL/py3/OpenSSL/__init__.py
@@ -17,7 +17,6 @@ from OpenSSL.version import (
__version__,
)
-
__all__ = [
"SSL",
"crypto",
diff --git a/contrib/python/pyOpenSSL/py3/OpenSSL/crypto.py b/contrib/python/pyOpenSSL/py3/OpenSSL/crypto.py
index a9b673c53f5..cccc216bbda 100644
--- a/contrib/python/pyOpenSSL/py3/OpenSSL/crypto.py
+++ b/contrib/python/pyOpenSSL/py3/OpenSSL/crypto.py
@@ -1,6 +1,7 @@
import calendar
import datetime
import functools
+import typing
from base64 import b16encode
from functools import partial
from os import PathLike
@@ -22,23 +23,36 @@ from cryptography import utils, x509
from cryptography.hazmat.primitives.asymmetric import (
dsa,
ec,
- ed25519,
ed448,
+ ed25519,
rsa,
)
from OpenSSL._util import (
UNSPECIFIED as _UNSPECIFIED,
+)
+from OpenSSL._util import (
byte_string as _byte_string,
+)
+from OpenSSL._util import (
exception_from_error_queue as _exception_from_error_queue,
+)
+from OpenSSL._util import (
ffi as _ffi,
+)
+from OpenSSL._util import (
lib as _lib,
+)
+from OpenSSL._util import (
make_assert as _make_assert,
+)
+from OpenSSL._util import (
path_bytes as _path_bytes,
+)
+from OpenSSL._util import (
text_to_bytes_and_warn as _text_to_bytes_and_warn,
)
-
__all__ = [
"FILETYPE_PEM",
"FILETYPE_ASN1",
@@ -63,7 +77,6 @@ __all__ = [
"dump_privatekey",
"Revoked",
"CRL",
- "PKCS7",
"PKCS12",
"NetscapeSPKI",
"load_publickey",
@@ -74,8 +87,6 @@ __all__ = [
"verify",
"dump_crl",
"load_crl",
- "load_pkcs7_data",
- "load_pkcs12",
]
@@ -114,7 +125,7 @@ def _untested_error(where: str) -> NoReturn:
encountered isn't one that's exercised by the test suite so future behavior
of pyOpenSSL is now somewhat less predictable.
"""
- raise RuntimeError("Unknown %s failure" % (where,))
+ raise RuntimeError(f"Unknown {where} failure")
def _new_mem_buf(buffer: Optional[bytes] = None) -> Any:
@@ -451,7 +462,7 @@ class _EllipticCurve:
circumstance.
"""
if isinstance(other, _EllipticCurve):
- return super(_EllipticCurve, self).__ne__(other)
+ return super().__ne__(other)
return NotImplemented
@classmethod
@@ -521,7 +532,7 @@ class _EllipticCurve:
self.name = name
def __repr__(self) -> str:
- return "<Curve %r>" % (self.name,)
+ return f"<Curve {self.name!r}>"
def _to_EC_KEY(self) -> Any:
"""
@@ -605,14 +616,15 @@ class X509Name:
def __setattr__(self, name: str, value: Any) -> None:
if name.startswith("_"):
- return super(X509Name, self).__setattr__(name, value)
+ return super().__setattr__(name, value)
# Note: we really do not want str subclasses here, so we do not use
# isinstance.
- if type(name) is not str:
+ if type(name) is not str: # noqa: E721
raise TypeError(
- "attribute name must be string, not '%.200s'"
- % (type(value).__name__,)
+ "attribute name must be string, not '{:.200}'".format(
+ type(value).__name__
+ )
)
nid = _lib.OBJ_txt2nid(_byte_string(name))
@@ -704,7 +716,7 @@ class X509Name:
)
_openssl_assert(format_result != _ffi.NULL)
- return "<X509Name object '%s'>" % (
+ return "<X509Name object '{}'>".format(
_ffi.string(result_buffer).decode("utf-8"),
)
@@ -842,7 +854,7 @@ class X509Extension:
_lib.X509_EXTENSION_get_object(self._extension)
)
- _prefixes = {
+ _prefixes: typing.ClassVar[typing.Dict[int, str]] = {
_lib.GEN_EMAIL: "email",
_lib.GEN_DNS: "DNS",
_lib.GEN_URI: "URI",
@@ -904,7 +916,14 @@ class X509Extension:
"""
obj = _lib.X509_EXTENSION_get_object(self._extension)
nid = _lib.OBJ_obj2nid(obj)
- return _ffi.string(_lib.OBJ_nid2sn(nid))
+ # OpenSSL 3.1.0 has a bug where nid2sn returns NULL for NIDs that
+ # previously returned UNDEF. This is a workaround for that issue.
+ # https://github.com/openssl/openssl/commit/908ba3ed9adbb3df90f76
+ buf = _lib.OBJ_nid2sn(nid)
+ if buf != _ffi.NULL:
+ return _ffi.string(buf)
+ else:
+ return b"UNDEF"
def get_data(self) -> bytes:
"""
@@ -922,6 +941,19 @@ class X509Extension:
return _ffi.buffer(char_result, result_length)[:]
+_X509ExtensionInternal = X509Extension
+utils.deprecated(
+ X509Extension,
+ __name__,
+ (
+ "X509Extension support in pyOpenSSL is deprecated. You should use the "
+ "APIs in cryptography."
+ ),
+ DeprecationWarning,
+ name="X509Extension",
+)
+
+
class X509Req:
"""
An X.509 certificate signing requests.
@@ -1003,6 +1035,12 @@ class X509Req:
:param int version: The version number.
:return: ``None``
"""
+ if not isinstance(version, int):
+ raise TypeError("version must be an int")
+ if version != 0:
+ raise ValueError(
+ "Invalid version. The only valid version for X509Req is 0."
+ )
set_result = _lib.X509_REQ_set_version(self._req, version)
_openssl_assert(set_result == 1)
@@ -1038,7 +1076,9 @@ class X509Req:
return name
- def add_extensions(self, extensions: Iterable[X509Extension]) -> None:
+ def add_extensions(
+ self, extensions: Iterable[_X509ExtensionInternal]
+ ) -> None:
"""
Add extensions to the certificate signing request.
@@ -1052,7 +1092,7 @@ class X509Req:
stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free)
for ext in extensions:
- if not isinstance(ext, X509Extension):
+ if not isinstance(ext, _X509ExtensionInternal):
raise ValueError("One of the elements is not an X509Extension")
# TODO push can fail (here and elsewhere)
@@ -1061,7 +1101,7 @@ class X509Req:
add_result = _lib.X509_REQ_add_extensions(self._req, stack)
_openssl_assert(add_result == 1)
- def get_extensions(self) -> List[X509Extension]:
+ def get_extensions(self) -> List[_X509ExtensionInternal]:
"""
Get X.509 extensions in the certificate signing request.
@@ -1081,7 +1121,7 @@ class X509Req:
)
for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)):
- ext = X509Extension.__new__(X509Extension)
+ ext = _X509ExtensionInternal.__new__(_X509ExtensionInternal)
extension = _lib.X509_EXTENSION_dup(
_lib.sk_X509_EXTENSION_value(native_exts_obj, i)
)
@@ -1282,8 +1322,10 @@ class X509:
.. versionadded:: 0.13
"""
- algor = _lib.X509_get0_tbs_sigalg(self._x509)
- nid = _lib.OBJ_obj2nid(algor.algorithm)
+ sig_alg = _lib.X509_get0_tbs_sigalg(self._x509)
+ alg = _ffi.new("ASN1_OBJECT **")
+ _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg)
+ nid = _lib.OBJ_obj2nid(alg[0])
if nid == _lib.NID_undef:
raise ValueError("Undefined signature algorithm")
return _ffi.string(_lib.OBJ_nid2ln(nid))
@@ -1427,7 +1469,9 @@ class X509:
time_string = time_bytes.decode("utf-8")
not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
- return not_after < datetime.datetime.utcnow()
+ UTC = datetime.timezone.utc
+ utcnow = datetime.datetime.now(UTC).replace(tzinfo=None)
+ return not_after < utcnow
def _get_boundary_time(self, which: Any) -> Optional[bytes]:
return _get_asn1_time(which(self._x509))
@@ -1573,7 +1617,9 @@ class X509:
"""
return _lib.X509_get_ext_count(self._x509)
- def add_extensions(self, extensions: Iterable[X509Extension]) -> None:
+ def add_extensions(
+ self, extensions: Iterable[_X509ExtensionInternal]
+ ) -> None:
"""
Add extensions to the certificate.
@@ -1582,14 +1628,14 @@ class X509:
:return: ``None``
"""
for ext in extensions:
- if not isinstance(ext, X509Extension):
+ if not isinstance(ext, _X509ExtensionInternal):
raise ValueError("One of the elements is not an X509Extension")
add_result = _lib.X509_add_ext(self._x509, ext._extension, -1)
if not add_result:
_raise_current_error()
- def get_extension(self, index: int) -> X509Extension:
+ def get_extension(self, index: int) -> _X509ExtensionInternal:
"""
Get a specific extension of the certificate by index.
@@ -1603,7 +1649,7 @@ class X509:
.. versionadded:: 0.12
"""
- ext = X509Extension.__new__(X509Extension)
+ ext = _X509ExtensionInternal.__new__(_X509ExtensionInternal)
ext._extension = _lib.X509_get_ext(self._x509, index)
if ext._extension == _ffi.NULL:
raise IndexError("extension index out of bounds")
@@ -1632,7 +1678,6 @@ class X509StoreFlags:
POLICY_CHECK: int = _lib.X509_V_FLAG_POLICY_CHECK
EXPLICIT_POLICY: int = _lib.X509_V_FLAG_EXPLICIT_POLICY
INHIBIT_MAP: int = _lib.X509_V_FLAG_INHIBIT_MAP
- NOTIFY_POLICY: int = _lib.X509_V_FLAG_NOTIFY_POLICY
CHECK_SS_SIGNATURE: int = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE
PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN
@@ -1677,7 +1722,9 @@ class X509Store:
res = _lib.X509_STORE_add_cert(self._store, cert._x509)
_openssl_assert(res == 1)
- def add_crl(self, crl: "CRL") -> None:
+ def add_crl(
+ self, crl: Union["_CRLInternal", x509.CertificateRevocationList]
+ ) -> None:
"""
Add a certificate revocation list to this store.
@@ -1687,11 +1734,29 @@ class X509Store:
.. versionadded:: 16.1.0
- :param CRL crl: The certificate revocation list to add to this store.
+ :param crl: The certificate revocation list to add to this store.
+ :type crl: ``Union[CRL, cryptography.x509.CertificateRevocationList]``
:return: ``None`` if the certificate revocation list was added
successfully.
"""
- _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl._crl) != 0)
+ if isinstance(crl, x509.CertificateRevocationList):
+ from cryptography.hazmat.primitives.serialization import Encoding
+
+ bio = _new_mem_buf(crl.public_bytes(Encoding.DER))
+ openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL)
+ if openssl_crl == _ffi.NULL:
+ _raise_current_error()
+
+ crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free)
+ elif isinstance(crl, _CRLInternal):
+ crl = crl._crl
+ else:
+ raise TypeError(
+ "CRL must be of type OpenSSL.crypto.CRL or "
+ "cryptography.x509.CertificateRevocationList"
+ )
+
+ _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl) != 0)
def set_flags(self, flags: int) -> None:
"""
@@ -1803,7 +1868,7 @@ class X509StoreContextError(Exception):
def __init__(
self, message: str, errors: List[Any], certificate: X509
) -> None:
- super(X509StoreContextError, self).__init__(message)
+ super().__init__(message)
self.errors = errors
self.certificate = certificate
@@ -2155,7 +2220,7 @@ class Revoked:
# which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches
# OCSP_crl_reason_str. We use the latter, just like the command line
# program.
- _crl_reasons = [
+ _crl_reasons: typing.ClassVar[typing.List[bytes]] = [
b"unspecified",
b"keyCompromise",
b"CACompromise",
@@ -2323,6 +2388,19 @@ class Revoked:
return _get_asn1_time(dt)
+_RevokedInternal = Revoked
+utils.deprecated(
+ Revoked,
+ __name__,
+ (
+ "CRL support in pyOpenSSL is deprecated. You should use the APIs "
+ "in cryptography."
+ ),
+ DeprecationWarning,
+ name="Revoked",
+)
+
+
class CRL:
"""
A certificate revocation list.
@@ -2342,13 +2420,13 @@ class CRL:
"""
from cryptography.x509 import load_der_x509_crl
- der = dump_crl(FILETYPE_ASN1, self)
+ der = _dump_crl_internal(FILETYPE_ASN1, self)
return load_der_x509_crl(der)
@classmethod
def from_cryptography(
cls, crypto_crl: x509.CertificateRevocationList
- ) -> "CRL":
+ ) -> "_CRLInternal":
"""
Construct based on a ``cryptography`` *crypto_crl*.
@@ -2365,9 +2443,9 @@ class CRL:
from cryptography.hazmat.primitives.serialization import Encoding
der = crypto_crl.public_bytes(Encoding.DER)
- return load_crl(FILETYPE_ASN1, der)
+ return _load_crl_internal(FILETYPE_ASN1, der)
- def get_revoked(self) -> Optional[Tuple[Revoked, ...]]:
+ def get_revoked(self) -> Optional[Tuple[_RevokedInternal, ...]]:
"""
Return the revocations in this certificate revocation list.
@@ -2382,14 +2460,14 @@ class CRL:
for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)):
revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i)
revoked_copy = _lib.X509_REVOKED_dup(revoked)
- pyrev = Revoked.__new__(Revoked)
+ pyrev = _RevokedInternal.__new__(_RevokedInternal)
pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free)
results.append(pyrev)
if results:
return tuple(results)
return None
- def add_revoked(self, revoked: Revoked) -> None:
+ def add_revoked(self, revoked: _RevokedInternal) -> None:
"""
Add a revoked (by value not reference) to the CRL structure
@@ -2552,54 +2630,20 @@ class CRL:
if not sign_result:
_raise_current_error()
- return dump_crl(type, self)
-
-
-class PKCS7:
-
- _pkcs7: Any
-
- def type_is_signed(self) -> bool:
- """
- Check if this NID_pkcs7_signed object
-
- :return: True if the PKCS7 is of type signed
- """
- return bool(_lib.PKCS7_type_is_signed(self._pkcs7))
-
- def type_is_enveloped(self) -> bool:
- """
- Check if this NID_pkcs7_enveloped object
-
- :returns: True if the PKCS7 is of type enveloped
- """
- return bool(_lib.PKCS7_type_is_enveloped(self._pkcs7))
-
- def type_is_signedAndEnveloped(self) -> bool:
- """
- Check if this NID_pkcs7_signedAndEnveloped object
-
- :returns: True if the PKCS7 is of type signedAndEnveloped
- """
- return bool(_lib.PKCS7_type_is_signedAndEnveloped(self._pkcs7))
-
- def type_is_data(self) -> bool:
- """
- Check if this NID_pkcs7_data object
-
- :return: True if the PKCS7 is of type data
- """
- return bool(_lib.PKCS7_type_is_data(self._pkcs7))
+ return _dump_crl_internal(type, self)
- def get_type_name(self) -> str:
- """
- Returns the type name of the PKCS7 structure
- :return: A string with the typename
- """
- nid = _lib.OBJ_obj2nid(self._pkcs7.type)
- string_type = _lib.OBJ_nid2sn(nid)
- return _ffi.string(string_type)
+_CRLInternal = CRL
+utils.deprecated(
+ CRL,
+ __name__,
+ (
+ "CRL support in pyOpenSSL is deprecated. You should use the APIs "
+ "in cryptography."
+ ),
+ DeprecationWarning,
+ name="CRL",
+)
class PKCS12:
@@ -2703,7 +2747,7 @@ class PKCS12:
self._friendlyname = None
elif not isinstance(name, bytes):
raise TypeError(
- "name must be a byte string or None (not %r)" % (name,)
+ f"name must be a byte string or None (not {name!r})"
)
self._friendlyname = name
@@ -2789,6 +2833,18 @@ class PKCS12:
return _bio_to_string(bio)
+utils.deprecated(
+ PKCS12,
+ __name__,
+ (
+ "PKCS#12 support in pyOpenSSL is deprecated. You should use the APIs "
+ "in cryptography."
+ ),
+ DeprecationWarning,
+ name="PKCS12",
+)
+
+
class NetscapeSPKI:
"""
A Netscape SPKI object.
@@ -2879,6 +2935,15 @@ class NetscapeSPKI:
_openssl_assert(set_result == 1)
+utils.deprecated(
+ NetscapeSPKI,
+ __name__,
+ "NetscapeSPKI support in pyOpenSSL is deprecated.",
+ DeprecationWarning,
+ name="NetscapeSPKI",
+)
+
+
class _PassphraseHelper:
def __init__(
self,
@@ -2920,7 +2985,6 @@ class _PassphraseHelper:
def raise_if_problem(self, exceptionType: Type[Exception] = Error) -> None:
if self._problems:
-
# Flush the OpenSSL error queue
try:
_exception_from_error_queue(exceptionType)
@@ -3124,6 +3188,15 @@ def sign(pkey: PKey, data: Union[str, bytes], digest: str) -> bytes:
return _ffi.buffer(signature_buffer, signature_length[0])[:]
+utils.deprecated(
+ sign,
+ __name__,
+ "sign() is deprecated. Use the equivilant APIs in cryptography.",
+ DeprecationWarning,
+ name="sign",
+)
+
+
def verify(
cert: X509, signature: bytes, data: Union[str, bytes], digest: str
) -> None:
@@ -3162,7 +3235,16 @@ def verify(
_raise_current_error()
-def dump_crl(type: int, crl: CRL) -> bytes:
+utils.deprecated(
+ verify,
+ __name__,
+ "verify() is deprecated. Use the equivilant APIs in cryptography.",
+ DeprecationWarning,
+ name="verify",
+)
+
+
+def dump_crl(type: int, crl: _CRLInternal) -> bytes:
"""
Dump a certificate revocation list to a buffer.
@@ -3191,7 +3273,20 @@ def dump_crl(type: int, crl: CRL) -> bytes:
return _bio_to_string(bio)
-def load_crl(type: int, buffer: Union[str, bytes]) -> CRL:
+_dump_crl_internal = dump_crl
+utils.deprecated(
+ dump_crl,
+ __name__,
+ (
+ "CRL support in pyOpenSSL is deprecated. You should use the APIs "
+ "in cryptography."
+ ),
+ DeprecationWarning,
+ name="dump_crl",
+)
+
+
+def load_crl(type: int, buffer: Union[str, bytes]) -> _CRLInternal:
"""
Load Certificate Revocation List (CRL) data from a string *buffer*.
*buffer* encoded with the type *type*.
@@ -3216,146 +3311,19 @@ def load_crl(type: int, buffer: Union[str, bytes]) -> CRL:
if crl == _ffi.NULL:
_raise_current_error()
- result = CRL.__new__(CRL)
+ result = _CRLInternal.__new__(_CRLInternal)
result._crl = _ffi.gc(crl, _lib.X509_CRL_free)
return result
-def load_pkcs7_data(type: int, buffer: Union[str, bytes]) -> PKCS7:
- """
- Load pkcs7 data from the string *buffer* encoded with the type
- *type*.
-
- :param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1)
- :param buffer: The buffer with the pkcs7 data.
- :return: The PKCS7 object
- """
- if isinstance(buffer, str):
- buffer = buffer.encode("ascii")
-
- bio = _new_mem_buf(buffer)
-
- if type == FILETYPE_PEM:
- pkcs7 = _lib.PEM_read_bio_PKCS7(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL)
- elif type == FILETYPE_ASN1:
- pkcs7 = _lib.d2i_PKCS7_bio(bio, _ffi.NULL)
- else:
- raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1")
-
- if pkcs7 == _ffi.NULL:
- _raise_current_error()
-
- pypkcs7 = PKCS7.__new__(PKCS7)
- pypkcs7._pkcs7 = _ffi.gc(pkcs7, _lib.PKCS7_free)
- return pypkcs7
-
-
-utils.deprecated(
- load_pkcs7_data,
- __name__,
- (
- "PKCS#7 support in pyOpenSSL is deprecated. You should use the APIs "
- "in cryptography."
- ),
- DeprecationWarning,
- name="load_pkcs7_data",
-)
-
-
-def load_pkcs12(
- buffer: Union[str, bytes], passphrase: Optional[bytes] = None
-) -> PKCS12:
- """
- Load pkcs12 data from the string *buffer*. If the pkcs12 structure is
- encrypted, a *passphrase* must be included. The MAC is always
- checked and thus required.
-
- See also the man page for the C function :py:func:`PKCS12_parse`.
-
- :param buffer: The buffer the certificate is stored in
- :param passphrase: (Optional) The password to decrypt the PKCS12 lump
- :returns: The PKCS12 object
- """
- passphrase = _text_to_bytes_and_warn("passphrase", passphrase)
-
- if isinstance(buffer, str):
- buffer = buffer.encode("ascii")
-
- bio = _new_mem_buf(buffer)
-
- # Use null passphrase if passphrase is None or empty string. With PKCS#12
- # password based encryption no password and a zero length password are two
- # different things, but OpenSSL implementation will try both to figure out
- # which one works.
- if not passphrase:
- passphrase = _ffi.NULL
-
- p12 = _lib.d2i_PKCS12_bio(bio, _ffi.NULL)
- if p12 == _ffi.NULL:
- _raise_current_error()
- p12 = _ffi.gc(p12, _lib.PKCS12_free)
-
- pkey = _ffi.new("EVP_PKEY**")
- cert = _ffi.new("X509**")
- cacerts = _ffi.new("Cryptography_STACK_OF_X509**")
-
- parse_result = _lib.PKCS12_parse(p12, passphrase, pkey, cert, cacerts)
- if not parse_result:
- _raise_current_error()
-
- cacerts = _ffi.gc(cacerts[0], _lib.sk_X509_free)
-
- # openssl 1.0.0 sometimes leaves an X509_check_private_key error in the
- # queue for no particular reason. This error isn't interesting to anyone
- # outside this function. It's not even interesting to us. Get rid of it.
- try:
- _raise_current_error()
- except Error:
- pass
-
- if pkey[0] == _ffi.NULL:
- pykey = None
- else:
- pykey = PKey.__new__(PKey)
- pykey._pkey = _ffi.gc(pkey[0], _lib.EVP_PKEY_free)
-
- if cert[0] == _ffi.NULL:
- pycert = None
- friendlyname = None
- else:
- pycert = X509._from_raw_x509_ptr(cert[0])
-
- friendlyname_length = _ffi.new("int*")
- friendlyname_buffer = _lib.X509_alias_get0(
- cert[0], friendlyname_length
- )
- friendlyname = _ffi.buffer(
- friendlyname_buffer, friendlyname_length[0]
- )[:]
- if friendlyname_buffer == _ffi.NULL:
- friendlyname = None
-
- pycacerts = []
- for i in range(_lib.sk_X509_num(cacerts)):
- x509 = _lib.sk_X509_value(cacerts, i)
- pycacert = X509._from_raw_x509_ptr(x509)
- pycacerts.append(pycacert)
-
- pkcs12 = PKCS12.__new__(PKCS12)
- pkcs12._pkey = pykey
- pkcs12._cert = pycert
- pkcs12._cacerts = pycacerts if pycacerts else None
- pkcs12._friendlyname = friendlyname
- return pkcs12
-
-
+_load_crl_internal = load_crl
utils.deprecated(
- load_pkcs12,
+ load_crl,
__name__,
(
- "PKCS#12 support in pyOpenSSL is deprecated. You should use the APIs "
+ "CRL support in pyOpenSSL is deprecated. You should use the APIs "
"in cryptography."
),
DeprecationWarning,
- name="load_pkcs12",
+ name="load_crl",
)
diff --git a/contrib/python/pyOpenSSL/py3/OpenSSL/debug.py b/contrib/python/pyOpenSSL/py3/OpenSSL/debug.py
index e39b128a7e2..e0ed3f81d62 100644
--- a/contrib/python/pyOpenSSL/py3/OpenSSL/debug.py
+++ b/contrib/python/pyOpenSSL/py3/OpenSSL/debug.py
@@ -1,17 +1,13 @@
-from __future__ import print_function
-
import ssl
import sys
import cffi
-
import cryptography
import OpenSSL.SSL
from . import version
-
_env_info = """\
pyOpenSSL: {pyopenssl}
cryptography: {cryptography}
diff --git a/contrib/python/pyOpenSSL/py3/OpenSSL/version.py b/contrib/python/pyOpenSSL/py3/OpenSSL/version.py
index 61ec17b2d8f..4120cff6dda 100644
--- a/contrib/python/pyOpenSSL/py3/OpenSSL/version.py
+++ b/contrib/python/pyOpenSSL/py3/OpenSSL/version.py
@@ -17,7 +17,7 @@ __all__ = [
"__version__",
]
-__version__ = "23.0.0"
+__version__ = "23.3.0"
__title__ = "pyOpenSSL"
__uri__ = "https://pyopenssl.org/"
@@ -25,4 +25,4 @@ __summary__ = "Python wrapper module around the OpenSSL library"
__author__ = "The pyOpenSSL developers"
__email__ = "[email protected]"
__license__ = "Apache License, Version 2.0"
-__copyright__ = "Copyright 2001-2023 {0}".format(__author__)
+__copyright__ = f"Copyright 2001-2023 {__author__}"
diff --git a/contrib/python/pyOpenSSL/py3/tests/conftest.py b/contrib/python/pyOpenSSL/py3/tests/conftest.py
index 5bae6b8f717..573b4b3d602 100644
--- a/contrib/python/pyOpenSSL/py3/tests/conftest.py
+++ b/contrib/python/pyOpenSSL/py3/tests/conftest.py
@@ -7,9 +7,10 @@ import pytest
def pytest_report_header(config):
- import OpenSSL.SSL
import cryptography
+ import OpenSSL.SSL
+
return "OpenSSL: {openssl}\ncryptography: {cryptography}".format(
openssl=OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION),
cryptography=cryptography.__version__,
diff --git a/contrib/python/pyOpenSSL/py3/tests/memdbg.py b/contrib/python/pyOpenSSL/py3/tests/memdbg.py
index 0dd8c318e4e..5eed1ba8b0a 100644
--- a/contrib/python/pyOpenSSL/py3/tests/memdbg.py
+++ b/contrib/python/pyOpenSSL/py3/tests/memdbg.py
@@ -3,7 +3,6 @@ import traceback
from cffi import api as _api
-
sys.modules["ssl"] = None
sys.modules["_hashlib"] = None
@@ -22,7 +21,7 @@ _ffi.cdef(
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
"""
-) # noqa
+)
_api = _ffi.verify(
"""
#include <openssl/crypto.h>
@@ -79,7 +78,7 @@ def free(p):
if p != _ffi.NULL:
C.free(p)
del heap[p]
- log("free(0x%x)" % (int(_ffi.cast("int", p)),))
+ log("free(0x{:x})".format(int(_ffi.cast("int", p))))
if _api.CRYPTO_set_mem_functions(malloc, realloc, free):
diff --git a/contrib/python/pyOpenSSL/py3/tests/test_crypto.py b/contrib/python/pyOpenSSL/py3/tests/test_crypto.py
index 44bbd0f2aba..6351f0d703c 100644
--- a/contrib/python/pyOpenSSL/py3/tests/test_crypto.py
+++ b/contrib/python/pyOpenSSL/py3/tests/test_crypto.py
@@ -6,34 +6,27 @@ Unit tests for :py:mod:`OpenSSL.crypto`.
"""
import base64
import sys
-from datetime import datetime, timedelta
+import warnings
+from datetime import datetime, timedelta, timezone
from subprocess import PIPE, Popen
-from warnings import simplefilter
-
-from cryptography import x509
-from cryptography.hazmat.primitives import serialization
-from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448, rsa
import flaky
-
import pytest
+from cryptography import x509
+from cryptography.hazmat.primitives import hashes, serialization
+from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa
-from OpenSSL._util import ffi as _ffi, lib as _lib
+from OpenSSL._util import ffi as _ffi
+from OpenSSL._util import lib as _lib
from OpenSSL.crypto import (
- CRL,
- Error,
FILETYPE_ASN1,
FILETYPE_PEM,
FILETYPE_TEXT,
- NetscapeSPKI,
- PKCS12,
- PKCS7,
- PKey,
- Revoked,
TYPE_DSA,
TYPE_RSA,
X509,
- X509Extension,
+ Error,
+ PKey,
X509Name,
X509Req,
X509Store,
@@ -42,26 +35,33 @@ from OpenSSL.crypto import (
X509StoreFlags,
dump_certificate,
dump_certificate_request,
- dump_crl,
dump_privatekey,
dump_publickey,
get_elliptic_curve,
get_elliptic_curves,
load_certificate,
load_certificate_request,
- load_crl,
- load_pkcs12,
- load_pkcs7_data,
load_privatekey,
load_publickey,
sign,
verify,
)
+with pytest.warns(DeprecationWarning):
+ from OpenSSL.crypto import (
+ CRL,
+ PKCS12,
+ NetscapeSPKI,
+ Revoked,
+ X509Extension,
+ dump_crl,
+ load_crl,
+ )
+
from .util import (
- EqualityTestsMixin,
NON_ASCII,
WARNING_TYPE_EXPECTED,
+ EqualityTestsMixin,
is_consistent_type,
)
@@ -70,6 +70,10 @@ def normalize_privatekey_pem(pem):
return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem))
+def utcnow():
+ return datetime.now(timezone.utc).replace(tzinfo=None)
+
+
GOOD_CIPHER = "blowfish"
BAD_CIPHER = "zippers"
@@ -554,57 +558,6 @@ ywIDAQAB
-----END PUBLIC KEY-----
"""
-# Some PKCS#7 stuff. Generated with the openssl command line:
-#
-# openssl crl2pkcs7 -inform pem -outform pem -certfile s.pem -nocrl
-#
-# with a certificate and key (but the key should be irrelevant) in s.pem
-pkcs7Data = b"""\
------BEGIN PKCS7-----
-MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID
-BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G
-A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN
-MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA
-cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG
-A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx
-HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD
-SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI
-zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw
-LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
-A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp
-65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
-Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g
-Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv
-bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu
-VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG
-/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9
-Ho4EzbYCOaEAMQA=
------END PKCS7-----
-"""
-
-pkcs7DataASN1 = base64.b64decode(
- b"""
-MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID
-BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G
-A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN
-MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA
-cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG
-A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx
-HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD
-SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI
-zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw
-LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
-A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp
-65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
-Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g
-Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv
-bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu
-VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG
-/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9
-Ho4EzbYCOaEAMQA=
-"""
-)
-
crlData = b"""\
-----BEGIN X509 CRL-----
MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
@@ -1601,20 +1554,12 @@ class TestX509Req(_PKeyInteractionTestsMixin):
"""
`X509Req.set_version` sets the X.509 version of the certificate
request. `X509Req.get_version` returns the X.509 version of the
- certificate request. The only defined version is 0. Others may or
- may not be supported depending on backend.
+ certificate request. The only defined version is 0.
"""
request = X509Req()
assert request.get_version() == 0
request.set_version(0)
assert request.get_version() == 0
- try:
- request.set_version(1)
- assert request.get_version() == 1
- request.set_version(3)
- assert request.get_version() == 3
- except Error:
- pass
def test_version_wrong_args(self):
"""
@@ -1624,6 +1569,8 @@ class TestX509Req(_PKeyInteractionTestsMixin):
request = X509Req()
with pytest.raises(TypeError):
request.set_version("foo")
+ with pytest.raises(ValueError):
+ request.set_version(2)
def test_get_subject(self):
"""
@@ -1681,6 +1628,14 @@ class TestX509Req(_PKeyInteractionTestsMixin):
exts = request.get_extensions()
assert len(exts) == 2
+ def test_undef_oid(self):
+ assert (
+ X509Extension(
+ b"1.2.3.4.5.6.7", False, b"DER:05:00"
+ ).get_short_name()
+ == b"UNDEF"
+ )
+
def test_add_extensions_wrong_args(self):
"""
`X509Req.add_extensions` raises `TypeError` if called with a
@@ -1916,14 +1871,14 @@ class TestX509(_PKeyInteractionTestsMixin):
current time plus the number of seconds passed in.
"""
cert = load_certificate(FILETYPE_PEM, self.pemData)
- not_before_min = datetime.utcnow().replace(microsecond=0) + timedelta(
+ not_before_min = utcnow().replace(microsecond=0) + timedelta(
seconds=100
)
cert.gmtime_adj_notBefore(100)
not_before = datetime.strptime(
cert.get_notBefore().decode(), "%Y%m%d%H%M%SZ"
)
- not_before_max = datetime.utcnow() + timedelta(seconds=100)
+ not_before_max = utcnow() + timedelta(seconds=100)
assert not_before_min <= not_before <= not_before_max
def test_gmtime_adj_notAfter_wrong_args(self):
@@ -1942,14 +1897,14 @@ class TestX509(_PKeyInteractionTestsMixin):
to be the current time plus the number of seconds passed in.
"""
cert = load_certificate(FILETYPE_PEM, self.pemData)
- not_after_min = datetime.utcnow().replace(microsecond=0) + timedelta(
+ not_after_min = utcnow().replace(microsecond=0) + timedelta(
seconds=100
)
cert.gmtime_adj_notAfter(100)
not_after = datetime.strptime(
cert.get_notAfter().decode(), "%Y%m%d%H%M%SZ"
)
- not_after_max = datetime.utcnow() + timedelta(seconds=100)
+ not_after_max = utcnow() + timedelta(seconds=100)
assert not_after_min <= not_after <= not_after_max
def test_has_expired(self):
@@ -2384,7 +2339,7 @@ class TestX509Store:
class TestPKCS12:
"""
- Test for `OpenSSL.crypto.PKCS12` and `OpenSSL.crypto.load_pkcs12`.
+ Test for `OpenSSL.crypto.PKCS12`.
"""
def test_type(self):
@@ -2427,7 +2382,7 @@ class TestPKCS12:
def test_key_only(self):
"""
A `PKCS12` with only a private key can be exported using
- `PKCS12.export` and loaded again using `load_pkcs12`.
+ `PKCS12.export`.
"""
passwd = b"blah"
p12 = PKCS12()
@@ -2435,25 +2390,12 @@ class TestPKCS12:
p12.set_privatekey(pkey)
assert None is p12.get_certificate()
assert pkey == p12.get_privatekey()
- try:
- dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
- except Error:
- # Some versions of OpenSSL will throw an exception
- # for this nearly useless PKCS12 we tried to generate:
- # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')]
- return
- p12 = load_pkcs12(dumped_p12, passwd)
- assert None is p12.get_ca_certificates()
- assert None is p12.get_certificate()
-
- # OpenSSL fails to bring the key back to us. So sad. Perhaps in the
- # future this will be improved.
- assert isinstance(p12.get_privatekey(), (PKey, type(None)))
+ p12.export(passphrase=passwd, iter=2, maciter=3)
def test_cert_only(self):
"""
A `PKCS12` with only a certificate can be exported using
- `PKCS12.export` and loaded again using `load_pkcs12`.
+ `PKCS12.export`.
"""
passwd = b"blah"
p12 = PKCS12()
@@ -2461,51 +2403,26 @@ class TestPKCS12:
p12.set_certificate(cert)
assert cert == p12.get_certificate()
assert None is p12.get_privatekey()
- try:
- dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
- except Error:
- # Some versions of OpenSSL will throw an exception
- # for this nearly useless PKCS12 we tried to generate:
- # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')]
- return
- p12 = load_pkcs12(dumped_p12, passwd)
- assert None is p12.get_privatekey()
-
- # OpenSSL fails to bring the cert back to us. Groany mcgroan.
- assert isinstance(p12.get_certificate(), (X509, type(None)))
-
- # Oh ho. It puts the certificate into the ca certificates list, in
- # fact. Totally bogus, I would think. Nevertheless, let's exploit
- # that to check to see if it reconstructed the certificate we expected
- # it to. At some point, hopefully this will change so that
- # p12.get_certificate() is actually what returns the loaded
- # certificate.
- assert root_cert_pem == dump_certificate(
- FILETYPE_PEM, p12.get_ca_certificates()[0]
- )
+ p12.export(passphrase=passwd, iter=2, maciter=3)
- def gen_pkcs12(
- self, cert_pem=None, key_pem=None, ca_pem=None, friendly_name=None
- ):
+ def gen_pkcs12(self, cert_pem=None, key_pem=None, ca_pem=None):
"""
Generate a PKCS12 object with components from PEM. Verify that the set
functions return None.
"""
p12 = PKCS12()
- if cert_pem:
- ret = p12.set_certificate(load_certificate(FILETYPE_PEM, cert_pem))
- assert ret is None
- if key_pem:
- ret = p12.set_privatekey(load_privatekey(FILETYPE_PEM, key_pem))
- assert ret is None
+
+ ret = p12.set_certificate(load_certificate(FILETYPE_PEM, cert_pem))
+ assert ret is None
+
+ ret = p12.set_privatekey(load_privatekey(FILETYPE_PEM, key_pem))
+ assert ret is None
+
if ca_pem:
ret = p12.set_ca_certificates(
(load_certificate(FILETYPE_PEM, ca_pem),)
)
assert ret is None
- if friendly_name:
- ret = p12.set_friendlyname(friendly_name)
- assert ret is None
return p12
def check_recovery(
@@ -2515,29 +2432,29 @@ class TestPKCS12:
Use openssl program to confirm three components are recoverable from a
PKCS12 string.
"""
- if key:
- recovered_key = _runopenssl(
- p12_str,
- b"pkcs12",
- b"-nocerts",
- b"-nodes",
- b"-passin",
- b"pass:" + passwd,
- *extra,
- )
- assert recovered_key[-len(key) :] == key
- if cert:
- recovered_cert = _runopenssl(
- p12_str,
- b"pkcs12",
- b"-clcerts",
- b"-nodes",
- b"-passin",
- b"pass:" + passwd,
- b"-nokeys",
- *extra,
- )
- assert recovered_cert[-len(cert) :] == cert
+ recovered_key = _runopenssl(
+ p12_str,
+ b"pkcs12",
+ b"-nocerts",
+ b"-nodes",
+ b"-passin",
+ b"pass:" + passwd,
+ *extra,
+ ).replace(b"\r\n", b"\n")
+ assert recovered_key[-len(key) :] == key
+
+ recovered_cert = _runopenssl(
+ p12_str,
+ b"pkcs12",
+ b"-clcerts",
+ b"-nodes",
+ b"-passin",
+ b"pass:" + passwd,
+ b"-nokeys",
+ *extra,
+ ).replace(b"\r\n", b"\n")
+ assert recovered_cert[-len(cert) :] == cert
+
if ca:
recovered_cert = _runopenssl(
p12_str,
@@ -2548,141 +2465,9 @@ class TestPKCS12:
b"pass:" + passwd,
b"-nokeys",
*extra,
- )
+ ).replace(b"\r\n", b"\n")
assert recovered_cert[-len(ca) :] == ca
- def verify_pkcs12_container(self, p12):
- """
- Verify that the PKCS#12 container contains the correct client
- certificate and private key.
-
- :param p12: The PKCS12 instance to verify.
- :type p12: `PKCS12`
- """
- cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate())
- key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey())
- assert (client_cert_pem, client_key_pem, None) == (
- cert_pem,
- key_pem,
- p12.get_ca_certificates(),
- )
-
- def test_load_pkcs12(self):
- """
- A PKCS12 string generated using the openssl command line can be loaded
- with `load_pkcs12` and its components extracted and examined.
- """
- passwd = b"whatever"
- pem = client_key_pem + client_cert_pem
- p12_str = _runopenssl(
- pem,
- b"pkcs12",
- b"-export",
- b"-clcerts",
- b"-passout",
- b"pass:" + passwd,
- )
- p12 = load_pkcs12(p12_str, passphrase=passwd)
- self.verify_pkcs12_container(p12)
-
- def test_load_pkcs12_text_passphrase(self):
- """
- A PKCS12 string generated using the openssl command line can be loaded
- with `load_pkcs12` and its components extracted and examined.
- Using text as passphrase instead of bytes. DeprecationWarning expected.
- """
- pem = client_key_pem + client_cert_pem
- passwd = b"whatever"
- p12_str = _runopenssl(
- pem,
- b"pkcs12",
- b"-export",
- b"-clcerts",
- b"-passout",
- b"pass:" + passwd,
- )
- with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
- p12 = load_pkcs12(p12_str, passphrase=b"whatever".decode("ascii"))
- msg = "{0} for passphrase is no longer accepted, use bytes".format(
- WARNING_TYPE_EXPECTED
- )
- assert msg == str(w[-1].message)
-
- self.verify_pkcs12_container(p12)
-
- def test_load_pkcs12_no_passphrase(self):
- """
- A PKCS12 string generated using openssl command line can be loaded with
- `load_pkcs12` without a passphrase and its components extracted
- and examined.
- """
- pem = client_key_pem + client_cert_pem
- p12_str = _runopenssl(
- pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:"
- )
- p12 = load_pkcs12(p12_str)
- self.verify_pkcs12_container(p12)
-
- def _dump_and_load(self, dump_passphrase, load_passphrase):
- """
- A helper method to dump and load a PKCS12 object.
- """
- p12 = self.gen_pkcs12(client_cert_pem, client_key_pem)
- dumped_p12 = p12.export(passphrase=dump_passphrase, iter=2, maciter=3)
- return load_pkcs12(dumped_p12, passphrase=load_passphrase)
-
- def test_load_pkcs12_null_passphrase_load_empty(self):
- """
- A PKCS12 string can be dumped with a null passphrase, loaded with an
- empty passphrase with `load_pkcs12`, and its components
- extracted and examined.
- """
- self.verify_pkcs12_container(
- self._dump_and_load(dump_passphrase=None, load_passphrase=b"")
- )
-
- def test_load_pkcs12_null_passphrase_load_null(self):
- """
- A PKCS12 string can be dumped with a null passphrase, loaded with a
- null passphrase with `load_pkcs12`, and its components
- extracted and examined.
- """
- self.verify_pkcs12_container(
- self._dump_and_load(dump_passphrase=None, load_passphrase=None)
- )
-
- def test_load_pkcs12_empty_passphrase_load_empty(self):
- """
- A PKCS12 string can be dumped with an empty passphrase, loaded with an
- empty passphrase with `load_pkcs12`, and its components
- extracted and examined.
- """
- self.verify_pkcs12_container(
- self._dump_and_load(dump_passphrase=b"", load_passphrase=b"")
- )
-
- def test_load_pkcs12_empty_passphrase_load_null(self):
- """
- A PKCS12 string can be dumped with an empty passphrase, loaded with a
- null passphrase with `load_pkcs12`, and its components
- extracted and examined.
- """
- self.verify_pkcs12_container(
- self._dump_and_load(dump_passphrase=b"", load_passphrase=None)
- )
-
- def test_load_pkcs12_garbage(self):
- """
- `load_pkcs12` raises `OpenSSL.crypto.Error` when passed
- a string which is not a PKCS12 dump.
- """
- passwd = b"whatever"
- with pytest.raises(Error) as err:
- load_pkcs12(b"fruit loops", passwd)
- assert err.value.args[0][0][0] == "asn1 encoding routines"
- assert len(err.value.args[0][0]) == 3
-
def test_replace(self):
"""
`PKCS12.set_certificate` replaces the certificate in a PKCS12
@@ -2713,20 +2498,7 @@ class TestPKCS12:
for friendly_name in [b"Serverlicious", None, b"###"]:
p12.set_friendlyname(friendly_name)
assert p12.get_friendlyname() == friendly_name
- dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3)
- reloaded_p12 = load_pkcs12(dumped_p12, passwd)
- assert p12.get_friendlyname() == reloaded_p12.get_friendlyname()
- # We would use the openssl program to confirm the friendly
- # name, but it is not possible. The pkcs12 command
- # does not store the friendly name in the cert's
- # alias, which we could then extract.
- self.check_recovery(
- dumped_p12,
- key=server_key_pem,
- cert=server_cert_pem,
- ca=root_cert_pem,
- passwd=passwd,
- )
+ p12.export(passphrase=passwd, iter=2, maciter=3)
def test_various_empty_passphrases(self):
"""
@@ -2777,19 +2549,7 @@ class TestPKCS12:
"""
passwd = b"Lake Michigan"
p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
- dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2)
- try:
- recovered_p12 = load_pkcs12(dumped_p12, passwd)
- # The person who generated this PCKS12 should be flogged,
- # or better yet we should have a means to determine
- # whether a PCKS12 had a MAC that was verified.
- # Anyway, libopenssl chooses to allow it, so the
- # pyopenssl binding does as well.
- assert isinstance(recovered_p12, PKCS12)
- except Error:
- # Failing here with an exception is preferred as some openssl
- # versions do.
- pass
+ p12.export(maciter=-1, passphrase=passwd, iter=2)
def test_zero_len_list_for_ca(self):
"""
@@ -2821,9 +2581,9 @@ class TestPKCS12:
p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem)
with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
+ warnings.simplefilter("always")
dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii"))
- msg = "{0} for passphrase is no longer accepted, use bytes".format(
+ msg = "{} for passphrase is no longer accepted, use bytes".format(
WARNING_TYPE_EXPECTED
)
assert msg == str(w[-1].message)
@@ -2849,7 +2609,7 @@ def _runopenssl(pem, *args):
Run the command line openssl tool with the given arguments and write
the given PEM to its stdin. Not safe for quotes.
"""
- proc = Popen([b"openssl"] + list(args), stdin=PIPE, stdout=PIPE)
+ proc = Popen([b"openssl", *list(args)], stdin=PIPE, stdout=PIPE)
proc.stdin.write(pem)
proc.stdin.close()
output = proc.stdout.read()
@@ -3288,36 +3048,6 @@ class TestFunction:
with pytest.raises(Error):
load_privatekey(FILETYPE_PEM, encrypted_key_pem, passphrase)
- def test_load_pkcs7_data_pem(self):
- """
- `load_pkcs7_data` accepts a PKCS#7 string and returns an instance of
- `PKCS`.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert isinstance(pkcs7, PKCS7)
-
- def test_load_pkcs7_data_asn1(self):
- """
- `load_pkcs7_data` accepts a bytes containing ASN1 data representing
- PKCS#7 and returns an instance of `PKCS7`.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_ASN1, pkcs7DataASN1)
- assert isinstance(pkcs7, PKCS7)
-
- def test_load_pkcs7_data_invalid(self):
- """
- If the data passed to `load_pkcs7_data` is invalid, `Error` is raised.
- """
- with pytest.raises(Error):
- load_pkcs7_data(FILETYPE_PEM, b"foo")
-
- def test_load_pkcs7_type_invalid(self):
- """
- If the type passed to `load_pkcs7_data`, `ValueError` is raised.
- """
- with pytest.raises(ValueError):
- load_pkcs7_data(object(), b"foo")
-
class TestLoadCertificate:
"""
@@ -3343,61 +3073,6 @@ class TestLoadCertificate:
load_certificate(FILETYPE_ASN1, b"lol")
-class TestPKCS7:
- """
- Tests for `PKCS7`.
- """
-
- def test_type_is_signed(self):
- """
- `PKCS7.type_is_signed` returns `True` if the PKCS7 object is of
- the type *signed*.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert pkcs7.type_is_signed()
-
- def test_type_is_enveloped(self):
- """
- `PKCS7.type_is_enveloped` returns `False` if the PKCS7 object is not
- of the type *enveloped*.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert not pkcs7.type_is_enveloped()
-
- def test_type_is_signed_and_enveloped(self):
- """
- `PKCS7.type_is_signedAndEnveloped` returns `False`
- if the PKCS7 object is not of the type *signed and enveloped*.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert not pkcs7.type_is_signedAndEnveloped()
-
- def test_type_is_data(self):
- """
- `PKCS7.type_is_data` returns `False` if the PKCS7 object is not of
- the type data.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert not pkcs7.type_is_data()
-
- def test_get_type_name(self):
- """
- `PKCS7.get_type_name` returns a `str` giving the
- type name.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- assert pkcs7.get_type_name() == b"pkcs7-signedData"
-
- def test_attribute(self):
- """
- If an attribute other than one of the methods tested here is accessed
- on an instance of `PKCS7`, `AttributeError` is raised.
- """
- pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data)
- with pytest.raises(AttributeError):
- pkcs7.foo
-
-
class TestNetscapeSPKI(_PKeyInteractionTestsMixin):
"""
Tests for `OpenSSL.crypto.NetscapeSPKI`.
@@ -3676,8 +3351,8 @@ class TestCRL:
not emit a deprecation warning.
"""
crl = self._get_crl()
- with pytest.warns(None) as catcher:
- simplefilter("always")
+ with warnings.catch_warnings(record=True) as catcher:
+ warnings.simplefilter("always")
assert 0 == len(catcher)
dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5")
text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text")
@@ -3830,7 +3505,8 @@ class TestCRL:
buf = dump_crl(FILETYPE_PEM, crl)
assert buf == crlData
- def _make_test_crl(self, issuer_cert, issuer_key, certs=()):
+ @staticmethod
+ def _make_test_crl(issuer_cert, issuer_key, certs=()):
"""
Create a CRL.
@@ -3856,7 +3532,55 @@ class TestCRL:
crl.sign(issuer_cert, issuer_key, digest=b"sha512")
return crl
- def test_verify_with_revoked(self):
+ @staticmethod
+ def _make_test_crl_cryptography(issuer_cert, issuer_key, certs=()):
+ """
+ Create a CRL using cryptography's API.
+
+ :param list[X509] certs: A list of certificates to revoke.
+ :rtype: ``cryptography.x509.CertificateRevocationList``
+ """
+ from cryptography.x509.extensions import CRLReason, ReasonFlags
+
+ builder = x509.CertificateRevocationListBuilder()
+ builder = builder.issuer_name(
+ X509.to_cryptography(issuer_cert).subject
+ )
+ for cert in certs:
+ revoked = (
+ x509.RevokedCertificateBuilder()
+ .serial_number(cert.get_serial_number())
+ .revocation_date(datetime(2014, 6, 1, 0, 0, 0))
+ .add_extension(CRLReason(ReasonFlags.unspecified), False)
+ .build()
+ )
+ builder = builder.add_revoked_certificate(revoked)
+
+ builder = builder.last_update(datetime(2014, 6, 1, 0, 0, 0))
+ # The year 5000 is far into the future so that this CRL isn't
+ # considered to have expired.
+ builder = builder.next_update(datetime(5000, 6, 1, 0, 0, 0))
+
+ crl = builder.sign(
+ private_key=PKey.to_cryptography_key(issuer_key),
+ algorithm=hashes.SHA512(),
+ )
+ return crl
+
+ @pytest.mark.parametrize(
+ "create_crl",
+ [
+ pytest.param(
+ _make_test_crl.__func__,
+ id="pyOpenSSL CRL",
+ ),
+ pytest.param(
+ _make_test_crl_cryptography.__func__,
+ id="cryptography CRL",
+ ),
+ ],
+ )
+ def test_verify_with_revoked(self, create_crl):
"""
`verify_certificate` raises error when an intermediate certificate is
revoked.
@@ -3864,10 +3588,10 @@ class TestCRL:
store = X509Store()
store.add_cert(self.root_cert)
store.add_cert(self.intermediate_cert)
- root_crl = self._make_test_crl(
+ root_crl = create_crl(
self.root_cert, self.root_key, certs=[self.intermediate_cert]
)
- intermediate_crl = self._make_test_crl(
+ intermediate_crl = create_crl(
self.intermediate_cert, self.intermediate_key, certs=[]
)
store.add_crl(root_crl)
@@ -3880,7 +3604,20 @@ class TestCRL:
store_ctx.verify_certificate()
assert str(err.value) == "certificate revoked"
- def test_verify_with_missing_crl(self):
+ @pytest.mark.parametrize(
+ "create_crl",
+ [
+ pytest.param(
+ _make_test_crl.__func__,
+ id="pyOpenSSL CRL",
+ ),
+ pytest.param(
+ _make_test_crl_cryptography.__func__,
+ id="cryptography CRL",
+ ),
+ ],
+ )
+ def test_verify_with_missing_crl(self, create_crl):
"""
`verify_certificate` raises error when an intermediate certificate's
CRL is missing.
@@ -3888,7 +3625,7 @@ class TestCRL:
store = X509Store()
store.add_cert(self.root_cert)
store.add_cert(self.intermediate_cert)
- root_crl = self._make_test_crl(
+ root_crl = create_crl(
self.root_cert, self.root_key, certs=[self.intermediate_cert]
)
store.add_crl(root_crl)
@@ -4235,7 +3972,7 @@ class TestX509StoreContext:
@staticmethod
def _create_ca_file(base_path, hash_directory, cacert):
- ca_hash = "{:08x}.0".format(cacert.subject_name_hash())
+ ca_hash = f"{cacert.subject_name_hash():08x}.0"
cafile = base_path.join(hash_directory, ca_hash)
cafile.write_binary(
dump_certificate(FILETYPE_PEM, cacert), ensure=True
@@ -4366,16 +4103,16 @@ class TestSignVerify:
cert = load_certificate(FILETYPE_PEM, root_cert_pem)
for digest in ["md5", "sha1", "sha256"]:
with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
+ warnings.simplefilter("always")
sig = sign(priv_key, content, digest)
- assert "{0} for data is no longer accepted, use bytes".format(
+ assert "{} for data is no longer accepted, use bytes".format(
WARNING_TYPE_EXPECTED
) == str(w[-1].message)
with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
+ warnings.simplefilter("always")
verify(cert, sig, content, digest)
- assert "{0} for data is no longer accepted, use bytes".format(
+ assert "{} for data is no longer accepted, use bytes".format(
WARNING_TYPE_EXPECTED
) == str(w[-1].message)
@@ -4459,7 +4196,7 @@ class TestEllipticCurve:
"""
curves = get_elliptic_curves()
curve = next(iter(curves))
- assert "<Curve %r>" % (curve.name,) == repr(curve)
+ assert f"<Curve {curve.name!r}>" == repr(curve)
def test_to_EC_KEY(self):
"""
diff --git a/contrib/python/pyOpenSSL/py3/tests/test_ssl.py b/contrib/python/pyOpenSSL/py3/tests/test_ssl.py
index fdedd7133a0..b5305eb9200 100644
--- a/contrib/python/pyOpenSSL/py3/tests/test_ssl.py
+++ b/contrib/python/pyOpenSSL/py3/tests/test_ssl.py
@@ -9,6 +9,7 @@ import datetime
import gc
import select
import sys
+import time
import uuid
from errno import (
EAFNOSUPPORT,
@@ -26,42 +27,50 @@ from socket import (
AF_INET6,
MSG_PEEK,
SHUT_RDWR,
- error,
+ gaierror,
socket,
)
from sys import getfilesystemencoding, platform
from typing import Union
-from warnings import simplefilter
from weakref import ref
+import flaky
+import pytest
from cryptography import x509
-from cryptography.hazmat.primitives import hashes
-from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
-
-import flaky
-
from pretend import raiser
-import pytest
-
from OpenSSL import SSL
+from OpenSSL._util import ffi as _ffi
+from OpenSSL._util import lib as _lib
+from OpenSSL.crypto import (
+ FILETYPE_PEM,
+ TYPE_RSA,
+ X509,
+ PKey,
+ X509Store,
+ dump_certificate,
+ dump_privatekey,
+ get_elliptic_curves,
+ load_certificate,
+ load_privatekey,
+)
+
+with pytest.warns(DeprecationWarning):
+ from OpenSSL.crypto import X509Extension
+
from OpenSSL.SSL import (
- Connection,
- Context,
DTLS_METHOD,
- Error,
MODE_RELEASE_BUFFERS,
NO_OVERLAPPING_PROTOCOLS,
- OPENSSL_VERSION_NUMBER,
OP_COOKIE_EXCHANGE,
OP_NO_COMPRESSION,
OP_NO_QUERY_MTU,
- OP_NO_SSLv2,
- OP_NO_SSLv3,
OP_NO_TICKET,
OP_SINGLE_DH_USE,
+ OPENSSL_VERSION_NUMBER,
RECEIVED_SHUTDOWN,
SENT_SHUTDOWN,
SESS_CACHE_BOTH,
@@ -72,11 +81,6 @@ from OpenSSL.SSL import (
SESS_CACHE_NO_INTERNAL_STORE,
SESS_CACHE_OFF,
SESS_CACHE_SERVER,
- SSLEAY_BUILT_ON,
- SSLEAY_CFLAGS,
- SSLEAY_DIR,
- SSLEAY_PLATFORM,
- SSLEAY_VERSION,
SSL_CB_ACCEPT_EXIT,
SSL_CB_ACCEPT_LOOP,
SSL_CB_ALERT,
@@ -93,45 +97,41 @@ from OpenSSL.SSL import (
SSL_ST_ACCEPT,
SSL_ST_CONNECT,
SSL_ST_MASK,
- SSLeay_version,
- SSLv23_METHOD,
- Session,
- SysCallError,
+ SSLEAY_BUILT_ON,
+ SSLEAY_CFLAGS,
+ SSLEAY_DIR,
+ SSLEAY_PLATFORM,
+ SSLEAY_VERSION,
TLS1_1_VERSION,
TLS1_2_VERSION,
TLS1_3_VERSION,
TLS_METHOD,
- TLSv1_1_METHOD,
- TLSv1_2_METHOD,
- TLSv1_METHOD,
VERIFY_CLIENT_ONCE,
VERIFY_FAIL_IF_NO_PEER_CERT,
VERIFY_NONE,
VERIFY_PEER,
+ Connection,
+ Context,
+ Error,
+ OP_NO_SSLv2,
+ OP_NO_SSLv3,
+ Session,
+ SSLeay_version,
+ SSLv23_METHOD,
+ SysCallError,
+ TLSv1_1_METHOD,
+ TLSv1_2_METHOD,
+ TLSv1_METHOD,
WantReadError,
WantWriteError,
ZeroReturnError,
_make_requires,
)
-from OpenSSL._util import ffi as _ffi, lib as _lib
-from OpenSSL.crypto import (
- FILETYPE_PEM,
- PKey,
- TYPE_RSA,
- X509,
- X509Extension,
- X509Store,
- dump_certificate,
- dump_privatekey,
- get_elliptic_curves,
- load_certificate,
- load_privatekey,
-)
try:
from OpenSSL.SSL import (
- SSL_ST_INIT,
SSL_ST_BEFORE,
+ SSL_ST_INIT,
SSL_ST_OK,
SSL_ST_RENEGOTIATE,
)
@@ -153,7 +153,6 @@ from .test_crypto import (
)
from .util import NON_ASCII, WARNING_TYPE_EXPECTED, is_consistent_type
-
# openssl dhparam 2048 -out dh-2048.pem
dhparam = """\
-----BEGIN DH PARAMETERS-----
@@ -170,7 +169,7 @@ i5s5yYK7a/0eWxxRr2qraYaUj8RwDpH9CwIBAg==
def socket_any_family():
try:
return socket(AF_INET)
- except error as e:
+ except OSError as e:
if e.errno == EAFNOSUPPORT:
return socket(AF_INET6)
raise
@@ -359,11 +358,10 @@ def interact_in_memory(client_conn, server_conn):
# Copy stuff from each side's send buffer to the other side's
# receive buffer.
- for (read, write) in [
+ for read, write in [
(client_conn, server_conn),
(server_conn, client_conn),
]:
-
# Give the side a chance to generate some more bytes, or succeed.
try:
data = read.recv(2**16)
@@ -642,7 +640,7 @@ class TestContext:
key = PKey()
key.generate_key(TYPE_RSA, 1024)
- with open(pemfile, "wt") as pem:
+ with open(pemfile, "w") as pem:
pem.write(dump_privatekey(FILETYPE_PEM, key).decode("ascii"))
ctx = Context(SSLv23_METHOD)
@@ -1140,23 +1138,30 @@ class TestContext:
self._load_verify_locations_test(None, capath)
- def test_load_verify_directory_bytes_capath(self, tmpfile):
+ @pytest.mark.parametrize(
+ "pathtype",
+ [
+ "ascii_path",
+ pytest.param(
+ "unicode_path",
+ marks=pytest.mark.skipif(
+ platform == "win32",
+ reason="Unicode paths not supported on Windows",
+ ),
+ ),
+ ],
+ )
+ @pytest.mark.parametrize("argtype", ["bytes_arg", "unicode_arg"])
+ def test_load_verify_directory_capath(self, pathtype, argtype, tmpfile):
"""
`Context.load_verify_locations` accepts a directory name as a `bytes`
instance and uses the certificates within for verification purposes.
"""
- self._load_verify_directory_locations_capath(
- tmpfile + NON_ASCII.encode(getfilesystemencoding())
- )
-
- def test_load_verify_directory_unicode_capath(self, tmpfile):
- """
- `Context.load_verify_locations` accepts a directory name as a `unicode`
- instance and uses the certificates within for verification purposes.
- """
- self._load_verify_directory_locations_capath(
- tmpfile.decode(getfilesystemencoding()) + NON_ASCII
- )
+ if pathtype == "unicode_path":
+ tmpfile += NON_ASCII.encode(getfilesystemencoding())
+ if argtype == "unicode_arg":
+ tmpfile = tmpfile.decode(getfilesystemencoding())
+ self._load_verify_directory_locations_capath(tmpfile)
def test_load_verify_locations_wrong_args(self):
"""
@@ -1172,7 +1177,7 @@ class TestContext:
@pytest.mark.skipif(
not platform.startswith("linux"),
reason="Loading fallback paths is a linux-specific behavior to "
- "accommodate pyca/cryptography manylinux1 wheels",
+ "accommodate pyca/cryptography manylinux wheels",
)
def test_fallback_default_verify_paths(self, monkeypatch):
"""
@@ -1265,7 +1270,10 @@ class TestContext:
)
client = socket_any_family()
- client.connect(("encrypted.google.com", 443))
+ try:
+ client.connect(("encrypted.google.com", 443))
+ except gaierror:
+ pytest.skip("cannot connect to encrypted.google.com")
clientSSL = Connection(context, client)
clientSSL.set_connect_state()
clientSSL.set_tlsext_host_name(b"encrypted.google.com")
@@ -1717,7 +1725,7 @@ class TestContext:
"""
context = Context(SSLv23_METHOD)
with pytest.raises(TypeError):
- context.set_tlsext_use_srtp(str("SRTP_AES128_CM_SHA1_80"))
+ context.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80")
def test_set_tlsext_use_srtp_invalid_profile(self):
"""
@@ -1776,7 +1784,7 @@ class TestServerNameCallback:
if callback is not None:
referrers = get_referrers(callback)
if len(referrers) > 1: # pragma: nocover
- pytest.fail("Some references remain: %r" % (referrers,))
+ pytest.fail(f"Some references remain: {referrers!r}")
def test_no_servername(self):
"""
@@ -2364,7 +2372,7 @@ class TestConnection:
# 2.6: https://github.com/pytest-dev/pytest/issues/988
try:
clientSSL.connect((loopback_address(client), 1))
- except error as e:
+ except OSError as e:
exc = e
assert exc.args[0] == ECONNREFUSED
@@ -2380,10 +2388,6 @@ class TestConnection:
clientSSL.connect((loopback_address(port), port.getsockname()[1]))
# XXX An assertion? Or something?
- @pytest.mark.skipif(
- platform == "darwin",
- reason="connect_ex sometimes causes a kernel panic on OS X 10.6.4",
- )
def test_connect_ex(self):
"""
If there is a connection error, `Connection.connect_ex` returns the
@@ -2706,7 +2710,7 @@ class TestConnection:
if callback is not None: # pragma: nocover
referrers = get_referrers(callback)
if len(referrers) > 1:
- pytest.fail("Some references remain: %r" % (referrers,))
+ pytest.fail(f"Some references remain: {referrers!r}")
def test_get_session_unconnected(self):
"""
@@ -2762,7 +2766,7 @@ class TestConnection:
ctx = Context(TLSv1_2_METHOD)
ctx.use_privatekey(key)
ctx.use_certificate(cert)
- ctx.set_session_id("unity-test")
+ ctx.set_session_id(b"unity-test")
def makeServer(socket):
server = Connection(ctx, socket)
@@ -2838,23 +2842,24 @@ class TestConnection:
"""
client_socket, server_socket = socket_pair()
# Fill up the client's send buffer so Connection won't be able to write
- # anything. Only write a single byte at a time so we can be sure we
+ # anything. Start by sending larger chunks (Windows Socket I/O is slow)
+ # and continue by writing a single byte at a time so we can be sure we
# completely fill the buffer. Even though the socket API is allowed to
# signal a short write via its return value it seems this doesn't
# always happen on all platforms (FreeBSD and OS X particular) for the
# very last bit of available buffer space.
- msg = b"x"
- for i in range(1024 * 1024 * 64):
- try:
- client_socket.send(msg)
- except error as e:
- if e.errno == EWOULDBLOCK:
- break
- raise
- else:
- pytest.fail(
- "Failed to fill socket buffer, cannot test BIO want write"
- )
+ for msg in [b"x" * 65536, b"x"]:
+ for i in range(1024 * 1024 * 64):
+ try:
+ client_socket.send(msg)
+ except OSError as e:
+ if e.errno == EWOULDBLOCK:
+ break
+ raise # pragma: no cover
+ else: # pragma: no cover
+ pytest.fail(
+ "Failed to fill socket buffer, cannot test BIO want write"
+ )
ctx = Context(SSLv23_METHOD)
conn = Connection(ctx, client_socket)
@@ -3117,9 +3122,8 @@ class TestConnectionSend:
"""
server, client = loopback()
with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
count = server.send(b"xy".decode("ascii"))
- assert "{0} for buf is no longer accepted, use bytes".format(
+ assert "{} for buf is no longer accepted, use bytes".format(
WARNING_TYPE_EXPECTED
) == str(w[-1].message)
assert count == 2
@@ -3325,9 +3329,8 @@ class TestConnectionSendall:
"""
server, client = loopback()
with pytest.warns(DeprecationWarning) as w:
- simplefilter("always")
server.sendall(b"x".decode("ascii"))
- assert "{0} for buf is no longer accepted, use bytes".format(
+ assert "{} for buf is no longer accepted, use bytes".format(
WARNING_TYPE_EXPECTED
) == str(w[-1].message)
assert client.recv(1) == b"x"
@@ -3753,13 +3756,16 @@ class TestMemoryBIO:
"""
If the connection is lost before an orderly SSL shutdown occurs,
`OpenSSL.SSL.SysCallError` is raised with a message of
- "Unexpected EOF".
+ "Unexpected EOF" (or WSAECONNRESET on Windows).
"""
server_conn, client_conn = loopback()
client_conn.sock_shutdown(SHUT_RDWR)
with pytest.raises(SysCallError) as err:
server_conn.recv(1024)
- assert err.value.args == (-1, "Unexpected EOF")
+ if platform == "win32":
+ assert err.value.args == (10054, "WSAECONNRESET")
+ else:
+ assert err.value.args == (-1, "Unexpected EOF")
def _check_client_ca_list(self, func):
"""
@@ -4370,10 +4376,11 @@ class TestDTLS:
# new versions of OpenSSL, this is unnecessary, but harmless, because the
# DTLS state machine treats it like a network hiccup that duplicated a
# packet, which DTLS is robust against.
- def test_it_works_at_all(self):
- # arbitrary number larger than any conceivable handshake volley
- LARGE_BUFFER = 65536
+ # Arbitrary number larger than any conceivable handshake volley.
+ LARGE_BUFFER = 65536
+
+ def test_it_works_at_all(self):
s_ctx = Context(DTLS_METHOD)
def generate_cookie(ssl):
@@ -4404,7 +4411,7 @@ class TestDTLS:
def pump_membio(label, source, sink):
try:
- chunk = source.bio_read(LARGE_BUFFER)
+ chunk = source.bio_read(self.LARGE_BUFFER)
except WantReadError:
return False
# I'm not sure this check is needed, but I'm not sure it's *not*
@@ -4484,3 +4491,39 @@ class TestDTLS:
assert 0 < c.get_cleartext_mtu() < 500
except NotImplementedError: # OpenSSL 1.1.0 and earlier
pass
+
+ def test_timeout(self, monkeypatch):
+ c_ctx = Context(DTLS_METHOD)
+ c = Connection(c_ctx)
+
+ # No timeout before the handshake starts.
+ assert c.DTLSv1_get_timeout() is None
+ assert c.DTLSv1_handle_timeout() is False
+
+ # Start handshake and check there is data to send.
+ c.set_connect_state()
+ try:
+ c.do_handshake()
+ except SSL.WantReadError:
+ pass
+ assert c.bio_read(self.LARGE_BUFFER)
+
+ # There should now be an active timeout.
+ seconds = c.DTLSv1_get_timeout()
+ assert seconds is not None
+
+ # Handle the timeout and check there is data to send.
+ time.sleep(seconds)
+ assert c.DTLSv1_handle_timeout() is True
+ assert c.bio_read(self.LARGE_BUFFER)
+
+ # After the maximum number of allowed timeouts is reached,
+ # DTLSv1_handle_timeout will return -1.
+ #
+ # Testing this directly is prohibitively time consuming as the timeout
+ # duration is doubled on each retry, so the best we can do is to mock
+ # this condition.
+ monkeypatch.setattr(_lib, "DTLSv1_handle_timeout", lambda x: -1)
+
+ with pytest.raises(Error):
+ c.DTLSv1_handle_timeout()
diff --git a/contrib/python/pyOpenSSL/py3/ya.make b/contrib/python/pyOpenSSL/py3/ya.make
index 08bed6fbc0f..28f890402b4 100644
--- a/contrib/python/pyOpenSSL/py3/ya.make
+++ b/contrib/python/pyOpenSSL/py3/ya.make
@@ -4,7 +4,7 @@ PY3_LIBRARY()
SUBSCRIBER(g:python-contrib)
-VERSION(23.0.0)
+VERSION(23.3.0)
LICENSE(Apache-2.0)