File CVE-2026-21441.patch of Package python-urllib3_1

From 8864ac407bba8607950025e0979c4c69bc7abc7b Mon Sep 17 00:00:00 2001
From: Illia Volochii <illia.volochii@gmail.com>
Date: Wed, 7 Jan 2026 18:07:30 +0200
Subject: [PATCH] Merge commit from fork

* Stop decoding response content during redirects needlessly

* Rename the new query parameter

* Add a changelog entry
---
 CHANGES.rst                                  | 13 +++++++++++++
 dummyserver/app.py                           |  8 +++++++-
 src/urllib3/response.py                      |  6 +++++-
 test/with_dummyserver/test_connectionpool.py | 19 +++++++++++++++++++
 4 files changed, 44 insertions(+), 2 deletions(-)

Index: urllib3-1.26.20/src/urllib3/response.py
===================================================================
--- urllib3-1.26.20.orig/src/urllib3/response.py
+++ urllib3-1.26.20/src/urllib3/response.py
@@ -477,7 +477,11 @@ class HTTPResponse(io.IOBase):
         Unread data in the HTTPResponse connection blocks the connection from being released back to the pool.
         """
         try:
-            self.read()
+            self.read(
+                # Do not spend resources decoding the content unless
+                # decoding has already been initialized.
+                decode_content=self._has_decoded_content,
+            )
         except (HTTPError, SocketError, BaseSSLError, HTTPException):
             pass
 
Index: urllib3-1.26.20/test/with_dummyserver/test_connectionpool.py
===================================================================
--- urllib3-1.26.20.orig/test/with_dummyserver/test_connectionpool.py
+++ urllib3-1.26.20/test/with_dummyserver/test_connectionpool.py
@@ -467,6 +467,25 @@ class TestConnectionPool(HTTPDummyServer
             assert r.status == 200
             assert r.data == b"Dummy server!"
 
+    @mock.patch("urllib3.response.GzipDecoder.decompress")
+    def test_no_decoding_with_redirect_when_preload_disabled(
+        self, gzip_decompress
+    ):
+        """
+        Test that urllib3 does not attempt to decode a gzipped redirect
+        response when `preload_content` is set to `False`.
+        """
+        with HTTPConnectionPool(self.host, self.port) as pool:
+            # Three requests are expected: two redirects and one final / 200 OK.
+            response = pool.request(
+                "GET",
+                "/redirect",
+                fields={"target": "/redirect?compressed=true", "compressed": "true"},
+                preload_content=False,
+            )
+        assert response.status == 200
+        gzip_decompress.assert_not_called()
+
     def test_303_redirect_makes_request_lose_body(self):
         with HTTPConnectionPool(self.host, self.port) as pool:
             response = pool.request(
Index: urllib3-1.26.20/test/test_response.py
===================================================================
--- urllib3-1.26.20.orig/test/test_response.py
+++ urllib3-1.26.20/test/test_response.py
@@ -632,6 +632,41 @@ class TestResponse(object):
             next(reader)
         assert re.match("I/O operation on closed file.?", str(ctx.value))
 
+    def test_read_with_illegal_mix_decode_toggle(self) -> None:
+        compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS)
+        data = compress.compress(b"foo")
+        data += compress.flush()
+
+        fp = BytesIO(data)
+
+        resp = HTTPResponse(
+            fp, headers={"content-encoding": "deflate"}, preload_content=False
+        )
+
+        assert resp.read(1) == b"f"
+
+        with pytest.raises(
+            RuntimeError,
+            match=(
+                r"Calling read\(decode_content=False\) is not supported after "
+                r"read\(decode_content=True\) was called"
+            ),
+        ):
+            resp.read(1, decode_content=False)
+
+    def test_read_with_mix_decode_toggle(self) -> None:
+        compress = zlib.compressobj(6, zlib.DEFLATED, -zlib.MAX_WBITS)
+        data = compress.compress(b"foo")
+        data += compress.flush()
+
+        fp = BytesIO(data)
+
+        resp = HTTPResponse(
+            fp, headers={"content-encoding": "deflate"}, preload_content=False
+        )
+        resp.read(1, decode_content=False)
+        assert resp.read(1, decode_content=True) == b"o"
+
     def test_streaming(self):
         fp = BytesIO(b"foo")
         resp = HTTPResponse(fp, preload_content=False)
openSUSE Build Service is sponsored by