From c651ccbf255b5f9add7f628d62ba29215b04c537 Mon Sep 17 00:00:00 2001 From: Senthil Kumaran Date: Thu, 29 Oct 2026 12:58:50 -0700 Subject: [PATCH] gh-53852 Proxy Auth failure to raise 407 instead of 401 (GH-160917) Proxy Auth failure to raise 407 instead of 401 https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/407 is the correct status code to raise for the Proxy Authentication. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401 is the generic code that can be raised for unauthorized response. (cherry picked from commit 2e1666f1f24c128a9a85bd39680c59aee90f2275) Co-authored-by: Senthil Kumaran --- Lib/test/test_urllib2.py | 19 +++++++++++++++++++ Lib/test/test_urllib2_localnet.py | 2 ++ Lib/urllib/request.py | 6 +++++- ...6-10-28-12-00-00.gh-issue-53852.Pr0xy4.rst | 3 +++ 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-28-12-00-00.gh-issue-53852.Pr0xy4.rst diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 5bc8de9b82defbe..8c402a2ef0eeedb 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1871,6 +1871,25 @@ def test_unsupported_auth_digest_handler(self): opener.add_handler(http_handler) self.assertRaises(ValueError, opener.open, "http://www.example.com") + def test_digest_auth_failure_code(self): + # gh-53852: a failed proxy digest auth must report 407, not 401. + headers = http.client.HTTPMessage() + for handler_class, auth_header, code in [ + (urllib.request.HTTPDigestAuthHandler, + 'www-authenticate', 401), + (urllib.request.ProxyDigestAuthHandler, + 'proxy-authenticate', 407)]: + with self.subTest(handler=handler_class.__name__): + handler = handler_class() + handler.retried = 6 + req = Request("http://www.example.com/") + with self.assertRaises(urllib.error.HTTPError) as cm: + handler.http_error_auth_reqed(auth_header, + "www.example.com", + req, headers) + self.assertEqual(cm.exception.code, code) + cm.exception.close() + def test_unsupported_auth_basic_handler(self): # While using BasicAuthHandler opener = OpenerDirector() diff --git a/Lib/test/test_urllib2_localnet.py b/Lib/test/test_urllib2_localnet.py index c2646b9448f297f..8f93ee74252f5c6 100644 --- a/Lib/test/test_urllib2_localnet.py +++ b/Lib/test/test_urllib2_localnet.py @@ -368,12 +368,14 @@ def test_proxy_with_bad_password_raises_httperror(self): self.digest_auth_handler.set_qop("auth") with self.assertRaises(urllib.error.HTTPError) as cm: self.opener.open(self.URL) + self.assertEqual(cm.exception.code, 407) cm.exception.close() def test_proxy_with_no_password_raises_httperror(self): self.digest_auth_handler.set_qop("auth") with self.assertRaises(urllib.error.HTTPError) as cm: self.opener.open(self.URL) + self.assertEqual(cm.exception.code, 407) cm.exception.close() def test_proxy_qop_auth_works(self): diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index cd0d0dc785f5436..d9602e9b5060200 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1093,7 +1093,11 @@ def http_error_auth_reqed(self, auth_header, host, req, headers): # prompting for the information. Crap. This isn't great # but it's better than the current 'repeat until recursion # depth exceeded' approach - raise HTTPError(req.full_url, 401, "digest auth failed", + if auth_header.lower() == 'proxy-authenticate': + code = 407 + else: + code = 401 + raise HTTPError(req.full_url, code, "digest auth failed", headers, None) else: self.retried += 1 diff --git a/Misc/NEWS.d/next/Library/2026-10-28-12-00-00.gh-issue-53852.Pr0xy4.rst b/Misc/NEWS.d/next/Library/2026-10-28-12-00-00.gh-issue-53852.Pr0xy4.rst new file mode 100644 index 000000000000000..6b6c7fa1e175135 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-28-12-00-00.gh-issue-53852.Pr0xy4.rst @@ -0,0 +1,3 @@ +:class:`urllib.request.ProxyDigestAuthHandler` now raises +:exc:`~urllib.error.HTTPError` with status code 407 (Proxy Authentication +Required) instead of 401 when proxy authentication fails.