diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 3a77b9e5ab7928..b3cbb20557e37b 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -980,6 +980,23 @@ 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(). + h = urllib.request.AbstractHTTPHandler() + h.parent = MockOpener() + + req = Request("http://example.com/", headers={"Content-Type": "application/json"}) + req.timeout = None + req.add_unredirected_header("Content-Type", "application/x-www-form-urlencoded") + + http = MockHTTPClass() + h.do_open(http, req) + + sent_headers = dict(http.req_headers) + self.assertEqual(sent_headers["Content-Type"], "application/json") + 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 f5f17f223a4585..660301fef61258 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1293,8 +1293,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 00000000000000..646367f0fa069d --- /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`.