Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions Lib/test/test_urllib2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,31 @@ def test_proxy_digest_auth(self):
http_handler.requests[1]
.get_header("Proxy-authorization").startswith("Digest "))

def test_proxy_digest_auth_uri(self):
# The digest-uri of a request sent through an HTTP proxy is the
# absolute URL, matching the absolute-form request line
opener = OpenerDirector()
ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128"))
opener.add_handler(ph)
password_manager = urllib.request.HTTPPasswordMgr()
digest_handler = urllib.request.ProxyDigestAuthHandler(password_manager)
realm = "ACME Networks"
http_handler = MockHTTPHandlerRedirect(
407,
'Proxy-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
'algorithm="SHA-256"\r\n\r\n' % realm)
opener.add_handler(digest_handler)
opener.add_handler(http_handler)

request_url = "http://acme.example.com/protected"
digest_handler.add_password(realm, "proxy.example.com:3128",
"wile", "coyote")
opener.open(request_url)

self.assertEqual(len(http_handler.requests), 2)
auth = http_handler.requests[1].get_header("Proxy-authorization")
self.assertIn('uri="%s"' % request_url, auth)

def test_digest_auth_uses_full_url(self):
# The non-proxy case must keep using the full request URL (not
# just the host) for the password lookup, so that sub-URI realm
Expand All @@ -1794,6 +1819,26 @@ def test_digest_auth_uses_full_url(self):
self.assertEqual(password_manager.target_realm, realm)
self.assertEqual(password_manager.target_url, request_url)

def test_digest_auth_empty_path(self):
# gh-53089: the digest-uri must match the Request-URI, which is
# '/' for a URL with an empty path
password_manager = urllib.request.HTTPPasswordMgr()
digest_handler = urllib.request.HTTPDigestAuthHandler(password_manager)
realm = "ACME Networks"
digest_handler.add_password(realm, "http://acme.example.com",
"wile", "coyote")
req = Request("http://acme.example.com")
chal = {"realm": realm, "nonce": "abcdef0123456789",
"algorithm": "SHA-256"}

auth = digest_handler.get_authorization(req, chal)

H, KD = digest_handler.get_algorithm_impls("SHA-256")
respdig = KD(H("wile:%s:coyote" % realm),
"abcdef0123456789:%s" % H("GET:/"))
self.assertIn('uri="/"', auth)
self.assertIn('response="%s"' % respdig, auth)

def test_basic_and_digest_auth_handlers(self):
# HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
# response (https://bugs.python.org/issue1479302), where it should instead
Expand Down
14 changes: 9 additions & 5 deletions Lib/urllib/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,10 +1162,15 @@ def get_authorization(self, req, chal):
else:
entdig = None

# The digest-uri is the request-target as sent on the request line:
# the path, or the absolute URL when the request goes through an
# HTTP proxy. RFC 7616, Section 3.4 allows both. An empty path is
# sent as '/' by http.client, so use the same here.

uri = req.selector or '/'

A1 = "%s:%s:%s" % (user, realm, pw)
A2 = "%s:%s" % (req.get_method(),
# XXX selector: what about proxies and full urls
req.selector)
A2 = "%s:%s" % (req.get_method(), uri)
# NOTE: As per RFC 2617, when server sends "auth,auth-int", the client could use either `auth`
# or `auth-int` to the response back. we use `auth` to send the response back.
if qop is None:
Expand All @@ -1187,8 +1192,7 @@ def get_authorization(self, req, chal):
# XXX should the partial digests be encoded too?

base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
'response="%s"' % (user, realm, nonce, req.selector,
respdig)
'response="%s"' % (user, realm, nonce, uri, respdig)
if opaque:
base += ', opaque="%s"' % opaque
if entdig:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:class:`urllib.request.HTTPDigestAuthHandler` now sends ``uri="/"`` in the
Digest ``Authorization`` header for URLs with an empty path, such as
``http://example.com``, matching the request-target sent by :mod:`http.client`.
Previously it sent ``uri=""``, which servers could reject.
Patch by Senthil Kumaran.
Loading