Compare commits

..

2 Commits

Author SHA1 Message Date
Xavier Roche
87f178e65d 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>
2026-07-16 19:28:37 +02:00
Xavier Roche
b9b188d83b 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>
2026-07-16 18:58:36 +02:00
4 changed files with 42 additions and 90 deletions

View File

@@ -113,7 +113,6 @@ HTSEXT_API int hts_main(int argc, char **argv) {
}
static int hts_main_internal(int argc, char **argv, httrackp * opt);
static hts_boolean cmdl_shortopt_has(const char *s, char c);
// Main, récupère les paramètres et appelle le robot
HTSEXT_API int hts_main2(int argc, char **argv, httrackp * opt) {
@@ -305,12 +304,12 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
hts_get_version_info(opt));
return 0;
} else {
if (strncmp(tmp_argv[0], "--", 2)) { /* not a long option */
if (cmdl_shortopt_has(tmp_argv[0], 'q'))
opt->quiet = HTS_TRUE; // never ask questions (nohup)
if (cmdl_shortopt_has(tmp_argv[0], 'i')) { // doit.log!
argv_url = -1;
opt->quiet = HTS_TRUE;
if (strncmp(tmp_argv[0], "--", 2)) { /* pas */
if ((strchr(tmp_argv[0], 'q') != NULL))
opt->quiet = 1; // ne pas poser de questions! (nohup par exemple)
if ((strchr(tmp_argv[0], 'i') != NULL)) { // doit.log!
argv_url = -1; /* forcer */
opt->quiet = 1;
}
} else if (strcmp(tmp_argv[0] + 2, "quiet") == 0) {
opt->quiet = 1; // ne pas poser de questions! (nohup par exemple)
@@ -2808,32 +2807,6 @@ int check_path(String * s, char *defaultname) {
return return_value;
}
/* Does the short-option cluster s carry c from the main option set (-i, -iC2,
-%Mi)? Walked as the parser does below: %, &, @ and # each take the letter
after them into another set, so the i of -%i is not the main-set -i. */
static hts_boolean cmdl_shortopt_has(const char *s, char c) {
const char *com;
if (s[0] != '-' || s[1] == '-')
return HTS_FALSE;
for (com = s + 1; *com != '\0'; com++) {
switch (*com) {
case '%':
case '&':
case '@':
case '#':
if (*(com + 1) != '\0')
com++; /* skip the other set's letter */
break;
default:
if (*com == c)
return HTS_TRUE;
break;
}
}
return HTS_FALSE;
}
// détermine si l'argument est une option
int cmdl_opt(char *s) {
if (s[0] == '-') { // c'est peut être une option

View File

@@ -65,6 +65,7 @@ Please visit our Website: http://www.httrack.com
#endif /* _WIN32 */
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
@@ -3747,6 +3748,20 @@ static int proxy_default_port(const char *arg) {
return hts_proxy_is_socks(arg) ? 1080 : 8080;
}
// port "a" of -P argument "arg": digits fitting TCP's 1..65535, else the scheme
// default. Not sscanf("%d"): past INT_MAX it wraps to a garbage port (#602)
static int parse_proxy_port(const char *a, const char *arg) {
char *end;
long p;
if (!isdigit((unsigned char) *a)) // strtol would eat a sign or leading space
return proxy_default_port(arg);
p = strtol(a, &end, 10);
if (*end != '\0' || p < 1 || p > 65535) // ERANGE lands out of range too
return proxy_default_port(arg);
return (int) p;
}
void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
const char *authority = strstr(arg, "://");
const char *a;
@@ -3762,10 +3777,7 @@ void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
while (a > authority && a[-1] != ':' && a[-1] != '@' && a[-1] != ']')
a--;
if (a > authority && a[-1] == ':') {
int p = -1;
sscanf(a, "%d", &p);
*port = (p > 0) ? p : proxy_default_port(arg);
*port = parse_proxy_port(a, arg);
namelen = (size_t) (a - 1 - arg);
} else {
*port = proxy_default_port(arg);

View File

@@ -54,13 +54,6 @@ refused() {
! echo "FAIL: $1 (exit $RC)" || exit 1
}
# assert continue mode was entered: it drops the URL list, so with no cache to
# resume the run ends on the usage screen rather than on any other error
continued() {
{ test "$RC" -ne 0 && grep -q 'usage:' "$1/.log"; } ||
! echo "FAIL: $2 (exit $RC)" || exit 1
}
# a value past the old 126/256 caps but within the cap is accepted, on both the
# short and long form of each option
long=$(nchars 900)
@@ -109,50 +102,4 @@ for bad in nan nan:5 5:nan inf 10:5 99999; do
refused "#185: invalid --pause '$bad' not refused cleanly"
done
# An option is not -i (continue) merely because its name contains an 'i' (#615).
# These used to wipe the URL given before them and exit on the usage screen.
run "$tmp/ord-bti" --build-top-index
accepted "$tmp/ord-bti" "#615: --build-top-index after the URL wiped the URL list"
run "$tmp/ord-bti-s" "-%i"
accepted "$tmp/ord-bti-s" "#615: -%i after the URL wiped the URL list"
run "$tmp/ord-proto" --protocol 2
accepted "$tmp/ord-proto" "#615: --protocol after the URL wiped the URL list"
run "$tmp/ord-proto-s" "-@i2"
accepted "$tmp/ord-proto-s" "#615: -@i2 after the URL wiped the URL list"
# %, &, @ and # take the letter after them into another option set, wherever
# they sit in the cluster, so the i of -q%i is not the main-set -i either.
run "$tmp/ord-qpi" "-q%i"
accepted "$tmp/ord-qpi" "#615: -q%i after the URL wiped the URL list"
run "$tmp/ord-qai" "-q@i"
accepted "$tmp/ord-qai" "#615: -q@i after the URL wiped the URL list"
# -%q only forces quiet mode, which nothing here can see: a run whose stdout is
# not a tty is quiet from the start (htscoremain.c:174). Just check it crawls.
run "$tmp/ord-iqs" "-%q"
accepted "$tmp/ord-iqs" "#615: -%q after the URL broke the crawl"
# The real -i still forces continue mode and drops the URL list, in every form:
# a fix that merely stopped looking for 'i' would pass the checks above.
run "$tmp/cont-s" -i
continued "$tmp/cont-s" "#615: -i after the URL no longer forces continue mode"
run "$tmp/cont-c" -iC2
continued "$tmp/cont-c" "#615: -iC2 after the URL no longer forces continue mode"
run "$tmp/cont-l" --continue
continued "$tmp/cont-l" "#615: --continue after the URL no longer forces continue mode"
run "$tmp/cont-u" --update
continued "$tmp/cont-u" "#615: --update after the URL no longer forces continue mode"
# ...including where another set's letter precedes it in the cluster.
run "$tmp/cont-pq" "-%qi"
continued "$tmp/cont-pq" "#615: -%qi after the URL no longer forces continue mode"
run "$tmp/cont-pm" "-%Mi"
continued "$tmp/cont-pm" "#615: -%Mi after the URL no longer forces continue mode"
# Placed before the URL these always worked; they must keep working.
run_only "$tmp/pre-bti" "-%i" "file://$tmp/index.html"
accepted "$tmp/pre-bti" "#615: -%i before the URL broke the crawl"
run_only "$tmp/pre-proto" "-@i2" "file://$tmp/index.html"
accepted "$tmp/pre-proto" "#615: -@i2 before the URL broke the crawl"
exit 0

View File

@@ -71,3 +71,23 @@ 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'