Repository: xroche/httrack (2026-07-16T08:24:06Z)

Issue:
A -P proxy given as a bracketed IPv6 literal with no port parses to a truncated host and port 1
`hts_parse_proxy` scans backward from the end of the `-P` argument looking for the port colon, stopping at the authority start or a userinfo `@`. Nothing stops it at a `]`, so for a bracketed IPv6 literal with no explicit port the scan walks into the literal and stops at one of the literal's own colons.

`httrack -#test=proxyurl 'http://[2001:db8::1]'` prints:

```
name=http://[2001:db8: port=1 host=[2001:db8: kind=http
```

The name is truncated mid-literal and the port becomes 1, from `sscanf("1]", "%d")`. Same for `socks5://[::1]` and for the bare `[::1]` form. An explicit port (`[::1]:3128`) is unaffected, which is why the usual form works.

The bug is long-standing. The pre-#566 inline parser in `htscoremain.c` used the same backward scan with no bracket guard; #566 moved it into `hts_parse_proxy` and kept the shape. It affects `http://`, `connect://` and `socks5://` proxies equally.



Discussion:

Patch:
diff --git a/src/htslib.c b/src/htslib.c
index ddf5f6f42..55899b264 100644
--- a/src/htslib.c
+++ b/src/htslib.c
@@ -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;


Test patch:
diff --git a/tests/01_engine-proxyurl.test b/tests/01_engine-proxyurl.test
index 1bab54ac3..41a5329aa 100755
--- a/tests/01_engine-proxyurl.test
+++ b/tests/01_engine-proxyurl.test
@@ -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'