Skip to content

Commit 5cd3984

Browse files
committed
Merge remote-tracking branch 'origin/main' into longobject-xmul-memset
2 parents b1e93ff + 90dc8bd commit 5cd3984

8 files changed

Lines changed: 102 additions & 5 deletions

File tree

‎Doc/library/urllib.error.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ The following exceptions are raised by :mod:`!urllib.error` as appropriate:
2727
:exc:`URLError` used to be a subtype of :exc:`IOError`, which is now an
2828
alias of :exc:`OSError`.
2929

30+
.. versionchanged:: 3.16
31+
The :attr:`~OSError.errno` and :attr:`~OSError.strerror` attributes are
32+
now set from :attr:`reason` when it is an :exc:`OSError`.
33+
3034

3135
.. exception:: HTTPError(url, code, msg, hdrs, fp)
3236

‎Lib/test/test_urllib2.py‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,27 @@ def test_parse_http_list(self):
8282
def test_URLError_reasonstr(self):
8383
err = urllib.error.URLError('reason')
8484
self.assertIn(err.reason, str(err))
85+
self.assertIsNone(err.errno)
86+
self.assertIsNone(err.strerror)
87+
88+
def test_URLError_OSError_attributes(self):
89+
# gh-50720: errno and strerror are copied from a wrapped OSError.
90+
socket_error = socket.gaierror(-2, 'Name or service not known')
91+
92+
class FailingHTTPConnection(MockHTTPClass):
93+
def request(self, *args, **kwargs):
94+
raise socket_error
95+
96+
h = urllib.request.AbstractHTTPHandler()
97+
req = Request("http://example.com/")
98+
req.timeout = None
99+
100+
with self.assertRaises(urllib.error.URLError) as cm:
101+
h.do_open(FailingHTTPConnection(), req)
102+
err = cm.exception
103+
self.assertIs(err.reason, socket_error)
104+
self.assertEqual(err.errno, -2)
105+
self.assertEqual(err.strerror, 'Name or service not known')
85106

86107

87108
class RequestHdrsTests(unittest.TestCase):
@@ -1789,6 +1810,31 @@ def test_proxy_digest_auth(self):
17891810
http_handler.requests[1]
17901811
.get_header("Proxy-authorization").startswith("Digest "))
17911812

1813+
def test_proxy_digest_auth_uri(self):
1814+
# The digest-uri of a request sent through an HTTP proxy is the
1815+
# absolute URL, matching the absolute-form request line
1816+
opener = OpenerDirector()
1817+
ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128"))
1818+
opener.add_handler(ph)
1819+
password_manager = urllib.request.HTTPPasswordMgr()
1820+
digest_handler = urllib.request.ProxyDigestAuthHandler(password_manager)
1821+
realm = "ACME Networks"
1822+
http_handler = MockHTTPHandlerRedirect(
1823+
407,
1824+
'Proxy-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
1825+
'algorithm="SHA-256"\r\n\r\n' % realm)
1826+
opener.add_handler(digest_handler)
1827+
opener.add_handler(http_handler)
1828+
1829+
request_url = "http://acme.example.com/protected"
1830+
digest_handler.add_password(realm, "proxy.example.com:3128",
1831+
"wile", "coyote")
1832+
opener.open(request_url)
1833+
1834+
self.assertEqual(len(http_handler.requests), 2)
1835+
auth = http_handler.requests[1].get_header("Proxy-authorization")
1836+
self.assertIn('uri="%s"' % request_url, auth)
1837+
17921838
def test_digest_auth_uses_full_url(self):
17931839
# The non-proxy case must keep using the full request URL (not
17941840
# just the host) for the password lookup, so that sub-URI realm
@@ -1811,6 +1857,26 @@ def test_digest_auth_uses_full_url(self):
18111857
self.assertEqual(password_manager.target_realm, realm)
18121858
self.assertEqual(password_manager.target_url, request_url)
18131859

1860+
def test_digest_auth_empty_path(self):
1861+
# gh-53089: the digest-uri must match the Request-URI, which is
1862+
# '/' for a URL with an empty path
1863+
password_manager = urllib.request.HTTPPasswordMgr()
1864+
digest_handler = urllib.request.HTTPDigestAuthHandler(password_manager)
1865+
realm = "ACME Networks"
1866+
digest_handler.add_password(realm, "http://acme.example.com",
1867+
"wile", "coyote")
1868+
req = Request("http://acme.example.com")
1869+
chal = {"realm": realm, "nonce": "abcdef0123456789",
1870+
"algorithm": "SHA-256"}
1871+
1872+
auth = digest_handler.get_authorization(req, chal)
1873+
1874+
H, KD = digest_handler.get_algorithm_impls("SHA-256")
1875+
respdig = KD(H("wile:%s:coyote" % realm),
1876+
"abcdef0123456789:%s" % H("GET:/"))
1877+
self.assertIn('uri="/"', auth)
1878+
self.assertIn('response="%s"' % respdig, auth)
1879+
18141880
def test_basic_and_digest_auth_handlers(self):
18151881
# HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
18161882
# response (https://bugs.python.org/issue1479302), where it should instead

‎Lib/urllib/error.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ def __init__(self, reason, filename=None):
2727
self.reason = reason
2828
if filename is not None:
2929
self.filename = filename
30+
if isinstance(reason, OSError):
31+
# OSError.__init__() is not called, so copy the attributes
32+
# it would have set from the wrapped error (gh-50720).
33+
self.errno = reason.errno
34+
self.strerror = reason.strerror
3035

3136
def __str__(self):
3237
return '<urlopen error %s>' % self.reason

‎Lib/urllib/request.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,10 +1164,15 @@ def get_authorization(self, req, chal):
11641164
else:
11651165
entdig = None
11661166

1167+
# The digest-uri is the request-target as sent on the request line:
1168+
# the path, or the absolute URL when the request goes through an
1169+
# HTTP proxy. RFC 7616, Section 3.4 allows both. An empty path is
1170+
# sent as '/' by http.client, so use the same here.
1171+
1172+
uri = req.selector or '/'
1173+
11671174
A1 = "%s:%s:%s" % (user, realm, pw)
1168-
A2 = "%s:%s" % (req.get_method(),
1169-
# XXX selector: what about proxies and full urls
1170-
req.selector)
1175+
A2 = "%s:%s" % (req.get_method(), uri)
11711176
# NOTE: As per RFC 2617, when server sends "auth,auth-int", the client could use either `auth`
11721177
# or `auth-int` to the response back. we use `auth` to send the response back.
11731178
if qop is None:
@@ -1189,8 +1194,7 @@ def get_authorization(self, req, chal):
11891194
# XXX should the partial digests be encoded too?
11901195

11911196
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
1192-
'response="%s"' % (user, realm, nonce, req.selector,
1193-
respdig)
1197+
'response="%s"' % (user, realm, nonce, uri, respdig)
11941198
if opaque:
11951199
base += ', opaque="%s"' % opaque
11961200
if entdig:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:class:`urllib.request.HTTPDigestAuthHandler` now sends ``uri="/"`` in the
2+
Digest ``Authorization`` header for URLs with an empty path, such as
3+
``http://example.com``, matching the request-target sent by :mod:`http.client`.
4+
Previously it sent ``uri=""``, which servers could reject.
5+
Patch by Senthil Kumaran.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:exc:`urllib.error.URLError` now sets the :attr:`~OSError.errno` and
2+
:attr:`~OSError.strerror` attributes from
3+
:attr:`~urllib.error.URLError.reason` when it is an :exc:`OSError`.

‎configure‎

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎configure.ac‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,6 +4466,11 @@ esac
44664466
# check for systems that require aligned memory access
44674467
AC_CACHE_CHECK([aligned memory access is required], [ac_cv_aligned_required],
44684468
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
4469+
#ifdef __has_attribute
4470+
# if __has_attribute(no_sanitize)
4471+
__attribute__((no_sanitize("alignment")))
4472+
# endif
4473+
#endif
44694474
int main(void)
44704475
{
44714476
char s[16];

0 commit comments

Comments
 (0)