Repository navigation
gh-51540: Let proxy auth handlers answer a 407 to CONNECT for https URLs - #161596
Merged
Merged
Conversation
Documentation build overview
53 files changed ·
|
…ttps URLs urllib.request could not authenticate with a proxy for an https URL using ProxyBasicAuthHandler or ProxyDigestAuthHandler: http.client raised a plain OSError when the proxy answered the CONNECT request with 407, so the http_error_407 handlers never ran. * http.client: add ProxyTunnelError, an OSError subclass carrying the code, reason and headers of the proxy's response. It holds no socket and can be pickled. * urllib.request: do_open() passes a 407 to CONNECT on to the error handlers, so the request is retried with Proxy-Authorization on the CONNECT. Other CONNECT failures still raise URLError. * Request.set_proxy() keeps the tunnel when a request is re-opened, so the retried request still sends the path, not the absolute URL. * ProxyDigestAuthHandler computes the digest over the CONNECT request (CONNECT host:port) when tunnelling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
orsenthil
force-pushed
the
issues/51540
branch
from
November 11, 2026 19:52
0d2ae79 to
92755b1
Compare
orsenthil
enabled auto-merge (squash)
November 11, 2026 22:03
orsenthil
added a commit
to orsenthil/cpython
that referenced
this pull request
Nov 12, 2026
…ttps URLs (python#161596) urllib.request could not authenticate with a proxy for an https URL using ProxyBasicAuthHandler or ProxyDigestAuthHandler: http.client raised a plain OSError when the proxy answered the CONNECT request with 407, so the http_error_407 handlers never ran. * http.client: add ProxyTunnelError, an OSError subclass carrying the code, reason and headers of the proxy's response. It holds no socket and can be pickled. * urllib.request: do_open() passes a 407 to CONNECT on to the error handlers, so the request is retried with Proxy-Authorization on the CONNECT. Other CONNECT failures still raise URLError. * Request.set_proxy() keeps the tunnel when a request is re-opened, so the retried request still sends the path, not the absolute URL. * ProxyDigestAuthHandler computes the digest over the CONNECT request (CONNECT host:port) when tunnelling. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Python's Proxy Handler when it sent an authentication request simply failed as discussed in gh-51540.
ProxyBasicAuthHandlerandProxyDigestAuthHandlercould not authenticate with a proxy for anhttpsURL. When the proxy answered the CONNECT request with407 Proxy Authentication Required,http.clientraised a plainOSError,do_open()wrapped it inURLError, and thehttp_error_407handlers never ran. Only credentials embedded in the proxy URL worked.With this change, a 407 sent by the origin through an established tunnel is still handled like one from the proxy; the proxy credentials are sent only on the CONNECT, never to the origin. This is documented on both
http_error_407methods and covered by a test.Changes
http.client: raises aProxyTunnelError, anOSErrorsubclass when the proxy rejects CONNECT. It carriescode,reasonandheaders; the message is unchanged, soexcept OSErrorkeeps working. It holds no socket (the connection is closed before it is raised), which addresses the concern raised on the issue about exceptions that keep a socket open, and it can be pickled.urllib.request.AbstractHTTPHandler.do_open(): a 407 to CONNECT is returned as an empty-bodied response, so the error handlers run and the request is retried withProxy-Authorizationon the CONNECT. Without credentials the caller now getsHTTPError407 instead ofURLError. Other CONNECT failures still raiseURLError.Request.set_proxy(): re-opening a tunnelled request (as any retry does) switched it totype='http'with the absolute URL as selector, so the retried request inside the tunnel was sent asGET https://host/path. It now keeps the tunnel.AbstractDigestAuthHandler.get_authorization(): when tunnelling, the digest is computed over the CONNECT request (CONNECT host:port, RFC 7616 section 3.4) rather than the request for the origin, so strict proxies accept it.🤖 Generated with Claude Code
Closes #161596
Closes #51540