Compare commits

...

3 Commits

Author SHA1 Message Date
Xavier Roche
ba8f246a60 Add the controls that discriminate a ']' outside a literal
The earlier pair both put a real port after the ']', so a bug that truncated
the name there, or took the port from the first colon after it, agreed by
accident and passed. The portless form and the one whose post-']' colon is
not a port separate them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 15:16:48 +02:00
Xavier Roche
2fe570e1ec Pin that a ']' outside an IPv6 literal keeps its port
The new stop condition's real regression risk is over-triggering: a ']' in the
userinfo or a hostname must still leave the port readable, since the port colon
always sits to its right. Nothing asserted that, so an implementation that
stopped at ']' too eagerly passed the suite. Both cases parse identically on
master, so they are controls, not new behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 10:31:30 +02:00
Xavier Roche
a4f03169b1 Stop the -P port scan at an IPv6 literal's closing bracket
hts_parse_proxy scans backward for the port ':' and stopped only at the
authority start or a userinfo '@'. A bracketed IPv6 literal carries colons
of its own, so a portless -P 'http://[2001:db8::1]' scanned into the literal
and split on one, yielding name='http://[2001:db8:' and port 1. An explicit
port hid this, which is why the usual form worked.

Predates the socks5 work: the old inline parser in htscoremain.c had the
same scan, and #566 kept its shape when factoring out hts_parse_proxy.

Closes #598

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 10:16:07 +02:00
2 changed files with 22 additions and 4 deletions

View File

@@ -3754,12 +3754,12 @@ void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
if (name_size == 0)
return;
// scan back to the port ':' (or userinfo '@'), but never past the authority,
// so a scheme's own colon is not read as a port separator; inspect a[-1] from
// one-past-end so no pointer below the string is ever formed
// scan back to the port ':' (or userinfo '@'), never past the authority (a
// scheme's own colon is not a port separator) nor into an IPv6 literal (']'
// stops it); inspect a[-1] from one-past-end so no pointer underflows
authority = (authority != NULL) ? authority + 3 : arg;
a = arg + strlen(arg);
while (a > authority && a[-1] != ':' && a[-1] != '@')
while (a > authority && a[-1] != ':' && a[-1] != '@' && a[-1] != ']')
a--;
if (a > authority && a[-1] == ':') {
int p = -1;

View File

@@ -51,5 +51,23 @@ p 'socks5://us%3Aer:pass@host:1080' 'name=socks5://us%3Aer:pass@host port=1080 h
p 'socks5://a@b:c@host:1080' 'name=socks5://a@b:c@host port=1080 host=host kind=socks'
p 'socks5://naïve:pass@héllo:1080' 'name=socks5://naïve:pass@héllo port=1080 host=héllo kind=socks'
# a bracketed IPv6 literal holds colons of its own: the backward scan must stop
# at the ']', or a portless literal parses to a truncated name and port=1
p 'http://[2001:db8::1]' 'name=http://[2001:db8::1] port=8080 host=[2001:db8::1] kind=http'
p 'http://[2001:db8::1]:3128' 'name=http://[2001:db8::1] port=3128 host=[2001:db8::1] kind=http'
p 'socks5://[::1]' 'name=socks5://[::1] port=1080 host=[::1] kind=socks'
p 'socks5://[::1]:1080' 'name=socks5://[::1] port=1080 host=[::1] kind=socks'
p 'socks5://user:pass@[::1]' 'name=socks5://user:pass@[::1] port=1080 host=[::1] kind=socks'
p 'socks5://user:pass@[::1]:9050' 'name=socks5://user:pass@[::1] port=9050 host=[::1] kind=socks'
p '[::1]' 'name=[::1] port=8080 host=[::1] kind=http'
p '[::1]:3128' 'name=[::1] port=3128 host=[::1] kind=http'
# the ']' only ends the scan: one outside a literal must eat neither the name nor
# the port. The last two discriminate: when a real port follows the ']', a
# truncating bug agrees by accident.
p 'http://user:p]ass@host:80' 'name=http://user:p]ass@host port=80 host=host kind=http'
p 'http://a]b:3128' 'name=http://a]b port=3128 host=a]b kind=http'
p 'http://a]b' 'name=http://a]b port=8080 host=a]b kind=http'
p 'http://a]b:c@host' 'name=http://a]b:c@host port=8080 host=host kind=http'
# a lone ':' must not underflow the backward scan
p ':' 'name= port=8080 host= kind=http'