Skip to content

Commit 9d590af

Browse files
committed
gh-153792: Fix urllib.request digest auth crash on a malformed challenge
AbstractDigestAuthHandler.retry_http_digest_auth parsed a server-controlled WWW-Authenticate digest challenge without guarding the parse, so a malformed challenge raised a bare ValueError or IndexError out of urlopen(). Catch those around the two parse statements and decline (return None), as the handler already does for a 40x it cannot handle.
1 parent e51b616 commit 9d590af

3 files changed

Lines changed: 34 additions & 2 deletions

File tree

Lib/test/test_urllib2.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,31 @@ def test_unsupported_auth_digest_handler(self):
17221722
opener.add_handler(http_handler)
17231723
self.assertRaises(ValueError, opener.open, "http://www.example.com")
17241724

1725+
def test_digest_auth_malformed_challenge(self):
1726+
# A malformed WWW-Authenticate digest challenge must make the handler
1727+
# decline (return None) rather than crash with a raw exception.
1728+
handler = urllib.request.HTTPDigestAuthHandler(None)
1729+
req = Request("http://www.example.com/")
1730+
for challenge in (
1731+
"Digest realm", # parse_keqv_list: no '=' -> ValueError
1732+
"Digest realm=", # parse_keqv_list: empty value -> IndexError
1733+
"Digest", # auth.split(' ', 1) -> ValueError
1734+
):
1735+
with self.subTest(challenge=challenge):
1736+
self.assertIsNone(handler.retry_http_digest_auth(req, challenge))
1737+
1738+
# Through the public opener.open() path the raw exception must not
1739+
# escape; the unhandled 401 surfaces as HTTPError instead.
1740+
opener = OpenerDirector()
1741+
opener.add_handler(urllib.request.HTTPDigestAuthHandler(None))
1742+
opener.add_handler(urllib.request.HTTPDefaultErrorHandler())
1743+
opener.add_handler(MockHTTPHandlerRedirect(
1744+
401, "WWW-Authenticate: Digest realm\r\n\r\n"))
1745+
with self.assertRaises(urllib.error.HTTPError) as cm:
1746+
opener.open("http://www.example.com/")
1747+
self.assertEqual(cm.exception.code, 401)
1748+
cm.exception.close()
1749+
17251750
def test_unsupported_auth_basic_handler(self):
17261751
# While using BasicAuthHandler
17271752
opener = OpenerDirector()

Lib/urllib/request.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,8 +1093,12 @@ def http_error_auth_reqed(self, auth_header, host, req, headers):
10931093
" the following scheme: '%s'" % scheme)
10941094

10951095
def retry_http_digest_auth(self, req, auth):
1096-
token, challenge = auth.split(' ', 1)
1097-
chal = parse_keqv_list(filter(None, parse_http_list(challenge)))
1096+
try:
1097+
token, challenge = auth.split(' ', 1)
1098+
chal = parse_keqv_list(filter(None, parse_http_list(challenge)))
1099+
except (ValueError, IndexError):
1100+
# A malformed challenge cannot be used to authenticate.
1101+
return None
10981102
auth = self.get_authorization(req, chal)
10991103
if auth:
11001104
auth_val = 'Digest %s' % auth
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`urllib.request.HTTPDigestAuthHandler` raising :exc:`ValueError` or
2+
:exc:`IndexError` for a malformed ``WWW-Authenticate`` digest challenge. Patch
3+
by tonghuaroot.

0 commit comments

Comments
 (0)