diff --git a/5097.patch b/5097.patch deleted file mode 100644 index ac3af0f..0000000 --- a/5097.patch +++ /dev/null @@ -1,133 +0,0 @@ -From 8d2659ac68ec8d46dfd8c2fc0ab057b647ac7940 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= -Date: Wed, 1 Jul 2026 15:05:18 +0200 -Subject: [PATCH] Deal with ssl.PROTOCOL_TLSv1 removal from Python built with - OpenSSL 4+ - -https://github.com/python/cpython/commit/3364e7e62fa24d0e19133fb0f90b1c24ef1110c5 -removed ssl.PROTOCOL_TLSv1 when python is built with OpenSSL 4+. - -We are hitting this error in Fedora (where we already updated OpenSSL): - - ImportError while loading conftest '/builddir/build/BUILD/python-urllib3-2.7.0-build/urllib3-2.7.0/test/conftest.py'. - test/__init__.py:42: in - import urllib3.contrib.pyopenssl as pyopenssl - ../BUILDROOT/usr/lib/python3.15/site-packages/urllib3/contrib/pyopenssl.py:72: in - ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, - ^^^^^^^^^^^^^^^^^^ - E AttributeError: module 'ssl' has no attribute 'PROTOCOL_TLSv1'. Did you mean '.PROTOCOL_TLS' instead of '.PROTOCOL_TLSv1'? - -Used LLM to figure out what to use as a replacement protocol in tests. - -Assisted-By: Claude Opus 4.6 ---- - changelog/5097.bugfix.rst | 2 ++ - src/urllib3/contrib/pyopenssl.py | 4 +++- - test/test_ssl.py | 14 ++++++++------ - test/test_util.py | 24 +++++++++++++++--------- - 4 files changed, 28 insertions(+), 16 deletions(-) - create mode 100644 changelog/5097.bugfix.rst - -diff --git a/changelog/5097.bugfix.rst b/changelog/5097.bugfix.rst -new file mode 100644 -index 0000000000..73ed0ddaa1 ---- /dev/null -+++ b/changelog/5097.bugfix.rst -@@ -0,0 +1,2 @@ -+Fixed an ``AttributeError`` on Python built with OpenSSL 4+, where -+``ssl.PROTOCOL_TLSv1`` no longer exists. -diff --git a/src/urllib3/contrib/pyopenssl.py b/src/urllib3/contrib/pyopenssl.py -index 42781590ee..76a225b7bf 100644 ---- a/src/urllib3/contrib/pyopenssl.py -+++ b/src/urllib3/contrib/pyopenssl.py -@@ -68,9 +68,11 @@ class UnsupportedExtension(Exception): # type: ignore[no-redef] - _openssl_versions: dict[int, int] = { - util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] - util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] -- ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, - } - -+if hasattr(ssl, "PROTOCOL_TLSv1") and hasattr(OpenSSL.SSL, "TLSv1_METHOD"): -+ _openssl_versions[ssl.PROTOCOL_TLSv1] = OpenSSL.SSL.TLSv1_METHOD -+ - if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): - _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD - -diff --git a/test/test_ssl.py b/test/test_ssl.py -index 6b1ab8589b..1e4063bf68 100644 ---- a/test/test_ssl.py -+++ b/test/test_ssl.py -@@ -176,19 +176,21 @@ def test_create_urllib3_context_default_ciphers( - - context.set_ciphers.assert_not_called() - -+ # PROTOCOL_TLS_SERVER is used as a stand-in for any non-default ssl_version. -+ # PROTOCOL_TLSv1/TLSv1_2 are unavailable when Python is built with OpenSSL 4+. - @pytest.mark.parametrize( - "kwargs", - [ - { -- "ssl_version": ssl.PROTOCOL_TLSv1, -+ "ssl_version": ssl.PROTOCOL_TLS_SERVER, - "ssl_minimum_version": ssl.TLSVersion.MINIMUM_SUPPORTED, - }, - { -- "ssl_version": ssl.PROTOCOL_TLSv1, -+ "ssl_version": ssl.PROTOCOL_TLS_SERVER, - "ssl_maximum_version": ssl.TLSVersion.TLSv1, - }, - { -- "ssl_version": ssl.PROTOCOL_TLSv1, -+ "ssl_version": ssl.PROTOCOL_TLS_SERVER, - "ssl_minimum_version": ssl.TLSVersion.MINIMUM_SUPPORTED, - "ssl_maximum_version": ssl.TLSVersion.MAXIMUM_SUPPORTED, - }, -@@ -229,10 +231,10 @@ def test_create_urllib3_context_ssl_version_and_ssl_min_max_version_no_warning( - @pytest.mark.parametrize( - "kwargs", - [ -- {"ssl_version": ssl.PROTOCOL_TLSv1, "ssl_minimum_version": None}, -- {"ssl_version": ssl.PROTOCOL_TLSv1, "ssl_maximum_version": None}, -+ {"ssl_version": ssl.PROTOCOL_TLS_SERVER, "ssl_minimum_version": None}, -+ {"ssl_version": ssl.PROTOCOL_TLS_SERVER, "ssl_maximum_version": None}, - { -- "ssl_version": ssl.PROTOCOL_TLSv1, -+ "ssl_version": ssl.PROTOCOL_TLS_SERVER, - "ssl_minimum_version": None, - "ssl_maximum_version": None, - }, -diff --git a/test/test_util.py b/test/test_util.py -index 8612ab5eaf..18c65f0312 100644 ---- a/test/test_util.py -+++ b/test/test_util.py -@@ -1216,15 +1216,21 @@ def test_resolve_cert_reqs( - ) -> None: - assert resolve_cert_reqs(candidate) == requirements - -- @pytest.mark.parametrize( -- "candidate, version", -- [ -- (ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1), -- ("PROTOCOL_TLSv1", ssl.PROTOCOL_TLSv1), -- ("TLSv1", ssl.PROTOCOL_TLSv1), -- (ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23), -- ], -- ) -+ candidate_version = [ -+ (ssl.PROTOCOL_SSLv23, ssl.PROTOCOL_SSLv23), -+ ("PROTOCOL_SSLv23", ssl.PROTOCOL_SSLv23), -+ ("SSLv23", ssl.PROTOCOL_SSLv23), -+ ] -+ if hasattr(ssl, "PROTOCOL_TLSv1"): -+ candidate_version.extend( -+ [ -+ (ssl.PROTOCOL_TLSv1, ssl.PROTOCOL_TLSv1), -+ ("PROTOCOL_TLSv1", ssl.PROTOCOL_TLSv1), -+ ("TLSv1", ssl.PROTOCOL_TLSv1), -+ ] -+ ) -+ -+ @pytest.mark.parametrize("candidate, version", candidate_version) - def test_resolve_ssl_version(self, candidate: int | str, version: int) -> None: - assert resolve_ssl_version(candidate) == version - diff --git a/5103.patch b/5103.patch deleted file mode 100644 index c1b28d5..0000000 --- a/5103.patch +++ /dev/null @@ -1,83 +0,0 @@ -From 13fa1e033b37405b228599ee590af485dc3e38aa Mon Sep 17 00:00:00 2001 -From: Illia Volochii -Date: Sat, 18 Jul 2026 13:25:41 +0200 -Subject: [PATCH] Replace deprecated pyOpenSSL `X509.get_subject` and - `Context.set_passwd_cb` methods (#5103) - -Rebased on top of urllib3-2.7.0. ---- - src/urllib3/contrib/pyopenssl.py | 35 ++++++++++++++++++++++++++------- - 1 file changed, 28 insertions(+), 7 deletions(-) - -diff --git a/src/urllib3/contrib/pyopenssl.py b/src/urllib3/contrib/pyopenssl.py -index 76a225b7bf..a1b2c3d4e5 100644 ---- a/src/urllib3/contrib/pyopenssl.py -+++ b/src/urllib3/contrib/pyopenssl.py -@@ -42,6 +42,8 @@ - - import OpenSSL.SSL # type: ignore[import-not-found] - from cryptography import x509 -+from cryptography.hazmat.primitives.serialization import load_pem_private_key -+from cryptography.x509.oid import NameOID - - try: - from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] -@@ -272,6 +274,15 @@ - return names - - -+def _get_common_name(peer_cert: X509) -> str | None: -+ """ -+ Given a pyOpenSSL certificate, return the subject's common name. -+ """ -+ cert = peer_cert.to_cryptography() -+ names = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) -+ return typing.cast(str, names[0].value) if names else None -+ -+ - class WrappedSocket: - """API-compatibility wrapper for Python OpenSSL's Connection-class.""" - -@@ -396,7 +407,7 @@ - return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] - - return { -- "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] -+ "subject": ((("commonName", _get_common_name(x509)),),), # type: ignore[dict-item] - "subjectAltName": get_subj_alt_name(x509), - } - -@@ -482,16 +493,28 @@ - self, - certfile: str, - keyfile: str | None = None, -- password: str | None = None, -+ password: str | bytes | None = None, - ) -> None: - try: - self._ctx.use_certificate_chain_file(certfile) - if password is not None: - if not isinstance(password, bytes): -- password = password.encode("utf-8") # type: ignore[assignment] -- self._ctx.set_passwd_cb(lambda *_: password) -- self._ctx.use_privatekey_file(keyfile or certfile) -- except OpenSSL.SSL.Error as e: -+ password = password.encode("utf-8") -+ # pyOpenSSL added cryptography-key support in 24.3.0. -+ # Keep using the older password-callback path until 2026's -+ # versions because set_passwd_cb() became deprecated in 26.3.0. -+ if int(OpenSSL.__version__.split(".")[0]) >= 26: -+ with open(keyfile or certfile, "rb") as key_file: -+ private_key = load_pem_private_key(key_file.read(), password) -+ # cryptography's loader returns a wider private-key union -+ # than pyOpenSSL accepts, so we add `type: ignore` here. -+ self._ctx.use_privatekey(private_key) # type: ignore[arg-type] -+ else: -+ self._ctx.set_passwd_cb(lambda *_: password) -+ self._ctx.use_privatekey_file(keyfile or certfile) -+ else: -+ self._ctx.use_privatekey_file(keyfile or certfile) -+ except (OpenSSL.SSL.Error, TypeError, ValueError) as e: - raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e - - def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: diff --git a/c420e267.patch b/c420e267.patch deleted file mode 100644 index 74cf1fd..0000000 --- a/c420e267.patch +++ /dev/null @@ -1,66 +0,0 @@ -From c420e267acb739917bee96e021ad9a2d27de8aae Mon Sep 17 00:00:00 2001 -From: Colin Watson -Date: Tue, 30 Jun 2026 21:48:57 +0100 -Subject: [PATCH] Fix test failures with pytest >= 9.1 (#5094) - -Co-authored-by: Illia Volochii ---- - changelog/5094.misc.rst | 1 + - test/test_util.py | 3 +-- - uv.lock | 6 +++--- - 3 files changed, 5 insertions(+), 5 deletions(-) - create mode 100644 changelog/5094.misc.rst - -diff --git a/changelog/5094.misc.rst b/changelog/5094.misc.rst -new file mode 100644 -index 0000000000..e6f2a24f2f ---- /dev/null -+++ b/changelog/5094.misc.rst -@@ -0,0 +1 @@ -+Fixed test failures with pytest >= 9.1. -diff --git a/test/test_util.py b/test/test_util.py -index fb8f81eb7e..8612ab5eaf 100644 ---- a/test/test_util.py -+++ b/test/test_util.py -@@ -7,7 +7,6 @@ - import sys - import typing - import warnings --from itertools import chain - from test import ImportBlocker, ModuleStash, notBrotli, notZstd, onlyBrotli, onlyZstd - from unittest import mock - from unittest.mock import MagicMock, Mock, patch -@@ -438,7 +437,7 @@ def test_percent_encoded_control_ipv6_zone_ids_raise(self, url: str) -> None: - - @pytest.mark.parametrize( - "url, expected_url", -- chain(parse_url_host_map, non_round_tripping_parse_url_host_map), -+ [*parse_url_host_map, *non_round_tripping_parse_url_host_map], - ) - def test_parse_url(self, url: str, expected_url: Url) -> None: - returned_url = parse_url(url) -diff --git a/uv.lock b/uv.lock -index 03ec5fb75a..82d0c070eb 100644 ---- a/uv.lock -+++ b/uv.lock -@@ -1707,7 +1707,7 @@ wheels = [ - - [[package]] - name = "pytest" --version = "9.0.3" -+version = "9.1.1" - source = { registry = "https://pypi.org/simple" } - dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'group-7-urllib3-dev' and extra == 'group-7-urllib3-dev-min-pyopenssl') or (extra == 'group-7-urllib3-dev-min-pyopenssl' and extra == 'group-7-urllib3-mypy')" }, -@@ -1718,9 +1718,9 @@ dependencies = [ - { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'group-7-urllib3-dev' and extra == 'group-7-urllib3-dev-min-pyopenssl') or (extra == 'group-7-urllib3-dev-min-pyopenssl' and extra == 'group-7-urllib3-mypy')" }, - ] --sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } -+sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } - wheels = [ -- { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, -+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, - ] - - [[package]] diff --git a/python-urllib3.spec b/python-urllib3.spec index aebd212..b2854a7 100644 --- a/python-urllib3.spec +++ b/python-urllib3.spec @@ -31,15 +31,6 @@ Source0: %{url}/archive/%{version}/urllib3-%{version}.tar.gz %global hypercorn_commit d1719f8c1570cbd8e6a3719ffdb14a4d72880abb Source1: %{hypercorn_url}/archive/%{hypercorn_commit}/hypercorn-%{hypercorn_commit}.tar.gz -# Deal with ssl.PROTOCOL_TLSv1 removal from Python built with OpenSSL 4+ -Patch: https://github.com/urllib3/urllib3/pull/5097.patch -# Replace deprecated pyOpenSSL X509.get_subject and Context.set_passwd_cb methods -# https://github.com/urllib3/urllib3/pull/5103 rebased -Patch: 5103.patch - -# Compatibility with the latest pytest -Patch: https://github.com/urllib3/urllib3/commit/c420e267.patch - BuildArch: noarch BuildRequires: python3-devel @@ -91,7 +82,7 @@ Recommends: python3-urllib3+socks %prep -%autosetup -p1 -n urllib3-%{version} +%autosetup -n urllib3-%{version} %setup -q -n urllib3-%{version} -T -D -b 1 # Allow setuptools-scm 10+