Bracket IPv6 address literals when rendering URLs and authorities - #1091
Open
AdrianAtZyte wants to merge 1 commit into
Open
Bracket IPv6 address literals when rendering URLs and authorities#1091AdrianAtZyte wants to merge 1 commit into
AdrianAtZyte wants to merge 1 commit into
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
AdrianAtZyte
marked this pull request as draft
July 29, 2026 17:56
AdrianAtZyte
marked this pull request as ready for review
July 29, 2026 18:14
Author
|
I am assuming the CI issue is unrelated, please let me know otherwise. |
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.
Summary
Working on scrapy/scrapy#7090, it seemed like httpx2 would support IPv6 in general but not through a proxy. Apparently the brackets get lost when proxying, thus the issue.
What Opus wanted me to use a PR description
URL.hostandOrigin.hosthold a host in the form needed to open a connection,which for IPv6 is the bare address. Four places rendered that bare host into a URL
or an authority without adding the square brackets that an
IP-literalrequires,putting malformed values on the wire:
::1:8443cannot be split back into a hostand a port, and
http://::1:8443/is not a valid URI.The most visible consequence is that no request to an IPv6 address literal works
through a proxy, in either forwarding or tunnelling mode.
https://[::1]:8443/via proxy)CONNECT ::1:8443CONNECT [::1]:8443http://[::1]:1234/via proxy)GET http://::1:1234/GET http://[::1]:1234/Hostheader, no proxyHost: ::1:1234Host: [::1]:1234bytes(URL("http://[::1]:8080/x"))http://::1:8080/xhttp://[::1]:8080/xstr(Origin(b"http", b"::1", 8080))http://::1:8080http://[::1]:8080The
Hostheader row applies tohttpcore2used on its own; throughhttpx2it ismasked, because
httpx2setsHostitself from the bracketedURL.netloc.The change
A single helper in
_models.py, used at each of those sites:The
":" in hosttest is the same onehttpx2already uses inParseResult.authorityandParseResult.netloc(src/httpx2/httpx2/_urlparse.py),so both packages now build an authority the same way. A colon cannot occur in a
registered name or in an IPv4 address, so its presence is enough to identify an
IPv6 address.
URL.hostis bare by construction, sinceURL.__init__goes throughurlparse().hostname, which strips brackets.Origin.hostdeliberately keeps the bare address: it feeds the socket connect, theTLS
server_hostname, and the SOCKS address, none of which want brackets. Only therendering sites changed.
_sync/http_proxy.pyis regenerated byscripts/unasync.py, not hand-edited.Context
This is the bug from encode/httpx#392,
carried over as #145. It was closed in 2019 as not reproducible, correctly at the
time:
httpx.URL("https://[::1]:8080").origin.hostwas then'[::1]', bracketsincluded. Delegating transport to
httpcore, whoseOrigin.hostis bare,reintroduced it. The same lines are present in
encode/httpcoretoday, andtests/_async/test_http_proxy.pythere has no IPv6 case.Out of scope, to keep this atomic:
Hostheader. Different layer(
httpx2header rendering rather thanhttpcore2authority rendering), and thezone still arrives percent-encoded as
%25. Both need fixing for link-localaddresses to work through a proxy.
socksioas a protocol addressfield rather than as part of a URL, so the bare form is correct.
Checklist
I’ll risk it 🙂
No doc changes I assume, this is a bug fix.