Compare commits

...

1 Commits

Author SHA1 Message Date
Xavier Roche
1965912f6e Stripping a default :80 with leading zeros mangled the host
The link-rewrite step that drops a redundant :80 read the port with a
hand-rolled digit accumulator, then skipped a hardcoded 3 chars (":80")
when the value equaled 80. Any longer spelling that still evaluates to
80 lost only its first 3 chars and glued the rest onto the host:
http://127.0.0.1:080/x became 127.0.0.10/x, :0080 became 127.0.0.180.
The accumulator was also unchecked, so :4294967376 wrapped to 80 and
took the same path (#614 shape).

Extract the strip into hts_strip_default_port() and parse the matched
digits with the range-checked hts_parse_url_port(): a wrapped or
out-of-range value no longer aliases 80, and a genuine default is
dropped by its full matched length instead of 3. Covered by the
"stripport" engine self-test (tests/66_engine-port80-strip.test).

Closes #627

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-18 07:19:09 +02:00
5 changed files with 93 additions and 33 deletions

View File

@@ -315,6 +315,43 @@ static void escape_url_parens(char *const s, const size_t size) {
strlcpybuff(s, buff, size);
}
/* Strip a default ":80" from lien's authority in place. Any spelling that
range-parses to 80 (":80", ":080") is dropped by its matched length, not a
hardcoded 3 chars; a value that merely wraps to 80 as an int (#614) stays. */
void hts_strip_default_port(char *lien, size_t size) {
char *a;
if (!link_has_authority(lien))
return;
a = strstr(lien, "//"); // "//" authority
if (a)
a += 2;
else
a = lien;
a = jump_toport(a);
if (a) { // port present
char *b = a + 1;
char saved;
int port;
hts_boolean is_default;
while (isdigit((unsigned char) *b))
b++;
saved = *b;
*b = '\0';
is_default = hts_parse_url_port(a + 1, &port) && port == 80;
*b = saved;
if (is_default) { // default port, strip it
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
tempo[0] = '\0';
strncatbuff(tempo, lien, a - lien);
strcatbuff(tempo, b); // skip the whole matched :port
strlcpybuff(lien, tempo, size);
}
}
}
/* Main parser */
int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
char catbuff[CATBUFF_SIZE];
@@ -2138,38 +2175,8 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
} while((b != a) && (b));
}
}
// éliminer les éventuels :80 (port par défaut!)
if (link_has_authority(lien)) {
char *a;
a = strstr(lien, "//"); // "//" authority
if (a)
a += 2;
else
a = lien;
a = jump_toport(a);
if (a) { // port
int port = 0;
int defport = 80;
char *b = a + 1;
#if HTS_USEOPENSSL
#endif
while(isdigit((unsigned char) *b)) {
port *= 10;
port += (int) (*b - '0');
b++;
}
if (port == defport) { // port 80, default - c'est débile
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
tempo[0] = '\0';
strncatbuff(tempo, lien, a - lien);
strcatbuff(tempo, a + 3); // sauter :80
strcpybuff(lien, tempo);
}
}
}
// drop a default :80 port from the authority
hts_strip_default_port(lien, sizeof(lien));
// filtrer les parazites (mailto & cie)
/*
if (strfield(lien,"mailto:")) { // ne pas traiter

View File

@@ -106,6 +106,10 @@ struct htsmoduleStructExtended {
*/
int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre);
/* Strip a default ":80" (any spelling) from an absolute link's authority, in
place into a buffer of the given size. */
void hts_strip_default_port(char *lien, size_t size);
/*
Check for 301,302.. errors ("moved") and handle them; re-isuue requests, make
rediretc file, handle filters considerations..

View File

@@ -1589,6 +1589,41 @@ static int st_identabs(httrackp *opt, int argc, char **argv) {
return 0;
}
/* Default-port strip (#627): a genuine 80 (any spelling) is removed by its
matched length, host preserved; a non-80 port or one that only wraps to 80 as
a 32-bit int (#614) is left intact. Guards the old bug where ":080"/":0080"
dropped a hardcoded 3 chars and glued the leftover digits onto the host. */
static int st_stripport(httrackp *opt, int argc, char **argv) {
static const struct {
const char *in, *out;
} cases[] = {
{"http://127.0.0.1:80/x", "http://127.0.0.1/x"},
{"http://127.0.0.1:080/x", "http://127.0.0.1/x"},
{"http://127.0.0.1:0080/x", "http://127.0.0.1/x"},
{"http://127.0.0.1:80", "http://127.0.0.1"},
{"http://127.0.0.1:0081/x", "http://127.0.0.1:0081/x"},
{"http://127.0.0.1:81/x", "http://127.0.0.1:81/x"},
{"http://127.0.0.1:8080/x", "http://127.0.0.1:8080/x"},
{"http://127.0.0.1:4294967376/x", "http://127.0.0.1:4294967376/x"},
{"http://127.0.0.1/x", "http://127.0.0.1/x"},
};
size_t k;
(void) opt;
(void) argc;
(void) argv;
for (k = 0; k < sizeof(cases) / sizeof(cases[0]); k++) {
char BIGSTK buff[HTS_URLMAXSIZE * 2];
strcpybuff(buff, cases[k].in);
hts_strip_default_port(buff, sizeof(buff));
assertf(strcmp(buff, cases[k].out) == 0);
}
printf("stripport self-test OK\n");
return 0;
}
/* Extra args are key=value: adr= cdispo= statuscode= status= strip= urlhack=
no-www= no-slash= no-query= n83= type=, plus repeatable prior=adr|fil|sav
registering an already-crawled link (dedup/collision paths). */
@@ -3189,6 +3224,8 @@ static const struct selftest_entry {
st_socks5},
{"identabs", "", "ident_url_absolute one-byte fil[] overflow self-test",
st_identabs},
{"stripport", "", "default :80 port strip preserves host (#627)",
st_stripport},
{"header", "<raw-header-line> ...", "response header-line parsing",
st_header},
{"headerlong", "[header-name:]",

View File

@@ -0,0 +1,11 @@
#!/bin/bash
#
set -euo pipefail
# Stripping a default :80 from a crawled link used to skip a hardcoded 3 chars
# (#627): ":080"/":0080" lost only ":80" and glued the rest onto the host
# (127.0.0.1:080/x -> 127.0.0.10/x), and a value wrapping to 80 as a 32-bit int
# (#614) was stripped as if it were the default. All assertions live in the
# engine self-test.
httrack -O /dev/null -#test=stripport | grep -q "stripport self-test OK"

View File

@@ -146,6 +146,7 @@ TESTS = \
62_lang-integrity.test \
63_webhttrack-home.test \
64_local-intl-outdir.test \
65_port-siblings.test
65_port-siblings.test \
66_engine-port80-strip.test
CLEANFILES = check-network_sh.cache