Bug report
HTTPConnection.putrequest() raises a raw AssertionError instead of
sending the request (or raising an http.client-family exception) when
given an absolute-URL request whose netloc contains RFC 3986-legal
percent-encoding outside of an IPv6 zone identifier -- most plausibly
percent-encoded userinfo, which is required whenever a username or
password contains @, :, /, or %.
Root cause
_strip_ipv6_iface() (Lib/http/client.py:187-193) is meant to strip an
IPv6 zone id from a bracketed literal ([fe80::1%eth0]). It partitions the
whole netloc on the first % and asserts the remainder starts with [:
enc_name = enc_name.partition(b"%")[0]
assert enc_name.startswith(b'[')
HTTPConnection.putrequest() (Lib/http/client.py:1268-1276) passes the
entire netloc of an absolute-URL request (the normal shape for HTTP-proxy
requests, which is exactly what urllib.request uses when going through an
HTTP proxy) into this helper -- not just a bracketed IPv6 literal. Any %
earlier in the netloc than an IPv6 zone id (e.g. in percent-encoded
userinfo) makes the assertion fail.
Reproduction
import http.client
conn = http.client.HTTPConnection("example.com")
conn.putrequest("GET", "http://user:p%40ss@host.example/path")
AssertionError: b'user:p'
A percent-encoded hostname alone triggers the same thing:
conn.putrequest("GET", "http://ex%41mple.com/path")
A plain host with no % in the netloc proceeds normally.
Note this would also break the case it's actually meant to handle,
whenever userinfo is present alongside an IPv6 zone id (e.g.
user%41@[fe80::1%eth0]/), since the netloc is partitioned at the
userinfo's % first.
Your environment
- CPython versions tested: main (commit
64d315ac11a341fa5622718a679d0f6ef1a8dd6b)
- Also reproduces under shipped CPython 3.12; this is long-standing, not a
new regression.
Suggested fix
Have _strip_ipv6_iface only act on an actual bracketed literal, e.g.
return the input unchanged unless it starts with [ after the partition,
and/or strip userinfo from the netloc before generating the Host header
(RFC 7230 forbids userinfo in Host regardless), which fixes both the
crash and the currently-malformed Host: user:p%40ss@host value that would
otherwise be sent.
I'll follow up with a PR.
Found via an automated code-review pass using GLM-5.3-flash paired with
scopegrep, a semantic code-retrieval tool; independently re-verified by
hand against the source above.
Linked PRs
Bug report
HTTPConnection.putrequest()raises a rawAssertionErrorinstead ofsending the request (or raising an
http.client-family exception) whengiven an absolute-URL request whose netloc contains RFC 3986-legal
percent-encoding outside of an IPv6 zone identifier -- most plausibly
percent-encoded userinfo, which is required whenever a username or
password contains
@,:,/, or%.Root cause
_strip_ipv6_iface()(Lib/http/client.py:187-193) is meant to strip anIPv6 zone id from a bracketed literal (
[fe80::1%eth0]). It partitions thewhole netloc on the first
%and asserts the remainder starts with[:HTTPConnection.putrequest()(Lib/http/client.py:1268-1276) passes theentire netloc of an absolute-URL request (the normal shape for HTTP-proxy
requests, which is exactly what
urllib.requestuses when going through anHTTP proxy) into this helper -- not just a bracketed IPv6 literal. Any
%earlier in the netloc than an IPv6 zone id (e.g. in percent-encoded
userinfo) makes the assertion fail.
Reproduction
A percent-encoded hostname alone triggers the same thing:
A plain host with no
%in the netloc proceeds normally.Note this would also break the case it's actually meant to handle,
whenever userinfo is present alongside an IPv6 zone id (e.g.
user%41@[fe80::1%eth0]/), since the netloc is partitioned at theuserinfo's
%first.Your environment
64d315ac11a341fa5622718a679d0f6ef1a8dd6b)new regression.
Suggested fix
Have
_strip_ipv6_ifaceonly act on an actual bracketed literal, e.g.return the input unchanged unless it starts with
[after the partition,and/or strip userinfo from the netloc before generating the
Hostheader(RFC 7230 forbids userinfo in
Hostregardless), which fixes both thecrash and the currently-malformed
Host: user:p%40ss@hostvalue that wouldotherwise be sent.
I'll follow up with a PR.
Found via an automated code-review pass using GLM-5.3-flash paired with
scopegrep, a semantic code-retrieval tool; independently re-verified by
hand against the source above.
Linked PRs