https://github.com/python/cpython/commit/c323ea71f9d529db8c56fe5421e9bc7392cc66e5
commit: c323ea71f9d529db8c56fe5421e9bc7392cc66e5
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: orsenthil <[email protected]>
date: 2026-07-09T04:33:56Z
summary:

[3.14] gh-47005: fix do_open() to let regular headers override unredirected … 
(GH-146506) (#153378)

gh-47005: fix do_open() to let regular headers override unredirected … 
(GH-146506)

AbstractHTTPHandler.do_open() was building the request header dict by
starting with unredirected_hdrs and only inserting regular headers that
were not already present, giving unredirected headers priority.  This
contradicts get_header() and header_items(), both of which give regular
headers the higher priority.

Fix by unconditionally updating with req.headers so that a header set
via add_header() always overrides one set via add_unredirected_header().

---------
(cherry picked from commit 6d386684ad69f42cb57c72fe0e0ffcec82ec7e12)

Co-authored-by: CHINMAY <[email protected]>
Co-authored-by: Senthil Kumaran <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-03-26-00-00-00.gh-issue-47005.xxc89c.rst
M Lib/test/test_urllib2.py
M Lib/urllib/request.py

diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py
index 7d7f2fa00d35b62..eeea9cda2f420fd 100644
--- a/Lib/test/test_urllib2.py
+++ b/Lib/test/test_urllib2.py
@@ -963,6 +963,35 @@ def test_http(self):
             self.assertEqual(req.unredirected_hdrs["Host"], "baz")
             self.assertEqual(req.unredirected_hdrs["Spam"], "foo")
 
+    def test_http_header_priority(self):
+        # gh-47005: regular headers set via add_header() must override
+        # unredirected headers with the same name in do_open(), consistent
+        # with get_header() and header_items().
+        cases = [
+            ("Content-Type", "application/json", 
"application/x-www-form-urlencoded"),
+            ("Content-Length", "99", "0"),
+            ("Host", "override.example.com", "internal.example.com"),
+            ("Authorization", "Bearer user-token", "Basic stale="),
+            ("Cookie", "a=1", "b=2"),
+            ("User-Agent", "MyApp/1.0", "Python-urllib/test"),
+        ]
+        h = urllib.request.AbstractHTTPHandler()
+        h.parent = MockOpener()
+
+        for key, regular, unredirected in cases:
+            req = Request("http://example.com/";, headers={key: regular})
+            req.timeout = None
+            req.add_unredirected_header(key, unredirected)
+
+            http = MockHTTPClass()
+            h.do_open(http, req)
+
+            sent_headers = dict(http.req_headers)
+            self.assertEqual(sent_headers[key], regular)
+            # key is capitalized by add_header() and add_unredirected_header() 
calls
+            self.assertEqual(req.get_header(key.capitalize()), regular)
+            self.assertEqual(dict(req.header_items())[key.capitalize()], 
regular)
+
     def test_http_body_file(self):
         # A regular file - chunked encoding is used unless Content Length is
         # already set.
diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py
index 8d7470a22739ab7..a8f40100b7b440c 100644
--- a/Lib/urllib/request.py
+++ b/Lib/urllib/request.py
@@ -1291,8 +1291,7 @@ def do_open(self, http_class, req, **http_conn_args):
         h.set_debuglevel(self._debuglevel)
 
         headers = dict(req.unredirected_hdrs)
-        headers.update({k: v for k, v in req.headers.items()
-                        if k not in headers})
+        headers.update(req.headers)
 
         # TODO(jhylton): Should this be redesigned to handle
         # persistent connections?
diff --git 
a/Misc/NEWS.d/next/Library/2026-03-26-00-00-00.gh-issue-47005.xxc89c.rst 
b/Misc/NEWS.d/next/Library/2026-03-26-00-00-00.gh-issue-47005.xxc89c.rst
new file mode 100644
index 000000000000000..646367f0fa069d3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-26-00-00-00.gh-issue-47005.xxc89c.rst
@@ -0,0 +1,4 @@
+Fix :meth:`!urllib.request.AbstractHTTPHandler.do_open` to give regular
+headers set via :meth:`~urllib.request.Request.add_header` priority over
+unredirected headers, consistent with 
:meth:`~urllib.request.Request.get_header`
+and :meth:`~urllib.request.Request.header_items`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to