mirror of
https://github.com/xroche/httrack.git
synced 2026-07-18 23:01:36 +03:00
Compare commits
4 Commits
release-3.
...
fix-623-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e0def63ea | ||
|
|
8a8fd7f9c0 | ||
|
|
1c1c0b1267 | ||
|
|
2acc796fbe |
12
configure.ac
12
configure.ac
@@ -1,6 +1,6 @@
|
||||
AC_PREREQ([2.71])
|
||||
|
||||
AC_INIT([httrack], [3.49.12], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_INIT([httrack], [3.49.13], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_COPYRIGHT([
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 1998-2015 Xavier Roche and other contributors
|
||||
@@ -29,11 +29,13 @@ AC_CONFIG_SRCDIR(src/httrack.c)
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
# 3:4:0: 3.49.12 tail-appends one field (why_url) to httrackp and otherwise only
|
||||
# deletes dead comments; no struct layout or exported signature broke vs 3.49.11,
|
||||
# so it stays soname .so.3; bump revision.
|
||||
# 3:5:0: 3.49.13 leaves every installed struct layout and the exported symbol set
|
||||
# untouched vs 3.49.12; the 2 GB (_stat64) and UTF-8 export widenings are _WIN32-only,
|
||||
# and INTsys widened on POSIX (#600) but sits in no installed struct. Stays soname
|
||||
# .so.3; bump revision. x32 alone changes layout (LLint was 4 bytes there, #524), and
|
||||
# is not a release architecture.
|
||||
# (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.)
|
||||
VERSION_INFO="3:4:0"
|
||||
VERSION_INFO="3:5:0"
|
||||
AM_MAINTAINER_MODE
|
||||
AC_USE_SYSTEM_EXTENSIONS
|
||||
|
||||
|
||||
15
debian/changelog
vendored
15
debian/changelog
vendored
@@ -1,3 +1,18 @@
|
||||
httrack (3.49.13-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: SOCKS5 and CONNECT proxy support, brotli and zstd
|
||||
content decoding, --update data-loss fixes, and hardening of several
|
||||
network-facing parsers; full list in history.txt.
|
||||
* Add libbrotli-dev and libzstd-dev to Build-Depends for the new content
|
||||
codings.
|
||||
* libhttrack3 and libhttrack-dev no longer ship libhtsjava.so.3*: the
|
||||
obsolete Java-applet parser was removed upstream. It was a dlopen plugin
|
||||
with no reverse dependencies, so this is a plain file drop on upgrade;
|
||||
libhttrack.so.3 itself is unchanged and needs no rename.
|
||||
* Drop the htsjava attribution from debian/copyright.
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Fri, 17 Jul 2026 10:49:52 +0200
|
||||
|
||||
httrack (3.49.12-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: security and crawl-correctness fixes (remote stack
|
||||
|
||||
25
history.txt
25
history.txt
@@ -4,6 +4,31 @@ HTTrack Website Copier release history:
|
||||
|
||||
This file lists all changes and fixes that have been made for HTTrack
|
||||
|
||||
3.49-13
|
||||
+ New: SOCKS5 proxy support, with scheme-aware -P URLs (socks5://, socks5h://, connect://) and plain HTTP tunneled through a CONNECT-only proxy (#563, #564)
|
||||
+ New: decode brotli and zstd content codings, advertised over TLS only as browsers do (#556)
|
||||
+ New: webhttrack exposes the engine options added since 3.49-2, among them --cookies-file, --pause and --strip-query (#587)
|
||||
+ Fixed: files of 2 GB or more were mishandled on Windows and on every 32-bit build (#569)
|
||||
+ Fixed: --update destroyed a good local copy when the re-fetch returned an HTTP error, was aborted by -M/-E, failed to decode, or came in short (#176, #521, #557, #562)
|
||||
+ Fixed: a self-redirect cookie wall was dropped instead of being re-fetched with the cookie (#15)
|
||||
+ Fixed: a stalled TLS handshake ignored --timeout, and synchronous DNS resolution could wedge a crawl past --max-time (#607, #613)
|
||||
+ Fixed: -M metered saved bytes rather than received volume, and overshot its cap under a slow server (#77, #520)
|
||||
+ Fixed: several network-facing overflows and denial-of-service paths in the Content-Range, chunked-transfer, cookie, filter and ProxyTrack cache parsers
|
||||
+ Fixed: a failed connect did not fall back to the next address on Windows (#579)
|
||||
+ Fixed: -P took an out-of-range port as a garbage port, and scanned past an IPv6 literal's closing bracket (#598, #602)
|
||||
+ Fixed: reject a port outside 1..65535 wherever one is parsed (a crawled URL, the htsserver and proxytrack listen arguments, an ftp:// URL), instead of letting a bare sscanf wrap a huge value into a plausible port and silently use it (#614, #626)
|
||||
+ Fixed: a configured proxy still resolved and dialed the origin itself (#592)
|
||||
+ Fixed: ~/ in the -O base path was never expanded (#270)
|
||||
+ Fixed: a non-ASCII -O output path was double-encoded on Windows once argv became UTF-8 (#621)
|
||||
+ Fixed: files under a non-ASCII project path were saved to a mangled directory on Windows (#217)
|
||||
+ Fixed: --build-top-index (-%i) and --protocol (-@i) were taken for the -i continue flag, wiping the URL list and exiting on the usage screen (#615)
|
||||
+ Fixed: webhttrack ignored LC_ALL/LC_MESSAGES and picked the wrong Chinese and Portuguese (#95)
|
||||
+ Fixed: webhttrack wrote its base path and httrack.ini to the filesystem root when $HOME was empty (#625)
|
||||
+ Fixed: crawls on a non-default port were slowed by a per-request pre-resolve (#181)
|
||||
+ Changed: Windows builds moved to Visual Studio 2022 and OpenSSL 3.x, the VS2008 project files are retired, and the binaries carry a version resource
|
||||
+ Changed: removed the obsolete Java-applet (.class) parser and the dead SWF module remnants
|
||||
+ Changed: multiple internal hardening, test and CI improvements (Windows and macOS crawl suites, HTML-parser fuzzing, parallel make check)
|
||||
|
||||
3.49-12
|
||||
+ New: --why explains which filter rule accepts or rejects a given URL, then exits (#505)
|
||||
+ Fixed: links carrying raw UTF-8 bytes were fetched double-encoded and 404'd (#516)
|
||||
|
||||
@@ -43,8 +43,8 @@ Please visit our Website: http://www.httrack.com
|
||||
configure.ac, decoupled from these). VERSION is the display form, VERSIONID
|
||||
the dotted numeric form, AFF_VERSION the short form shown in footers,
|
||||
LIB_VERSION the data/cache format generation. */
|
||||
#define HTTRACK_VERSION "3.49-12"
|
||||
#define HTTRACK_VERSIONID "3.49.12"
|
||||
#define HTTRACK_VERSION "3.49-13"
|
||||
#define HTTRACK_VERSIONID "3.49.13"
|
||||
#define HTTRACK_AFF_VERSION "3.x"
|
||||
#define HTTRACK_LIB_VERSION "2.0"
|
||||
|
||||
|
||||
@@ -1532,8 +1532,30 @@ int url_savename(lien_adrfilsave *const afs,
|
||||
// last segment
|
||||
wsave[j++] = '/';
|
||||
#define MAX_UTF8_SEQ_CHARS 4
|
||||
for(i = lastSeg; wsave[i] != '\0' && j < maxLen; i++) {
|
||||
wsave[j++] = wsave[i];
|
||||
{
|
||||
// #623: the ".delayed" placeholder marker sits at the tail; cutting
|
||||
// through it drops IS_DELAYED_EXT, so the file is never renamed to its
|
||||
// final name. Reserve the trailing ".<id>.delayed" across the cut.
|
||||
size_t markStart = wsaveLen;
|
||||
if (IS_DELAYED_EXT(afs->save)) {
|
||||
const size_t extDot = wsaveLen - strlen("." DELAYED_EXT);
|
||||
size_t p = extDot; /* walk back over a dot-separated ".<hexid>" tag */
|
||||
|
||||
while (p > lastSeg && ((wsave[p - 1] >= '0' && wsave[p - 1] <= '9') ||
|
||||
(wsave[p - 1] >= 'a' && wsave[p - 1] <= 'f')))
|
||||
p--;
|
||||
// keep the tag only if truly ".<hexid>.delayed"; else the bare marker
|
||||
// (a wholly-hex base, e.g. a hashed #133 name, must not be absorbed)
|
||||
markStart = (p > lastSeg && p < extDot && wsave[p - 1] == '.')
|
||||
? p - 1
|
||||
: extDot;
|
||||
}
|
||||
// head, bounded so the marker still fits, then the marker itself
|
||||
for (i = lastSeg; i < markStart && j + (wsaveLen - markStart) < maxLen;
|
||||
i++)
|
||||
wsave[j++] = wsave[i];
|
||||
for (i = markStart; i < wsaveLen && j < maxLen; i++)
|
||||
wsave[j++] = wsave[i];
|
||||
}
|
||||
// terminating \0
|
||||
wsave[j++] = '\0';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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..
|
||||
|
||||
@@ -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:]",
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 3, 49, 12, 0
|
||||
PRODUCTVERSION 3, 49, 12, 0
|
||||
FILEVERSION 3, 49, 13, 0
|
||||
PRODUCTVERSION 3, 49, 13, 0
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
@@ -35,12 +35,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Xavier Roche"
|
||||
VALUE "FileDescription", VER_FILE_DESCRIPTION
|
||||
VALUE "FileVersion", "3.49.12"
|
||||
VALUE "FileVersion", "3.49.13"
|
||||
VALUE "InternalName", VER_ORIGINAL_FILENAME
|
||||
VALUE "LegalCopyright", "Copyright (C) 1998-2026 Xavier Roche and other contributors. GNU GPL v3 or later."
|
||||
VALUE "OriginalFilename", VER_ORIGINAL_FILENAME
|
||||
VALUE "ProductName", "HTTrack Website Copier"
|
||||
VALUE "ProductVersion", "3.49-12"
|
||||
VALUE "ProductVersion", "3.49-13"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
11
tests/66_engine-port80-strip.test
Executable file
11
tests/66_engine-port80-strip.test
Executable 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"
|
||||
72
tests/67_engine-delayed-truncate.test
Executable file
72
tests/67_engine-delayed-truncate.test
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# #623: url_savename enforces the 236-char path ceiling by cutting the tail of
|
||||
# the last segment, where the mandatory ".delayed" placeholder marker lives. A
|
||||
# cut marker fails IS_DELAYED_EXT, so back_delayed_rename never renames the file
|
||||
# to its final name and the download is lost. The marker must survive the cut.
|
||||
|
||||
# statuscode=302 status=-1 = a redirect answer still downloading: no type is
|
||||
# resolved, so the name gets a ".<id>.delayed" placeholder (see 01_engine-savename).
|
||||
CEIL=236
|
||||
|
||||
httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
cd "$scratch"
|
||||
|
||||
run() {
|
||||
"$httrack_bin" -O /dev/null -#test=savename "$@" | sed -n 's/^savename: //p'
|
||||
}
|
||||
|
||||
# A deep path ending in a long segment that overruns the ceiling.
|
||||
deep="/d1$(printf 'a%.0s' {1..40})/d2$(printf 'b%.0s' {1..40})"
|
||||
deep="$deep/d3$(printf 'c%.0s' {1..40})/d4$(printf 'd%.0s' {1..40})"
|
||||
long="$deep/$(printf 'z%.0s' {1..90})"
|
||||
|
||||
out="$(run "$long" text/html statuscode=302 status=-1)"
|
||||
case "$out" in
|
||||
*.delayed) ;;
|
||||
*)
|
||||
echo "FAIL: delayed marker cut by truncation: '$out'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
test "${#out}" -le "$CEIL" || {
|
||||
echo "FAIL: truncated name ${#out} > $CEIL ceiling: '$out'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# #133-style hashed name: an all-hex last segment must keep the marker too (the
|
||||
# ".<id>." tag is not mistaken for part of the hash).
|
||||
hexseg=$(printf 'a1b2c3d4e5f60718%.0s' {1..8})
|
||||
hexpath="/d1$(printf 'x%.0s' {1..30})/d2$(printf 'y%.0s' {1..30})/$hexseg"
|
||||
out="$(run "$hexpath" text/html statuscode=302 status=-1)"
|
||||
case "$out" in
|
||||
*.delayed) ;;
|
||||
*)
|
||||
echo "FAIL: hashed-name marker cut by truncation: '$out'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
test "${#out}" -le "$CEIL" || {
|
||||
echo "FAIL: hashed name ${#out} > $CEIL ceiling: '$out'"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# A non-delayed name of the same shape still truncates, with no marker to keep.
|
||||
out="$(run "$long.html" text/html)"
|
||||
test "${#out}" -le "$CEIL" || {
|
||||
echo "FAIL: non-delayed name ${#out} > $CEIL ceiling: '$out'"
|
||||
exit 1
|
||||
}
|
||||
case "$out" in
|
||||
*.delayed)
|
||||
echo "FAIL: non-delayed name grew a .delayed marker: '$out'"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "delayed-truncate OK"
|
||||
@@ -146,6 +146,8 @@ 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 \
|
||||
67_engine-delayed-truncate.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
Reference in New Issue
Block a user