mirror of
https://github.com/xroche/httrack.git
synced 2026-07-23 01:01:41 +03:00
* Reject an out-of-range -P proxy port instead of wrapping it hts_parse_proxy read the port with sscanf(a, "%d", &p), which is signed overflow UB past INT_MAX. glibc wraps rather than fails, so a garbage port got through: -P 'http://host:99999999999999' parsed to port 276447231, while 'http://host:4294967296' happened to fail and fall back to the default. 65536 was accepted as-is. Parse with strtol and range-check to *DIGIT in 1..65535 (RFC 3986); an out-of-range or malformed port now falls back to proxy_default_port, exactly as an unparsable one already did. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * Attribute the proxy port range to TCP, not RFC 3986 RFC 3986 defines port as *DIGIT with no range; the 1..65535 bound comes from TCP's 16-bit port field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> --------- Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
5.6 KiB
Bash
Executable File
94 lines
5.6 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# hts_parse_proxy splits a -P argument "[scheme://][user:pass@]host[:port]" into
|
|
# the proxy host string and port. -#test=proxyurl <arg> prints
|
|
# "name=.. port=.. host=.. kind=..", where host= is what the connect resolves and
|
|
# kind= is the transport the scheme selects (http, socks or connect).
|
|
|
|
p() {
|
|
test "$(httrack -O /dev/null -#test=proxyurl "$1")" == "$2" || exit 1
|
|
}
|
|
|
|
# bare host, with and without an explicit port
|
|
p 'proxy.example.com:8080' 'name=proxy.example.com port=8080 host=proxy.example.com kind=http'
|
|
p 'proxy.example.com' 'name=proxy.example.com port=8080 host=proxy.example.com kind=http'
|
|
|
|
# user:pass@ is kept in name (for proxy auth) and stripped from host
|
|
p 'user:pass@proxy:3128' 'name=user:pass@proxy port=3128 host=proxy kind=http'
|
|
p 'user:pass@proxy' 'name=user:pass@proxy port=8080 host=proxy kind=http'
|
|
|
|
# a scheme colon must not be read as a port: http://host with no port used to
|
|
# parse to name=http with a garbage port
|
|
p 'http://proxy:8080' 'name=http://proxy port=8080 host=proxy kind=http'
|
|
p 'http://proxy' 'name=http://proxy port=8080 host=proxy kind=http'
|
|
|
|
# socks5/socks5h default to 1080 and resolve to the bare host
|
|
p 'socks5://host:1080' 'name=socks5://host port=1080 host=host kind=socks'
|
|
p 'socks5://host' 'name=socks5://host port=1080 host=host kind=socks'
|
|
p 'socks5h://host' 'name=socks5h://host port=1080 host=host kind=socks'
|
|
# explicit port wins over the 1080 default, with and without userinfo
|
|
p 'socks5://host:9050' 'name=socks5://host port=9050 host=host kind=socks'
|
|
p 'socks5://user:pass@host:9050' 'name=socks5://user:pass@host port=9050 host=host kind=socks'
|
|
|
|
# connect:// is an http proxy driven with CONNECT for every request (#564); it
|
|
# defaults to the http 8080 port and resolves to the bare host
|
|
p 'connect://host' 'name=connect://host port=8080 host=host kind=connect'
|
|
p 'connect://host:8082' 'name=connect://host port=8082 host=host kind=connect'
|
|
p 'connect://user:pass@host:8082' 'name=connect://user:pass@host port=8082 host=host kind=connect'
|
|
# only the scheme selects connect mode: a host literally named "connect" is a
|
|
# plain http proxy, not a tunnel
|
|
p 'connect:8080' 'name=connect port=8080 host=connect kind=http'
|
|
p 'connect.example.com:3128' 'name=connect.example.com port=3128 host=connect.example.com kind=http'
|
|
|
|
# the split is byte-transparent: percent-escapes stay encoded (decoded later at
|
|
# auth time), only structural ASCII ':'/'@' delimit, and the last '@' ends the
|
|
# userinfo. So %40/%3A and UTF-8 bytes never mis-split host or port.
|
|
p 'socks5://user:p%40ss@host:1080' 'name=socks5://user:p%40ss@host port=1080 host=host kind=socks'
|
|
p 'socks5://us%3Aer:pass@host:1080' 'name=socks5://us%3Aer:pass@host port=1080 host=host kind=socks'
|
|
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'
|
|
|
|
# a port is *DIGIT in 1..65535, else the scheme default: sscanf("%d") used to
|
|
# wrap past INT_MAX and hand back a garbage port (#602)
|
|
p 'http://host:65535' 'name=http://host port=65535 host=host kind=http'
|
|
p 'http://host:1' 'name=http://host port=1 host=host kind=http'
|
|
p 'http://host:65536' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:2147483648' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:4294967296' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:99999999999999' 'name=http://host port=8080 host=host kind=http'
|
|
# discriminating: this one wrapped to a plausible 80, so range-checking the
|
|
# sscanf result instead of rejecting the overflow still passes everything above
|
|
p 'http://host:4294967376' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:999999999999999999999999999999' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:0' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:-1' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:80x' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host: 80' 'name=http://host port=8080 host=host kind=http'
|
|
p 'http://host:' 'name=http://host port=8080 host=host kind=http'
|
|
# the fallback is the scheme's own default, not a hardcoded 8080
|
|
p 'socks5://host:99999999999999' 'name=socks5://host port=1080 host=host kind=socks'
|