Compare commits

...

44 Commits

Author SHA1 Message Date
Xavier Roche
d9d25196bb Fix two more tests the Win32 CI leg exposed
Both are test bugs surfaced by running on 32-bit Windows for the first
time, not engine bugs:

- strsafe drove its overflow through a char[4]. On a 32-bit build that
  equals sizeof(char*), so htssafe's array-vs-pointer heuristic
  (sizeof(A) != sizeof(char*), the MSVC path) reads the array as a
  pointer and skips the bound: the copy then genuinely overflows and
  crashes instead of aborting cleanly. Size the buffer off the pointer
  width so the checked path runs everywhere.

- idna's malformed-UTF-8 rejection case passed the bad bytes through
  argv. They cannot survive a Windows command line: the UTF-16 round-trip
  turns them into valid U+FFFD, which the encoder then accepts. Feed them
  as hex so the exact bytes reach the bundled punycode encoder, which
  rejects them identically on every platform.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 19:08:34 +02:00
Xavier Roche
d6153cbc86 Fix the Win32 CLI build: include htscharset.h after winsock2.h
htscharset.h pulls in <windows.h>, which drags in the legacy <winsock.h>
unless <winsock2.h> was included first. In httrack.c it sat ahead of the
net headers, so the x86 build redefined every sockaddr/winsock symbol
(C2011). Move it below htslib.h, where winsock2.h is already in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:52:25 +02:00
Xavier Roche
2438f55327 Log the launch banner argv without re-encoding it
The banner ran hts_convertStringSystemToUTF8() over each argv element on
Windows, which was right when argv came in the ANSI codepage. argv is
UTF-8 on every platform now, so that pass double-encoded a non-ASCII
argument into mojibake in hts-log.txt. Log the bytes as they are.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:41:10 +02:00
Xavier Roche
0e5c2d6ee9 Run the engine test suite on Windows CI
The ~90 tests only ever ran on Linux and macOS, so a Windows-only
regression was invisible until a user hit it - and the first run of the
offline ones found a third of them failing.

Drive them from Git Bash against the native MSVC httrack.exe. Two things
that silently ruin the run: MSYS rewrites any argument shaped like a POSIX
path, and a URL path is shaped exactly like one, so "-#test=mime /a/b.html"
reached the engine as "C:/Program Files/Git/a/b.html"; and a suite that
degrades to all-skipped would report green having tested nothing, hence
the floor on tests actually passed.

This subsumes the codec and cache self-tests the workflow ran inline. The
.gitattributes pins the test scripts to LF: a converting checkout rewrites
them to CRLF and bash then dies on $'\r' on every line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:24:54 +02:00
Xavier Roche
ae0298aac6 Make the engine tests pass on Windows
Three of them could never pass there, and none of the three was an engine
bug:

- rcfile wrote .httrackrc, but HTS_HTTRACKRC is "httrackrc" on Windows,
  so the engine never found it. Write both names.
- filelist made a file unreadable with chmod 000, which Windows ignores.
  Probe whether the mode is enforced instead of assuming, which also
  subsumes the root special-case.
- ftp-line was #ifndef _WIN32 for its socketpair(), though get_ftp_line()
  itself is portable. Pair over loopback TCP and register it everywhere.

The charset self-tests fed raw non-UTF-8 bytes through argv to probe the
decoders. Those bytes cannot survive any Windows command line, whatever
argv does, so take them as hex - the convention the sniff self-test
already uses - and keep the coverage on every platform.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:24:54 +02:00
Xavier Roche
8a4ca8357a Take argv as UTF-8 on Windows, not the ANSI codepage (#573)
httrack.exe is an MBCS build, so the CRT transcodes the UTF-16 command
line down to the machine's ANSI codepage before handing us char **argv.
Non-ASCII arguments are lossy, and anything outside that codepage is
destroyed: a Cyrillic or CJK URL on a Western-codepage box becomes '?'.

Everything else in the engine already treats char* as UTF-8 on Windows -
FOPEN, STAT, UNLINK are hts_*_utf8 wrappers converting to UTF-16 at the
syscall boundary. argv is the one thing that never got the memo.

Decode the real UTF-16 command line at the entry point instead. The
manifest's activeCodePage would fix it in one line, but it needs Windows
10 1903+ and is silently ignored below that, which is precisely the
population running the codepages that mangle non-Latin URLs today.

Windows-only, so no POSIX symbol and no soname change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:24:37 +02:00
Xavier Roche
1184839060 Cap the wildcard matcher's recursion depth (#574)
strjoker() recursed once per pattern segment, bounded only by the length
cap: a hostile filter of 1023 stars reached 2046 frames, ~900KB of stack.
That fits Linux's 8MB but not the 1MB a Windows thread gets, so
-#test=filterbounds died silently on MSVC x64 and Win32 instead of
rejecting the pattern it is meant to reject.

Cap the depth at 256 (real filters use fewer than ten). A cut branch does
not memoize its failure: the same pair may still match when reached at a
shallower depth.

The self-test now asserts the depth reached equals the cap, and the .test
re-runs it under a 512K stack, which segfaults without the cap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-14 18:15:26 +02:00
Xavier Roche
ee1aebd935 Harden the ProxyTrack cache readers against corrupt input (#572)
proxy/store.c carried its own copy of htscache.c's old-format (.dat/.ndx) cache readers without the bounds hardening the engine copy got in #568. cache_brstr/cache_rstr trusted a length prefix from the cache file and wrote it into fixed buffers unbounded, and cache_rint/cache_rLLint used an unchecked sscanf result as a size or offset, so a crafted cache overflowed firstline[256] and the PT_Element fields and could wrap a malloc(size+1).

Ports the engine's discipline into the proxy copy: thread the destination capacity through the readers and clamp while consuming the declared length, check every sscanf, allocate the body through a bounded helper, fix an index[index_id] off-by-one, and bound the startUrl/previous_save concatenations. Also casts the coucal void* key at the %s snprintf. The .dat/.ndx format is dead (zip only now), so this is defense-in-depth on the legacy reader; a corrupt-cache test guards it under the sanitizer CI.
2026-07-14 17:58:39 +02:00
Xavier Roche
b4957f0625 Extract the proxy tunneling code into its own module (#571)
* Extract the proxy tunneling code into htsproxy.c

htslib.c is the engine's catch-all "subroutines" file; the two proxy
tunnels living in it (HTTP CONNECT and the SOCKS5 handshake) are one
coherent thing: blocking raw-socket negotiation with the proxy, run from
the same back_wait callsite before TLS. Move both, plus proxy_getline and
the socks5 statics, into htsproxy.c/.h, which also gives the socks5_test_io
self-test seam a home away from the public-ish htslib.h.

Pure code motion: the moved bytes are identical, and hts_proxy_is_socks
stays in htslib.c next to hts_parse_proxy and jump_protocol_const, being a
URL-scheme predicate rather than handshake logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Keep the vcxproj source list alphabetical

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>
2026-07-14 16:21:51 +02:00
Xavier Roche
c9d36c82c7 Files of 2 GB or more are mishandled on Windows, and on any 32-bit build (#568)
* Report file sizes as 64-bit, so Windows stops mishandling files past 2GB

MSVC's off_t is a 32-bit long in x64 builds as well as Win32, and struct
_stat expands to _stat64i32, whose st_size is that same 32-bit long. So
fsize(), fsize_utf8() and fpsize() truncated twice over: once in the stat,
once in their own return type. A mirrored file of 2GB or more got a wrapped,
often negative size, which fed resume/206 handling, the cache body-size
reads, and readfile2()'s allocation.

Use the 64-bit _stat64 family on Windows and return LLint from the three
size helpers, widening the callers' locals to match. INTsys and HTS_FSEEKO
now cover MSVC too, so readfile2() and fpsize() reach the _fseeki64/_ftelli64
aliases htslib.h already declared. htsback.c's two 'not (off_t)' comments
described this trap but the value feeding that path was still 32-bit.

STRUCT_STAT is in an installed header, so hts_stat_utf8()'s signature changes:
a deliberate Windows-only ABI break, agreed with Xavier. The DLL ships next to
the exe with no soname contract. POSIX is untouched, where off_t is already
64-bit under LFS.

The new -#test=fsize self-test builds a 3GB sparse file and asserts both the
reported size and the 8-byte return width. Forcing the helpers back to a
32-bit return reproduces the Windows failure exactly (-1073741824) and the
test catches it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Report errno when the fsize self-test cannot create or reopen its file

The linux i386 leg fails with 'fpsize is -1', which only says the reopen
returned NULL. Print strerror(errno) on both the create and the reopen path,
and check fclose() so a failed flush stops being silent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Size files with fseeko/ftello, never ftell: ftell is 32-bit on i386

fpsize() gated fseeko/ftello behind HTS_FSEEKO, which is defined from
'#ifdef LFS_FLAG'. LFS_FLAG is a configure make variable carrying the
-D_FILE_OFFSET_BITS=64 flags, never a C macro, so that test is dead and the
fallback ftell/fseek branch is the one every autotools build compiled. ftell
returns long: 64-bit on x86-64, which hid the bug, but 32-bit on i386, where
it fails with EOVERFLOW past 2GB and fpsize() returns -1.

Call fseeko/ftello unconditionally, as htsback.c already does; MSVC gets them
via the _fseeki64/_ftelli64 aliases in htslib.h. HTS_FSEEKO had no other user.

The dead LFS_FLAG test also leaves INTsys as int on POSIX, capping readfile2()
at 2GB. Widening it changes a typedef in an installed header, so that is left
for a separate change; the FIXME records it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fix the INTsys widening's fallout, and make the fsize test bite

Widening INTsys to 64-bit on MSVC broke two call sites that assumed it was
an int, both found by an adversarial review of this branch:

- proxy/store.c's cache_rstr_addr() parsed it with sscanf("%d"), writing 4
  bytes into an 8-byte object and leaving the high word indeterminate; that
  value then sized a malloc() and an fread(). Its sibling cache_rstr() already
  used INTsysP. proxytrack.vcxproj builds this file, so it was live on MSVC.
- readfile2()/readfile_utf8() fed a now-64-bit length to malloct() and fread(),
  which take size_t: 32-bit on Win32 and i386. The two truncate independently,
  so a file of 4GiB-1 gave malloc(0) with a 4GiB read into it. Route the size
  through llint_to_size_t() and fail closed when it does not fit.

The self-test only exercised fsize_utf8(), so reverting fsize()'s _stat64 hunk
left it green, and fsize() is what sizes file:// bodies and --list. It now
checks both variants, the absent-file -1 contract, and uses 5GB rather than
3GB: a 32-bit truncation of 3GB is negative and obvious, of 5GB it is
1073741824, a plausible size. NTFS needs FSCTL_SET_SPARSE to leave the hole
unallocated; POSIX gives it for free.

Windows CI now runs the cache, cache-corrupt and fsize self-tests. The cache
fixes in #567 cannot be verified on POSIX at all, where fconv() is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Check the sscanf return in proxytrack's cache reader

cache_rstr() and cache_rstr_addr() declared an uninitialized INTsys and never
checked whether sscanf matched. On a truncated or malformed cache entry the
length stays indeterminate, and it then sizes an fread() and, in the addr
case, a malloc(). Initialize it and require a match, so a bad entry reads as
length 0.

Flagged by CodeQL (cpp/missing-check-scanf) once the widening touched that
line; the alert predates it on master, and its sibling had the same hole.

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>
2026-07-14 15:49:59 +02:00
Xavier Roche
1fa0184468 SOCKS5 proxy support (#570)
* Tunnel crawls through a SOCKS5 proxy (RFC 1928/1929)

-P socks5://[user:pass@]host[:port] now negotiates a SOCKS5 tunnel to the
origin instead of speaking HTTP to the proxy. The handshake runs on the raw
socket right after connect, so it covers plain http as well as https (TLS then
runs end-to-end over the tunnel, no CONNECT); the origin request stays
origin-form, and the proxy credentials ride the RFC 1929 sub-negotiation rather
than a Proxy-Authorization header.

The origin hostname is sent as-is (ATYP=domain): the proxy resolves it, like
curl's socks5h, which is what makes .onion and split-horizon names work and
keeps the crawl from leaking DNS. ftp:// links fail under a socks proxy rather
than silently dial the origin direct.

Reply frames are drained exactly per ATYP, since the socket is shared with the
origin stream: one byte too few and the leftovers corrupt the first TLS record.
-#test=socks5 asserts that byte count against scripted replies, plus the
truncated/hostile cases; 52_local-socks5.test crawls http and https through a
local SOCKS5 server that only answers for a .invalid name, so a locally-resolved
origin could not reach it.

Closes #563

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Don't re-run the SOCKS5 handshake on a reused keep-alive socket

back_wait re-enters the connect-completed block for a keep-alive socket
revived by back_trylive (plain http; https is covered by the ssl_con guard).
The socket is already tunneled, so re-sending the SOCKS greeting injected
\x05\x01\x00... into the established origin stream and corrupted every request
after the first. Gate the handshake on !keep_alive, which back_connxfr carries
onto the revived slot and which is zero on a fresh connect.

52_local-socks5.test now crawls a keep-alive origin (advertising max>1) with
-c1: the whole plain-http crawl rides one tunnel, exactly one handshake, every
subpage intact. Without the gate the reused stream desyncs and the crawl hangs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Use scheme-less origins in the SOCKS5 self-test (CodeQL cpp/non-https-url)

The scripted handshake strips the scheme and takes the TLS flag as a parameter,
so the "http://origin.test" fixtures were inert cleartext-URL literals that
tripped CodeQL. Pass the bare authority instead; the parsing is identical and
the real scheme handling is covered end-to-end by 52_local-socks5.test.

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>
2026-07-14 15:38:47 +02:00
Xavier Roche
79e46eeceb Store the cache self-tests' X-Save verbatim, and feed the sniff BOM as hex (#567)
The cache self-tests built the X-Save path with fconcat(), which rewrites
every '/' to '\' on Windows -- including the path_html_utf8 prefix the
reader matches on. cache_readex then took the pre-3.40 compat branch and
re-rooted the name into a path that does not exist, so -#test=cache's
disk-fallback read and -#test=cache-corrupt's headers-only and in-memory
cases all failed on MSVC builds. The engine keeps save names in '/' form and
DOS-ifies only at the syscall boundary; the self-tests now do the same.

The sniff test passed a UTF-8 BOM as raw bytes through argv, where Windows
routes it via the ANSI codepage and U+FEFF has no mapping, so it arrived as
'?'. Use the hex: body form the self-test already accepts.

Both are self-test bugs: neither path is reachable from the crawler, which
never sniffs argv and never DOS-ifies a stored save name.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 14:29:12 +02:00
Xavier Roche
436ac53793 Parse -P proxy URLs scheme-aware, recognize socks5 schemes (#566)
* Parse -P proxy URLs scheme-aware, and recognize socks5 schemes

The -P argument parser scanned backward for the port ':' from the end of the
string with no notion of a scheme, so a scheme-prefixed proxy with no explicit
port ("-P http://proxy") mistook the scheme's own colon for a port separator:
the name came out as "http" with a garbage port. A port or a "user:pass@" prefix
happened to shield the scan, so this only bit the no-port case.

Extract the parse into hts_parse_proxy(), which skips any "scheme://" before
hunting the port, so the scheme colon is never read as a port. Teach
jump_protocol_const the socks5/socks5h schemes (so the proxy host resolves with
the scheme stripped) and default the port by scheme (1080 for socks, 8080
otherwise). This is the parsing groundwork for SOCKS5 support (#563); the
handshake lands separately.

Add a -#test=proxyurl self-test and tests/01_engine-proxyurl.test covering bare
hosts, user:pass, the scheme-colon regression, and the socks defaults.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* hts_parse_proxy: scan from one-past-end, avoid forming arg-1

The backward port scan started at arg+strlen(arg)-1 and decremented while
a >= authority, so on any argument with no ':' or '@' in the authority (every
bare-host proxy, and -P "") it stepped a to arg-1: a pointer formed before the
string, which is undefined behavior. It is not a memory-safety bug (never
dereferenced, no wrap, so UBSan stays green), but it is latent UB reachable from
-P.

Scan from one-past-end and test a[-1] with a > authority instead, so no pointer
below the string is ever formed. Guard name_size == 0 against a future
small-buffer caller (the name_size - 1 truncation would otherwise underflow).

Add a userinfo-free socks explicit-port case and a lone ':' boundary case to the
self-test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* proxyurl test: percent-encoded specials and UTF-8

Pin that the host/port split is byte-transparent: a percent-encoded '@' (%40) or
':' (%3A) in the userinfo stays encoded and never mis-splits host or port, a
second raw '@' is handled by the last-'@' host rule, and UTF-8 bytes in the
username and host label pass through intact. Decoding happens later at auth time,
not in the parser. Raw UTF-8 CLI args match the existing 01_engine-idna.test.

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>
2026-07-14 14:11:57 +02:00
Xavier Roche
bc4b2c7b8e Keep the mirrored file when a coded re-fetch fails to decode (#560)
* Keep the mirrored file when a coded re-fetch fails to decode

An --update re-fetch of a content-coded URL decoded straight into url_sav: the
live mirror was truncated (filecreateempty) before the decode was even attempted,
so a truncated gzip, a corrupt br/zstd stream, a decode-size overrun or a coding
we have no decoder for destroyed the good copy we were merely refreshing. The
uncompressed path has been rollback-safe since #522, but its backup is explicitly
skipped for coded bodies, so nothing covered them.

Decode into a temporary file instead and only commit it over the mirror once the
decode succeeded; on failure url_sav is never touched. The failure path also has
to filenote() the surviving copy, or the end-of-update purge (in old.lst, absent
from new.lst) would delete the very file the decode took care not to overwrite.

Closes #557

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Never drop the decoded body when the mirror replace fails, and probe the good path

replace_file() unlinks the destination and retries when the first rename is
refused (Windows never clobbers). If that retry also fails, the previous copy is
gone, so deleting the decoded temp as well destroyed the last copy of the body:
keep it and log where it went, the way the backup restore does. That restore is
the same remove-then-rename, so it now shares the helper.

The test grew the missing positive control: freshdisk.bin decodes on both passes
on the direct-to-disk path, so the update pass renames a decoded temp over an
existing mirror file, which no case exercised. The leftover-temporaries sweep in
local-crawl.sh now covers the .z/.u decode temps for every crawl test, not just
the three names this one asserted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Restore the 0644 mode on a decoded direct-to-disk file

Committing the decode by renaming the temp bypassed filecreate(), which is what
chmods the mirrored file to HTS_ACCESS_FILE. Under a restrictive umask a coded
binary landed 0600 while every other mirrored file stayed 0644, so a mirror
served from a web root or shared with a group silently lost read access. chmod
it at the rename, as the plain direct-to-disk path already does.

The test now crawls under umask 077 and asserts the mode, via a --file-mode
assertion in local-crawl.sh; without the chmod it fails with 0600.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Pin the codec-update assertions to the URLs and to fresh content

The exact error count did not say which links failed: an engine that logged one
URL twice while another failed silently still counted three and passed. Match the
three decode errors per URL instead. The content checks only looked for the new
marker, so a fix that appended the decoded body to the old file rather than
replacing it would pass too; assert the pass-1 marker is gone. Also assert the
mode on a kept file, not just on the two rewritten ones.

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>
2026-07-14 13:10:28 +02:00
Xavier Roche
4fafe283b4 Store the .rc blobs LF-normalized, as .gitattributes declares (#561)
.gitattributes marks *.rc as "text eol=crlf": the blob is LF, the checkout is
CRLF. The two resource scripts added in #558 went in with CRLF blobs, so git
renormalizes them on every add and every checkout reports them as modified.
Renormalized with git add --renormalize; version.rc was already correct. The
working-tree files stay CRLF, which is what the Windows toolchain reads.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 12:42:19 +02:00
Xavier Roche
9ec5a3a067 Decode brotli and zstd content codings (#556)
* Decode the br and zstd content codings, and bound every decode

httrack advertised gzip and deflate only, so it took the oldest coding on
offer while every browser negotiates brotli (46% of CDN traffic) or zstd.
Worse, the response side had no codec identity at all: Content-Encoding was
collapsed into a boolean and hts_zunpack guessed the framing from the body
bytes, so a br or zstd body sent unsolicited (mis-keyed Vary caches do this)
fell through the identity path and was saved as the page, coded bytes and all.

Content-Encoding now maps to a codec (htscodec.c), and the decode dispatches on
it: brotli and zstd get streaming decoders, a known coding we cannot undo fails
the fetch instead of saving garbage, and an unrecognized token still means
identity, because servers do put charsets in that header. br and zstd are
advertised over TLS only, as browsers do.

On any decompression failure the undecoded body is now dropped rather than left
in memory for the writer to commit as the page. That closes the coded-bytes-as-
page hole for the new unsupported-coding path, and with it a latent leak on the
gzip path, where a body that failed to inflate (a truncated stream) was written
to disk verbatim.

Every decode is now bounded: 4096x the coded body, floor 1 MiB, ceiling INT_MAX.
Nothing capped the decoded size before, which was survivable while deflate was
the only codec (it cannot pass 1032x) but not with brotli and zstd, which reach
a million to one. The zlib accumulator also moves to LLint, so a body over 2 GiB
fails instead of overflowing an int.

libbrotlidec and libzstd are optional at configure time and required on Windows,
where libhttrack.dll links them from vcpkg and CI runs the self-tests to prove
the decoders are in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fix copyright year on the new codec files (2026, not 1998)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* tests: probe the direct-to-disk decode-failure discard, and assert absence

The wire test only exercised the in-memory discard path (bad.html is HTML).
Add a non-HTML body under an unsupported coding (bin.dat) so the is_write
branch is covered too, and assert both are absent from the mirror rather than
grepping a file that the fix now removes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Factor the budget-enforced write shared by the brotli and zstd decoders

The two streaming decoders duplicated the decoded-size budget check, the
security-critical part of each loop. Pull it into one codec_sink helper so the
bomb ceiling lives in a single place. Behavior-preserving; the codec self-test
(exact decoded bytes, truncation and bomb rejection) proves equivalence.

Also compile the streaming-decode body only when a streaming codec is built,
which drops an unused-variable warning in the --without-brotli --without-zstd
build.

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>
2026-07-14 08:26:20 +02:00
Xavier Roche
cd38e6e68d Merge pull request #559 from xroche/fix/robots-default-comment
Put the robots.txt "(default)" label on the option that is the default
2026-07-13 20:51:23 +02:00
Xavier Roche
e692e56592 Put the robots.txt "(default)" label on the option that is the default
The enum said HTS_ROBOTS_SOMETIMES was the default. It is not: hts_init_opt
sets opt->robots = HTS_ROBOTS_ALWAYS (htslib.c:5990), and SOMETIMES is what
the -s option selects when the user asks for it (htscoremain.c:1234).

So HTTrack obeys robots.txt fully by default, and the header said the opposite.
Anyone reading it to answer "does this crawler respect robots.txt" got the
wrong answer from the one place they would look.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-13 20:37:03 +02:00
Xavier Roche
7d9d4ed890 Give the Windows binaries a version resource (#558)
libhttrack.dll and httrack.exe have never carried a VERSIONINFO: no product
name, no version, nothing. Right-click either one on Windows and Properties
has no Details worth reading.

That becomes a hard problem now that we are signing them. SignPath enforce
file metadata restrictions: every binary in a release must name the same
product and the same product version, and a binary that names nothing cannot
satisfy the rule. It fails the signing request, on release day, rather than
the build.

So add src/version.rc, and two wrappers that set the parts which differ per
binary. The version is spelled out there as numbers, because a VERSIONINFO
needs 3,49,12,0 and cannot take a string apart, and because htsglobal.h drags
in C declarations that a resource compiler has no business parsing.

That leaves the version written twice, which is how a version resource quietly
starts lying about which release it is. tests/01_engine-version-macros.test
fails the build if version.rc and htsglobal.h ever disagree, on any of the four
values, and on the product name too. I checked that it fires rather than
assuming: drift the numbers and it fails, restore them and it passes.

The product name is HTTrack Website Copier, on all of them. WinHTTrack is the
name of the Windows GUI, not of the command line program or of the engine.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 20:20:02 +02:00
Xavier Roche
573167e8dd Port the CLI, webhttrack and proxytrack to VS2022, and retire the VS2008 project files (#555)
Follow-up to #554, which ported libhttrack. The CLI, webhttrack and proxytrack were still VS2008 .vcproj, which v143 cannot open, so WinHTTrack.sln still could not be opened as a whole. They are recreated as v143 .vcxproj (x86+x64, /MD, vcpkg, no hardcoded C:\Dev paths), and the legacy .vcproj retire along with the VC6-era .dsp/.dsw beside them. EXTRA_DIST now ships the .vcxproj and vcpkg.json, which #554 had missed, so the tarball no longer carries a project file with no manifest beside it.

Building them turned up two more bugs no Windows compiler had seen. httrack.c's main() calls WSAStartup and WSACleanup itself, so the CLI needs Ws2_32 of its own. proxytrack's getGMT reads the POSIX global timezone, which the UCRT only exposes as _timezone; the existing BSD branch grew an MSVC arm rather than switching to timegm, which would have changed the date arithmetic.

CI builds all four projects and checks the binaries land where WinHTTrack expects. It also asserts, through dumpbin, that libhttrack.dll and httrack.exe import the same vcruntime140.dll and are built for the expected machine: a CRT split across that boundary links cleanly and only crashes later, on the first cross-heap free. The assertion runs against cmd.exe first, which links the old msvcrt, so a detector that matched anything would fail the job instead of quietly blessing a mismatch. A malformed project file is now caught by an XML check in the Linux lint job rather than by a Windows runner minutes in.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 10:50:23 +02:00
Xavier Roche
e26a5f5c6a Build libhttrack on VS2022 with OpenSSL 3.x from vcpkg (#554)
The MSVC build had no CI and had rotted: libhttrack.vcproj is a VS2008 file that v143 cannot open, pinning OpenSSL 1.0.1j and zlib from hardcoded C:\Dev paths. WinHTTrack, ported to VS2022, links libhttrack.dll and takes OpenSSL and zlib through it, so the GUI could not link at all: nothing produced a modern libhttrack.lib.

Adds src/libhttrack.vcxproj (v143, x86+x64, /MD dynamic CRT, DLL) beside the legacy .vcproj, which stays until the CLI, webhttrack and proxytrack are ported. OpenSSL 3.x and zlib come from vcpkg on dynamic triplets, so an OpenSSL CVE is a DLL swap rather than a rebuild. The source list comes from libhttrack_la_SOURCES rather than the old .vcproj, which predated htssniff.c and the selftest files and pulled in httrack.c, the CLI main(). Also adds a Windows CI job, which the repo never had; that gap is how the project file drifted this far.

The C needed no OpenSSL port, but the build did surface three bugs no Windows compiler had ever seen:

- MSVC has no fseeko/ftello/timegm. They are aliased to _fseeki64/_ftelli64/_mkgmtime in htslib.h, which is not an installed header: an object-like macro of that name in an installed one would rewrite the identifier in a consumer's own declarations and C++ member names.
- Winsock's getaddrinfo/freeaddrinfo are __stdcall, while hts_resolver_backend declared its function pointers plain. That compiles on x64, which has a single calling convention, and fails on x86. All three backends now carry HTS_RESOLVER_CALL. hts_dns_set_resolver_backend is not exported (hidden visibility), so despite htsnet.h being installed this is not an ABI change and owes no soname bump.
- The --continue resume path cast a 64-bit offset to off_t, which is 32-bit on MSVC, for both the truncate and the seek. A resume past 2GB wrapped, and _chsize_s then destroyed the partial file.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:00:39 +02:00
Xavier Roche
627ae2b043 tests: assert the strjoker budget fired, not just that work stayed bounded (#553)
The filterbounds self-test added in #551 asserted `steps < 10*maxsteps`, which
catches a budget whose enforcement is dropped (the counter still climbs to
~1.26e9). But the counter and the enforcement are the same statement, so
deleting the budget line outright leaves steps at 0: `0 < 10*maxsteps` holds and
the probe passes. Only the trailing strjokerfind call then catches it, by
hanging into a harness timeout, which is exactly what #551 set out to remove.

Add the floor `steps > maxsteps`: the budget must push the counter past the cap.
Deleting the budget now aborts the test in milliseconds instead of timing out.
Verified both regressions (line deleted; enforcement dropped) trip an assertion.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 18:10:57 +02:00
Xavier Roche
71ab3574ef Remove the obsolete Java-applet parser and dead SWF module remnants (#552)
* Remove the obsolete Java-applet .class parser and dead SWF vestiges

Java applets stopped running in every mainstream browser years ago (NPAPI
dropped by Chrome in 2015 and Firefox in 2017; the JDK browser plugin gone
since JDK 11), so htsjava -- a hand-rolled parser of hostile .class bytecode
fetched off the network -- chased dependencies no live site serves while
carrying real attack surface for no functional gain. The SWF module was
already gone; only vestiges remained (a dead libhtsswf.so.1 dlopen entry and
help-text examples).

htsjava was a dlopen plugin, never linked into libhttrack, so its removal
leaves libhttrack's ABI untouched: libhttrack.so.3 is unchanged and the
libhttrack3/-dev packages just stop shipping libhtsjava.so.3*. That is a
plain file drop dpkg removes on upgrade -- no package rename, no
Replaces/Breaks, and nothing ever linked the library (it was dlopened), so
there are no reverse dependencies. The parsejava/-j option stays; it also
gates JavaScript parsing.

Drops the plugin sources, the build/config/vcproj wiring, the java self-test
and its two engine tests, and the now-unused hts_count_fits helper (whose
only caller was the plugin), and regenerates the man page. The <applet> URL
rewriting in the HTML parser and the .class codebase bookkeeping are left
intact, so applet pages are still mirrored as plain files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Scrub the removed java/swf modules from the shipped HTML docs

Follow-up to the htsjava/SWF removal: the pre-generated docs in html/ (shipped
in httrack-doc) still named the gone modules. Update the two stale --help
mirrors in httrack.man.html by hand (a full groff regen would rewrite the whole
file under a newer groff) and drop the htsjava.c plugin-example reference from
plug.html.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Finish scrubbing the java-class docs across the guides and man source

The -j option still exists (it drives JavaScript parsing), so every "parse
Java Classes" summary in the HTML guides becomes "parse scripts", and the
obsolete .class-parsing descriptions and java-applet troubleshooting entries
are reworded to drop the removed capability. The "Some java classes may not
find..." engine-limit line is dropped at its source (README, from which
makeman.sh regenerates the man LIMITS section), and httrack.1 regenerated.

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>
2026-07-12 18:09:56 +02:00
Xavier Roche
076127ddea tests: make the strjoker work-budget test deterministic, not timeout-reliant (#551)
The filterbounds self-test's step-budget case (900 stars, subject cut to 900)
stayed under the 2,000,000-step budget: the #501 failure memo alone bounded it,
so the budget never fired and the `== NULL` assertion held with or without it.
Only a harness timeout would have caught the budget's removal.

Size the star-heavy dead-end to the length cap (1023 stars, 2048-char subject),
where the memoized matcher runs ~1.26e9 steps (~6s) unbounded, and assert
through a new test-only strjoker_steps() that the work stays near the cap.
Deleting the budget now trips the assertion in milliseconds instead of timing
the suite out.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:59:38 +02:00
Xavier Roche
dced0208e1 Bound the cookie default-domain copy against a hostile over-long host (#550)
treathead seeds each Set-Cookie's default domain from the request host with a
strcpybuff into domain[256]. A 256+ byte host, reachable via a redirect
Location (sized HTS_URLMAXSIZE), overflows the fail-safe copy and aborts the
whole mirror. Drop such a Set-Cookie instead, matching the existing too-long
handling of the explicit domain= attribute.

Adds a cookies self-test driving treathead with a 599-byte host (aborts
without the guard) plus a normal-host control.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:21:21 +02:00
Xavier Roche
6e34c30a7c Prune signals that can't fire from the webhttrack and engine cleanup handlers (#549)
* Prune signals that can't meaningfully fire from the cleanup handlers

Follow-up to #547. webhttrack's trap listed the whole CPU/program-fault
class (ILL TRAP ABRT BUS FPE SEGV, plus the now-removed STKFLT) alongside
ALRM/XCPU/XFSZ. None of these do anything useful in a shell wrapper: bash
doesn't raise them, and a crash in the htsserver child delivers SIGCHLD to
the parent, not the fault signal. Keep the signals that actually ask the
wrapper to quit and warrant tearing the server down: HUP INT QUIT PIPE TERM.
All POSIX, so no more macOS/BSD trap-install breakage.

In the engine, drop the SIGSTKFLT crash handler for the same reason: Linux
marks SIGSTKFLT unused (legacy x87 coprocessor fault) and never raises it;
a genuine stack overflow arrives as SIGSEGV, which sig_fatal already
catches. The SEGV/BUS/ILL/ABRT handlers stay -- those are the real crash
reporter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Drop the leftover SIGSTKFLT comment

A comment documenting a signal we deliberately don't handle is noise.

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>
2026-07-12 14:37:52 +02:00
Xavier Roche
eac13ea248 webhttrack: let a configured browser win over Darwin's open -W (#546)
The Darwin block pre-set BROWSEREXE="/usr/bin/open -W" before the browser
search loop, whose per-step "test -n $BROWSEREXE && break" then exited on the
first iteration. The search was dead on macOS: $BROWSER and any installed
browser were ignored and webhttrack always handed the URL to open -W, which
opens the system default and blocks until the whole app quits.

Keep open -W only as a fallback after the search, so a configured browser wins.

Closes #544

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:55:40 +02:00
Xavier Roche
dc3e485f92 webhttrack: drop Linux-only SIGSTKFLT from the cleanup trap (#547)
SIGSTKFLT does not exist on macOS/BSD, so bash rejects the trap command there
and the signal cleanup may fail to install. Interrupting webhttrack (Ctrl-C)
could then leave htsserver running and the temp server file behind. Drop
STKFLT; the remaining signals are all POSIX.

Closes #545

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:54:42 +02:00
Xavier Roche
fac84f1631 ci: harden the webhttrack smoke test and drop its leaky watchdog (#548)
Follow-up to #540. A narrow-charter review found two issues in the smoke test
as merged:

- The content check matched only the HTTrack brand string, which is a literal
  in every page header, so a truncated or degraded template served 200 would
  still pass. Also require the step-2 form action, which such a page lacks.
- The hard watchdog subshell was un-redirected, so its orphaned sleep kept the
  CI step stdout open ~35s after every run. The poll loop and teardown already
  bound the run, so drop the watchdog and SIGKILL webhttrack if it ignores TERM.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:52:31 +02:00
Xavier Roche
c32d286938 ci: smoke-test webhttrack on macOS (#540)
* ci: smoke-test webhttrack on macOS

Nothing exercised the WebHTTrack GUI launcher or its htsserver backend, and
webhttrack has a Darwin-only browser path (open -W). Install into a temp prefix
on a macOS runner and assert webhttrack brings up htsserver and serves the UI
(fetched via a stub browser that shadows the first name in webhttrack's list).

The UI is served ISO-8859-1, so the content check uses grep -a.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* ci: reap htsserver in the webhttrack smoke test

webhttrack backgrounds htsserver and leaves it running; killing only webhttrack
orphaned the server, which held the macOS CI step open until timeout. Kill
htsserver explicitly (scoped to the test prefix) in teardown and on exit, and
dump webhttrack.log so a real failure is diagnosable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* ci: bound and instrument the webhttrack smoke test

The smoke step hung on the macOS runner. Add a hard watchdog (macOS has no
timeout(1)), drop the blocking wait for a bounded liveness poll, and log each
step plus webhttrack.log so a macOS-specific stall is visible instead of
running to the job timeout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* ci: route the webhttrack smoke past macOS open -W

On Darwin webhttrack hardcodes the browser to "open -W", which launches a real
GUI browser and blocks headless (the macOS smoke stalled here). Shadow uname so
webhttrack takes the generic path and uses the stub browser; htsserver and
webhttrack path resolution still run for real on macOS.

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>
2026-07-12 13:18:06 +02:00
Xavier Roche
5a7ab7a653 ci: alias 127.0.0.2/.3 on macOS lo0 for the connect-fallback test (#543)
macOS configures only 127.0.0.1 as loopback, so 19_local-connect-fallback's
dead 127.0.0.2/.3 addresses have no fast-refuse path: the connect intermittently
stalls to the timeout and logs "Connect Time Out", which the zero-errors
assertion rejects (flaked ~1/6 on the macOS runner). #531's --timeout/--retries
bound the stall but not the spurious error. Aliasing the addresses onto lo0
makes them refuse instantly like Linux, so the fallback runs cleanly.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 13:00:41 +02:00
Xavier Roche
5756a9830c Bound strjoker recursion depth and work to stop OSS-Fuzz stack-overflow and timeout (#542)
The wildcard filter matcher recurses one frame per star group, so a 16000-star
pattern overflows the call stack. And strjokerfind re-runs the polynomial
matcher at every subject position, so a star-heavy pattern against a long
subject runs for minutes. The #501 failure memo made a single match polynomial
but bounds neither recursion depth nor the outer sweep.

Cap matcher input at HTS_URLMAXSIZE*2 (the length the engine already bounds
URLs to), which holds recursion depth far below the overflow point and leaves
real crawls unaffected. Add a work budget that strjokerfind shares across its
whole scan so the per-position cost no longer multiplies. Both limits fail the
match safely once exceeded. A filterbounds self-test covers each and fails
without the caps.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:59:19 +02:00
Xavier Roche
28c11d55eb Fix one-byte fil[] overflow in ident_url_absolute on hostless ?-URLs (#541)
OSS-Fuzz caught an abort in ident_url_absolute: strncat_safe_'s bounds
check fired on a one-byte overflow of the 2048-byte fil[] path buffer.
URLs of 2048 bytes or more are rejected up front, so the path suffix is
at most 2047 bytes; but when it has no leading slash, one is prepended
before the copy, pushing the result to exactly 2048. A hostless URL
starting with '?' and 2047 bytes long reaches that copy before the
empty-host check at the end rejects it. Subtract the possible slash from
the limit and return -1 for those inputs, leaving valid URLs untouched.

The identabs self-test feeds the 2047-byte trigger and asserts a clean
-1, plus two valid URLs still parse.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:59:05 +02:00
Xavier Roche
b5f28f6eb5 Un-isolate bigcrawl on macOS and bump it to -c16 (#539)
PR #538's larger listen backlog fixed the real cause of the macOS bigcrawl
file-count flake: Python's default 5-slot accept queue overflowed under parallel
CPU contention, and macOS drops SYNs on overflow. With that gone, macOS no
longer needs the second-pass "run bigcrawl alone" workaround, so it runs the
full suite in parallel like Linux.

The crawl can also push more connections now: -c8 to -c16 shaves ~14% off its
wall time (33.4s to 28.6s locally; -%c100 caps the rest) and stresses the engine
harder. 128 backlog slots leave ample room for 16 connections.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:24:42 +02:00
Xavier Roche
3d65bb2e3b Raise the test server's listen backlog so -c8 bigcrawl stops flaking on macOS (#538)
local-server.py used ThreadingHTTPServer's default request_queue_size of 5.
macOS/BSD drop SYNs once the accept queue overflows; Linux is lenient. Under a
parallel make check the single-threaded accept loop is starved for CPU, drains
the 5-deep queue slowly, and an -c8 bigcrawl burst overflows it. The
36_local-bigcrawl file count then flaked (e.g. 361 -> 283) because --retries=0
turns each dropped connection into a lost file. Raise the backlog to 128 for
ample headroom; -c8 is unchanged.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:38:20 +02:00
Xavier Roche
68897b09ce Re-fetch a self-redirect cookie wall instead of dropping it (#537)
* Re-fetch a self-redirect cookie wall instead of dropping it (#15)

A page that 302-redirects to itself only to set a cookie (a consent or session
"cookie wall") was lost: the self-redirect guard treated the loop as a crazy
server and never re-issued the request, so the real content behind the cookie
was never fetched.

The redirect's Set-Cookie is already folded into the shared jar before the
guard runs, so a re-issue would carry it. In the delayed-type loop
(hts_wait_delayed) snapshot the jar before the request; when a self-redirect
changed it, evict the cached fast-header for that URL and re-fetch once with the
new cookie. Termination is bounded: the jar stops changing once the cookie is
satisfied (real walls resolve in two requests), and the existing loops<7 cap
backstops a server that mints a fresh cookie on every hit.

Under the default HARD delayed-type mode every URL routes through this loop, so
both unknown-ext (wall.php) and known-ext (wall.html) walls are covered. A wall
reached only under -%N0/1 still takes the hts_mirror_check_moved path, which
cannot see the Set-Cookie without an ABI change; left as a known limitation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Test the cookie-wall give-up and retry-cap paths (#15)

The happy-path test only drove a wall that sets its cookie on the first hit, so
an engine that dropped the jar-changed gate and retried every self-redirect would
still pass. Add two cases that pin the gate and the bound:

- cookiewall3: a self-redirect that sets no cookie must give up at once (asserts
  the "loop to same filename" guard fires and the page is not mirrored). A
  dropped gate would retry and skip that log line.
- cookiewall4: a self-redirect that mints a fresh cookie every hit must stop at
  the loops<7 cap, not spin (asserts the crawl terminates, takes the retry path,
  and does not mirror the wall).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Scope the cookie-wall retry to this URL's own cookies (#15)

The retry trigger compared the whole shared cookie jar before and after the
request. Because the jar is one process-wide store mutated by every in-flight
fetch, a concurrent slot's Set-Cookie landing mid-loop could flip the compare
and force a needless (though bounded) re-fetch of an unrelated self-redirect.

Compare instead the Cookie header THIS url would send, built for its own
host/path. A Set-Cookie from another host no longer trips the retry; a change
in this URL's own outgoing cookies (its self-redirect setting one) still does,
which is exactly the wall we want to re-fetch. No jar-wide snapshot, and the
same natural termination: once the cookie stabilizes the header stops changing.

Reuses append_cookie_header via http_cookie_header (renamed from the
_selftest-suffixed wrapper, now that engine code calls it too).

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>
2026-07-12 09:44:40 +02:00
Xavier Roche
e8d1bf9c19 Bound the in-memory receive buffer against a hostile Content-Length (#536)
* Bound the in-memory receive buffer against a hostile Content-Length

http_xfread1 buffers a non-disk (hypertext) response whole in RAM, sized
straight from the server's Content-Length, or grown without limit for a
chunked/unknown-length stream. The reads there already assume the buffer fits a
32-bit index (the "no 4GB html" comment and the int offsets), but nothing
enforced it: a hostile text/html response with a multi-GB Content-Length drove
an unbounded allocation on the first fetch. Reject an in-memory size that would
exceed INT32_MAX and return a clean error. A resource httrack holds whole in RAM
is hypertext and small; binary content (video, archives, ...) streams to disk
and never reaches this branch, so nothing legitimate is refused.

This is the initial-fetch companion to #535, which bounded the same allocation
on the 206-resume path. Self-test -#test=xfread-limit + 01_engine-xfread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Reject exactly INT32_MAX and cover the accept path

An adversarial review of the in-memory bound found an off-by-one: the guard used
`> INT32_MAX`, but at `size + bufl == INT32_MAX` the unknown-length reallocs
compute `(int) size + bufl + 1 == INT32_MAX + 1`, a signed-overflow UB (it fails
safe because the negative width becomes a huge size_t and realloc returns NULL,
but it still aborts under UBSan). Use `>= INT32_MAX`.

The self-test gains a boundary case (that exact size must be refused, the teeth
for the off-by-one) and an accept case (a normal small size must not be), so a
stray `>` or a refuse-everything guard both fail it.

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>
2026-07-11 23:21:50 +02:00
Xavier Roche
ef40277c24 Bound the in-memory 206-resume allocation to a 32-bit size (#535)
CodeQL flagged the memory-branch resume buffer as an uncontrolled allocation
size: malloct(resume + 1 + totalsize) takes an attacker-controlled
Content-Length. #534 guarded the int64 add against overflow but left the
allocation itself unbounded, and the (size_t) cast still truncated on a 32-bit
build. Cap the buffer at INT32_MAX -- a resource held whole in RAM is far
smaller -- and route an over-large or overflowing size to the existing
drop-and-refetch path. Test 48_local-crange-memresume already exercises that
path (its INT64_MAX Content-Length now trips the tighter bound).

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 21:03:07 +02:00
Xavier Roche
797acecfd2 Harden Content-Range integer handling against hostile and oversize values (#534)
* Harden Content-Range integer handling against hostile and oversize values

Two integer-safety defects in the Content-Range / size path, both reachable
from a hostile or buggy server and abort-worthy under the project's UBSan/
OSS-Fuzz CI (a sanitizer abort is a real bug, not flakiness).

C2 (signed-overflow UB). The signed int64 crange fields are parsed straight
from the header with no validation, so a crafted Content-Range drives the
crange +/- 1 range checks past the int64 extremes. Clamp any negative field to
the all-zero "no range" sentinel at parse time (this covers the parse-side
crange-1 fallback and the 200->206 fake-range check), and rewrite the 206
resume check crange_end + 1 == crange as crange_end == crange - 1, which is
guarded by the preceding crange > 0 so an INT64_MAX end cannot overflow.

C4 (off_t truncated to int). The sizehack read the on-disk size into a 32-bit
int, truncating it for files >= 2 GiB, so a same-size match could force a 304
that records the wrong size (cache/disk metadata desync, never a truncated
file). Widen to off_t like every sibling call site.

Tests: -#test=crange drives the parser (UBSan aborts pre-fix on the INT64_MIN
fallback); 47_local-crange-overflow drives the 206-resume path with an
INT64_MAX Content-Range (UBSan aborts pre-fix at the crange+1 check).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Also guard the in-memory 206-resume buffer size against a hostile Content-Length

An adversarial review of the crange hardening surfaced a third overflow in the
same 206-resume path: the in-memory branch sizes its buffer as
`resume + 1 + totalsize`, and `totalsize` (Content-Length) is attacker-
controlled and unclamped. A resume answered with a 206 that lies `text/html`
(so the resume buffers in memory) and a matching INT64_MAX Content-Length drives
the add to INT64_MAX + 1 — a signed-overflow UBSan abort, reachable from a single
hostile response. Reject a Content-Length that would overflow the size, drop the
partial, and refetch from scratch (the retry restarts clean).

Test 48_local-crange-memresume drives it end to end (UBSan aborts pre-fix at
`alloc_mem += totalsize`); 01_engine-crange gains a `-5-10/20` case exercising
the clamp's independent per-field negativity.

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>
2026-07-11 20:04:47 +02:00
Xavier Roche
8237c3ed2b Don't finalize a resume's partial as complete on an out-of-protocol 304 (#533)
* Don't finalize a resume's partial as complete on an out-of-protocol 304

A resume request carries Range plus If-Unmodified-Since/If-Match, to which a
conformant server answers 206/200/412, never 304. The 304 handler's
"if-unmodified-since hack" re-accepted any existing on-disk file and recorded
it complete at its current byte count, so a broken or hostile 304 finalized
the partial as the whole file. In the repro the partial was never committed,
leaving the mirror claiming a complete file that is absent.

Capture whether the server itself sent the 304 (the size-match hacks force
NOT_MODIFIED only after confirming completeness). When it did on a Range
resume, drop the partial and its temp-ref and refetch, mirroring the
unusable-206 restart. Non-resume validations (range_req_size == 0) and the
416/sizehack completions are unaffected.

Test 46_local-update-304-resume drives it end to end: a stalled first fetch
leaves a partial + temp-ref, the resume's Range gets a bogus 304, and httrack
must recover the whole file rather than trust the 304.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Test 46: assert the resume's Range actually drew the 304

The size and request-count checks alone could pass a wrong-fix that silently
switched to a fresh re-crawl instead of handling the 304 on the resume path.
Mark when the server 304s a Range request and assert it fired, so recovery is
proven to go through the resume rather than around it.

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>
2026-07-11 18:27:57 +02:00
Xavier Roche
75b815e1fb tests: drop GNU-only uname -o so make check is quiet on macOS (#532)
The crawl harness built the test user-agent with uname -omrs. BSD/macOS
uname has no -o flag, so every crawl printed "uname: illegal option -- o"
to stderr and emitted an empty OS field. -s already names the kernel
(Linux/Darwin), making -o redundant; drop it for a portable uname -mrs.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:57:34 +02:00
Xavier Roche
8d02e60907 Bound test 19's dead-address connects so it is fast on macOS (#531)
19_local-connect-fallback pins dead loopback IPs (127.0.0.2/.3) before the live
one to exercise address fallback. On Linux those refuse instantly (all of
127.0.0.0/8 is loopback); macOS has only 127.0.0.1, so the connects block to
--timeout, and default retries replay the whole crawl, so the exhaustion case
took ~2 minutes. It passed but dominated the macOS suite once the rest runs in
parallel. Drop --timeout to 5s (bounds the last candidate's stall; the earlier
ones already fall back at min(timeout,10)) and set --retries=0 (no ×3 replay).
Linux is unchanged: the refused connects never reach the timeout.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:18:18 +02:00
Xavier Roche
e70607e70c Run the test suite in parallel in CI (make check -j) (#528)
* Run the test suite in parallel in CI (make check -j)

The suite already builds under automake's parallel test harness (no
serial-tests), but every make check in CI ran it serially, so the ~90 tests
executed one at a time. Each crawl test spawns its own Python server on an
ephemeral port into a private mktemp dir, so nothing is shared across tests and
-j never contends on a port or a fixture; the only cross-test file
(check-network_sh.cache) is idempotent and gated behind the online tests.

Pass -j (nproc, or hw.ncpu on macOS) to every make check. Measured 3:26 -> 0:37
on a 12-core box, stable across runs including 2x oversubscription. Past a few
cores the wall time floors on the single longest test (bigcrawl ~24s), so this
is the whole win short of trimming those long poles.

The Debian build already parallelizes its packaged test pass: debhelper's
dh_auto_test emits `make -jN check` from DEB_BUILD_OPTIONS parallel=N, which the
CI deb job sets to nproc and Debian buildds set from their job count. Nothing to
change in debian/rules (Policy requires honoring DEB_BUILD_OPTIONS, not
hard-coding -j).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* AGENTS.md: note new tests auto-spread across -j workers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Oversubscribe make check to 2x cores (capped 16) in CI

The suite is sleep-heavy: bigcrawl self-paces over ~365 files, resume-overlap
polls and waits, and the trickle/delayed tests sleep server-side. On a
core-constrained runner those idle cores go to waste at -j=cores. Running twice
as many test jobs as cores lets a ready test use a core while another sleeps.

Measured on a 3-core cpu-set (the macOS/Linux runner regime): -j3 79.5s, -j6
(2x) 43.1s (1.85x), then diminishing returns to the ~34s wall-clock floor set
by the longest single test (-j9 39.9s, -j16 37.3s). Stable across repeats, all
tests pass. The cap keeps big machines from spawning 100+ server+httrack pairs
for no gain once floor-bound.

Only the make check steps change; the build stays at -j nproc (CPU-bound, where
oversubscription only hurts), and the Debian job keeps parallel=nproc since that
knob drives both its build and test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Keep macOS at one test job per core (loopback drops under 2x)

2x oversubscription overloads macOS's loopback: six httrack processes each at
-c8 push ~48 concurrent connections, the stack drops a couple, and
36_local-bigcrawl under-fetches (expected 361 files, got 359). This is the same
macOS-only flake #527 fixed for the error count, now surfaced on the exact file
count. Linux tolerates 2x (all Linux jobs green, stable in local sim); macOS
does not, so pin it to one job per core.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Run macOS test suite serially (parallel flakes bigcrawl on loopback)

Parallel make check on macOS runs 36_local-bigcrawl's -c8 crawl alongside the
other crawl tests; the aggregate load overloads macOS's loopback, which drops
fetches, so bigcrawl under-fetches and its exact file count fails (saw 359 and
341 vs 361, at both 2x and one-per-core). This is the macOS-only loopback drop
#527 handled for the error count, now on the file count. Linux does not exhibit
it. Pin macOS to a serial run until bigcrawl tolerates transient drops.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Isolate bigcrawl in a second pass on macOS to parallelize the rest

macOS was the CI bottleneck (~460s serial, the ~8min total): its loopback drops
fetches when bigcrawl's sustained -c8 crawl competes with other crawls, so a
parallel make check flaked bigcrawl's exact file count. Rather than keep the
whole macOS suite serial, run everything except bigcrawl in parallel (2x cores,
capped 16), then bigcrawl alone in a second pass, which is the serial-safe
condition green on master. Linux still runs the full suite in parallel.

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>
2026-07-11 17:18:04 +02:00
Xavier Roche
cada6a1ae3 Meter -M against received volume, not saved bytes (#530)
* Meter -M against received volume, not saved bytes (#520)

-M ("maximum overall size that can be uploaded/scanned") enforced its
cap against HTS_STAT.stat_bytes, which counts only saved HTTP 200 bodies
(minus robots.txt). On crawls dominated by redirects, error bodies, or
plugin-written files the counter lags the volume actually pulled off the
network, so the cap barely bit — #77's reporter saw ~12.5 MB on disk
while "Bytes saved" hovered near a 3 MB cap.

Enforce against HTS_STAT.HTS_TOTAL_RECV instead: the true received
volume, incremented on every recv()/fread and monotonic, matching the
option's "scanned" wording. The #519 smooth-stop/grace/hard-abort
machinery is unchanged; only the metered quantity moves, and it stays
monotonic so the sticky size-branch invariant still holds. Received is
credited live per-chunk rather than at transfer completion, so the cap
also bounds overshoot slightly tighter.

Test 45_local-maxsize-recv drives a fixture of large 404 bodies
(received but never saved): the cap fires though only the index lands on
disk. It fails against master (stat_bytes never reaches the cap, all
links fetched) and passes with the fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Test 45: bound saved bytes so the cap-meter differential is pinned

The single --log-found assertion tripped on "some counter crossed the
cap"; its master-fails differential silently depended on 404 bodies not
being saved. Add --max-mirror-bytes 100000 (observed 573 B) so the test
asserts the cap fired while almost nothing landed on disk, which only the
received-volume meter can do. A build that saved the 404 bodies would
push stat_bytes past the cap and the mirror past the bound, failing here.

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>
2026-07-11 17:17:30 +02:00
107 changed files with 5293 additions and 6164 deletions

8
.gitattributes vendored Normal file
View File

@@ -0,0 +1,8 @@
# Resource scripts are Windows tooling input and must stay CRLF: the engine has no
# encoding guard, so an autocrlf checkout could otherwise flatten them silently.
*.rc text eol=crlf
# The test scripts run under Git Bash on the Windows CI; a CRLF checkout makes
# bash die on $'\r' on every line of them.
*.test text eol=lf
*.sh text eol=lf

View File

@@ -41,7 +41,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential clang autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: Configure
run: |
@@ -50,12 +50,17 @@ jobs:
# committed generated files already let a plain checkout build.
autoreconf -fi
./configure
# a missing decoder would silently drop the coding from Accept-Encoding
grep -q "define HTS_USEBROTLI 1" config.h
grep -q "define HTS_USEZSTD 1" config.h
- name: Build
run: make -j"$(nproc)"
- name: Test
run: make check
run: |
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
@@ -80,7 +85,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: Configure
run: |
@@ -99,7 +104,8 @@ jobs:
sudo find /usr/bin /usr/local/bin -maxdepth 1 -name 'python3*' \
-exec mv {} {}.hidden \;
! command -v python3
make check
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
@@ -120,25 +126,70 @@ jobs:
- name: Install build dependencies
run: |
set -euo pipefail
brew install autoconf automake libtool autoconf-archive
brew install autoconf automake libtool autoconf-archive brotli zstd
- name: Configure
run: |
set -euo pipefail
ssl="$(brew --prefix openssl@3)"
brewp="$(brew --prefix)"
autoreconf -fi
./configure CPPFLAGS="-I${ssl}/include" LDFLAGS="-L${ssl}/lib"
./configure CPPFLAGS="-I${ssl}/include -I${brewp}/include" \
LDFLAGS="-L${ssl}/lib -L${brewp}/lib"
grep -q "define HTS_USEBROTLI 1" config.h
grep -q "define HTS_USEZSTD 1" config.h
- name: Build
run: make -j"$(sysctl -n hw.ncpu)"
- name: Add loopback aliases (macOS lacks 127.0.0.2/.3)
# 19_local-connect-fallback needs the dead 127.0.0.2/.3 to refuse
# instantly like Linux; alias them onto lo0 so they don't stall to timeout.
run: |
set -euo pipefail
sudo ifconfig lo0 alias 127.0.0.2 up
sudo ifconfig lo0 alias 127.0.0.3 up
- name: Test
run: make check
run: |
jobs=$(( $(sysctl -n hw.ncpu) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
run: cat tests/test-suite.log 2>/dev/null || true
# Runtime smoke of the WebHTTrack launcher on macOS: it carries a Darwin-only
# browser path (open -W) and nothing else exercises htsserver. Install into a
# temp prefix, then check webhttrack brings up htsserver and serves the UI.
webhttrack-macos:
name: webhttrack smoke (macOS arm64)
runs-on: macos-14
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install build dependencies
run: |
set -euo pipefail
brew install autoconf automake libtool autoconf-archive brotli zstd
- name: Build and install into a temp prefix
run: |
set -euo pipefail
ssl="$(brew --prefix openssl@3)"
brewp="$(brew --prefix)"
autoreconf -fi
./configure CPPFLAGS="-I${ssl}/include -I${brewp}/include" \
LDFLAGS="-L${ssl}/lib -L${brewp}/lib" \
--prefix="$RUNNER_TEMP/inst"
make -j"$(sysctl -n hw.ncpu)"
make install
- name: Smoke-test webhttrack
run: bash tests/webhttrack-smoke.sh "$RUNNER_TEMP/inst"
# Portability/hardening: 32-bit (i386) build on the x86-64 runner via multilib
# -- no extra hardware. Exercises the 32-bit size_t/pointer ABI, where size
# and bounds math can truncate or wrap in ways 64-bit never reveals (the axis
@@ -159,7 +210,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential gcc-multilib autoconf automake libtool \
autoconf-archive zlib1g-dev:i386 libssl-dev:i386
autoconf-archive zlib1g-dev:i386 libssl-dev:i386 \
libbrotli-dev:i386 libzstd-dev:i386
- name: Configure
run: |
@@ -171,7 +223,9 @@ jobs:
run: make -j"$(nproc)"
- name: Test
run: make check
run: |
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
@@ -198,7 +252,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: Configure (sanitized)
run: |
@@ -226,7 +280,9 @@ jobs:
env:
ASAN_OPTIONS: detect_leaks=0:abort_on_error=1:halt_on_error=1:strict_string_checks=1:malloc_fill_byte=202:max_malloc_fill_size=2147483647:free_fill_byte=203:max_free_fill_size=2147483647
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: make check
run: |
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
@@ -272,7 +328,8 @@ jobs:
# 01_engine-* only; zlib-dependent self-tests are named 01_zlib-* and
# skipped here (uninstrumented libz floods MSan with false positives).
tests="$(cd tests && ls 01_engine-*.test | tr '\n' ' ')"
make check TESTS="$tests"
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs" TESTS="$tests"
- name: Print the test log on failure
if: failure()
@@ -297,7 +354,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential clang autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: Configure (fuzzers, static)
run: |
@@ -347,7 +404,9 @@ jobs:
run: make -j"$(nproc)"
- name: Test
run: make check
run: |
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
@@ -375,7 +434,7 @@ jobs:
apt-get install -y --no-install-recommends \
ca-certificates git \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev \
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev \
debhelper devscripts lintian fakeroot
- uses: actions/checkout@v6
@@ -419,7 +478,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: distcheck
run: |
@@ -495,6 +554,16 @@ jobs:
- name: shfmt
run: shfmt -d -i 4 $SHELL_SCRIPTS
# MSBuild rejects a malformed .vcxproj with a bare MSB4025 and no build, so
# catch it here in seconds rather than on a Windows runner minutes in.
- name: XML well-formedness (MSBuild project files)
run: |
set -euo pipefail
for f in src/*.vcxproj; do
python3 -c "import sys,xml.dom.minidom; xml.dom.minidom.parse(sys.argv[1])" "$f"
echo "ok $f"
done
# Check clang-format on CHANGED LINES ONLY. The engine predates clang-format
# (it was shaped by an old Visual Studio formatter) and does not round-trip,
# so we never reformat the whole tree -- only the lines a PR touches.

184
.github/workflows/windows-build.yml vendored Normal file
View File

@@ -0,0 +1,184 @@
# Windows build of the engine (VS2022 v143 + vcpkg): libhttrack, the CLI,
# webhttrack, proxytrack.
#
# The autotools CI covers the unix builds; this covers the MSVC one, which had
# no coverage at all and had drifted (the old .vcproj pinned OpenSSL 1.0.1j).
# libhttrack.dll is what the WinHTTrack GUI links against.
name: windows-build
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
libhttrack:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
platform: [x64, Win32]
configuration: [Release]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive # coucal lives in src/coucal
# Located through vswhere rather than microsoft/setup-msbuild: the repo
# only allows GitHub-owned actions.
- name: Find MSBuild
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild `
-find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1
if (-not $msbuild) { throw "MSBuild not found" }
Write-Host "MSBuild: $msbuild"
"MSBUILD=$msbuild" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Enable vcpkg MSBuild integration
shell: pwsh
run: vcpkg integrate install
# httrack and webhttrack carry a ProjectReference to libhttrack, so building
# them builds it first; proxytrack is standalone.
- name: Build
shell: pwsh
run: |
foreach ($proj in @("libhttrack", "httrack", "webhttrack", "proxytrack")) {
& $env:MSBUILD "src\$proj.vcxproj" `
/m `
/p:Configuration=${{ matrix.configuration }} `
/p:Platform=${{ matrix.platform }} `
/p:VcpkgEnableManifest=true `
/flp:LogFile=msbuild-$proj.log`;Verbosity=normal
if ($LASTEXITCODE -ne 0) { throw "$proj failed" }
}
# WinHTTrack links src\$(Platform)\$(Configuration)\libhttrack.lib, so the
# import lib and the DLL both have to land there under that exact name.
- name: Check the binaries landed where they are expected
shell: pwsh
run: |
$dir = "src\${{ matrix.platform }}\${{ matrix.configuration }}"
$want = @("libhttrack.dll", "libhttrack.lib", "httrack.exe",
"webhttrack.exe", "proxytrack.exe")
foreach ($f in $want) {
if (-not (Test-Path "$dir\$f")) { throw "missing $dir\$f" }
Write-Host "found $dir\$f"
}
# A CRT split across the exe/DLL boundary links fine and crashes on the
# first cross-heap free, so assert both sides import the *same* vcruntime.
# Also assert the machine type: Test-Path alone would accept an x64 binary
# sitting in the Win32 output dir.
- name: Check the CRT is shared across the boundary
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$dir = "src\${{ matrix.platform }}\${{ matrix.configuration }}"
$dumpbin = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" `
-latest -find VC\Tools\MSVC\**\bin\Host*\*\dumpbin.exe | Select-Object -First 1
if (-not $dumpbin) { throw "dumpbin not found" }
$wantMachine = if ("${{ matrix.platform }}" -eq "x64") { "x64" } else { "x86" }
function Get-Crt($path) {
$out = & $dumpbin /dependents /headers $path
if ($LASTEXITCODE -ne 0) { throw "dumpbin failed on $path" }
,@($out | Select-String -Pattern '^\s*(vcruntime\d+d?\.dll)$' |
ForEach-Object { $_.Matches[0].Groups[1].Value.ToLower() })
}
# Negative control: cmd.exe links the old msvcrt, not vcruntime140. If
# this came back non-empty the detector would match anything, and the
# assertion below would pass on a mismatched CRT too.
if ((Get-Crt "$env:SystemRoot\System32\cmd.exe").Count -ne 0) {
throw "negative control failed: the vcruntime detector matches anything"
}
$crts = @{}
foreach ($f in @("libhttrack.dll", "httrack.exe")) {
$crt = Get-Crt "$dir\$f"
if ($crt.Count -eq 0) { throw "$f imports no vcruntime: static or mismatched CRT" }
$crts[$f] = $crt
$hdr = & $dumpbin /headers "$dir\$f"
if (-not ($hdr | Select-String -SimpleMatch "machine ($wantMachine)")) {
throw "$f is not $wantMachine"
}
Write-Host "$f -> $($crt -join ',') ($wantMachine)"
}
$a = $crts["libhttrack.dll"] | Sort-Object
$b = $crts["httrack.exe"] | Sort-Object
if (Compare-Object $a $b) {
throw "CRT mismatch across the boundary: libhttrack.dll=$a httrack.exe=$b"
}
# The engine ships ~90 tests and none had ever run on Windows: "make check"
# is Linux/macOS only. These are the offline ones, driven from Git Bash
# against the native httrack.exe. They subsume the self-tests this step
# used to run inline (codecs, cache, fsize).
- name: Run the engine test suite (offline tests)
shell: bash
working-directory: tests
run: |
set -u
bin="$(cygpath -u "$GITHUB_WORKSPACE")/src/${{ matrix.platform }}/${{ matrix.configuration }}"
export PATH="$bin:$PATH"
command -v httrack >/dev/null || { echo "::error::no httrack.exe in $bin"; exit 1; }
# httrack.exe is native, so MSYS rewrites any argument shaped like a
# POSIX path, and a URL path is shaped exactly like one: "/a/b.html"
# reached the engine as "C:/Program Files/Git/a/b.html". Switch that
# off, and hand the tests a TMPDIR that is already a Windows path.
export MSYS_NO_PATHCONV=1
export MSYS2_ARG_CONV_EXCL='*'
TMPDIR="$(cygpath -m "$RUNNER_TEMP")"
export TMPDIR
pass=0 fail=0 skip=0 failed=""
for t in 00_runnable.test 01_engine-*.test 01_zlib-*.test; do
rc=0
bash "$t" >"$t.log" 2>&1 || rc=$?
case "$rc" in
0) pass=$((pass + 1)); echo "PASS $t" ;;
77) skip=$((skip + 1)); echo "SKIP $t" ;;
*)
fail=$((fail + 1)) failed="$failed $t"
echo "FAIL $t (exit $rc)"
# These assert with `test "$(...)" == "..." || exit 1`, which
# says nothing at all on failure. Re-run traced.
bash -x "$t" >>"$t.log" 2>&1 || true
tail -n 25 "$t.log" | sed 's/^/ /'
;;
esac
done
echo "ran=$((pass + fail + skip)) pass=$pass fail=$fail skip=$skip" |
tee -a "$GITHUB_STEP_SUMMARY"
# Every gate in these scripts exits 77, so a suite that degraded to
# all-skipped would report green having tested nothing: assert a floor
# on what actually ran, not just the absence of failures.
[ "$pass" -ge 45 ] || { echo "::error::only $pass tests passed ($skip skipped)"; exit 1; }
[ "$fail" -eq 0 ] || { echo "::error::failing:$failed"; exit 1; }
- name: Upload the test logs
if: always()
uses: actions/upload-artifact@v4
with:
name: engine-tests-${{ matrix.platform }}-${{ matrix.configuration }}
path: tests/*.log
if-no-files-found: warn
- name: Upload MSBuild logs
if: always()
uses: actions/upload-artifact@v4
with:
name: msbuild-${{ matrix.platform }}-${{ matrix.configuration }}
path: msbuild-*.log
if-no-files-found: ignore

View File

@@ -6,8 +6,19 @@ the operational checklist: toolchain, invariants, and how to ship a change.
## Build & test
- Fresh clone first: `git submodule update --init src/coucal`
- `./bootstrap` (regenerates `configure` via `autoreconf`; needs autoconf,
automake, libtool), then `bash configure && make && make check`. Or run
`sh build.sh` to do bootstrap + configure + make in one shot.
automake, libtool), then `bash configure && make -j"$(nproc)" && make check
-j"$(nproc)"`. Always pass `-j` to `make check`: the suite runs under
automake's parallel harness and each crawl test binds its own ephemeral-port
server, so `-j` never contends and a multi-minute serial run drops to
seconds. A new `.test` added to `$(TESTS)` is scheduled onto a free worker
automatically; only a test slower than the current longest raises the floor.
On a few-core Linux box, `-j` at 2x the core count is faster still: the tests
spend much of their wall time asleep (server trickles, httrack self-pacing),
so an idle core covers a sleeping one. CI uses `min(2*cores, 16)` on every
platform, macOS included: the test server raises its listen backlog
(`request_queue_size`) so macOS/BSD don't drop connections under a parallel
`-c16` bigcrawl the way Python's default backlog of 5 did.
Or run `sh build.sh` to do bootstrap + configure + make in one shot.
## Hard invariants
- **Generated autotools files are NOT in git.** `configure`, every

1
CLAUDE.local.md Symbolic link
View File

@@ -0,0 +1 @@
/home/roche/git/httrack-works/CLAUDE.httrack.local.md

1
README
View File

@@ -40,7 +40,6 @@ These are the principals limits of HTTrack for that moment. Note that we did not
that would have solved them.
- Several scripts generating complex filenames may not find them (ex: img.src='image'+a+Mobj.dst+'.gif')
- Some java classes may not find some files on them (class included)
- Cgi-bin links may not work properly in some cases (parameters needed). To avoid them: use filters like -*cgi-bin*

View File

@@ -64,16 +64,6 @@ AC_SUBST(LT_CV_OBJDIR,$lt_cv_objdir)
# Export version info
AC_SUBST(VERSION_INFO)
# Versioned plugin name for dlopen() in hts_create_opt(); soname major is
# libtool's current - age, so this tracks VERSION_INFO bumps automatically.
HTS_SONAME_MAJOR=$((${VERSION_INFO%%:*} - ${VERSION_INFO##*:}))
case "$host_os" in
darwin*) HTS_LIBHTSJAVA_NAME="libhtsjava.$HTS_SONAME_MAJOR.dylib" ;;
*) HTS_LIBHTSJAVA_NAME="libhtsjava.so.$HTS_SONAME_MAJOR" ;;
esac
AC_DEFINE_UNQUOTED([HTS_LIBHTSJAVA_NAME], ["$HTS_LIBHTSJAVA_NAME"],
[Versioned libhtsjava runtime name, derived from VERSION_INFO])
### Default CFLAGS
DEFAULT_CFLAGS="-Wall -Wformat -Wformat-security \
-Wmultichar -Wwrite-strings -Wcast-qual -Wcast-align \
@@ -119,8 +109,8 @@ case "$CFLAGS" in
esac
# Force libc back into DT_NEEDED for libraries that reach it only through
# libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style
# linker; Apple ld rejects these flags and links libSystem unconditionally.
# libhttrack (the libtest callbacks), but only with a GNU-style linker; Apple
# ld rejects these flags and links libSystem unconditionally.
AX_CHECK_LINK_FLAG([-Wl,--push-state,--no-as-needed,-lc,--pop-state],
[LIBC_FORCE_LINK="-Wl,--push-state,--no-as-needed,-lc,--pop-state"])
AC_SUBST([LIBC_FORCE_LINK])
@@ -183,6 +173,10 @@ AC_CHECK_HEADERS([execinfo.h])
### zlib
CHECK_ZLIB()
### brotli and zstd content codings (optional)
CHECK_BROTLI()
CHECK_ZSTD()
### OpenSSL is explicitly enabled/disabled ?
AC_MSG_CHECKING(whether to enable https support)
AC_ARG_ENABLE([https],

2
debian/control vendored
View File

@@ -2,7 +2,7 @@ Source: httrack
Section: web
Maintainer: Xavier Roche <roche@httrack.com>
Standards-Version: 4.7.4
Build-Depends: debhelper-compat (= 14), autoconf, autoconf-archive, automake, libtool, zlib1g-dev, libssl-dev
Build-Depends: debhelper-compat (= 14), autoconf, autoconf-archive, automake, libtool, zlib1g-dev, libssl-dev, libbrotli-dev, libzstd-dev
Rules-Requires-Root: no
Homepage: http://www.httrack.com
Vcs-Git: https://github.com/xroche/httrack.git

5
debian/copyright vendored
View File

@@ -7,9 +7,8 @@ Files: *
Copyright: 1998-2026 Xavier Roche and other contributors
License: GPL-3+
Comment:
The engine includes contributions from Yann Philippot (src/htsjava.c,
src/htsjava.h). htsbasenet.h links against the system OpenSSL library
(originally by Eric Young); no OpenSSL/SSLeay code is bundled here.
htsbasenet.h links against the system OpenSSL library (originally by Eric
Young); no OpenSSL/SSLeay code is bundled here.
Files: src/minizip/*
Copyright: 1998-2010 Gilles Vollant

View File

@@ -1,5 +1,4 @@
usr/include/httrack
usr/lib/*/libhttrack.{so}
usr/lib/*/libhtsjava.{so}
usr/share/httrack/libtest
usr/lib/*/httrack/libtest

View File

@@ -1,3 +1,2 @@
usr/lib/*/libhttrack.so.3*
usr/lib/*/libhtsjava.so.3*
usr/share/httrack/templates

View File

@@ -609,10 +609,9 @@ most mirrors do work. We still are working to improve the mirror quality of HTTr
<br>
</a><a NAME="Q2b">Q: <strong>Some Java applets do not work properly!</strong><br>
A: <em>Java applets may not work in some cases, for example if HTTrack failed to detect all included classes
or files called within the class file. Sometimes, Java applets need to be online, because remote files are
directly caught. Finally, the site structure can be incompatible with the class (always try to keep the original site structure
when you want to get Java classes)<br>
A: <em>Java applets may not work in some cases, because HTTrack does not parse compiled class files to find
the resources they load. Java applets often need to be online anyway, since remote files are fetched directly
at runtime.<br>
If there is no way to make some classes work properly, you can exclude them with the filters.
They will be available, but only online.
</em>

View File

@@ -243,7 +243,7 @@ Build options:
Spider options:
bN accept cookies in cookies.txt (0=do not accept,* 1=accept) (--cookies[=N])
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (--check-type[=N])
j *parse Java Classes (j0 don't parse) (--parse-java[=N])
j *parse scripts (j0 don't parse) (--parse-java[=N])
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always) (--robots[=N])
%h force HTTP/1.0 requests (reduce update features, only for old servers or proxies) (--http-10)
%B tolerant requests (accept bogus responses on some servers, but not standard!) (--tolerant)
@@ -417,7 +417,7 @@ site. Specifically, the defauls are:
X *purge old files after update (X0 keep delete)
bN accept cookies in cookies.txt (0=do not accept,* 1=accept)
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)
j *parse Java Classes (j0 don't parse)
j *parse scripts (j0 don't parse)
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always)
C create/use a cache for updates and retries (C0 no cache,C1 cache is prioritary,* C2 test update before)
f *log file mode
@@ -497,9 +497,9 @@ about your browser retaining them.
<p align=justify> This causes different document types to be analyzed
differently.
<pre><b><i> j *parse Java Classes (j0 don't parse) </i></b></pre>
<pre><b><i> j *parse scripts (j0 don't parse) </i></b></pre>
<p align=justify> This causes Java class files to be parsed looking for
<p align=justify> This causes scripts to be parsed looking for
URLs.
<pre><b><i> sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always) </i></b></pre>
@@ -1178,7 +1178,7 @@ accepted and sent back in order to allow access.
Spider options:
bN accept cookies in cookies.txt (0=do not accept,* 1=accept)
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)
j *parse Java Classes (j0 don't parse)
j *parse scripts (j0 don't parse)
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always)
%h force HTTP/1.0 requests (reduce update features, only for old servers or proxies)
%B tolerant requests (accept bogus responses on some servers, but not standard!)
@@ -2451,12 +2451,10 @@ work. We still are working to improve the mirror quality of HTTrack.
<p align=justify><b>Q: Some Java applets do not work properly! </b>
<p align=justify> A: Java applets may not work in some cases, for
example if HTTrack failed to detect all included classes or files called
within the class file. Sometimes, Java applets need to be online,
because remote files are directly caught. Finally, the site structure
can be incompatible with the class (always try to keep the original site
structure when you want to get Java classes)
<p align=justify> A: Java applets may not work in some cases, because
HTTrack does not parse compiled class files to find the resources they
load. Java applets often need to be online anyway, since remote files
are fetched directly at runtime.
<p align=justify> If there is no way to make some classes work properly,
you can exclude them with the filters. They will be available, but only

View File

@@ -764,9 +764,9 @@ check, * u1 check but /, u2 check always)
<td width="78%">
<p>*parse Java Classes (j0 don t parse, bitmask: |1 parse
default, |2 don t parse .class |4 don t parse .js |8 don t
be aggressive) (--parse-java[=N])</p></td></tr>
<p>*parse scripts (j0 don t parse, bitmask: |1 parse
default, |4 don t parse .js |8 don t be aggressive)
(--parse-java[=N])</p></td></tr>
<tr valign="top" align="left">
<td width="11%"></td>
<td width="6%">
@@ -889,8 +889,8 @@ only) (--protocol[=N])</p></td></tr>
<td width="78%">
<p>disable a specific external mime module (-%w htsswf -%w
htsjava) (--disable-module &lt;param&gt;)</p></td></tr>
<p>disable a specific external mime module (-%w
httrack-plugin) (--disable-module &lt;param&gt;)</p></td></tr>
</table>
<p style="margin-left:11%; margin-top: 1em"><b>Browser
@@ -2175,10 +2175,6 @@ Several scripts generating complex filenames may not find
them (ex:
img.src=&rsquo;image&rsquo;+a+Mobj.dst+&rsquo;.gif&rsquo;)</small></p>
<p style="margin-left:11%; margin-top: 1em"><small>- Some
java classes may not find some files on them (class
included)</small></p>
<p style="margin-left:11%; margin-top: 1em"><small>-
Cgi-bin links may not work properly in some cases
(parameters needed). To avoid them: use filters like

View File

@@ -288,8 +288,8 @@ This option define whether the engine has to abandon a host if a timeout/"too sl
&P extended parsing, attempt to parse all links (even in unknown tags or Javascript)
This option activates the extended parsing, that attempt to find links in unknown Html code/javascript
j *parse Java Classes (j0 don't parse)
This option define whether the engine has to parse java files or not to catch included files
j *parse scripts (j0 don't parse)
This option defines whether the engine has to parse scripts or not to catch included files
I *make an index (I0 don't make)
This option define whether the engine has to generate an index.html on the top directory

View File

@@ -103,220 +103,219 @@ h4 { margin: 0; font-weight: bold; font-size: 1.18em; }
<br>
You can write external functions to be plugged in the httrack library very easily.
We'll see there some examples.
You can write external functions to be plugged in the httrack library very easily.
We'll see there some examples.
<br><br>
The <tt>httrack</tt> commandline tool allows (since the 3.30 release) to plug external functions to various callbacks defined in httrack.
The 3.41 release introduces a cleaned up verion of callbacks, with two major changes:
<ul>
<li>Cleaned up function prototypes, with two arguments always passed (the caller carg structure, and the httrackp* object), convenient to pass an user-defined pointer (see <tt>CALLBACKARG_USERDEF(carg)</tt>)</li>
<li>The httrackp* option structure can be directly accessed to plug callbacks (no need to give the callback name and function name in the commandline!)</li>
<li>The callback plug is made through the CHAIN_FUNCTION() helper, allowing to chain multiple callbacks of the same type (the callbacks MUST preserve the chain by calling ancestors)</li>
</ul>
<br>
References:
<ul>
<li>the <tt>httrack-library.h</tt> prototype file
<br />
The <tt>httrack</tt> commandline tool allows (since the 3.30 release) to plug external functions to various callbacks defined in httrack.
The 3.41 release introduces a cleaned up verion of callbacks, with two major changes:
<ul>
<li>Cleaned up function prototypes, with two arguments always passed (the caller carg structure, and the httrackp* object), convenient to pass an user-defined pointer (see <tt>CALLBACKARG_USERDEF(carg)</tt>)</li>
<li>The httrackp* option structure can be directly accessed to plug callbacks (no need to give the callback name and function name in the commandline!)</li>
<li>The callback plug is made through the CHAIN_FUNCTION() helper, allowing to chain multiple callbacks of the same type (the callbacks MUST preserve the chain by calling ancestors)</li>
</ul>
<br>
References:
<ul>
<li>the <tt>httrack-library.h</tt> prototype file
<br />
Note: the <i>Initialization</i>, <i>Main functions</i>, <i>Options handling</i> and <i>Wrapper functions</i> sections are generally the only ones to be considered.
</li>
<li>the <tt>htsdefines.h</tt> prototype file, which describes callback function prototypes</li>
<li>the <tt>htsopt.h</tt> prototype file, which describes the full httrackp* structure</li>
<li>the <tt>callbacks-example*.c</tt> files given in the httrack archive</li>
<li>the <tt>htsjava.c</tt> source file (the java class plugin ; overrides 'detect' and 'parse')</li>
<li>the example given at the end of this document</li>
</ul>
<br />
Below the list of functions to be defined in the module (plugin).<br />
<br />
<table width="100%">
<tr><td><b><i>module function name</i></b></td><td><b>function description</b></td><td><b>function signature</b></td></tr>
<tr><td background="img/fade.gif"><i>hts_plug</i></td><td background="img/fade.gif">
The module entry point. The opt structure can be used to plug callbacks, using the CHAIN_FUNCTION() macro helper. The argv optional argument is the one passed in the commandline as --wrapper parameter.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)<br />
<br />
Wrappers can be plugged inside hts_plug() using:<br />
<tt>
CHAIN_FUNCTION(opt, &lt;callback name&gt;, &lt;our callback function name&gt;, &lt;our callback function optional custom pointer argument&gt;);
</tt>
<br />
<br />
Example:
<br />
<tt>
CHAIN_FUNCTION(opt, check_html, process, userdef);
</tt>
<br />
</td><td background="img/fade.gif"><tt>extern int hts_plug(httrackp *opt, const char* argv);</tt></td></tr>
<!-- -->
<tr><td background="img/fade.gif"><i>hts_unplug</i></td><td background="img/fade.gif">
The module exit point. To free allocated resources without using global variables, use the uninit callback (see below)</td><td background="img/fade.gif"><tt>extern int hts_unplug(httrackp *opt);</tt></td></tr>
</table>
<br />
Note that all callbacks (except init and uninit) take as first two argument:
<ul>
<li>the t_hts_callbackarg structure<br />
this structure holds the callback chain (parent callbacks defined before the current callback) pointers, and the user-defined pointer ; see <tt>CALLBACKARG_USERDEF(carg)</tt>)
</li>
<li>the httrackp structure<br />
this structure, holding all current httrack options and mirror state, can be read or mofidied
</li>
</ul>
<br />
Below the list of callbacks, and associated external wrappers.
<table width="100%">
<tr><td><b><i>callback name</i></b></td><td><b>callback description</b></td><td><b>callback function signature</b></td></tr>
<tr><td background="img/fade.gif"><i>init</i></td><td background="img/fade.gif">Note: the use the "start" callback is advised. Called during initialization.<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
<tr><td background="img/fade.gif"><i>uninit</i></td><td background="img/fade.gif">Note: the use os the "end" callback is advised.<br />Called during un-initialization<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
<tr><td background="img/fade.gif"><i>start</i></td><td background="img/fade.gif">Called when the mirror starts. The <tt>opt</tt> structure passed lists all options defined for this mirror. You may modify the <tt>opt</tt> structure to fit your needs.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>end</i></td><td background="img/fade.gif">Called when the mirror ends<br>return value: 1 upon success, 0 upon error (the mirror will then be considered aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>chopt</i></td><td background="img/fade.gif">Called when options are to be changed. The <tt>opt</tt> structure passed lists all options, updated to take account of recent changes<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>preprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is to be parsed (original, not yet modified document). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>postprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is parsed and transformed (links rewritten). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_html</i></td><td background="img/fade.gif">Called when a document (which may not be an html document) is to be parsed. The <tt>html</tt> address points to the document data, of lenth <tt>len</tt>. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the parsing can be processed, 0 if the file must be skipped without being parsed</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* html, int len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question. The <tt>question</tt> string contains the question for the (human) user<br>return value: the string answer ("" for default reply)</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query2</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query3</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>loop</i></td><td background="img/fade.gif">Called periodically (informational, to display statistics)<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back, int back_max, int back_index, int lien_tot, int lien_ntot, int stat_time, hts_stat_struct* stats);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_link</i></td><td background="img/fade.gif">Called when a link has to be tested. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, int status);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_mime</i></td><td background="img/fade.gif">Called when a link download has begun, and needs to be tested against its MIME type. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested, and the <tt>mime</tt> string contains the link type being processed. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, const char* mime, int status);</tt></td></tr>
<tr><td background="img/fade.gif"><i>pause</i></td><td background="img/fade.gif">Called when the engine must pause. When the <tt>lockfile</tt> passed is deleted, the function can return<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* lockfile);</tt></td></tr>
<tr><td background="img/fade.gif"><i>filesave</i></td><td background="img/fade.gif">Called when a file is to be saved on disk<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>filesave2</i></td><td background="img/fade.gif">Called when a file is to be saved or checked on disk<br>The hostname, filename and local filename are given. Two additional flags tells if the local file is new (is_new), if the local file is to be modified (is_modified), and if the file was not updated remotely (not_updated).<br>(!is_new && !is_modified): the file is up-to-date, and will not be modified<br>(is_new && is_modified): a new file will be written (or an updated file is being written)<br>(!is_new && is_modified): a file is being updated (append)<br>(is_new && !is_modified): an empty file will be written ("do not recatch locally erased files")<br>not_updated: the file was not re-downloaded because it was up-to-date (no data transferred again)<br><br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* hostname, const char* filename, const char* localfile, int is_new, int is_modified, int not_updated);</tt></td></tr>
<tr><td background="img/fade.gif"><i>linkdetected</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link);</tt></td></tr>
<tr><td background="img/fade.gif"><i>linkdetected2</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link, const const char* tag_start);</tt></td></tr>
<tr><td background="img/fade.gif"><i>xfrstatus</i></td><td background="img/fade.gif">Called when a file has been processed (downloaded, updated, or error)<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back);</tt></td></tr>
<tr><td background="img/fade.gif"><i>savename</i></td><td background="img/fade.gif">Called when a local filename has to be processed. The <tt>adr_complete</tt> and <tt>fil_complete</tt> are the address and URI of the file being saved ; the <tt>referer_adr</tt> and <tt>referer_fil</tt> are the address and URI of the referer link. The <tt>save</tt> string contains the local filename being used. You may modifiy the <tt>save</tt> string to fit your needs, up to 1024 bytes (note: filename collisions, if any, will be handled by the engine by renaming the file into file-2.ext, file-3.ext ..).<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr_complete, const char* fil_complete, const char* referer_adr, const char* referer_fil, char* save);</tt></td></tr>
<tr><td background="img/fade.gif"><i>sendhead</i></td><td background="img/fade.gif">Called when HTTP headers are to be sent to the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>outgoing</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* outgoing);</tt></td></tr>
<tr><td background="img/fade.gif"><i>receivehead</i></td><td background="img/fade.gif">Called when HTTP headers are recevived from the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>incoming</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* incoming);</tt></td></tr>
<tr><td background="img/fade.gif"><i>detect</i></td><td background="img/fade.gif">Called when an unknown document is to be parsed. The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the type is known and can be parsed, 0 if the document type is unknown</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
<tr><td background="img/fade.gif"><i>parse</i></td><td background="img/fade.gif">The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the document was successfully parsed, 0 if an error occurred</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
</table>
<li>the <tt>htsdefines.h</tt> prototype file, which describes callback function prototypes</li>
<li>the <tt>htsopt.h</tt> prototype file, which describes the full httrackp* structure</li>
<li>the <tt>callbacks-example*.c</tt> files given in the httrack archive</li>
<li>the example given at the end of this document</li>
</ul>
<br />
Below the list of functions to be defined in the module (plugin).<br />
<br />
<table width="100%">
<tr><td><b><i>module function name</i></b></td><td><b>function description</b></td><td><b>function signature</b></td></tr>
<tr><td background="img/fade.gif"><i>hts_plug</i></td><td background="img/fade.gif">
The module entry point. The opt structure can be used to plug callbacks, using the CHAIN_FUNCTION() macro helper. The argv optional argument is the one passed in the commandline as --wrapper parameter.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)<br />
<br />
Wrappers can be plugged inside hts_plug() using:<br />
<tt>
CHAIN_FUNCTION(opt, &lt;callback name&gt;, &lt;our callback function name&gt;, &lt;our callback function optional custom pointer argument&gt;);
</tt>
<br />
<br />
Example:
<br />
<tt>
CHAIN_FUNCTION(opt, check_html, process, userdef);
</tt>
<br />
</td><td background="img/fade.gif"><tt>extern int hts_plug(httrackp *opt, const char* argv);</tt></td></tr>
<!-- -->
<tr><td background="img/fade.gif"><i>hts_unplug</i></td><td background="img/fade.gif">
The module exit point. To free allocated resources without using global variables, use the uninit callback (see below)</td><td background="img/fade.gif"><tt>extern int hts_unplug(httrackp *opt);</tt></td></tr>
</table>
<br />
Note that all callbacks (except init and uninit) take as first two argument:
<ul>
<li>the t_hts_callbackarg structure<br />
this structure holds the callback chain (parent callbacks defined before the current callback) pointers, and the user-defined pointer ; see <tt>CALLBACKARG_USERDEF(carg)</tt>)
</li>
<li>the httrackp structure<br />
this structure, holding all current httrack options and mirror state, can be read or mofidied
</li>
</ul>
<br />
Below the list of callbacks, and associated external wrappers.
<table width="100%">
<tr><td><b><i>callback name</i></b></td><td><b>callback description</b></td><td><b>callback function signature</b></td></tr>
<tr><td background="img/fade.gif"><i>init</i></td><td background="img/fade.gif">Note: the use the "start" callback is advised. Called during initialization.<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
<tr><td background="img/fade.gif"><i>uninit</i></td><td background="img/fade.gif">Note: the use os the "end" callback is advised.<br />Called during un-initialization<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
<tr><td background="img/fade.gif"><i>start</i></td><td background="img/fade.gif">Called when the mirror starts. The <tt>opt</tt> structure passed lists all options defined for this mirror. You may modify the <tt>opt</tt> structure to fit your needs.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>end</i></td><td background="img/fade.gif">Called when the mirror ends<br>return value: 1 upon success, 0 upon error (the mirror will then be considered aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>chopt</i></td><td background="img/fade.gif">Called when options are to be changed. The <tt>opt</tt> structure passed lists all options, updated to take account of recent changes<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
<tr><td background="img/fade.gif"><i>preprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is to be parsed (original, not yet modified document). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>postprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is parsed and transformed (links rewritten). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_html</i></td><td background="img/fade.gif">Called when a document (which may not be an html document) is to be parsed. The <tt>html</tt> address points to the document data, of lenth <tt>len</tt>. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the parsing can be processed, 0 if the file must be skipped without being parsed</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* html, int len, const char* url_address, const char* url_file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question. The <tt>question</tt> string contains the question for the (human) user<br>return value: the string answer ("" for default reply)</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query2</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>query3</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
<tr><td background="img/fade.gif"><i>loop</i></td><td background="img/fade.gif">Called periodically (informational, to display statistics)<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back, int back_max, int back_index, int lien_tot, int lien_ntot, int stat_time, hts_stat_struct* stats);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_link</i></td><td background="img/fade.gif">Called when a link has to be tested. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, int status);</tt></td></tr>
<tr><td background="img/fade.gif"><i>check_mime</i></td><td background="img/fade.gif">Called when a link download has begun, and needs to be tested against its MIME type. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested, and the <tt>mime</tt> string contains the link type being processed. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, const char* mime, int status);</tt></td></tr>
<tr><td background="img/fade.gif"><i>pause</i></td><td background="img/fade.gif">Called when the engine must pause. When the <tt>lockfile</tt> passed is deleted, the function can return<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* lockfile);</tt></td></tr>
<tr><td background="img/fade.gif"><i>filesave</i></td><td background="img/fade.gif">Called when a file is to be saved on disk<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* file);</tt></td></tr>
<tr><td background="img/fade.gif"><i>filesave2</i></td><td background="img/fade.gif">Called when a file is to be saved or checked on disk<br>The hostname, filename and local filename are given. Two additional flags tells if the local file is new (is_new), if the local file is to be modified (is_modified), and if the file was not updated remotely (not_updated).<br>(!is_new && !is_modified): the file is up-to-date, and will not be modified<br>(is_new && is_modified): a new file will be written (or an updated file is being written)<br>(!is_new && is_modified): a file is being updated (append)<br>(is_new && !is_modified): an empty file will be written ("do not recatch locally erased files")<br>not_updated: the file was not re-downloaded because it was up-to-date (no data transferred again)<br><br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* hostname, const char* filename, const char* localfile, int is_new, int is_modified, int not_updated);</tt></td></tr>
<tr><td background="img/fade.gif"><i>linkdetected</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link);</tt></td></tr>
<tr><td background="img/fade.gif"><i>linkdetected2</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link, const const char* tag_start);</tt></td></tr>
<tr><td background="img/fade.gif"><i>xfrstatus</i></td><td background="img/fade.gif">Called when a file has been processed (downloaded, updated, or error)<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back);</tt></td></tr>
<tr><td background="img/fade.gif"><i>savename</i></td><td background="img/fade.gif">Called when a local filename has to be processed. The <tt>adr_complete</tt> and <tt>fil_complete</tt> are the address and URI of the file being saved ; the <tt>referer_adr</tt> and <tt>referer_fil</tt> are the address and URI of the referer link. The <tt>save</tt> string contains the local filename being used. You may modifiy the <tt>save</tt> string to fit your needs, up to 1024 bytes (note: filename collisions, if any, will be handled by the engine by renaming the file into file-2.ext, file-3.ext ..).<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr_complete, const char* fil_complete, const char* referer_adr, const char* referer_fil, char* save);</tt></td></tr>
<tr><td background="img/fade.gif"><i>sendhead</i></td><td background="img/fade.gif">Called when HTTP headers are to be sent to the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>outgoing</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* outgoing);</tt></td></tr>
<tr><td background="img/fade.gif"><i>receivehead</i></td><td background="img/fade.gif">Called when HTTP headers are recevived from the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>incoming</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* incoming);</tt></td></tr>
<tr><td background="img/fade.gif"><i>detect</i></td><td background="img/fade.gif">Called when an unknown document is to be parsed. The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the type is known and can be parsed, 0 if the document type is unknown</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
<tr><td background="img/fade.gif"><i>parse</i></td><td background="img/fade.gif">The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the document was successfully parsed, 0 if an error occurred</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
</table>
<br><br>
Note: the optional libhttrack-plugin module (libhttrack-plugin.dll or libhttrack-plugin.so), if found in the library environment, is loaded automatically, and its <tt>hts_plug()</tt> function being called.<br />
Note: the optional libhttrack-plugin module (libhttrack-plugin.dll or libhttrack-plugin.so), if found in the library environment, is loaded automatically, and its <tt>hts_plug()</tt> function being called.<br />
<br />
An example is generally more efficient than anything else, so let's write our first module, aimed to stupidely print all parsed html files:
<table width="100%" border="2">
<tr><td>
<pre>
/* system includes */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
/* standard httrack module includes */
#include "httrack-library.h"
#include "htsopt.h"
#include "htsdefines.h"
/* local function called as "check_html" callback */
static int process_file(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt,
/*other parameters are callback-specific*/
char* html, int len, const char* url_address, const char* url_file) {
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
if (CALLBACKARG_PREV_FUN(carg, check_html) != NULL) {
if (!CALLBACKARG_PREV_FUN(carg, check_html)(CALLBACKARG_PREV_CARG(carg), opt,
html, len, url_address, url_file)) {
return 0; /* abort */
}
}
printf("file %s%s content: %s\n", url_address, url_file, html);
return 1; /* success */
}
/* local function called as "end" callback */
static int end_of_mirror(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt) {
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
/* processing */
fprintf(stderr, "That's all, folks!\n");
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
if (CALLBACKARG_PREV_FUN(carg, end) != NULL) {
/* status is ok on our side, return other callabck's status */
return CALLBACKARG_PREV_FUN(carg, end)(CALLBACKARG_PREV_CARG(carg), opt);
}
return 1; /* success */
}
/*
module entry point
the function name and prototype MUST match this prototype
*/
EXTERNAL_FUNCTION int hts_plug(httrackp *opt, const char* argv) {
/* optional argument passed in the commandline we won't be using here */
const char *arg = strchr(argv, ',');
if (arg != NULL)
arg++;
/* plug callback functions */
CHAIN_FUNCTION(opt, check_html, process_file, /*optional user-defined arg*/NULL);
CHAIN_FUNCTION(opt, end, end_of_mirror, /*optional user-defined arg*/NULL);
return 1; /* success */
}
/*
module exit point
the function name and prototype MUST match this prototype
*/
EXTERNAL_FUNCTION int hts_unplug(httrackp *opt) {
fprintf(stder, "Module unplugged");
return 1; /* success */
}
</pre>
</td></tr></table>
<br />
Compile this file ; for example:
<br />
<tt>
gcc -O -g3 -shared -o mylibrary.so myexample.c
</tt>
<br />
and plug the module using the commandline ; for example:
<br />
<tt>
httrack --wrapper mylibrary http://www.example.com
</tt>
<br />
or, if some parameters are desired:
<br />
<tt>
httrack --wrapper mylibrary,myparameter-string http://www.example.com
</tt>
<br />
(the "myparameter-string" string will be available in the 'arg' parameter passed to the hts_plug entry point)
<br />
<br />
An example is generally more efficient than anything else, so let's write our first module, aimed to stupidely print all parsed html files:
<table width="100%" border="2">
<tr><td>
<pre>
/* system includes */
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
/* standard httrack module includes */
#include "httrack-library.h"
#include "htsopt.h"
#include "htsdefines.h"
/* local function called as "check_html" callback */
static int process_file(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt,
/*other parameters are callback-specific*/
char* html, int len, const char* url_address, const char* url_file) {
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
if (CALLBACKARG_PREV_FUN(carg, check_html) != NULL) {
if (!CALLBACKARG_PREV_FUN(carg, check_html)(CALLBACKARG_PREV_CARG(carg), opt,
html, len, url_address, url_file)) {
return 0; /* abort */
}
}
printf("file %s%s content: %s\n", url_address, url_file, html);
return 1; /* success */
}
/* local function called as "end" callback */
static int end_of_mirror(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt) {
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
/* processing */
fprintf(stderr, "That's all, folks!\n");
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
if (CALLBACKARG_PREV_FUN(carg, end) != NULL) {
/* status is ok on our side, return other callabck's status */
return CALLBACKARG_PREV_FUN(carg, end)(CALLBACKARG_PREV_CARG(carg), opt);
}
return 1; /* success */
}
/*
module entry point
the function name and prototype MUST match this prototype
*/
EXTERNAL_FUNCTION int hts_plug(httrackp *opt, const char* argv) {
/* optional argument passed in the commandline we won't be using here */
const char *arg = strchr(argv, ',');
if (arg != NULL)
arg++;
/* plug callback functions */
CHAIN_FUNCTION(opt, check_html, process_file, /*optional user-defined arg*/NULL);
CHAIN_FUNCTION(opt, end, end_of_mirror, /*optional user-defined arg*/NULL);
return 1; /* success */
}
/*
module exit point
the function name and prototype MUST match this prototype
*/
EXTERNAL_FUNCTION int hts_unplug(httrackp *opt) {
fprintf(stder, "Module unplugged");
return 1; /* success */
}
</pre>
</td></tr></table>
<br />
Compile this file ; for example:
<br />
<tt>
gcc -O -g3 -shared -o mylibrary.so myexample.c
</tt>
<br />
and plug the module using the commandline ; for example:
<br />
<tt>
httrack --wrapper mylibrary http://www.example.com
</tt>
<br />
or, if some parameters are desired:
<br />
<tt>
httrack --wrapper mylibrary,myparameter-string http://www.example.com
</tt>
<br />
(the "myparameter-string" string will be available in the 'arg' parameter passed to the hts_plug entry point)
<br />
<br><br>
<!-- ==================== Start epilogue ==================== -->

View File

@@ -118,8 +118,8 @@ h4 { margin: 0; font-weight: bold; font-size: 1.18em; }
<br>Avoid "never", because the local mirror could be bogus
</small><br><br>
<!-- -->
<li>Parse java files</li>
<br><small>Must the engine parse .java files (java classes) to seek included filenames?
<li>Parse scripts</li>
<br><small>Must the engine parse scripts to seek included filenames?
<br>It is checked by default
</small><br><br>
<!-- -->

66
m4/check_codecs.m4 Normal file
View File

@@ -0,0 +1,66 @@
dnl @synopsis CHECK_BROTLI()
dnl @synopsis CHECK_ZSTD()
dnl
dnl Look for libbrotlidec and libzstd, the decoders for the "br" and "zstd"
dnl HTTP content codings. Both are optional: a missing library only drops the
dnl coding from Accept-Encoding. --with-brotli=DIR / --with-zstd=DIR point at a
dnl prefix; --without-brotli / --without-zstd disable the coding; the default
dnl is to use the library when it is found.
dnl
dnl Define HTS_USEBROTLI / HTS_USEZSTD to 1 or 0, and substitute BROTLI_LIBS /
dnl ZSTD_LIBS.
dnl CHECK_CODEC(name, NAME, header, library, symbol, companion-libs)
AC_DEFUN([CHECK_CODEC], [
AC_ARG_WITH([$1],
[AS_HELP_STRING([--with-$1@<:@=DIR@:>@],[Enable the $1 content coding @<:@default=auto@:>@])],
[codec_want=$withval], [codec_want=auto])
$2_LIBS=""
codec_have=no
if test "$codec_want" != "no"; then
codec_old_cppflags="$CPPFLAGS"
codec_old_ldflags="$LDFLAGS"
codec_dir=no
if test "$codec_want" != "yes" -a "$codec_want" != "auto"; then
codec_dir=yes
# An explicit prefix is authoritative: if the header is not under
# it, error rather than silently pick a system copy.
if test ! -f "$codec_want/include/$3"; then
AC_MSG_ERROR([$1 requested at $codec_want but $codec_want/include/$3 is missing])
fi
CPPFLAGS="$CPPFLAGS -I$codec_want/include"
LDFLAGS="$LDFLAGS -L$codec_want/lib"
fi
AC_CHECK_HEADER([$3], [codec_h=yes], [codec_h=no])
AC_CHECK_LIB([$4], [$5], [codec_lib=yes], [codec_lib=no], [$6])
CPPFLAGS="$codec_old_cppflags"
LDFLAGS="$codec_old_ldflags"
if test "$codec_h" = "yes" -a "$codec_lib" = "yes"; then
codec_have=yes
$2_LIBS="-l$4 $6"
if test "$codec_dir" = "yes"; then
$2_LIBS="-L$codec_want/lib -l$4 $6"
# The rest of configure still needs the header path.
CPPFLAGS="$CPPFLAGS -I$codec_want/include"
fi
elif test "$codec_want" != "auto"; then
AC_MSG_ERROR([$1 requested but lib$4/$3 not found])
fi
fi
AC_MSG_CHECKING([whether to enable the $1 content coding])
AC_MSG_RESULT([$codec_have])
if test "$codec_have" = "yes"; then
AC_DEFINE([HTS_USE$2], [1], [Define to 1 to decode the $1 content coding])
else
AC_DEFINE([HTS_USE$2], [0], [Define to 1 to decode the $1 content coding])
fi
AC_SUBST([$2_LIBS])
AC_SUBST([$2_ENABLED], [$codec_have])
])
dnl brotlidec needs brotlicommon (the static dictionary) for a fully-static link.
AC_DEFUN([CHECK_BROTLI],
[CHECK_CODEC([brotli], [BROTLI], [brotli/decode.h], [brotlidec], [BrotliDecoderDecompressStream], [-lbrotlicommon])])
AC_DEFUN([CHECK_ZSTD],
[CHECK_CODEC([zstd], [ZSTD], [zstd.h], [zstd], [ZSTD_decompressStream], [])])

View File

@@ -3,7 +3,7 @@
.\"
.\" This file is generated by man/makeman.sh; do not edit by hand.
.\" SPDX-License-Identifier: GPL-3.0-or-later
.TH httrack 1 "07 July 2026" "httrack website copier"
.TH httrack 1 "14 July 2026" "httrack website copier"
.SH NAME
httrack \- offline browser : copy websites to a local directory
.SH SYNOPSIS
@@ -135,7 +135,7 @@ continue an interrupted mirror using the cache (\-\-continue)
mirror ALL links located in the first level pages (mirror links) (\-\-mirrorlinks)
.SS Proxy options:
.IP \-P
proxy use (\-P proxy:port or \-P user:pass@proxy:port) (\-\-proxy <param>)
proxy use (\-P [socks5://][user:pass@]proxy:port) (\-\-proxy <param>)
.IP \-%f
*use proxy for ftp (f0 don't use) (\-\-httpproxy\-ftp[=N])
.IP \-%b
@@ -224,7 +224,7 @@ explain which filter rule accepts or rejects a URL, then exit (\-\-why <param>)
.IP \-u
check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N])
.IP \-j
*parse Java Classes (j0 don't parse, bitmask: |1 parse default, |2 don't parse .class |4 don't parse .js |8 don't be aggressive) (\-\-parse\-java[=N])
*parse scripts (j0 don't parse, bitmask: |1 parse default, |4 don't parse .js |8 don't be aggressive) (\-\-parse\-java[=N])
.IP \-sN
follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always, 3=always (even strict rules)) (\-\-robots[=N])
.IP \-%h
@@ -248,7 +248,7 @@ can also be used to force a specific file type: \-\-assume foo.cgi=text/html
.IP \-@iN
internet protocol (0=both ipv6+ipv4, 4=ipv4 only, 6=ipv6 only) (\-\-protocol[=N])
.IP \-%w
disable a specific external mime module (\-%w htsswf \-%w htsjava) (\-\-disable\-module <param>)
disable a specific external mime module (\-%w httrack\-plugin) (\-\-disable\-module <param>)
.SS Browser ID:
.IP \-F
user\-agent field sent in HTTP headers (\-F "user\-agent name") (\-\-user\-agent <param>)
@@ -514,8 +514,6 @@ that would have solved them.
.SM
\- Several scripts generating complex filenames may not find them (ex: img.src='image'+a+Mobj.dst+'.gif')
.SM
\- Some java classes may not find some files on them (class included)
.SM
\- Cgi\-bin links may not work properly in some cases (parameters needed). To avoid them: use filters like \-*cgi\-bin*
.SH BUGS
Please reports bugs to

View File

@@ -44,7 +44,7 @@ httrack_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
proxytrack_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
htsserver_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
lib_LTLIBRARIES = libhttrack.la libhtsjava.la
lib_LTLIBRARIES = libhttrack.la
htsserver_SOURCES = htsserver.c htsserver.h htsweb.c htsweb.h
proxytrack_SOURCES = proxy/main.c \
@@ -61,7 +61,7 @@ libhttrack_la_SOURCES = htscore.c htsparse.c htsback.c htscache.c \
htshelp.c htslib.c htscoremain.c \
htsname.c htsrobots.c htstools.c htswizard.c \
htsalias.c htsthread.c htsindex.c htsbauth.c \
htsmd5.c htszlib.c htswrap.c htsconcat.c \
htsmd5.c htscodec.c htsproxy.c htszlib.c htswrap.c htsconcat.c \
htsmodules.c htscharset.c punycode.c htsencoding.c htssniff.c \
md5.c \
minizip/ioapi.c minizip/mztools.c minizip/unzip.c minizip/zip.c \
@@ -72,25 +72,20 @@ libhttrack_la_SOURCES = htscore.c htsparse.c htsback.c htscache.c \
htshelp.h htsindex.h htslib.h htsmd5.h \
htsmodules.h htsname.h htsnet.h htssniff.h \
htsopt.h htsrobots.h htsthread.h \
htstools.h htswizard.h htswrap.h htszlib.h \
htstools.h htswizard.h htswrap.h htscodec.h htsproxy.h htszlib.h \
htsstrings.h htsarrays.h httrack-library.h \
htscharset.h punycode.h htsencoding.h \
htsentities.h htsentities.sh htsbasiccharsets.sh htscodepages.h \
md5.h coucal/murmurhash3.h \
minizip/crypt.h minizip/ioapi.h minizip/mztools.h minizip/unzip.h minizip/zip.h
libhttrack_la_LIBADD = $(THREADS_LIBS) $(ZLIB_LIBS) $(OPENSSL_LIBS) $(DL_LIBS) $(SOCKET_LIBS) $(ICONV_LIBS)
libhttrack_la_LIBADD = $(THREADS_LIBS) $(ZLIB_LIBS) $(BROTLI_LIBS) $(ZSTD_LIBS) $(OPENSSL_LIBS) $(DL_LIBS) $(SOCKET_LIBS) $(ICONV_LIBS)
libhttrack_la_CFLAGS = $(AM_CFLAGS) -DLIBHTTRACK_EXPORTS -DZLIB_CONST
libhttrack_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(VERSION_INFO)
libhtsjava_la_SOURCES = htsjava.c htsjava.h
libhtsjava_la_LIBADD = $(THREADS_LIBS) $(DL_LIBS) libhttrack.la
# This thin JNI wrapper reaches libc only through libhttrack, so the direct
# libc edge is dropped from DT_NEEDED (library-not-linked-against-libc). Force
# libc back as a dependency; configure gates the flag since only a GNU-style
# linker accepts it (LIBC_FORCE_LINK is empty on e.g. macOS).
libhtsjava_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(VERSION_INFO) $(LIBC_FORCE_LINK)
EXTRA_DIST = httrack.h webhttrack \
version.rc \
libhttrack.rc \
httrack.rc \
coucal/murmurhash3.h.diff \
coucal/murmurhash3.h.orig \
minizip/iowin32.c \
@@ -113,13 +108,13 @@ EXTRA_DIST = httrack.h webhttrack \
proxy/proxystrings.h \
proxy/proxytrack.h \
proxy/store.h \
proxy/proxytrack.vcproj \
coucal/LICENSE \
coucal/Makefile \
coucal/README.md \
coucal/sample.c \
coucal/tests.c \
htsjava.vcproj \
httrack.dsp httrack.dsw httrack.vcproj \
libhttrack.dsp libhttrack.dsw libhttrack.vcproj \
webhttrack.dsp webhttrack.dsw webhttrack.vcproj
vcpkg.json \
httrack.vcxproj \
libhttrack.vcxproj \
proxytrack.vcxproj \
webhttrack.vcxproj

View File

@@ -44,6 +44,8 @@ Please visit our Website: http://www.httrack.com
#include "htsback.h"
#include "htsftp.h"
#include "htscodec.h"
#include "htsproxy.h"
#if HTS_USEZLIB
#include "htszlib.h"
#else
@@ -556,6 +558,14 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
return 0;
}
/* Move src onto dst; RENAME does not clobber an existing target on Windows. */
static hts_boolean replace_file(const char *src, const char *dst) {
if (RENAME(src, dst) == 0)
return HTS_TRUE;
(void) UNLINK(dst);
return RENAME(src, dst) == 0 ? HTS_TRUE : HTS_FALSE;
}
/* Commit or restore a re-fetch backup (#77 follow-up): a re-fetch over an
existing file moved the good copy to back->tmpfile before truncating url_sav.
commit keeps the new file and drops the backup; else restore it so an aborted
@@ -571,10 +581,9 @@ static void back_finalize_backup(httrackp *opt, lien_back *const back,
fclose(back->r.out);
back->r.out = NULL;
}
(void) UNLINK(back->url_sav); /* drop the failed partial */
/* On rename failure keep the backup: it still holds the good copy, so
losing it would be worse than an orphaned temp. */
if (RENAME(back->tmpfile, back->url_sav) != 0)
/* On failure keep the backup: an orphaned temp beats losing the good copy.
*/
if (!replace_file(back->tmpfile, back->url_sav))
hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
"could not restore %s; previous copy kept as %s",
back->url_sav, back->tmpfile);
@@ -627,8 +636,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
if (!back[p].testmode) { // not test mode
const char *state = "unknown";
/* décompression */
#if HTS_USEZLIB
/* Undo the content coding */
if (back[p].r.compressed) {
if (back[p].r.size > 0) {
// stats
@@ -674,28 +682,67 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
// décompression
if (back[p].tmpfile != NULL) {
if (back[p].url_sav[0]) {
const hts_codec codec =
hts_codec_parse(back[p].r.contentencoding);
/* Never decode over url_sav: a failed decode would destroy the
copy an --update re-fetch is supposed to refresh (#557). */
char BIGSTK unpacked[HTS_URLMAXSIZE * 2 + 4]; // room for ".u"
LLint size;
file_notify(opt, back[p].url_adr, back[p].url_fil,
back[p].url_sav, 1, 1, back[p].r.notmodified);
filecreateempty(&opt->state.strc, back[p].url_sav); // filenote & co
if ((size = hts_zunpack(back[p].tmpfile, back[p].url_sav)) >= 0) {
snprintf(unpacked, sizeof(unpacked), "%s.u", back[p].url_sav);
if ((size = hts_codec_unpack(codec, back[p].tmpfile,
unpacked)) >= 0) {
back[p].r.size = back[p].r.totalsize = size;
// fichier -> mémoire
if (!back[p].r.is_write) {
// fichier -> mémoire ; le fichier est écrit plus tard
deleteaddr(&back[p].r);
back[p].r.adr = readfile_utf8(back[p].url_sav);
back[p].r.adr = readfile_utf8(unpacked);
if (!back[p].r.adr) {
back[p].r.statuscode = STATUSCODE_INVALID;
strcpybuff(back[p].r.msg,
"Read error when decompressing");
}
UNLINK(back[p].url_sav);
UNLINK(unpacked);
} else if (replace_file(unpacked, back[p].url_sav)) {
/* The temp bypassed filecreate(), which is what chmods. */
#ifndef _WIN32
chmod(back[p].url_sav, HTS_ACCESS_FILE);
#endif
file_notify(opt, back[p].url_adr, back[p].url_fil,
back[p].url_sav, 1, 1, back[p].r.notmodified);
filenote(&opt->state.strc, back[p].url_sav, NULL);
} else {
back[p].r.statuscode = STATUSCODE_INVALID;
strcpybuff(back[p].r.msg,
"Write error when decompressing (can not rename "
"the temporary file)");
/* Keep the decoded body: the failed replace may have
removed the previous copy, leaving this as the only one.
*/
hts_log_print(
opt, LOG_WARNING | LOG_ERRNO,
"could not replace %s; decoded copy kept as %s",
back[p].url_sav, unpacked);
}
} else {
back[p].r.statuscode = STATUSCODE_INVALID;
strcpybuff(back[p].r.msg, "Error when decompressing");
snprintf(back[p].r.msg, sizeof(back[p].r.msg),
codec == HTS_CODEC_UNSUPPORTED
? "Unsupported Content-Encoding (%s)"
: "Error when decompressing (%s)",
back[p].r.contentencoding);
/* Drop the undecoded body so the writer can't commit the
coded bytes as the page; url_sav is left untouched. */
if (!back[p].r.is_write)
deleteaddr(&back[p].r);
UNLINK(unpacked);
}
/* A failed decode keeps the previously-mirrored copy: note it,
or the update purge (in old.lst, absent from new.lst) would
delete what we just took care not to overwrite. */
if (back[p].r.statuscode == STATUSCODE_INVALID &&
fexist_utf8(back[p].url_sav))
filenote(&opt->state.strc, back[p].url_sav, NULL);
}
/* ensure that no remaining temporary file exists */
unlink(back[p].tmpfile);
@@ -708,7 +755,6 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
// unflag
}
}
#endif
/* Body fully received: keep the freshly written url_sav, drop the
backup of the previous copy. */
back_finalize_backup(opt, &back[p], HTS_TRUE);
@@ -730,7 +776,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
if (back[p].r.statuscode == HTTP_OK) { // OK (ou 304 en backing)
if (back[p].r.is_write) { // Written file
if (may_be_hypertext_mime(opt, back[p].r.contenttype, back[p].url_fil)) { // to parse!
off_t sz;
LLint sz;
sz = fsize_utf8(back[p].url_sav);
if (sz > 0) { // ok, exists!
@@ -807,7 +853,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
else
strcatbuff(flags, "-");
if (back[p].r.compressed)
strcatbuff(flags, "Z"); // gzip
strcatbuff(flags, "Z"); // content coding
else
strcatbuff(flags, "-");
/* Err I had to split these.. */
@@ -816,9 +862,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
fprintf(cache->txt, LLintP, (LLint) back[p].r.totalsize);
fprintf(cache->txt, "\t%s\t", flags);
}
#if HTS_USEZLIB
back[p].r.compressed = 0;
#endif
if (back[p].r.statuscode == HTTP_OK) {
if (back[p].r.size >= 0) {
@@ -1909,7 +1953,7 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
/* Not in cache ; maybe in temporary cache ? Warning: non-movable
"url_sav" */
else if (back_unserialize_ref(opt, adr, fil, &itemback) == 0) {
const off_t file_size = fsize_utf8(itemback->url_sav);
const LLint file_size = fsize_utf8(itemback->url_sav);
/* Found file on disk */
if (file_size > 0) {
@@ -1944,7 +1988,7 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
}
/* Not in cache or temporary cache ; found on disk ? (hack) */
else if (fexist_utf8(save)) {
const off_t sz = fsize_utf8(save);
const LLint sz = fsize_utf8(save);
// Bon, là il est possible que le fichier ait été partiellement transféré
// (s'il l'avait été en totalité il aurait été inscrit dans le cache ET existerait sur disque)
@@ -2068,6 +2112,17 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
"error: forbidden test with ftp link for back_add");
return -1; // erreur pas de test permis
}
// the ftp client dials the origin itself: over socks that would bypass
// the proxy, so fail the link rather than leak the connection (#563)
if (back[p].r.req.proxy.active &&
hts_proxy_is_socks(back[p].r.req.proxy.name)) {
back[p].r.statuscode = STATUSCODE_NON_FATAL;
strcpybuff(back[p].r.msg,
"ftp:// is not supported over a SOCKS proxy");
back[p].status = STATUS_READY;
back_set_finished(sback, p);
return 0;
}
if (!(back[p].r.req.proxy.active && opt->ftp_proxy)) { // connexion directe, gérée en thread
FTPDownloadStruct *str =
(FTPDownloadStruct *) malloc(sizeof(FTPDownloadStruct));
@@ -2670,13 +2725,40 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
}
busy_state = 1;
// socks5: tunnel to the origin before anything is written, for http
// as well as https. Skip on a reused keep-alive socket (already
// tunneled) and on the post-TLS re-entry (ssl_con set) (#563).
if (back[i].r.req.proxy.active &&
hts_proxy_is_socks(back[i].r.req.proxy.name) &&
!back[i].r.keep_alive
#if HTS_USEOPENSSL
&& back[i].r.ssl_con == NULL
#endif
) {
const int timeout = back[i].timeout > 0 ? back[i].timeout : 30;
if (!socks5_handshake(opt, &back[i].r, back[i].url_adr, timeout)) {
if (!strnotempty(back[i].r.msg))
strcpybuff(back[i].r.msg, "SOCKS5 handshake failed");
deletehttp(&back[i].r);
back[i].r.soc = INVALID_SOCKET;
back[i].r.statuscode = STATUSCODE_NON_FATAL;
back[i].status = STATUS_READY;
back_set_finished(sback, i);
continue;
}
}
#if HTS_USEOPENSSL
/* SSL mode */
if (back[i].r.ssl) {
int tunnel_ok = 1;
// https via proxy: CONNECT-tunnel before TLS (#85)
if (back[i].r.req.proxy.active && back[i].r.ssl_con == NULL) {
// https via an http proxy: CONNECT-tunnel before TLS (#85); socks
// already carries the origin connection
if (back[i].r.req.proxy.active &&
!hts_proxy_is_socks(back[i].r.req.proxy.name) &&
back[i].r.ssl_con == NULL) {
const int timeout = back[i].timeout > 0 ? back[i].timeout : 30;
tunnel_ok =
@@ -2941,11 +3023,13 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
int last_errno = 0;
back[i].r.is_write = 1; // écrire
/* a .gz/.br/.zst saved under its own coding stays
packed on disk */
if (back[i].r.compressed &&
/* .gz are *NOT* depacked!! */
strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "gz") == 0
&& strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "tgz") == 0
) {
!hts_codec_is_archive_ext(
hts_codec_parse(back[i].r.contentencoding),
get_ext(catbuff, sizeof(catbuff),
back[i].url_sav))) {
if (create_back_tmpfile(opt, &back[i], "z") ==
0) {
assertf(back[i].tmpfile != NULL);
@@ -3512,6 +3596,11 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
continue;
}
// The server really sent 304 here; the *-hacks below force
// NOT_MODIFIED only after confirming the file is complete.
const hts_boolean server_sent_304 =
(back[i].r.statuscode == HTTP_NOT_MODIFIED);
/*
Solve "false" 416 problems
*/
@@ -3606,7 +3695,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
if (back[i].r.statuscode == HTTP_OK && !back[i].testmode) { // 'OK'
if (!is_hypertext_mime(opt, back[i].r.contenttype, back[i].url_fil)) { // not HTML
if (strnotempty(back[i].url_sav)) { // target found
int size = fsize_utf8(back[i].url_sav); // target size
LLint size = fsize_utf8(back[i].url_sav);
if (size >= 0) {
if (back[i].r.totalsize == size) { // same size!
@@ -3701,6 +3790,21 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
}
}
// Out-of-protocol 304 to a Range resume: the file is still
// partial, so drop it and refetch instead of trusting it.
if (server_sent_304 && back[i].range_req_size > 0) {
url_savename_refname_remove(opt, back[i].url_adr,
back[i].url_fil);
UNLINK(back[i].url_sav);
deletehttp(&back[i].r);
back[i].r.soc = INVALID_SOCKET;
back[i].r.statuscode = STATUSCODE_NON_FATAL;
strcpybuff(back[i].r.msg,
"Bogus 304 on resume, restarting");
back[i].status = STATUS_READY;
back_set_finished(sback, i);
}
/* sinon, continuer */
/* if (back[i].r.soc!=INVALID_SOCKET) { // ok récupérer body? */
// head: terminé
@@ -3810,7 +3914,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
// traiter 206 (partial content)
// xxc SI CHUNK VERIFIER QUE CA MARCHE??
if (back[i].r.statuscode == 206) { // on nous envoie un morceau (la fin) coz une partie sur disque!
off_t sz = fsize_utf8(back[i].url_sav);
LLint sz = fsize_utf8(back[i].url_sav);
/* RFC 7233: resume at the server's Content-Range start,
not the offset we requested; a server may resume
earlier and appending the overlap duplicates bytes
@@ -3819,7 +3923,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
const hts_boolean range_ok =
back[i].r.crange > 0 && resume >= 0 &&
resume <= (LLint) sz &&
back[i].r.crange_end + 1 == back[i].r.crange &&
back[i].r.crange_end == back[i].r.crange - 1 &&
(back[i].r.totalsize < 0 ||
back[i].r.totalsize ==
back[i].r.crange_end - resume + 1);
@@ -3848,8 +3952,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
// drop bytes past the resume point; a silent
// failure could leave a stale tail, so on error
// drop the partial and refetch the whole file
if (HTS_FTRUNCATE(back[i].r.out,
(off_t) resume) != 0) {
/* not (off_t): 32-bit on MSVC, wrapping a resume
past 2GB */
if (HTS_FTRUNCATE(back[i].r.out, resume) != 0) {
fclose(back[i].r.out);
back[i].r.out = NULL;
url_savename_refname_remove(
@@ -3861,7 +3966,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
"Can not truncate partial file, "
"restarting");
} else {
fseeko(back[i].r.out, (off_t) resume, SEEK_SET);
/* not (off_t): 32-bit on MSVC, truncating a
resume past 2GB */
fseeko(back[i].r.out, resume, SEEK_SET);
/* create a temporary reference file in case of
* broken mirror */
if (back_serialize_ref(opt, &back[i]) != 0) {
@@ -3888,11 +3995,20 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
if (fp) {
LLint alloc_mem = resume + 1;
if (back[i].r.totalsize >= 0)
// Bound the in-memory buffer to a 32-bit size (real
// in-RAM resources are far smaller); a hostile
// Content-Length that would overflow the add or the
// (size_t) cast is dropped and refetched instead.
if (back[i].r.totalsize > INT32_MAX - alloc_mem) {
url_savename_refname_remove(opt, back[i].url_adr,
back[i].url_fil);
UNLINK(back[i].url_sav);
alloc_mem = -1;
} else if (back[i].r.totalsize >= 0)
alloc_mem += back[i].r.totalsize; // AJOUTER RESTANT!
if (deleteaddr(&back[i].r)
&& (back[i].r.adr =
(char *) malloct((size_t) alloc_mem))) {
if (alloc_mem >= 0 && deleteaddr(&back[i].r) &&
(back[i].r.adr =
(char *) malloct((size_t) alloc_mem))) {
back[i].r.size = resume;
if (back[i].r.totalsize >= 0)
back[i].r.totalsize += resume; // -> full size
@@ -4178,10 +4294,13 @@ static int back_maxtime_grace(const int maxtime) {
No floor (unlike the time grace): a size overrun should abort promptly. */
static LLint back_maxsize_grace(const LLint maxsite) { return maxsite / 10; }
/* Which cap has overrun its grace and must hard-stop the mirror (#77, #481). */
/* Which cap has overrun its grace and must hard-stop the mirror (#77, #481).
-M measures received volume (HTS_TOTAL_RECV), not saved 200-only stat_bytes
which undercounts redirect/error-heavy crawls (#520). */
static hts_mirror_limit back_mirror_limit(httrackp *opt) {
if (opt->maxsite > 0 && HTS_STAT.stat_bytes >= opt->maxsite &&
HTS_STAT.stat_bytes - opt->maxsite >= back_maxsize_grace(opt->maxsite))
if (opt->maxsite > 0 && HTS_STAT.HTS_TOTAL_RECV >= opt->maxsite &&
HTS_STAT.HTS_TOTAL_RECV - opt->maxsite >=
back_maxsize_grace(opt->maxsite))
return HTS_MIRROR_LIMIT_SIZE;
if (opt->maxtime > 0) {
const TStamp elapsed = time_local() - HTS_STAT.stat_timestart;
@@ -4195,7 +4314,7 @@ static hts_mirror_limit back_mirror_limit(httrackp *opt) {
int back_checkmirror(httrackp *opt) {
/* request a smooth stop the first time each cap is reached */
if (opt->maxsite > 0 && HTS_STAT.stat_bytes >= opt->maxsite &&
if (opt->maxsite > 0 && HTS_STAT.HTS_TOTAL_RECV >= opt->maxsite &&
!opt->state.stop) {
hts_log_print(opt, LOG_ERROR,
"More than " LLintP

View File

@@ -356,7 +356,7 @@ void cache_add(httrackp * opt, cache_back * cache, const htsblk * r,
FILE *fp;
// On recopie le fichier->.
off_t file_size = fsize_utf8(fconv(catbuff, sizeof(catbuff), url_save));
LLint file_size = fsize_utf8(fconv(catbuff, sizeof(catbuff), url_save));
if (file_size >= 0) {
fp = FOPEN(fconv(catbuff, sizeof(catbuff), url_save), "rb");
@@ -1241,23 +1241,27 @@ char *readfile(const char *fil) {
char *readfile2(const char *fil, LLint * size) {
char *adr = NULL;
char catbuff[CATBUFF_SIZE];
INTsys len = 0;
const LLint len = fsize(fil);
len = fsize(fil);
if (len >= 0) { // exists
/* a size too large for size_t (32-bit Windows/i386) must fail closed: it
would wrap malloct() short while fread() still read the untruncated len */
const size_t buflen = len >= 0 ? llint_to_size_t(len) : (size_t) -1;
if (buflen != (size_t) -1) { // exists, and is addressable
FILE *fp;
fp = fopen(fconv(catbuff, sizeof(catbuff), fil), "rb");
if (fp != NULL) { // n'existe pas (!)
adr = (char *) malloct(len + 1);
adr = (char *) malloct(buflen + 1);
if (size != NULL)
*size = len;
if (adr != NULL) {
if (len > 0 && fread(adr, 1, len, fp) != len) { // fichier endommagé ?
if (buflen > 0 &&
fread(adr, 1, buflen, fp) != buflen) { // fichier endommagé ?
freet(adr);
adr = NULL;
} else
*(adr + len) = '\0';
*(adr + buflen) = '\0';
}
fclose(fp);
}
@@ -1269,19 +1273,21 @@ char *readfile2(const char *fil, LLint * size) {
char *readfile_utf8(const char *fil) {
char *adr = NULL;
char catbuff[CATBUFF_SIZE];
const off_t len = fsize_utf8(fil);
const LLint len = fsize_utf8(fil);
const size_t buflen = len >= 0 ? llint_to_size_t(len) : (size_t) -1;
if (len >= 0) { // exists
if (buflen != (size_t) -1) { // exists, and is addressable (see readfile2)
FILE *const fp = FOPEN(fconv(catbuff, sizeof(catbuff), fil), "rb");
if (fp != NULL) { // n'existe pas (!)
adr = (char *) malloct(len + 1);
adr = (char *) malloct(buflen + 1);
if (adr != NULL) {
if (len > 0 && fread(adr, 1, len, fp) != len) { // fichier endommagé ?
if (buflen > 0 &&
fread(adr, 1, buflen, fp) != buflen) { // fichier endommagé ?
freet(adr);
adr = NULL;
} else {
adr[len] = '\0';
adr[buflen] = '\0';
}
}
fclose(fp);

View File

@@ -232,11 +232,10 @@ static int disk_fallback_selftest(httrackp *opt) {
static const char body[] = "BINARY-on-disk-body-0123456789-no-trailing-nul";
const size_t body_len = sizeof(body) - 1;
/* X-Save must start with path_html_utf8 so the reader resolves it verbatim
(otherwise it re-roots it as a pre-3.40 relative path); then the body we
create at fconv(save) is exactly where cache_readex looks for it. */
fconcat(save, sizeof(save), StringBuff(opt->path_html_utf8),
"example.com/blob.bin");
/* a DOS-ified X-Save loses the path_html_utf8 prefix the reader matches on,
and gets re-rooted as a pre-3.40 relative path; fconv() only on access */
concat(save, sizeof(save), StringBuff(opt->path_html_utf8),
"example.com/blob.bin");
/* write only the header (X-In-Cache: 0); the body stays on disk */
selftest_open_for_write(&cache, opt);
@@ -939,9 +938,9 @@ static void reconcile_put(httrackp *opt, const char *name, size_t size) {
}
/* Expect `name` to weigh `size` bytes, or be absent when size == -1. */
static int reconcile_expect(httrackp *opt, const char *name, off_t size,
static int reconcile_expect(httrackp *opt, const char *name, LLint size,
const char *what) {
const off_t got = fsize(reconcile_st_path(opt, name));
const LLint got = fsize(reconcile_st_path(opt, name));
if (got != size) {
fprintf(stderr, "cache-reconcile: %s: %s is %d bytes, expected %d\n", what,
@@ -955,7 +954,7 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
int failures = 0;
/* around the interrupted-run thresholds (new < 32768, old > 65536) */
static const off_t TINY = 1024, MID = 40000, SOLID = 131072;
static const LLint TINY = 1024, MID = 40000, SOLID = 131072;
golden_setup(opt, dir);
#ifdef _WIN32
@@ -1253,8 +1252,9 @@ static void corrupt_build_disk(httrackp *opt) {
memset(corrupt_body_a, 'a', sizeof(corrupt_body_a) - 1);
remove(reconcile_st_path(opt, "hts-cache/new.zip"));
remove(reconcile_st_path(opt, "hts-cache/old.zip"));
fconcat(save, sizeof(save), StringBuff(opt->path_html_utf8),
CORRUPT_ADR "/victim.bin");
/* X-Save stored verbatim: see disk_fallback_selftest */
concat(save, sizeof(save), StringBuff(opt->path_html_utf8),
CORRUPT_ADR "/victim.bin");
selftest_open_for_write(&cache, opt);
store_entry(opt, &cache, CORRUPT_ADR, "/canary.html", "canary.html", 200,
"OK", "text/html", "utf-8", "", "", "", "", corrupt_body_a,

View File

@@ -31,6 +31,9 @@ Please visit our Website: http://www.httrack.com
/* ------------------------------------------------------------ */
#include "htscharset.h"
#ifdef _WIN32
#include <shellapi.h>
#endif
#include "htsbase.h"
#include "punycode.h"
#include "htssafe.h"
@@ -386,6 +389,40 @@ char *hts_convertStringSystemToUTF8(const char *s, size_t size) {
return hts_convertStringCPToUTF8(s, size, GetACP());
}
HTSEXT_API void hts_argv_utf8(int *pargc, char ***pargv) {
int wargc = 0;
LPWSTR *const wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
char **argv;
int i;
// On any failure keep the CRT's ANSI argv: lossy, but never half-converted.
if (wargv == NULL)
return;
if (wargc <= 0 ||
(argv = calloct((size_t) wargc + 1, sizeof(char *))) == NULL) {
LocalFree(wargv);
return;
}
for (i = 0; i < wargc; i++) {
const int wsize = (int) wcslen(wargv[i]);
// hts_convertUCS2StringToUTF8() returns NULL on an empty string
argv[i] =
wsize != 0 ? hts_convertUCS2StringToUTF8(wargv[i], wsize) : strdupt("");
if (argv[i] == NULL) {
while (i-- != 0)
freet(argv[i]);
freet(argv);
LocalFree(wargv);
return;
}
}
argv[wargc] = NULL; // callers may rely on argv[argc] == NULL
LocalFree(wargv);
*pargc = wargc;
*pargv = argv; // never freed: argv lives for the process
}
#else
#include <errno.h>

View File

@@ -34,6 +34,7 @@ Please visit our Website: http://www.httrack.com
#define HTS_CHARSET_DEFH
/** Standard includes. **/
#include "htsglobal.h"
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
@@ -175,6 +176,15 @@ extern char *hts_convertUCS2StringToUTF8(LPWSTR woutput, int wsize);
* This function is WIN32 specific.
**/
extern char *hts_convertStringSystemToUTF8(const char *s, size_t size);
/**
* Replace the CRT's ANSI argv by a UTF-8 one decoded from the real UTF-16
* command line: every char* is UTF-8 on Windows (FOPEN, STAT, ... convert at
* the syscall boundary). Keeps the CRT's argv on failure; the new array is
* writable, NULL-terminated, and lives for the process.
* This function is WIN32 specific.
**/
HTSEXT_API void hts_argv_utf8(int *pargc, char ***pargv);
#endif
#endif

366
src/htscodec.c Normal file
View File

@@ -0,0 +1,366 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 2026 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: HTTP content codings (gzip/deflate, brotli, zstd) */
/* Author: Xavier Roche */
/* ------------------------------------------------------------ */
/* Internal engine bytecode */
#define HTS_INTERNAL_BYTECODE
#include "htsbase.h"
#include "htscore.h"
#include "htscodec.h"
#include "htszlib.h"
#if HTS_USEBROTLI
#include <brotli/decode.h>
#endif
#if HTS_USEZSTD
#include <zstd.h>
/* 8 MB, the window an HTTP zstd decoder must honor (RFC 9659); libzstd
defaults to 128 MB. */
#define HTS_ZSTD_WINDOWLOG_MAX 23
#endif
/* Decoded-size budget, bounding a bomb: brotli and zstd reach a million to one.
Deflate cannot pass 1032x, so it only meets this at the 2 GiB ceiling. */
#define HTS_CODEC_MAX_RATIO 4096
#define HTS_CODEC_MIN_MAXOUT (1024 * 1024)
LLint hts_codec_maxout(LLint in_size) {
LLint maxout;
if (in_size <= 0 || in_size > INT_MAX / HTS_CODEC_MAX_RATIO)
maxout = INT_MAX;
else
maxout = in_size * HTS_CODEC_MAX_RATIO;
if (maxout < HTS_CODEC_MIN_MAXOUT)
maxout = HTS_CODEC_MIN_MAXOUT;
return maxout;
}
hts_codec hts_codec_parse(const char *encoding) {
if (encoding == NULL || encoding[0] == '\0' ||
strfield2(encoding, "identity"))
return HTS_CODEC_IDENTITY;
if (strfield2(encoding, "gzip") || strfield2(encoding, "x-gzip") ||
strfield2(encoding, "deflate") || strfield2(encoding, "x-deflate"))
return HTS_USEZLIB ? HTS_CODEC_DEFLATE : HTS_CODEC_UNSUPPORTED;
if (strfield2(encoding, "br"))
return HTS_USEBROTLI ? HTS_CODEC_BROTLI : HTS_CODEC_UNSUPPORTED;
if (strfield2(encoding, "zstd"))
return HTS_USEZSTD ? HTS_CODEC_ZSTD : HTS_CODEC_UNSUPPORTED;
if (strfield2(encoding, "compress") || strfield2(encoding, "x-compress"))
return HTS_CODEC_UNSUPPORTED; /* LZW: never advertised, no decoder here */
/* Not a coding at all: broken servers put charsets and the like here, and
the plain body they sent must survive it. */
return HTS_CODEC_IDENTITY;
}
#if HTS_USEBROTLI
#define HTS_AE_BROTLI ", br"
#else
#define HTS_AE_BROTLI ""
#endif
#if HTS_USEZSTD
#define HTS_AE_ZSTD ", zstd"
#else
#define HTS_AE_ZSTD ""
#endif
const char *hts_acceptencoding(hts_boolean compressible, hts_boolean secure) {
if (!compressible)
return "identity";
#if HTS_USEZLIB
/* br and zstd over TLS only, as browsers do: a cleartext intermediary that
rewrites a coding it can not read would corrupt the mirror. */
if (secure)
return "gzip, deflate" HTS_AE_BROTLI HTS_AE_ZSTD ", identity;q=0.9";
return "gzip, deflate, identity;q=0.9";
#else
(void) secure;
return "identity";
#endif
}
hts_boolean hts_codec_is_archive_ext(hts_codec codec, const char *ext) {
if (ext == NULL || ext[0] == '\0')
return HTS_FALSE;
switch (codec) {
case HTS_CODEC_DEFLATE:
return strfield2(ext, "gz") || strfield2(ext, "tgz") ? HTS_TRUE : HTS_FALSE;
case HTS_CODEC_BROTLI:
return strfield2(ext, "br") ? HTS_TRUE : HTS_FALSE;
case HTS_CODEC_ZSTD:
return strfield2(ext, "zst") || strfield2(ext, "tzst") ? HTS_TRUE
: HTS_FALSE;
default:
return HTS_FALSE;
}
}
#if HTS_USEBROTLI || HTS_USEZSTD
/* Append produced bytes to out under the decoded-size budget, advancing *total.
HTS_FALSE on a short write or once the budget is exceeded (a bomb); the
over-budget bytes are never written. */
static hts_boolean codec_sink(FILE *out, const void *buf, size_t produced,
LLint *total, LLint maxout) {
if (produced == 0)
return HTS_TRUE;
*total += (LLint) produced;
if (*total > maxout)
return HTS_FALSE;
return fwrite(buf, 1, produced, out) == produced ? HTS_TRUE : HTS_FALSE;
}
#endif
#if HTS_USEBROTLI
static int codec_unpack_brotli(FILE *in, FILE *out, LLint maxout) {
BrotliDecoderState *const state =
BrotliDecoderCreateInstance(NULL, NULL, NULL);
hts_boolean ok = HTS_TRUE, done = HTS_FALSE;
LLint total = 0;
if (state == NULL)
return -1;
while (ok && !done) {
unsigned char BIGSTK inbuf[8192];
size_t avail_in = fread(inbuf, 1, sizeof(inbuf), in);
const uint8_t *next_in = inbuf;
if (avail_in == 0)
break; /* EOF; a stream still hungry for input is truncated */
for (;;) {
unsigned char BIGSTK outbuf[8192];
uint8_t *next_out = outbuf;
size_t avail_out = sizeof(outbuf);
size_t produced;
BrotliDecoderResult res;
res = BrotliDecoderDecompressStream(state, &avail_in, &next_in,
&avail_out, &next_out, NULL);
produced = sizeof(outbuf) - avail_out;
if (!codec_sink(out, outbuf, produced, &total, maxout)) {
ok = HTS_FALSE;
break;
}
if (res == BROTLI_DECODER_RESULT_ERROR) {
ok = HTS_FALSE;
break;
}
if (res == BROTLI_DECODER_RESULT_SUCCESS) {
done = HTS_TRUE;
break;
}
if (res == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT)
break;
}
if (ferror(in))
ok = HTS_FALSE;
}
BrotliDecoderDestroyInstance(state);
return (ok && done && !ferror(in)) ? (int) total : -1;
}
static size_t codec_head_brotli(const void *in, size_t in_len, void *out,
size_t out_len) {
BrotliDecoderState *const state =
BrotliDecoderCreateInstance(NULL, NULL, NULL);
const uint8_t *next_in = (const uint8_t *) in;
uint8_t *next_out = (uint8_t *) out;
size_t avail_in = in_len, avail_out = out_len;
BrotliDecoderResult res;
if (state == NULL)
return 0;
res = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out,
&next_out, NULL);
BrotliDecoderDestroyInstance(state);
return (res != BROTLI_DECODER_RESULT_ERROR) ? out_len - avail_out : 0;
}
#endif
#if HTS_USEZSTD
static int codec_unpack_zstd(FILE *in, FILE *out, LLint maxout) {
ZSTD_DStream *const zds = ZSTD_createDStream();
hts_boolean ok = HTS_TRUE, eof = HTS_FALSE;
size_t zret = 1; /* 0 once a frame is fully flushed */
LLint total = 0;
if (zds == NULL)
return -1;
if (ZSTD_isError(ZSTD_DCtx_setParameter(zds, ZSTD_d_windowLogMax,
HTS_ZSTD_WINDOWLOG_MAX))) {
ZSTD_freeDStream(zds);
return -1;
}
while (ok && !eof) {
unsigned char BIGSTK inbuf[8192];
ZSTD_inBuffer input;
input.src = inbuf;
input.size = fread(inbuf, 1, sizeof(inbuf), in);
input.pos = 0;
if (input.size == 0) {
eof = HTS_TRUE;
break;
}
while (input.pos < input.size) {
unsigned char BIGSTK outbuf[8192];
ZSTD_outBuffer output;
output.dst = outbuf;
output.size = sizeof(outbuf);
output.pos = 0;
zret = ZSTD_decompressStream(zds, &output, &input);
if (ZSTD_isError(zret)) {
ok = HTS_FALSE;
break;
}
if (!codec_sink(out, outbuf, output.pos, &total, maxout)) {
ok = HTS_FALSE;
break;
}
}
if (ferror(in))
ok = HTS_FALSE;
}
ZSTD_freeDStream(zds);
/* zret != 0 at EOF: the last frame never completed */
return (ok && zret == 0 && !ferror(in)) ? (int) total : -1;
}
static size_t codec_head_zstd(const void *in, size_t in_len, void *out,
size_t out_len) {
ZSTD_DStream *const zds = ZSTD_createDStream();
ZSTD_inBuffer input;
ZSTD_outBuffer output;
size_t zret;
if (zds == NULL)
return 0;
if (ZSTD_isError(ZSTD_DCtx_setParameter(zds, ZSTD_d_windowLogMax,
HTS_ZSTD_WINDOWLOG_MAX))) {
ZSTD_freeDStream(zds);
return 0;
}
input.src = in;
input.size = in_len;
input.pos = 0;
output.dst = out;
output.size = out_len;
output.pos = 0;
zret = ZSTD_decompressStream(zds, &output, &input);
ZSTD_freeDStream(zds);
return ZSTD_isError(zret) ? 0 : output.pos;
}
#endif
LLint hts_codec_coded_size(FILE *in) {
long size;
if (fseek(in, 0, SEEK_END) != 0)
return -1;
size = ftell(in);
if (size < 0 || fseek(in, 0, SEEK_SET) != 0)
return -1;
return (LLint) size;
}
int hts_codec_unpack(hts_codec codec, const char *filename,
const char *newfile) {
if (filename == NULL || newfile == NULL || !filename[0] || !newfile[0])
return -1;
switch (codec) {
case HTS_CODEC_DEFLATE:
#if HTS_USEZLIB
return hts_zunpack(filename, newfile);
#else
return -1;
#endif
case HTS_CODEC_BROTLI:
case HTS_CODEC_ZSTD:
break;
default: /* IDENTITY never reaches the unpacker; UNSUPPORTED must fail */
return -1;
}
#if HTS_USEBROTLI || HTS_USEZSTD
{
char catbuff[CATBUFF_SIZE];
FILE *out, *in = FOPEN(fconv(catbuff, sizeof(catbuff), filename), "rb");
LLint maxout;
int ret = -1;
if (in == NULL)
return -1;
maxout = hts_codec_maxout(hts_codec_coded_size(in));
out = FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
if (out == NULL) {
fclose(in);
return -1;
}
#if HTS_USEBROTLI
if (codec == HTS_CODEC_BROTLI)
ret = codec_unpack_brotli(in, out, maxout);
#endif
#if HTS_USEZSTD
if (codec == HTS_CODEC_ZSTD)
ret = codec_unpack_zstd(in, out, maxout);
#endif
fclose(out);
fclose(in);
return ret;
}
#else
return -1;
#endif
}
size_t hts_codec_head(hts_codec codec, const void *in, size_t in_len, void *out,
size_t out_len) {
if (in == NULL || in_len == 0 || out == NULL || out_len == 0)
return 0;
switch (codec) {
#if HTS_USEZLIB
case HTS_CODEC_DEFLATE:
return hts_zhead(in, in_len, out, out_len);
#endif
#if HTS_USEBROTLI
case HTS_CODEC_BROTLI:
return codec_head_brotli(in, in_len, out, out_len);
#endif
#if HTS_USEZSTD
case HTS_CODEC_ZSTD:
return codec_head_zstd(in, in_len, out, out_len);
#endif
default:
return 0;
}
}

79
src/htscodec.h Normal file
View File

@@ -0,0 +1,79 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 2026 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: HTTP content codings (gzip/deflate, brotli, zstd) */
/* Author: Xavier Roche */
/* ------------------------------------------------------------ */
#ifndef HTS_DEFCODEC
#define HTS_DEFCODEC
#include "htsglobal.h"
/* Content codings we may meet in a Content-Encoding header. */
typedef enum {
HTS_CODEC_IDENTITY = 0, /**< no coding, or a token we treat as none */
HTS_CODEC_DEFLATE, /**< gzip, x-gzip, deflate, x-deflate */
HTS_CODEC_BROTLI, /**< br */
HTS_CODEC_ZSTD, /**< zstd */
HTS_CODEC_UNSUPPORTED /**< a real coding we can not decode */
} hts_codec;
#ifdef HTS_INTERNAL_BYTECODE
/* Codec for a Content-Encoding token. A known coding we can not decode is
UNSUPPORTED; an unrecognized token is IDENTITY (servers do emit junk). */
extern hts_codec hts_codec_parse(const char *encoding);
/* Accept-Encoding value to advertise; secure (TLS) also offers br and zstd. */
extern const char *hts_acceptencoding(hts_boolean compressible,
hts_boolean secure);
/* HTS_TRUE if ext is the codec's own archive extension: a foo.gz served as
Content-Encoding: gzip stays packed on disk. */
extern hts_boolean hts_codec_is_archive_ext(hts_codec codec, const char *ext);
/* Decode filename into newfile. Returns the decoded size, or -1 on a truncated
or corrupt stream, an unsupported coding, a bomb over the budget, or I/O. */
extern int hts_codec_unpack(hts_codec codec, const char *filename,
const char *newfile);
/* Decode up to out_len leading bytes of a coded body for the MIME sniffer;
0 when nothing could be decoded. */
extern size_t hts_codec_head(hts_codec codec, const void *in, size_t in_len,
void *out, size_t out_len);
/* Ceiling on the decoded size of a body of in_size coded bytes. */
extern LLint hts_codec_maxout(LLint in_size);
/* Coded size of an open stream, left rewound; -1 if unknown. */
extern LLint hts_codec_coded_size(FILE *in);
#endif
#endif

View File

@@ -855,7 +855,7 @@ int httpmirror(char *url1, httrackp * opt) {
char *filelist_buff = NULL;
size_t filelist_sz = 0;
const char *filelist_err = NULL; /* failure reason, NULL on success */
const off_t fs = fsize(StringBuff(opt->filelist));
const LLint fs = fsize(StringBuff(opt->filelist));
if (fs < 0) {
/* fsize() hides the cause; redo stat() for a precise errno (#49) */
@@ -863,7 +863,7 @@ int httpmirror(char *url1, httrackp * opt) {
filelist_err = stat(StringBuff(opt->filelist), &st) != 0
? strerror(errno)
: "not a regular file";
} else if ((filelist_sz = off_t_to_size_t(fs)) == (size_t) -1) {
} else if ((filelist_sz = llint_to_size_t(fs)) == (size_t) -1) {
filelist_err = "file too large";
filelist_sz = 0;
} else {
@@ -2089,10 +2089,9 @@ int httpmirror(char *url1, httrackp * opt) {
(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
"hts-cache/old.lst"), "rb");
if (old_lst) {
const size_t sz =
off_t_to_size_t(fsize(fconcat
(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
"hts-cache/new.lst")));
const size_t sz = llint_to_size_t(
fsize(fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
StringBuff(opt->path_log), "hts-cache/new.lst")));
new_lst =
fopen(fconcat
(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
@@ -3875,7 +3874,6 @@ void voidf(void) {
a = "| HTTrack Website Copier |" CRLF;
a = "|Code: Windows Interface Xavier Roche |" CRLF;
a = "| HTS/HTTrack Xavier Roche |" CRLF;
a = "| .class Parser Yann Philippot |" CRLF;
a = "| |" CRLF;
a = "|Tested on: Windows95,98,NT,2K |" CRLF;
a = "| Linux PC |" CRLF;

View File

@@ -1452,7 +1452,7 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
HTS_PANIC_PRINTF
("Option %w needs to be followed by a blank space, and a module name");
printf("Example: -%%w htsswf\n");
printf("Example: -%%w httrack-plugin\n");
htsmain_free();
return -1;
} else {
@@ -1509,7 +1509,7 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
htsmain_free();
return -1;
} else {
off_t fz;
LLint fz;
na++;
fz = fsize(argv[na]);
@@ -2253,21 +2253,12 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
htsmain_free();
return -1;
} else {
char *a;
char BIGSTK pname[HTS_URLMAXSIZE * 2];
na++;
opt->proxy.active = 1;
// Rechercher MAIS en partant de la fin à cause de user:pass@proxy:port
a = argv[na] + strlen(argv[na]) - 1;
while((a > argv[na]) && (*a != ':') && (*a != '@'))
a--;
if (*a == ':') { // un port est présent, <proxy>:port
sscanf(a + 1, "%d", &opt->proxy.port);
StringCopyN(opt->proxy.name, argv[na], (int) (a - argv[na]));
} else { // <proxy>
opt->proxy.port = 8080;
StringCopy(opt->proxy.name, argv[na]);
}
hts_parse_proxy(argv[na], pname, sizeof(pname), &opt->proxy.port);
StringCopy(opt->proxy.name, pname);
}
break;
case 'F': // user-agent field
@@ -2593,21 +2584,11 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
hts_get_version_info(opt), t, url);
fprintf(opt->log, "(");
for(i = 0; i < argc; i++) {
#ifdef _WIN32
char *carg =
hts_convertStringSystemToUTF8(argv[i], (int) strlen(argv[i]));
char *arg = carg != NULL ? carg : argv[i];
#else
const char *arg = argv[i];
#endif
const char *arg = argv[i]; // already UTF-8 on every platform
if (strchr(arg, ' ') == NULL || strchr(arg, '\"') != NULL)
fprintf(opt->log, "%s ", arg);
else // entre "" (si espace(s) et pas déja de ")
fprintf(opt->log, "\"%s\" ", arg);
#ifdef _WIN32
if (carg != NULL)
free(carg);
#endif
}
fprintf(opt->log, ")" LF);
fprintf(opt->log, LF);

View File

@@ -70,7 +70,7 @@ typedef struct t_hts_callbackarg t_hts_callbackarg;
#endif
/* Marks a symbol an external wrapper module exports back to the engine.
Must override -fvisibility=hidden on ELF, or dlopen()ed plugins (htsjava)
Must override -fvisibility=hidden on ELF, or dlopen()ed plugins
hide their own hts_plug()/hts_unplug() entry points. */
#ifndef EXTERNAL_FUNCTION
#ifdef _WIN32

View File

@@ -133,9 +133,10 @@ static struct addrinfo *mock_mkai(const mock_addr *a) {
return ai;
}
static int mock_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
static int HTS_RESOLVER_CALL mock_getaddrinfo(const char *node,
const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
mock_host *const h = mock_find(node);
const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
struct addrinfo *head = NULL, *tail = NULL;
@@ -164,7 +165,7 @@ static int mock_getaddrinfo(const char *node, const char *service,
return 0;
}
static void mock_freeaddrinfo(struct addrinfo *res) {
static void HTS_RESOLVER_CALL mock_freeaddrinfo(struct addrinfo *res) {
while (res != NULL) {
struct addrinfo *const next = res->ai_next;

View File

@@ -121,54 +121,78 @@ int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
return use1 ? jok1 : jok2;
}
/* Real filters/URLs fit HTS_URLMAXSIZE*2; past it a hostile pattern recurses
O(len) deep or runs O(n^2*stars). Cap length, recursion depth and steps
(work): the length cap alone still allows ~2000 frames, ~900KB of stack,
which overflows the 1MB a Windows thread gets (#574). */
#define STRJOKER_MAXLEN (HTS_URLMAXSIZE * 2)
#define STRJOKER_MAXDEPTH 256u
#define STRJOKER_MAXSTEPS 2000000u
/* Failure memo for the recursive matcher: one bit per (chaine, joker) offset
pair keeps star-heavy patterns polynomial instead of exponential (#501). */
typedef struct strjoker_memo {
const char *chaine0, *joker0; /* offsets are relative to these bases */
size_t stride; /* strlen(joker0) + 1 */
unsigned char *failed; /* failed-pair bitmap; NULL: no memo */
size_t *nsteps; /* shared work counter; NULL: unbounded (oracle) */
size_t maxdepth; /* deepest recursion reached */
hts_boolean cut; /* a branch hit the depth cap: its failures are not final */
} strjoker_memo;
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size,
int *size_flag);
static const char *strjoker_impl(strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size, int *size_flag,
size_t depth);
/* A pair that failed once fails forever (*size is only written on the success
path), so record NULL results and cut the re-exploration. */
static const char *strjoker_rec(const strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size,
int *size_flag) {
static const char *strjoker_rec(strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size, int *size_flag,
size_t depth) {
size_t bit = 0;
const char *adr;
if (depth > memo->maxdepth)
memo->maxdepth = depth;
if (depth >= STRJOKER_MAXDEPTH) {
memo->cut = HTS_TRUE;
return NULL; /* nesting beyond any real filter: fail the branch safely */
}
if (memo->nsteps != NULL && ++*memo->nsteps > STRJOKER_MAXSTEPS)
return NULL; /* work budget spent: fail the match safely */
if (memo->failed) {
bit = (size_t) (chaine - memo->chaine0) * memo->stride +
(size_t) (joker - memo->joker0);
if (memo->failed[bit >> 3] & (unsigned char) (1u << (bit & 7)))
return NULL;
}
adr = strjoker_impl(memo, chaine, joker, size, size_flag);
if (adr == NULL && memo->failed)
adr = strjoker_impl(memo, chaine, joker, size, size_flag, depth + 1);
/* a cut branch may fail here yet match when reached at a shallower depth */
if (adr == NULL && memo->failed && !memo->cut)
memo->failed[bit >> 3] |= (unsigned char) (1u << (bit & 7));
return adr;
}
// wildcard comparator: match chaine against joker (pattern), case-insensitive
// returns the address of the first matched letter past any leading joker
// (ie. *[..]toto.. returns the address of toto within chaine), NULL on mismatch
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
LLint *size, int *size_flag) {
strjoker_memo memo = {chaine, joker, 0, NULL};
/* Match chaine against joker with a shared work budget *nsteps (a strjokerfind
sweep passes one counter, bounding the whole scan). */
static const char *strjoker_bounded(const char *chaine, const char *joker,
LLint *size, int *size_flag, size_t *nsteps,
size_t *maxdepth) {
strjoker_memo memo = {chaine, joker, 0, NULL, nsteps, 0, HTS_FALSE};
unsigned char stackbits[2048];
hts_boolean onheap = HTS_FALSE;
const char *adr;
if (maxdepth != NULL)
*maxdepth = 0;
if (chaine != NULL && joker != NULL) {
const size_t n1 = strlen(chaine) + 1;
const size_t l1 = strlen(chaine), l2 = strlen(joker);
memo.stride = strlen(joker) + 1;
if (n1 <= (SIZE_MAX - 7) / memo.stride) { // overflow-safe bitmap size
const size_t bytes = (n1 * memo.stride + 7) / 8;
if (l1 > STRJOKER_MAXLEN || l2 > STRJOKER_MAXLEN)
return NULL; /* beyond any real filter/URL: bound depth+work */
memo.stride = l2 + 1;
if (l1 + 1 <= (SIZE_MAX - 7) / memo.stride) { // overflow-safe bitmap size
const size_t bytes = ((l1 + 1) * memo.stride + 7) / 8;
if (bytes <= sizeof(stackbits)) {
memset(stackbits, 0, bytes);
@@ -179,23 +203,54 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
}
}
}
adr = strjoker_rec(&memo, chaine, joker, size, size_flag);
adr = strjoker_rec(&memo, chaine, joker, size, size_flag, 0);
if (onheap)
freet(memo.failed);
if (maxdepth != NULL)
*maxdepth = memo.maxdepth;
return adr;
}
// wildcard comparator: match chaine against joker (pattern), case-insensitive
// returns the address of the first matched letter past any leading joker
// (ie. *[..]toto.. returns the address of toto within chaine), NULL on mismatch
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
LLint *size, int *size_flag) {
size_t nsteps = 0;
return strjoker_bounded(chaine, joker, size, size_flag, &nsteps, NULL);
}
/* Test-only oracle: the same matcher without the failure memo. */
const char *strjoker_nomemo(const char *chaine, const char *joker, LLint *size,
int *size_flag) {
const strjoker_memo memo = {chaine, joker, 0, NULL};
strjoker_memo memo = {chaine, joker, 0, NULL, NULL, 0, HTS_FALSE};
return strjoker_rec(&memo, chaine, joker, size, size_flag);
return strjoker_rec(&memo, chaine, joker, size, size_flag, 0);
}
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size,
int *size_flag) {
/* Test-only: strjoker() reporting the work and depth spent and their caps, so a
self-test can prove both bound a hostile pattern. */
const char *strjoker_bounds(const char *chaine, const char *joker,
size_t *nsteps_out, size_t *maxsteps_out,
size_t *depth_out, size_t *maxdepth_out) {
size_t nsteps = 0, depth = 0;
const char *r = strjoker_bounded(chaine, joker, NULL, NULL, &nsteps, &depth);
if (nsteps_out != NULL)
*nsteps_out = nsteps;
if (maxsteps_out != NULL)
*maxsteps_out = STRJOKER_MAXSTEPS;
if (depth_out != NULL)
*depth_out = depth;
if (maxdepth_out != NULL)
*maxdepth_out = STRJOKER_MAXDEPTH;
return r;
}
static const char *strjoker_impl(strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size, int *size_flag,
size_t depth) {
if (strnotempty(joker) == 0) { // fin de chaine joker
if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok
return chaine;
@@ -369,7 +424,8 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// tester sans le joker (pas ()+ mais ()*)
if (!unique) {
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag))) {
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag,
depth))) {
return adr;
}
}
@@ -382,7 +438,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
while(i < (int) max) {
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
if ((adr = strjoker_rec(memo, chaine + i + 1, joker + jmp, size,
size_flag))) {
size_flag, depth))) {
return adr;
}
i++;
@@ -393,7 +449,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// tester chaîne vide
if (i != max + 2) // avant c'est ok
if ((adr = strjoker_rec(memo, chaine + max, joker + jmp, size,
size_flag)))
size_flag, depth)))
return adr;
return NULL; // perdu
@@ -415,7 +471,8 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// comparaison ok?
if (ok) {
// continuer la comparaison.
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag))
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag,
depth))
return chaine; // retourner 1e lettre
}
@@ -431,12 +488,18 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// d'un strcpy sur une variable ayant un nom en lettres et copiant une chaine de chiffres
// ATTENTION!! Eviter les jokers en début, où gare au temps machine!
const char *strjokerfind(const char *chaine, const char *joker) {
size_t nsteps = 0; /* one budget for the whole scan */
const char *adr;
if (chaine != NULL && strlen(chaine) > STRJOKER_MAXLEN)
return NULL;
while(*chaine) {
if ((adr = strjoker(chaine, joker, NULL, NULL))) { // ok trouvé
if ((adr = strjoker_bounded(chaine, joker, NULL, NULL, &nsteps,
NULL))) { // ok trouvé
return adr;
}
if (nsteps > STRJOKER_MAXSTEPS) // scan budget spent: no match
return NULL;
chaine++;
}
return NULL;

View File

@@ -52,6 +52,12 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
oracle for the memoized matcher. */
const char *strjoker_nomemo(const char *chaine, const char *joker, LLint *size,
int *size_flag);
/* strjoker() reporting the work-budget steps and the recursion depth it spent,
with their caps; test-only, lets a self-test assert both bound a hostile
pattern (depth bounds the stack: an uncapped one overflows Windows' 1MB). */
const char *strjoker_bounds(const char *chaine, const char *joker,
size_t *nsteps_out, size_t *maxsteps_out,
size_t *depth_out, size_t *maxdepth_out);
const char *strjokerfind(const char *chaine, const char *joker);
#endif

View File

@@ -140,6 +140,15 @@ Please visit our Website: http://www.httrack.com
#define HTS_USEZLIB 1
#endif
// brotli and zstd content codings; off unless the build opted in (configure,
// or the Visual Studio projects, which link the vcpkg libraries)
#ifndef HTS_USEBROTLI
#define HTS_USEBROTLI 0
#endif
#ifndef HTS_USEZSTD
#define HTS_USEZSTD 0
#endif
#ifndef HTS_INET6
#define HTS_INET6 0
#endif
@@ -313,16 +322,15 @@ typedef int64_t TStamp;
/* Full printf conversion, '%' included (PRId64 has none): "X: " LLintP. */
#define LLintP "%" PRId64
/* Integer type for file offsets/sizes passed to the C library. Widens to
LLint (with HTS_FSEEKO for fseeko/ftello) under large-file support, plain
int otherwise; INTsysP is its printf conversion. */
#ifdef LFS_FLAG
/* Integer type for file offsets/sizes passed to the C library; INTsysP is its
printf conversion. FIXME: LFS_FLAG is a configure make variable, never a C
macro, so this test is dead and INTsys stays int on POSIX despite large-file
support (the real macro is HTS_LFS). Widening it there is an installed-header
type change, so it is left alone here. */
#if defined(LFS_FLAG) || defined(_MSC_VER)
typedef LLint INTsys;
#define INTsysP LLintP
#ifdef __linux
#define HTS_FSEEKO
#endif
#else
typedef int INTsys;

View File

@@ -477,7 +477,7 @@ void help(const char *app, int more) {
(" Y mirror ALL links located in the first level pages (mirror links)");
infomsg("");
infomsg("Proxy options:");
infomsg(" P proxy use (-P proxy:port or -P user:pass@proxy:port)");
infomsg(" P proxy use (-P [socks5://][user:pass@]proxy:port)");
infomsg(" %f *use proxy for ftp (f0 don't use)");
infomsg(" %b use this local hostname to make/send requests (-%b hostname)");
infomsg("");
@@ -546,8 +546,8 @@ void help(const char *app, int more) {
infomsg(" %Y explain which filter rule accepts or rejects a URL, then exit");
infomsg
(" u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)");
infomsg
(" j *parse Java Classes (j0 don't parse, bitmask: |1 parse default, |2 don't parse .class |4 don't parse .js |8 don't be aggressive)");
infomsg(" j *parse scripts (j0 don't parse, bitmask: |1 parse default, |4 "
"don't parse .js |8 don't be aggressive)");
infomsg
(" sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always, 3=always (even strict rules))");
infomsg
@@ -571,8 +571,7 @@ void help(const char *app, int more) {
(" can also be used to force a specific file type: --assume foo.cgi=text/html");
infomsg
(" @iN internet protocol (0=both ipv6+ipv4, 4=ipv4 only, 6=ipv6 only)");
infomsg
(" %w disable a specific external mime module (-%w htsswf -%w htsjava)");
infomsg(" %w disable a specific external mime module (-%w httrack-plugin)");
infomsg("");
infomsg("Browser ID:");
infomsg

View File

@@ -318,7 +318,7 @@ void index_finish(const char *indexpath, int mode) {
char catbuff[CATBUFF_SIZE];
char **tab;
char *blk;
off_t size = fpsize(fp_tmpproject);
LLint size = fpsize(fp_tmpproject);
if (size > 0) {
if (fp_tmpproject) {

View File

@@ -1,517 +0,0 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 1998 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: Java classes parser */
/* Author: Yann Philippot */
/* ------------------------------------------------------------ */
/* Version: Oct/2000 */
/* Fixed: problems with class structure (10/2000) */
// htsjava.c - Parseur de classes java
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#if ( defined(_WIN32) ||defined(HAVE_SYS_TYPES_H) )
#include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
/* Standard httrack module includes */
#include "httrack-library.h"
#include "htsopt.h"
#include "htsdefines.h"
/* Module structures */
#include "htsmodules.h"
/* We link to libhttrack, we can use its functions */
#include "httrack-library.h"
/* This file */
#include "htsjava.h"
/* calloct/freet wrappers */
#include "htssafe.h"
static int reverse_endian(void) {
int endian = 1;
return (*((char *) &endian) == 1);
}
/* big/little endian swap */
#define hts_swap16(A) ( (((A) & 0xFF)<<8) | (((A) & 0xFF00)>>8) )
#define hts_swap32(A) ( (( (hts_swap16(A)) & 0xFFFF)<<16) | (( (hts_swap16(A>>16)) & 0xFFFF)) )
/* Static definitions */
static RESP_STRUCT readtable(htsmoduleStruct * str, FILE * fp, RESP_STRUCT,
int *);
static unsigned short int readshort(FILE * fp);
static int tris(httrackp * opt, char *);
static char *printname(char[1024], char[1024]);
// ** HTS_xx sinon pas pris par VC++
#define HTS_CLASS 7
#define HTS_FIELDREF 9
#define HTS_METHODREF 10
#define HTS_STRING 8
#define HTS_INTEGER 3
#define HTS_FLOAT 4
#define HTS_LONG 5
#define HTS_DOUBLE 6
#define HTS_INTERFACE 11
#define HTS_NAMEANDTYPE 12
#define HTS_ASCIZ 1
#define HTS_UNICODE 2
#define JAVADEBUG 0
static const char *libName = "htsjava";
#ifdef _WIN32
#define strcasecmp(a,b) stricmp(a,b)
#define strncasecmp(a,b,n) strnicmp(a,b,n)
#endif
static int detect_mime(htsmoduleStruct * str) {
const char *savename = str->filename;
if (savename) {
int len = (int) strlen(savename);
if (len > 6 && strcasecmp(savename + len - 6, ".class") == 0) {
return 1;
}
}
return 0;
}
static int hts_detect_java(t_hts_callbackarg * carg, httrackp * opt,
htsmoduleStruct * str) {
/* Call parent functions if multiple callbacks are chained. */
if (CALLBACKARG_PREV_FUN(carg, detect) != NULL) {
if (CALLBACKARG_PREV_FUN(carg, detect)
(CALLBACKARG_PREV_CARG(carg), opt, str)) {
return 1; /* Found before us, let them have the priority */
}
}
/* Check MIME */
if (detect_mime(str)) {
str->wrapper_name = libName; /* Our ID */
return 1; /* Known format, we take it */
}
return 0; /* Unknown format */
}
static off_t fsize(const char *s) {
STRUCT_STAT st;
if (STAT(s, &st) == 0 && S_ISREG(st.st_mode)) {
return st.st_size;
} else {
return -1;
}
}
static int hts_parse_java(t_hts_callbackarg * carg, httrackp * opt,
htsmoduleStruct * str) {
/* The wrapper_name memebr has changed: not for us anymore */
if (str->wrapper_name == NULL || strcmp(str->wrapper_name, libName) != 0) {
/* Call parent functions if multiple callbacks are chained. */
if (CALLBACKARG_PREV_FUN(carg, parse) != NULL) {
return CALLBACKARG_PREV_FUN(carg, parse) (CALLBACKARG_PREV_CARG(carg),
opt, str);
}
strcpy(str->err_msg,
"unexpected error: bad wrapper_name and no previous wrapper");
return 0; /* Unexpected error */
} else {
if (detect_mime(str)) {
/* (Legacy code) */
char catbuff[CATBUFF_SIZE];
FILE *fpout;
JAVA_HEADER header;
RESP_STRUCT *tab;
const char *file = str->filename;
str->relativeToHtmlLink = 1;
#if JAVADEBUG
printf("fopen\n");
#endif
if ((fpout = FOPEN(fconv(catbuff, sizeof(catbuff), file), "r+b")) ==
NULL) {
sprintf(str->err_msg, "Unable to open file %s", file);
return 0; // une erreur..
}
#if JAVADEBUG
printf("fread\n");
#endif
if (fread(&header, 1, 10, fpout) != 10) { // pas complet..
fclose(fpout);
sprintf(str->err_msg, "File header too small (file len = " LLintP ")",
(LLint) fsize(file));
return 0;
}
#if JAVADEBUG
printf("header\n");
#endif
// tester en tête
if (reverse_endian()) {
header.magic = hts_swap32(header.magic);
header.count = hts_swap16(header.count);
}
if (header.magic != 0xCAFEBABE) {
sprintf(str->err_msg, "non java file");
if (fpout) {
fclose(fpout);
fpout = NULL;
}
return 0;
}
/* A constant-pool entry is >= 1 byte on disk; reject a count exceeding
the file size (hostile .class ~68 MB alloc DoS). */
if (!hts_count_fits(header.count, (LLint) fsize(file))) {
fclose(fpout);
sprintf(str->err_msg,
"Invalid constant pool count %u (file len " LLintP ")",
(unsigned) header.count, (LLint) fsize(file));
return 0;
}
tab = (RESP_STRUCT *) calloct(header.count, sizeof(RESP_STRUCT));
if (!tab) {
sprintf(str->err_msg, "Unable to alloc %d bytes",
(int) sizeof(RESP_STRUCT));
if (fpout) {
fclose(fpout);
fpout = NULL;
}
return 0; // erreur..
}
#if JAVADEBUG
printf("calchead\n");
#endif
{
int i;
for(i = 1; i < header.count; i++) {
int err = 0; // ++
tab[i] = readtable(str, fpout, tab[i], &err);
if (!err) {
if ((tab[i].type == HTS_LONG) || (tab[i].type == HTS_DOUBLE))
i++; //2 element si double ou float
} else { // ++ une erreur est survenue!
if (strnotempty(str->err_msg) == 0)
strcpy(str->err_msg, "Internal readtable error");
freet(tab);
if (fpout) {
fclose(fpout);
fpout = NULL;
}
return 0;
}
}
}
#if JAVADEBUG
printf("addfiles\n");
#endif
{
unsigned int Class;
unsigned int SClass;
int i;
Class = readshort(fpout);
SClass = readshort(fpout);
for(i = 1; i < header.count; i++) {
if (tab[i].type == HTS_CLASS) {
if ((tab[i].index1 < header.count) && (tab[i].index1 >= 0)) {
if ((tab[i].index1 != SClass) && (tab[i].index1 != Class)
&& (tab[tab[i].index1].name[0] != '[')) {
if (!strstr(tab[tab[i].index1].name, "java/")) {
char BIGSTK tempo[1024];
tempo[0] = '\0';
snprintf(tempo, sizeof(tempo), "%s.class", tab[tab[i].index1].name);
#if JAVADEBUG
printf("add %s\n", tempo);
#endif
if (tab[tab[i].index1].file_position >= 0)
str->addLink(str, tempo); /* tab[tab[i].index1].file_position */
}
}
} else {
i = header.count; // exit
}
}
}
}
#if JAVADEBUG
printf("end\n");
#endif
freet(tab);
if (fpout) {
fclose(fpout);
fpout = NULL;
}
return 1;
} else {
strcpy(str->err_msg, "bad MIME type");
}
}
return 0; /* Error */
}
/*
module entry point
*/
EXTERNAL_FUNCTION int hts_plug(httrackp * opt, const char *argv);
EXTERNAL_FUNCTION int hts_plug(httrackp * opt, const char *argv) {
/* Plug callback functions */
CHAIN_FUNCTION(opt, detect, hts_detect_java, NULL);
CHAIN_FUNCTION(opt, parse, hts_parse_java, NULL);
return 1; /* success */
}
// error: !=0 si erreur fatale
static RESP_STRUCT readtable(htsmoduleStruct * str, FILE * fp,
RESP_STRUCT trans, int *error) {
char rname[1024];
unsigned short int length;
int j;
*error = 0; // pas d'erreur
trans.file_position = -1;
trans.type = (int) (unsigned char) fgetc(fp);
switch (trans.type) {
case HTS_CLASS:
strcpy(trans.name, "Class");
trans.index1 = readshort(fp);
break;
case HTS_FIELDREF:
strcpy(trans.name, "Field Reference");
trans.index1 = readshort(fp);
readshort(fp);
break;
case HTS_METHODREF:
strcpy(trans.name, "Method Reference");
trans.index1 = readshort(fp);
readshort(fp);
break;
case HTS_INTERFACE:
strcpy(trans.name, "Interface Method Reference");
trans.index1 = readshort(fp);
readshort(fp);
break;
case HTS_NAMEANDTYPE:
strcpy(trans.name, "Name and Type");
trans.index1 = readshort(fp);
readshort(fp);
break;
case HTS_STRING: // CONSTANT_String
strcpy(trans.name, "String");
trans.index1 = readshort(fp);
break;
case HTS_INTEGER:
strcpy(trans.name, "Integer");
for(j = 0; j < 4; j++)
fgetc(fp);
break;
case HTS_FLOAT:
strcpy(trans.name, "Float");
for(j = 0; j < 4; j++)
fgetc(fp);
break;
case HTS_LONG:
strcpy(trans.name, "Long");
for(j = 0; j < 8; j++)
fgetc(fp);
break;
case HTS_DOUBLE:
strcpy(trans.name, "Double");
for(j = 0; j < 8; j++)
fgetc(fp);
break;
case HTS_ASCIZ:
case HTS_UNICODE:
if (trans.type == HTS_ASCIZ)
strcpy(trans.name, "HTS_ASCIZ");
else
strcpy(trans.name, "HTS_UNICODE");
{
char BIGSTK buffer[1024];
char *p;
p = &buffer[0];
trans.file_position = ftell(fp);
length = readshort(fp);
if (length < HTS_URLMAXSIZE) {
while(length > 0) {
*p++ = fgetc(fp);
length--;
}
*p = '\0';
if (tris(str->opt, buffer) == 1)
str->addLink(str, buffer); /* trans.file_position */
else if (tris(str->opt, buffer) == 2)
str->addLink(str, printname(rname, buffer));
strcpy(trans.name, buffer);
} else { // gros pb
while((length > 0) && (!feof(fp))) {
fgetc(fp);
length--;
}
if (!feof(fp)) {
trans.type = -1;
} else {
sprintf(str->err_msg, "Internal structure error (ASCII)");
*error = 1;
}
return (trans);
}
}
break;
default:
// on arrête tout
sprintf(str->err_msg, "Internal structure unknown (type %d)", trans.type);
*error = 1;
return (trans);
break;
}
return (trans);
}
static unsigned short int readshort(FILE * fp) {
unsigned short int valint;
if (fread(&valint, sizeof(valint), 1, fp) == 1) {
if (reverse_endian())
return hts_swap16(valint);
else
return valint;
} else {
return 0;
}
}
static int tris(httrackp * opt, char *buffer) {
char catbuff[CATBUFF_SIZE];
//
// Java
if ((buffer[0] == '[') && buffer[1] == 'L' && (!strstr(buffer, "java/")))
return 2;
if (strstr(buffer, ".gif") || strstr(buffer, ".jpg")
|| strstr(buffer, ".jpeg") || strstr(buffer, ".au"))
return 1;
// Ajouts R.X: test type
// Autres fichiers
{
char type[256];
if (get_httptype_sized(opt, type, sizeof(type), buffer,
0)) // recognized type
return 1;
// ajout RX 05/2001
else if (is_dyntype(get_ext(catbuff, sizeof(catbuff), buffer))) // asp,cgi...
return 1;
}
return 0;
}
static char *printname(char rname[1024], char name[1024]) {
char *p;
char *p1;
size_t j;
rname[0] = '\0';
//
p = &name[0];
if (*p != '[') {
return rname; // ""
}
p += 2;
p1 = rname;
for(j = 0; name[j] != '\0'; j++, p++) {
if (*p == '/')
*p1 = '.';
if (*p == ';') {
*p1 = '\0';
strcat(rname, ".class");
return rname;
} else
*p1 = *p;
p1++;
}
p1 -= 3;
*p1 = '\0';
return rname;
}

View File

@@ -1,70 +0,0 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 1998 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: Java classes parser .h */
/* Author: Yann Philippot */
/* ------------------------------------------------------------ */
#ifndef HTSJAVA_DEFH
#define HTSJAVA_DEFH
#include <stdint.h>
#ifndef HTS_DEF_FWSTRUCT_JAVA_HEADER
#define HTS_DEF_FWSTRUCT_JAVA_HEADER
typedef struct JAVA_HEADER JAVA_HEADER;
#endif
/* 10-byte on-disk .class header image, fread() directly: fields need exact
widths (LP64's 8-byte 'unsigned long' magic never matched 0xCAFEBABE). */
struct JAVA_HEADER {
uint32_t magic;
uint16_t minor;
uint16_t major;
uint16_t count;
};
#ifndef HTS_DEF_FWSTRUCT_RESP_STRUCT
#define HTS_DEF_FWSTRUCT_RESP_STRUCT
typedef struct RESP_STRUCT RESP_STRUCT;
#endif
struct RESP_STRUCT {
int file_position;
//
unsigned int index1;
unsigned int type;
char name[1024];
};
/* Library internal definictions */
#ifdef HTS_INTERNAL_BYTECODE
EXTERNAL_FUNCTION int hts_plug_java(httrackp * opt, const char *argv);
#endif
#endif

View File

@@ -1,373 +0,0 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Name="htsjava"
ProjectGUID="{2F353408-2C6C-4D31-9AC0-08BDC064DF64}"
RootNamespace="htsjava"
Keyword="Win32Proj"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="C:\temp\htsjava\Debug"
IntermediateDirectory="C:\temp\htsjava\Debug"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\openssl-1.0.1j\include"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="C:\temp\httrack\htsjava.dll"
LinkIncremental="2"
GenerateDebugInformation="true"
ProgramDatabaseFile="$(OutDir)/htsjava.pdb"
SubSystem="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
ImportLibrary="$(OutDir)/htsjava.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\openssl-1.0.1j\include"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="C:\temp\httrack64\htsjava.dll"
LinkIncremental="2"
GenerateDebugInformation="true"
ProgramDatabaseFile="$(OutDir)/htsjava.pdb"
SubSystem="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
ImportLibrary="$(OutDir)/htsjava.lib"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="C:\temp\htsjava\Release"
IntermediateDirectory="C:\temp\htsjava\Release"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories="C:\Dev\openssl-1.0.1j\include"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="O:\HTTrack\httrack\htsjava.dll"
LinkIncremental="1"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\htsjava.pdb"
SubSystem="2"
OptimizeReferences="2"
EnableCOMDATFolding="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
ImportLibrary="$(OutDir)/htsjava.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories="C:\Dev\openssl-1.0.1j\include"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="O:\HTTrack\httrack\x64\htsjava.dll"
LinkIncremental="1"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\x64\htsjava.pdb"
SubSystem="2"
OptimizeReferences="2"
EnableCOMDATFolding="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
ImportLibrary="$(OutDir)/htsjava.lib"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath="htsjava.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath="htsjava.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View File

@@ -48,6 +48,7 @@ Please visit our Website: http://www.httrack.com
#include "htsmodules.h"
#include "htscharset.h"
#include "htsencoding.h"
#include "htscodec.h"
#ifdef _WIN32
#include <direct.h>
@@ -648,165 +649,6 @@ T_SOC http_fopen(httrackp * opt, const char *adr, const char *fil, htsblk * reto
return http_xfopen(opt, 0, 1, 1, NULL, adr, fil, retour);
}
// Read a CRLF line from a non-blocking socket (waits up to timeout per recv).
// Returns the line length (0 = empty), or -1 on timeout/EOF/error.
static int proxy_getline(T_SOC soc, char *s, int max, int timeout) {
int j = 0;
for (;;) {
unsigned char ch;
int n;
if (!check_readinput_t(soc, timeout))
return -1; // timed out waiting for data
n = (int) recv(soc, &ch, 1, 0);
if (n == 1) {
if (ch == 13) // CR
continue;
if (ch == 10) // LF: end of line
break;
if (j >= max - 1)
return -1; // line too long: bound the read against a hostile proxy
s[j++] = (char) ch;
} else if (n == 0) {
return -1; // connection closed
} else {
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
continue;
#else
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
continue;
#endif
return -1;
}
}
s[j] = '\0';
return j;
}
int http_proxy_tunnel(httrackp *opt, htsblk *retour, const char *adr,
int timeout) {
const T_SOC soc = retour->soc;
const char *const host = jump_identification_const(adr); // host[:port]
const char *const portsep = jump_toport_const(adr); // ":port" or NULL
char BIGSTK authority[HTS_URLMAXSIZE * 2];
char BIGSTK req[HTS_URLMAXSIZE * 4 + 1100];
char line[1024];
int code;
if (soc == INVALID_SOCKET)
return 0;
// CONNECT needs an explicit host:port; default the https port
authority[0] = '\0';
if (portsep != NULL)
strlcatbuff(authority, host, sizeof(authority)); // already host:port
else
snprintf(authority, sizeof(authority), "%s:%d", host, 443);
// backstop: never let a stray CR/LF in the host smuggle a second line into
// the CONNECT request (the host is already sanitized upstream)
{
const char *c;
for (c = authority; *c != '\0'; c++) {
if ((unsigned char) *c < ' ') {
strcpybuff(retour->msg, "proxy CONNECT: invalid host");
return 0;
}
}
}
snprintf(req, sizeof(req), "CONNECT %s HTTP/1.0" H_CRLF "Host: %s" H_CRLF,
authority, authority);
// creds go on the CONNECT, not the tunneled origin request
if (link_has_authorization(retour->req.proxy.name)) {
const char *a = jump_identification_const(retour->req.proxy.name);
const char *astart = jump_protocol_const(retour->req.proxy.name);
char autorisation[1100];
char user_pass[256];
autorisation[0] = user_pass[0] = '\0';
strncatbuff(user_pass, astart, (int) (a - astart) - 1);
strcpybuff(user_pass, unescape_http(OPT_GET_BUFF(opt),
OPT_GET_BUFF_SIZE(opt), user_pass));
code64((unsigned char *) user_pass, (int) strlen(user_pass),
(unsigned char *) autorisation, 0);
strlcatbuff(req, "Proxy-Authorization: Basic ", sizeof(req));
strlcatbuff(req, autorisation, sizeof(req));
strlcatbuff(req, H_CRLF, sizeof(req));
}
strlcatbuff(req, H_CRLF, sizeof(req)); // end of request headers
// raw send: ssl is set, so sendc() would route to TLS
{
const char *p = req;
size_t remain = strlen(req);
int stalls = 0;
while (remain > 0) {
const int n = (int) send(soc, p, (int) remain, 0);
if (n > 0) {
p += n;
remain -= (size_t) n;
stalls = 0;
} else {
#ifdef _WIN32
const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK);
#else
const int wouldblock =
(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
#endif
// don't spin forever on a fatal error or an unwritable socket
if (!wouldblock || !check_writeinput_t(soc, timeout) ||
++stalls > 100) {
strcpybuff(retour->msg, "proxy CONNECT: write error");
return 0;
}
}
}
}
// proxy status line: "HTTP/1.x <code> ..."
if (proxy_getline(soc, line, sizeof(line), timeout) < 0) {
strcpybuff(retour->msg, "proxy CONNECT: no response");
return 0;
}
if (sscanf(line, "HTTP/%*d.%*d %d", &code) < 1)
code = 0;
if (code < 200 || code >= 300) {
snprintf(retour->msg, sizeof(retour->msg), "proxy CONNECT refused: %s",
strnotempty(line) ? line : "(no status)");
return 0;
}
// drain headers to the blank line; cap the count so a flooding proxy can't
// stall the crawl
{
int headers = 0;
for (;;) {
const int n = proxy_getline(soc, line, sizeof(line), timeout);
if (n < 0) {
strcpybuff(retour->msg, "proxy CONNECT: truncated response");
return 0;
}
if (n == 0)
break; // blank line: tunnel ready
if (++headers > 64) {
strcpybuff(retour->msg, "proxy CONNECT: too many response headers");
return 0;
}
}
}
return 1;
}
// ouverture d'une liaison http, envoi d'une requète
// mode: 0 GET 1 HEAD [2 POST]
// treat: traiter header?
@@ -1059,10 +901,10 @@ static int append_cookie_header(buff_struct *bstr, t_cookie *cookie,
return cook;
}
/* Self-test entry for append_cookie_header(): build the request Cookie line
into dst (always NUL-terminated). Returns the number of cookies emitted. */
int http_cookie_header_selftest(t_cookie *cookie, const char *domain,
const char *path, char *dst, size_t dst_size) {
/* Build the request Cookie line for domain/path into dst (always
NUL-terminated). Returns the number of cookies emitted. */
int http_cookie_header(t_cookie *cookie, const char *domain, const char *path,
char *dst, size_t dst_size) {
buff_struct bstr = {dst, dst_size, 0};
assertf(dst != NULL && dst_size > 0);
@@ -1105,8 +947,9 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
/* widths bound method[256], url[HTS_URLMAXSIZE*2], protocol[256] */
if (sscanf(line, "%255s %2047s %255s", method, url, protocol) == 3) {
size_t ret;
// selon que l'on a ou pas un proxy
if (retour->req.proxy.active) {
// absolute-URI for an http proxy; a socks tunnel takes origin-form
if (retour->req.proxy.active &&
!hts_proxy_is_socks(retour->req.proxy.name)) {
print_buffer(&bstr,
"%s http://%s%s %s\r\n", method, adr, url,
protocol);
@@ -1142,8 +985,10 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
print_buffer(&bstr, "HEAD ");
}
// si on gère un proxy, il faut une Absolute URI: on ajoute avant http://www.adr.dom
if (retour->req.proxy.active && (strncmp(adr, "https://", 8) != 0)) {
// an http proxy needs an absolute URI; a socks tunnel does not
if (retour->req.proxy.active &&
!hts_proxy_is_socks(retour->req.proxy.name) &&
(strncmp(adr, "https://", 8) != 0)) {
if (!link_has_authority(adr)) { // default http
#if HDEBUG
printf("Proxy Use: for %s%s proxy %d port %d\n", adr, fil,
@@ -1187,8 +1032,11 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
if (xsend)
print_buffer(&bstr, "%s", xsend); // éventuelles autres lignes
// for https, auth rides the CONNECT (the tunneled GET would leak it)
if (retour->req.proxy.active && strncmp(adr, "https://", 8) != 0) {
// for https, auth rides the CONNECT (the tunneled GET would leak it); for
// socks, the handshake
if (retour->req.proxy.active &&
!hts_proxy_is_socks(retour->req.proxy.name) &&
strncmp(adr, "https://", 8) != 0) {
if (link_has_authorization(retour->req.proxy.name)) { // et hop, authentification proxy!
const char *a = jump_identification_const(retour->req.proxy.name);
const char *astart = jump_protocol_const(retour->req.proxy.name);
@@ -1279,11 +1127,16 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
// Compression accepted ?
if (retour->req.http11) {
hts_boolean compressible = HTS_FALSE;
hts_boolean secure = HTS_FALSE;
#if HTS_USEZLIB
compressible = (!retour->req.range_used && !retour->req.nocompression);
#endif
#if HTS_USEOPENSSL
secure = retour->ssl ? HTS_TRUE : HTS_FALSE;
#endif
print_buffer(&bstr, "Accept-Encoding: %s" H_CRLF,
hts_acceptencoding(compressible));
hts_acceptencoding(compressible, secure));
}
/* Authentification */
@@ -1586,7 +1439,7 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
a = strchr(rcvd + p, '/');
if (a != NULL) {
a++;
if (sscanf(a, LLintP, &retour->crange) == 1) {
if (sscanf(a, LLintP, &retour->crange) == 1 && retour->crange >= 0) {
retour->crange_start = 0;
retour->crange_end = retour->crange - 1;
} else {
@@ -1594,6 +1447,12 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
}
}
}
// A valid Content-Range has no negative field; reject hostile values so
// the crange +/- 1 arithmetic downstream cannot sign-overflow (UB).
if (retour->crange_start < 0 || retour->crange_end < 0 ||
retour->crange < 0) {
retour->crange_start = retour->crange_end = retour->crange = 0;
}
}
} else if ((p = strfield(rcvd, "Connection:")) != 0) {
char *a = rcvd + p;
@@ -1662,22 +1521,10 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
strcpybuff(retour->contentencoding, tempo);
else
retour->contentencoding[0] = '\0';
#if HTS_USEZLIB
/* Check known encodings */
if (retour->contentencoding[0]) {
if ((strfield2(retour->contentencoding, "gzip"))
|| (strfield2(retour->contentencoding, "x-gzip"))
/*
|| (strfield2(retour->contentencoding, "compress"))
|| (strfield2(retour->contentencoding, "x-compress"))
*/
|| (strfield2(retour->contentencoding, "deflate"))
|| (strfield2(retour->contentencoding, "x-deflate"))
) {
retour->compressed = 1;
}
}
#endif
/* A coding to undo (or one we can not undo, which must fail the fetch
rather than save the coded bytes as the page) */
if (hts_codec_parse(retour->contentencoding) != HTS_CODEC_IDENTITY)
retour->compressed = 1;
}
} else if ((p = strfield(rcvd, "Location:")) != 0) {
if (retour) {
@@ -1702,6 +1549,9 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
#if DEBUG_COOK
printf("set-cookie detected\n");
#endif
/* Over-long host overflows the fail-safe domain[] copy (abort); drop it. */
if (adr && strlen(jump_identification_const(adr)) >= sizeof(domain))
return;
while(*a) {
char *token_st, *token_end;
char *value_st, *value_end;
@@ -1998,6 +1848,15 @@ LLint http_xfread1(htsblk * r, int bufl) {
if (bufl > 0) {
if (!r->is_write) { // stocker en mémoire
// In-memory content must fit a 32-bit index (allocs below add 1, reads
// use int offsets): reject a hostile Content-Length or endless stream.
const LLint inmem_want =
(r->totalsize >= 0) ? r->totalsize : (r->size + bufl);
if (inmem_want >= INT32_MAX) {
r->statuscode = STATUSCODE_INVALID;
strcpybuff(r->msg, "In-memory content too large");
return READ_ERROR;
}
if (r->totalsize >= 0) { // totalsize déterminé ET ALLOUE
if (r->adr == NULL) {
r->adr = (char *) malloct((size_t) r->totalsize + 1);
@@ -2560,6 +2419,11 @@ int ident_url_absolute(const char *url, lien_adrfil *adrfil) {
// recopier adrfil->adresse www..
strncatbuff(adrfil->adr, p, ((int) (q - p)));
// *( adrfil->adr+( ((int) q) - ((int) p) ) )=0; // faut arrêter la fumette!
// fil[] holds the path plus a possible leading '/' the top strlen() misses.
if (strlen(q) >= sizeof(adrfil->fil) - (q[0] != '/' ? 1 : 0))
return -1;
// recopier chemin /pub/..
if (q[0] != '/') // page par défaut (/)
strcatbuff(adrfil->fil, "/");
@@ -3849,6 +3713,10 @@ const char *jump_protocol_const(const char *source) {
source += p;
else if ((p = strfield(source, "file:")))
source += p;
else if ((p = strfield(source, "socks5h:")))
source += p;
else if ((p = strfield(source, "socks5:")))
source += p;
// net_path
if (strncmp(source, "//", 2) == 0)
source += 2;
@@ -3857,6 +3725,48 @@ const char *jump_protocol_const(const char *source) {
DECLARE_NON_CONST_VERSION(jump_protocol)
hts_boolean hts_proxy_is_socks(const char *name) {
if (name == NULL)
return HTS_FALSE;
return (strfield(name, "socks5h:") || strfield(name, "socks5:")) ? HTS_TRUE
: HTS_FALSE;
}
// default proxy port for a -P argument, keyed on the scheme
static int proxy_default_port(const char *arg) {
return hts_proxy_is_socks(arg) ? 1080 : 8080;
}
void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
const char *authority = strstr(arg, "://");
const char *a;
size_t namelen;
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
authority = (authority != NULL) ? authority + 3 : arg;
a = arg + strlen(arg);
while (a > authority && 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);
namelen = (size_t) (a - 1 - arg);
} else {
*port = proxy_default_port(arg);
namelen = strlen(arg);
}
if (namelen >= name_size) // arg is user-controlled: truncate, don't overflow
namelen = name_size - 1;
memcpy(name, arg, namelen);
name[namelen] = '\0';
}
// codage base 64 a vers b
void code64(unsigned char *a, int size_a, unsigned char *b, int crlf) {
int i1 = 0, i2 = 0, i3 = 0, i4 = 0;
@@ -4342,11 +4252,6 @@ HTSEXT_API void get_httptype(httrackp *opt, char *s, const char *fil,
(void) get_httptype_sized(opt, s, HTS_MIMETYPE_SIZE, fil, flag);
}
/* Advertised Accept-Encoding; gzip and deflate both decode via hts_zunpack */
const char *hts_acceptencoding(hts_boolean compressible) {
return compressible ? "gzip, deflate, identity;q=0.9" : "identity";
}
// get type of fil (php)
// s: buffer (text/html) or NULL
// return: 1 if known by user
@@ -4686,26 +4591,31 @@ int fexist_utf8(const char *s) {
return 0;
}
/* Taille d'un fichier, -1 si n'existe pas */
/* File size, -1 if absent */
/* Note: NOT utf-8 */
off_t fsize(const char *s) {
struct stat st;
LLint fsize(const char *s) {
STRUCT_STAT st;
if (!strnotempty(s)) // nom vide: erreur
if (!strnotempty(s)) // empty name: error
return -1;
/* _stat64, not stat(): the latter is 32-bit on MSVC */
#ifdef _WIN32
if (_stat64(s, &st) == 0 && S_ISREG(st.st_mode)) {
#else
if (stat(s, &st) == 0 && S_ISREG(st.st_mode)) {
#endif
return st.st_size;
} else {
return -1;
}
}
/* Taille d'un fichier, -1 si n'existe pas */
/* File size, -1 if absent */
/* Note: utf-8 */
off_t fsize_utf8(const char *s) {
LLint fsize_utf8(const char *s) {
STRUCT_STAT st;
if (!strnotempty(s)) // nom vide: erreur
if (!strnotempty(s)) // empty name: error
return -1;
if (STAT(s, &st) == 0 && S_ISREG(st.st_mode)) {
return st.st_size;
@@ -4714,24 +4624,17 @@ off_t fsize_utf8(const char *s) {
}
}
off_t fpsize(FILE * fp) {
off_t oldpos, size;
/* fseeko/ftello, never fseek/ftell: ftell returns long, which cannot carry an
offset past 2GB on a 32-bit platform and fails with EOVERFLOW there. */
LLint fpsize(FILE *fp) {
LLint oldpos, size;
if (!fp)
return -1;
#ifdef HTS_FSEEKO
oldpos = ftello(fp);
#else
oldpos = ftell(fp);
#endif
fseek(fp, 0, SEEK_END);
#ifdef HTS_FSEEKO
fseeko(fp, 0, SEEK_END);
size = ftello(fp);
fseeko(fp, oldpos, SEEK_SET);
#else
size = ftell(fp);
fseek(fp, oldpos, SEEK_SET);
#endif
return size;
}
@@ -4995,7 +4898,7 @@ static struct addrinfo *resolver_make_ai(const char *ip, int want_family) {
return ai;
}
static void override_freeaddrinfo(struct addrinfo *res) {
static void HTS_RESOLVER_CALL override_freeaddrinfo(struct addrinfo *res) {
while (res != NULL) {
struct addrinfo *const next = res->ai_next;
@@ -5005,9 +4908,10 @@ static void override_freeaddrinfo(struct addrinfo *res) {
}
}
static int override_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
static int HTS_RESOLVER_CALL override_getaddrinfo(const char *node,
const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
const char *const spec = getenv("HTTRACK_DEBUG_RESOLVE");
const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
const char *colon;
@@ -5903,17 +5807,7 @@ int multipleStringMatch(const char *s, const char *match) {
}
HTSEXT_API httrackp *hts_create_opt(void) {
#if ( defined(_WIN32) || defined(__ANDROID__) )
static const char *defaultModules[] = {
"htsswf", "htsjava", "httrack-plugin", NULL
};
#else
#ifndef HTS_LIBHTSJAVA_NAME
#define HTS_LIBHTSJAVA_NAME "libhtsjava.so" /* non-autoconf fallback */
#endif
static const char *defaultModules[] = {"libhtsswf.so.1", HTS_LIBHTSJAVA_NAME,
"httrack-plugin", NULL};
#endif
static const char *defaultModules[] = {"httrack-plugin", NULL};
httrackp *opt = malloc(sizeof(httrackp));
/* default options */
@@ -6504,13 +6398,13 @@ int hts_stat_utf8(const char *path, STRUCT_STAT * buf) {
LPWSTR wpath = hts_convertUTF8StringToUCS2(path, (int) strlen(path), NULL);
if (wpath != NULL) {
const int result = _wstat(wpath, buf);
const int result = _wstat64(wpath, buf);
free(wpath);
return result;
} else {
// Fallback on conversion error.
return _stat(path, buf);
return _stat64(path, buf);
}
}

View File

@@ -184,10 +184,10 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode, const char *xsend
const char *referer_adr, const char *referer_fil,
htsblk * retour);
/* Build the request "Cookie:" header line for stored cookies matching
domain/path into dst (NUL-terminated). Exposed for the -#Q self-test;
wraps the same logic http_sendhead() uses. Returns cookies emitted. */
int http_cookie_header_selftest(t_cookie *cookie, const char *domain,
const char *path, char *dst, size_t dst_size);
domain/path into dst (NUL-terminated), wrapping the logic http_sendhead()
uses. Returns cookies emitted. */
int http_cookie_header(t_cookie *cookie, const char *domain, const char *path,
char *dst, size_t dst_size);
T_SOC newhttp(httrackp * opt, const char *iadr, htsblk * retour, int port,
int waitconnect);
@@ -207,15 +207,9 @@ int check_readinput(htsblk * r);
int check_readinput_t(T_SOC soc, int timeout);
int check_writeinput_t(T_SOC soc, int timeout);
/* Open an HTTP CONNECT tunnel through the active proxy for an https request:
`retour->soc` must already be TCP-connected to the proxy, and `adr` is the
origin authority (url_adr, e.g. "https://host:port"). Sends the CONNECT
request (with Proxy-Authorization when the proxy carries credentials) and
reads the proxy's status line, so the caller's TLS handshake then runs
end-to-end with the origin. Blocks up to `timeout` seconds. Returns 1 on a
2xx tunnel, 0 on failure (retour->msg/statuscode set). */
int http_proxy_tunnel(httrackp *opt, htsblk *retour, const char *adr,
int timeout);
/* TRUE if this -P proxy name (which keeps its scheme) is a SOCKS5 proxy. */
hts_boolean hts_proxy_is_socks(const char *name);
void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * retour,
char *rcvd);
void treatfirstline(htsblk * retour, const char *rcvd);
@@ -283,15 +277,18 @@ int ishttperror(int err);
int get_userhttptype(httrackp * opt, char *s, const char *fil);
int give_mimext(char *s, size_t ssize, const char *st);
/* Advertised Accept-Encoding value (no header name/CRLF); see htslib.c. */
const char *hts_acceptencoding(hts_boolean compressible);
int may_bogus_multiple(httrackp * opt, const char *mime, const char *filename);
int may_unknown2(httrackp * opt, const char *mime, const char *filename);
const char *strrchr_limit(const char *s, char c, const char *limit);
char *jump_protocol(char *source);
const char *jump_protocol_const(const char *source);
/* Split a -P proxy argument "[scheme://][user:pass@]host[:port]" into the proxy
host string (scheme and any user:pass kept, for later stripping and auth),
written NUL-terminated into name[name_size] (truncated to fit), and the port
in *port. The port defaults by scheme: 1080 for socks5/socks5h, else 8080. */
void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port);
void code64(unsigned char *a, int size_a, unsigned char *b, int crlf);
#define copychar(catbuff,a) concat(catbuff,(a),NULL)
@@ -313,10 +310,11 @@ void cut_path(char *fullpath, char *path, size_t path_size, char *pname,
int fexist(const char *s);
int fexist_utf8(const char *s);
/*LLint fsize(const char* s); */
off_t fpsize(FILE * fp);
off_t fsize(const char *s);
off_t fsize_utf8(const char *s);
/* File size in bytes, -1 if absent or not a regular file. LLint, not off_t:
the latter is a 32-bit long on MSVC, truncating any file of 2GB or more. */
LLint fpsize(FILE *fp);
LLint fsize(const char *s);
LLint fsize_utf8(const char *s);
// Threads
typedef void *(*beginthread_type) (void *);
@@ -396,7 +394,8 @@ void *hts_get_callback(t_hts_htmlcheck_callbacks * callbacks,
HTSEXT_API FILE *hts_fopen_utf8(const char *path, const char *mode);
#define STAT hts_stat_utf8
typedef struct _stat STRUCT_STAT;
/* _stat64: _stat's st_size is a 32-bit long, even in x64 builds */
typedef struct _stat64 STRUCT_STAT;
HTSEXT_API int hts_stat_utf8(const char *path, STRUCT_STAT * buf);
#define UNLINK hts_unlink_utf8
@@ -493,6 +492,14 @@ HTS_STATIC int strcmpnocase(const char *a, const char *b) {
#define snprintf _snprintf
#endif
/* MSVC ships these POSIX functions under other names. Kept out of the installed
headers: they would rewrite the same identifiers in a consumer's own code. */
#ifdef _MSC_VER
#define fseeko _fseeki64
#define ftello _ftelli64
#define timegm _mkgmtime
#endif
#define strfield2(f,s) ( (strlen(f)!=strlen(s)) ? 0 : (strfield(f,s)) )
// is this MIME an hypertext MIME (text/html), html/js-style or other script/text type?
@@ -586,9 +593,9 @@ HTS_STATIC int compare_mime(httrackp * opt, const char *mime, const char *file,
#endif
// returns (size_t) -1 upon error
static HTS_UNUSED size_t off_t_to_size_t(off_t o) {
static HTS_UNUSED size_t llint_to_size_t(LLint o) {
const size_t so = (size_t) o;
if ((off_t) so == o) {
if ((LLint) so == o) {
return so;
} else {
return (size_t) -1;

View File

@@ -42,6 +42,7 @@ Please visit our Website: http://www.httrack.com
#include "htscharset.h"
#include "htsencoding.h"
#include "htssniff.h"
#include "htscodec.h"
#if HTS_USEZLIB
#include "htszlib.h"
#endif
@@ -151,29 +152,6 @@ typedef struct sniff_src {
const char *prev_save; /* previous run's save name (cache X-Save) */
} sniff_src;
#if HTS_USEZLIB
/* Inflate the head of a gzip/zlib stream; 0 when undecodable. */
static size_t sniff_inflate_head(const void *in, size_t in_len, void *out,
size_t out_len) {
z_stream zs;
size_t n = 0;
int err;
memset(&zs, 0, sizeof(zs));
if (inflateInit2(&zs, 47) != Z_OK) /* 47: gzip or zlib, autodetected */
return 0;
zs.next_in = (const Bytef *) in;
zs.avail_in = (uInt) in_len;
zs.next_out = (Bytef *) out;
zs.avail_out = (uInt) out_len;
err = inflate(&zs, Z_SYNC_FLUSH);
if (err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR)
n = out_len - zs.avail_out;
inflateEnd(&zs);
return n;
}
#endif
static size_t sniff_read_head(const char *path, void *buf, size_t len) {
char catbuff[CATBUFF_SIZE];
FILE *const fp = FOPEN(fconv(catbuff, sizeof(catbuff), path), "rb");
@@ -205,16 +183,12 @@ static size_t sniff_slot_head(const lien_back *slot, void *buf, size_t len) {
n = sniff_read_head(slot->tmpfile, buf, len);
}
if (n > 0 && r->compressed) {
#if HTS_USEZLIB
unsigned char raw[HTS_SNIFF_LEN];
if (n > sizeof(raw))
n = sizeof(raw);
memcpy(raw, buf, n);
n = sniff_inflate_head(raw, n, buf, len);
#else
n = 0;
#endif
n = hts_codec_head(hts_codec_parse(r->contentencoding), raw, n, buf, len);
}
return n;
}

View File

@@ -309,10 +309,19 @@ typedef socklen_t SOClen;
self-test can script DNS answers (families, multiplicity, errors)
in-process. The free function must match its getaddrinfo (a fake allocates
its own chain), hence the pair. */
/* Winsock's resolver is __stdcall; a plain pointer only compiles on x64, where
there is one convention. Backend implementations must carry this too. */
#ifdef _WIN32
#define HTS_RESOLVER_CALL WSAAPI
#else
#define HTS_RESOLVER_CALL
#endif
typedef struct hts_resolver_backend {
int (*getaddrinfo)(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
void (*freeaddrinfo)(struct addrinfo *res);
int(HTS_RESOLVER_CALL *getaddrinfo)(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
void(HTS_RESOLVER_CALL *freeaddrinfo)(struct addrinfo *res);
} hts_resolver_backend;
/** Install a resolver backend for the process; NULL restores the libc default.

View File

@@ -328,8 +328,8 @@ typedef enum hts_wizard {
typedef enum hts_robots {
HTS_ROBOTS_NEVER = 0, /**< ignore robots rules */
HTS_ROBOTS_SOMETIMES = 1, /**< partial obedience (default) */
HTS_ROBOTS_ALWAYS = 2, /**< obey robots rules */
HTS_ROBOTS_SOMETIMES = 1, /**< partial obedience, set by -s */
HTS_ROBOTS_ALWAYS = 2, /**< obey robots rules (default) */
HTS_ROBOTS_ALWAYS_STRICT = 3 /**< obey even strict rules */
} hts_robots;
#endif

View File

@@ -4311,6 +4311,8 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
*forbidden_url == 0 && IS_DELAYED_EXT(afs->save) && !opt->state.stop) {
int loops;
int continue_loop;
char BIGSTK
cookie_before[16384]; /* #15: this URL's Cookie header, pre-request */
hts_log_print(opt, LOG_DEBUG, "Waiting for type to be known: %s%s", afs->af.adr,
afs->af.fil);
@@ -4319,6 +4321,12 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
for(loops = 0, continue_loop = 1;
IS_DELAYED_EXT(afs->save) && continue_loop && loops < 7; loops++) {
continue_loop = 0;
/* #15: snapshot the Cookie header THIS url would send, so only its own
self-redirect Set-Cookie trips the retry, not a concurrent slot's. */
cookie_before[0] = '\0';
if (opt->accept_cookie && opt->cookie != NULL)
http_cookie_header(opt->cookie, jump_identification_const(afs->af.adr),
afs->af.fil, cookie_before, sizeof(cookie_before));
/* Wait for an available slot */
if (!hts_wait_available_socket(sback, opt, cache, ptr))
@@ -4516,6 +4524,17 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
/* Moved! */
else if (HTTP_IS_REDIRECT(back[b].r.statuscode)) {
char BIGSTK mov_url[HTS_URLMAXSIZE * 2];
char BIGSTK cookie_after[16384];
hts_boolean cookies_changed;
/* #15: cookie-wall signal: this URL's own Cookie header changed
across its self-redirect (a Set-Cookie it just set). */
cookie_after[0] = '\0';
if (opt->accept_cookie && opt->cookie != NULL)
http_cookie_header(
opt->cookie, jump_identification_const(afs->af.adr),
afs->af.fil, cookie_after, sizeof(cookie_after));
cookies_changed = strcmp(cookie_before, cookie_after) != 0;
mov_url[0] = '\0';
strcpybuff(mov_url, back[b].r.location); // copier URL
@@ -4585,11 +4604,24 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
url_savename(afs, former, heap(ptr)->adr, heap(ptr)->fil,
opt, sback, cache, hash, ptr, numero_passe,
&delayed_back);
} else if (cookies_changed) {
// #15: cookie-wall self-redirect; evict the cached
// fast-header so the re-issue refetches with the cookie.
if (cache->cached_tests != NULL)
coucal_remove(cache->cached_tests,
concat(OPT_GET_BUFF(opt),
OPT_GET_BUFF_SIZE(opt), afs->af.adr,
afs->af.fil));
afs->save[0] = '\0';
url_savename(afs, former, heap(ptr)->adr, heap(ptr)->fil, opt,
sback, cache, hash, ptr, numero_passe,
&delayed_back);
continue_loop = 1;
} else {
hts_log_print(opt, LOG_WARNING,
"Unable to test %s%s (loop to same filename)",
afs->af.adr, afs->af.fil);
} // loop to same location
} // loop to same location
} // ident_url_relatif()
} // location
} // redirect

539
src/htsproxy.c Normal file
View File

@@ -0,0 +1,539 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 2026 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: Proxy tunneling (HTTP CONNECT, SOCKS5) */
/* Author: Xavier Roche */
/* ------------------------------------------------------------ */
/* Internal engine bytecode */
#define HTS_INTERNAL_BYTECODE
#include "htscore.h"
#include "htslib.h"
#include "htsproxy.h"
#include <string.h>
// Read a CRLF line from a non-blocking socket (waits up to timeout per recv).
// Returns the line length (0 = empty), or -1 on timeout/EOF/error.
static int proxy_getline(T_SOC soc, char *s, int max, int timeout) {
int j = 0;
for (;;) {
unsigned char ch;
int n;
if (!check_readinput_t(soc, timeout))
return -1; // timed out waiting for data
n = (int) recv(soc, &ch, 1, 0);
if (n == 1) {
if (ch == 13) // CR
continue;
if (ch == 10) // LF: end of line
break;
if (j >= max - 1)
return -1; // line too long: bound the read against a hostile proxy
s[j++] = (char) ch;
} else if (n == 0) {
return -1; // connection closed
} else {
#ifdef _WIN32
if (WSAGetLastError() == WSAEWOULDBLOCK)
continue;
#else
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
continue;
#endif
return -1;
}
}
s[j] = '\0';
return j;
}
int http_proxy_tunnel(httrackp *opt, htsblk *retour, const char *adr,
int timeout) {
const T_SOC soc = retour->soc;
const char *const host = jump_identification_const(adr); // host[:port]
const char *const portsep = jump_toport_const(adr); // ":port" or NULL
char BIGSTK authority[HTS_URLMAXSIZE * 2];
char BIGSTK req[HTS_URLMAXSIZE * 4 + 1100];
char line[1024];
int code;
if (soc == INVALID_SOCKET)
return 0;
// CONNECT needs an explicit host:port; default the https port
authority[0] = '\0';
if (portsep != NULL)
strlcatbuff(authority, host, sizeof(authority)); // already host:port
else
snprintf(authority, sizeof(authority), "%s:%d", host, 443);
// backstop: never let a stray CR/LF in the host smuggle a second line into
// the CONNECT request (the host is already sanitized upstream)
{
const char *c;
for (c = authority; *c != '\0'; c++) {
if ((unsigned char) *c < ' ') {
strcpybuff(retour->msg, "proxy CONNECT: invalid host");
return 0;
}
}
}
snprintf(req, sizeof(req), "CONNECT %s HTTP/1.0" H_CRLF "Host: %s" H_CRLF,
authority, authority);
// creds go on the CONNECT, not the tunneled origin request
if (link_has_authorization(retour->req.proxy.name)) {
const char *a = jump_identification_const(retour->req.proxy.name);
const char *astart = jump_protocol_const(retour->req.proxy.name);
char autorisation[1100];
char user_pass[256];
autorisation[0] = user_pass[0] = '\0';
strncatbuff(user_pass, astart, (int) (a - astart) - 1);
strcpybuff(user_pass, unescape_http(OPT_GET_BUFF(opt),
OPT_GET_BUFF_SIZE(opt), user_pass));
code64((unsigned char *) user_pass, (int) strlen(user_pass),
(unsigned char *) autorisation, 0);
strlcatbuff(req, "Proxy-Authorization: Basic ", sizeof(req));
strlcatbuff(req, autorisation, sizeof(req));
strlcatbuff(req, H_CRLF, sizeof(req));
}
strlcatbuff(req, H_CRLF, sizeof(req)); // end of request headers
// raw send: ssl is set, so sendc() would route to TLS
{
const char *p = req;
size_t remain = strlen(req);
int stalls = 0;
while (remain > 0) {
const int n = (int) send(soc, p, (int) remain, 0);
if (n > 0) {
p += n;
remain -= (size_t) n;
stalls = 0;
} else {
#ifdef _WIN32
const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK);
#else
const int wouldblock =
(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
#endif
// don't spin forever on a fatal error or an unwritable socket
if (!wouldblock || !check_writeinput_t(soc, timeout) ||
++stalls > 100) {
strcpybuff(retour->msg, "proxy CONNECT: write error");
return 0;
}
}
}
}
// proxy status line: "HTTP/1.x <code> ..."
if (proxy_getline(soc, line, sizeof(line), timeout) < 0) {
strcpybuff(retour->msg, "proxy CONNECT: no response");
return 0;
}
if (sscanf(line, "HTTP/%*d.%*d %d", &code) < 1)
code = 0;
if (code < 200 || code >= 300) {
snprintf(retour->msg, sizeof(retour->msg), "proxy CONNECT refused: %s",
strnotempty(line) ? line : "(no status)");
return 0;
}
// drain headers to the blank line; cap the count so a flooding proxy can't
// stall the crawl
{
int headers = 0;
for (;;) {
const int n = proxy_getline(soc, line, sizeof(line), timeout);
if (n < 0) {
strcpybuff(retour->msg, "proxy CONNECT: truncated response");
return 0;
}
if (n == 0)
break; // blank line: tunnel ready
if (++headers > 64) {
strcpybuff(retour->msg, "proxy CONNECT: too many response headers");
return 0;
}
}
}
return 1;
}
/* SOCKS5 client (RFC 1928, RFC 1929 auth), hostname mode only: the proxy
resolves the origin name (remote DNS, curl's socks5h). The stream is the
proxy socket, or a scripted buffer under -#test=socks5. */
#define SOCKS5_VERSION 0x05
#define SOCKS5_AUTH_VERSION 0x01
#define SOCKS5_METHOD_NONE 0x00
#define SOCKS5_METHOD_USERPASS 0x02
#define SOCKS5_METHOD_NOACCEPTABLE 0xFF
#define SOCKS5_CMD_CONNECT 0x01
#define SOCKS5_ATYP_IPV4 0x01
#define SOCKS5_ATYP_DOMAIN 0x03
#define SOCKS5_ATYP_IPV6 0x04
#define SOCKS5_MAXFIELD 255 /* one length byte: host, user and password */
typedef struct socks5_stream {
T_SOC soc; /* INVALID_SOCKET when scripted (self-test) */
int timeout;
socks5_test_io *io;
} socks5_stream;
static int socks5_fail(char *msg, size_t msgsize, const char *text) {
if (msgsize != 0)
strlcpybuff(msg, text, msgsize);
return 0;
}
/* Read exactly n bytes, or fail: SOCKS frames are not self-delimiting, so a
short read would desync the stream shared with the origin traffic. */
static int socks5_read_n(socks5_stream *st, unsigned char *buf, size_t n) {
size_t got = 0;
int stalls = 0;
if (st->soc == INVALID_SOCKET) { /* scripted */
socks5_test_io *const io = st->io;
if (n > io->reply_len - io->consumed)
return 0;
memcpy(buf, io->reply + io->consumed, n);
io->consumed += n;
return 1;
}
while (got < n) {
const int r = (int) recv(st->soc, (char *) buf + got, (int) (n - got), 0);
if (r > 0) {
got += (size_t) r;
stalls = 0;
} else if (r == 0) {
return 0; // proxy closed mid-frame
} else {
#ifdef _WIN32
const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK);
#else
const int wouldblock =
(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
#endif
if (!wouldblock || !check_readinput_t(st->soc, st->timeout) ||
++stalls > 100)
return 0;
}
}
return 1;
}
static int socks5_write_all(socks5_stream *st, const unsigned char *buf,
size_t len) {
size_t remain = len;
int stalls = 0;
if (st->soc == INVALID_SOCKET) { /* scripted */
socks5_test_io *const io = st->io;
if (len > sizeof(io->sent) - io->sent_len)
return 0;
memcpy(io->sent + io->sent_len, buf, len);
io->sent_len += len;
return 1;
}
while (remain > 0) {
// raw send: the socket is still plain here, sendc() would route to TLS
const int n = (int) send(st->soc, (const char *) buf, (int) remain, 0);
if (n > 0) {
buf += n;
remain -= (size_t) n;
stalls = 0;
} else {
#ifdef _WIN32
const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK);
#else
const int wouldblock =
(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
#endif
if (!wouldblock || !check_writeinput_t(st->soc, st->timeout) ||
++stalls > 100)
return 0;
}
}
return 1;
}
static const char *socks5_rep_message(unsigned char rep) {
switch (rep) {
case 0x01:
return "general failure";
case 0x02:
return "connection not allowed by ruleset";
case 0x03:
return "network unreachable";
case 0x04:
return "host unreachable";
case 0x05:
return "connection refused";
case 0x06:
return "TTL expired";
case 0x07:
return "command not supported";
case 0x08:
return "address type not supported";
default:
return "unknown error";
}
}
/* The reply's BND.ADDR is variable-length: consume exactly what ATYP says, or
the leftover bytes prepend to the first origin (or TLS) read. */
static int socks5_read_reply(socks5_stream *st, char *msg, size_t msgsize) {
unsigned char head[4];
unsigned char addr[SOCKS5_MAXFIELD + 2];
size_t addrlen;
if (!socks5_read_n(st, head, sizeof(head)))
return socks5_fail(msg, msgsize, "SOCKS5: no reply from proxy");
if (head[0] != SOCKS5_VERSION)
return socks5_fail(msg, msgsize, "SOCKS5: bad version in reply");
if (head[1] != 0x00) {
snprintf(msg, msgsize, "SOCKS5 connect failed: %s",
socks5_rep_message(head[1]));
return 0;
}
switch (head[3]) {
case SOCKS5_ATYP_IPV4:
addrlen = 4;
break;
case SOCKS5_ATYP_IPV6:
addrlen = 16;
break;
case SOCKS5_ATYP_DOMAIN: {
unsigned char len;
if (!socks5_read_n(st, &len, 1))
return socks5_fail(msg, msgsize, "SOCKS5: truncated reply");
addrlen = len; // <= 255, always fits addr[]
break;
}
default: // unknown length: the stream cannot be resynchronized
return socks5_fail(msg, msgsize, "SOCKS5: unknown address type in reply");
}
if (addrlen != 0 && !socks5_read_n(st, addr, addrlen))
return socks5_fail(msg, msgsize, "SOCKS5: truncated reply address");
if (!socks5_read_n(st, addr, 2)) // BND.PORT, unused
return socks5_fail(msg, msgsize, "SOCKS5: truncated reply port");
return 1;
}
static int socks5_negotiate(socks5_stream *st, const char *host, size_t hostlen,
int port, const char *user, size_t userlen,
const char *pass, size_t passlen, int want_auth,
char *msg, size_t msgsize) {
unsigned char frame[3 + SOCKS5_MAXFIELD + SOCKS5_MAXFIELD];
unsigned char rep[2];
/* greeting */
frame[0] = SOCKS5_VERSION;
frame[1] = (unsigned char) (want_auth ? 2 : 1);
frame[2] = SOCKS5_METHOD_NONE;
frame[3] = SOCKS5_METHOD_USERPASS;
if (!socks5_write_all(st, frame, (size_t) 2 + frame[1]))
return socks5_fail(msg, msgsize, "SOCKS5: write error");
if (!socks5_read_n(st, rep, sizeof(rep)))
return socks5_fail(msg, msgsize, "SOCKS5: no method reply from proxy");
if (rep[0] != SOCKS5_VERSION)
return socks5_fail(msg, msgsize, "SOCKS5: bad version in method reply");
switch (rep[1]) {
case SOCKS5_METHOD_NONE:
break;
case SOCKS5_METHOD_USERPASS:
if (!want_auth)
return socks5_fail(msg, msgsize,
"SOCKS5: proxy requires authentication, none given");
/* RFC 1929 sub-negotiation; its version byte is 0x01, not 0x05 */
frame[0] = SOCKS5_AUTH_VERSION;
frame[1] = (unsigned char) userlen;
memcpy(frame + 2, user, userlen);
frame[2 + userlen] = (unsigned char) passlen;
memcpy(frame + 3 + userlen, pass, passlen);
if (!socks5_write_all(st, frame, 3 + userlen + passlen))
return socks5_fail(msg, msgsize, "SOCKS5: write error");
if (!socks5_read_n(st, rep, sizeof(rep)))
return socks5_fail(msg, msgsize, "SOCKS5: no authentication reply");
if (rep[1] != 0x00)
return socks5_fail(msg, msgsize, "SOCKS5: authentication failed");
break;
case SOCKS5_METHOD_NOACCEPTABLE:
return socks5_fail(
msg, msgsize,
"SOCKS5: proxy accepts no authentication method we offer");
default:
return socks5_fail(msg, msgsize,
"SOCKS5: proxy selected an unknown method");
}
/* CONNECT to the origin, by name */
frame[0] = SOCKS5_VERSION;
frame[1] = SOCKS5_CMD_CONNECT;
frame[2] = 0x00; // RSV
frame[3] = SOCKS5_ATYP_DOMAIN;
frame[4] = (unsigned char) hostlen;
memcpy(frame + 5, host, hostlen);
frame[5 + hostlen] = (unsigned char) (port >> 8);
frame[6 + hostlen] = (unsigned char) (port & 0xFF);
if (!socks5_write_all(st, frame, 7 + hostlen))
return socks5_fail(msg, msgsize, "SOCKS5: write error");
return socks5_read_reply(st, msg, msgsize);
}
/* Decode the proxy userinfo into the two RFC 1929 fields. Split on the first
colon of the still-escaped string, so a %3A stays inside the username. */
static int socks5_credentials(httrackp *opt, const char *proxy_name, char *user,
size_t user_size, size_t *userlen, char *pass,
size_t pass_size, size_t *passlen, char *msg,
size_t msgsize) {
const char *const a = jump_identification_const(proxy_name); // past the '@'
const char *const astart = jump_protocol_const(proxy_name);
char userinfo[1024];
char *colon;
if (a <= astart || (size_t) (a - astart) - 1 >= sizeof(userinfo))
return socks5_fail(msg, msgsize, "SOCKS5: credentials too long");
userinfo[0] = '\0';
strncatbuff(userinfo, astart, (int) (a - astart) - 1);
colon = strchr(userinfo, ':');
if (colon != NULL)
*colon++ = '\0';
strlcpybuff(
user, unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), userinfo),
user_size);
strlcpybuff(pass,
colon != NULL ? unescape_http(OPT_GET_BUFF(opt),
OPT_GET_BUFF_SIZE(opt), colon)
: "",
pass_size);
*userlen = strlen(user);
*passlen = strlen(pass);
// reject, never truncate: a clipped secret would authenticate as another one
if (*userlen > SOCKS5_MAXFIELD || *passlen > SOCKS5_MAXFIELD)
return socks5_fail(msg, msgsize, "SOCKS5: credentials too long");
if (*userlen == 0)
return socks5_fail(msg, msgsize, "SOCKS5: empty proxy username");
return 1;
}
static int socks5_handshake_stream(httrackp *opt, socks5_stream *st,
const char *adr, const char *proxy_name,
int ssl, char *msg, size_t msgsize) {
const char *const host = jump_identification_const(adr);
const char *const portsep = jump_toport_const(adr);
const size_t hostlen =
portsep != NULL ? (size_t) (portsep - host) : strlen(host);
// sized for the whole userinfo: decoding shrinks, so an over-long credential
// lands intact and is rejected below rather than silently clipped
char user[1024];
char pass[1024];
size_t userlen = 0, passlen = 0;
int want_auth = 0;
int port = ssl ? 443 : 80;
size_t i;
if (hostlen == 0 || hostlen > SOCKS5_MAXFIELD)
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin host");
if (host[0] == '[') // ATYP=domain cannot carry an IPv6 literal
return socks5_fail(msg, msgsize,
"SOCKS5: IPv6 literal origin is not supported");
for (i = 0; i < hostlen; i++) {
if ((unsigned char) host[i] < ' ')
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin host");
}
if (portsep != NULL) {
int p = -1;
sscanf(portsep + 1, "%d", &p);
if (p <= 0 || p > 65535)
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin port");
port = p;
}
if (link_has_authorization(proxy_name)) {
if (!socks5_credentials(opt, proxy_name, user, sizeof(user), &userlen, pass,
sizeof(pass), &passlen, msg, msgsize))
return 0;
want_auth = 1;
}
return socks5_negotiate(st, host, hostlen, port, user, userlen, pass, passlen,
want_auth, msg, msgsize);
}
int socks5_handshake(httrackp *opt, htsblk *retour, const char *adr,
int timeout) {
socks5_stream st;
#if HTS_USEOPENSSL
const int ssl = retour->ssl;
#else
const int ssl = 0;
#endif
if (retour->soc == INVALID_SOCKET)
return 0;
st.soc = retour->soc;
st.timeout = timeout;
st.io = NULL;
return socks5_handshake_stream(opt, &st, adr, retour->req.proxy.name, ssl,
retour->msg, sizeof(retour->msg));
}
int socks5_handshake_scripted(httrackp *opt, const char *adr,
const char *proxy_name, socks5_test_io *io) {
socks5_stream st;
st.soc = INVALID_SOCKET;
st.timeout = 0;
st.io = io;
io->consumed = 0;
io->sent_len = 0;
io->msg[0] = '\0';
return socks5_handshake_stream(opt, &st, adr, proxy_name, 0, io->msg,
sizeof(io->msg));
}

82
src/htsproxy.h Normal file
View File

@@ -0,0 +1,82 @@
/* ------------------------------------------------------------ */
/*
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 2026 Xavier Roche and other contributors
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Ethical use: we kindly ask that you NOT use this software to harvest email
addresses or to collect any other private information about people. Doing so
would dishonor our work and waste the many hours we have spent on it.
Please visit our Website: http://www.httrack.com
*/
/* ------------------------------------------------------------ */
/* File: Proxy tunneling (HTTP CONNECT, SOCKS5) .h */
/* Author: Xavier Roche */
/* ------------------------------------------------------------ */
#ifndef HTS_DEFPROXY
#define HTS_DEFPROXY
#include "htsglobal.h"
/* Forward definitions */
#ifndef HTS_DEF_FWSTRUCT_httrackp
#define HTS_DEF_FWSTRUCT_httrackp
typedef struct httrackp httrackp;
#endif
#ifndef HTS_DEF_FWSTRUCT_htsblk
#define HTS_DEF_FWSTRUCT_htsblk
typedef struct htsblk htsblk;
#endif
/* Open an HTTP CONNECT tunnel through the active proxy for an https request:
`retour->soc` must already be TCP-connected to the proxy, and `adr` is the
origin authority (url_adr, e.g. "https://host:port"). Sends the CONNECT
request (with Proxy-Authorization when the proxy carries credentials) and
reads the proxy's status line, so the caller's TLS handshake then runs
end-to-end with the origin. Blocks up to `timeout` seconds. Returns 1 on a
2xx tunnel, 0 on failure (retour->msg/statuscode set). */
int http_proxy_tunnel(httrackp *opt, htsblk *retour, const char *adr,
int timeout);
/* SOCKS5 (RFC 1928, RFC 1929 auth) handshake on the raw proxy socket, before
any TLS: `retour->soc` must be TCP-connected to the proxy and `adr` is the
origin (url_adr). The origin name is sent verbatim (ATYP=domain), so the
proxy resolves it; a bracketed IPv6 literal origin is rejected. Blocks up to
`timeout` seconds. Returns 1 with the stream positioned on the first origin
byte, 0 on failure (retour->msg set). */
int socks5_handshake(httrackp *opt, htsblk *retour, const char *adr,
int timeout);
/* Self-test hook (-#test=socks5): run the handshake against `reply`, a scripted
server byte stream, instead of a socket. `sent` captures the client frames,
`consumed` the reply bytes eaten (the frame-desync guard). */
typedef struct socks5_test_io {
const unsigned char *reply;
size_t reply_len;
size_t consumed;
unsigned char sent[1024];
size_t sent_len;
char msg[256];
} socks5_test_io;
int socks5_handshake_scripted(httrackp *opt, const char *adr,
const char *proxy_name, socks5_test_io *io);
#endif

View File

@@ -456,13 +456,6 @@ static HTS_INLINE HTS_UNUSED const char *htsbuff_str(const htsbuff *b) {
return b->buf;
}
/** True if 'count' records of >= 1 byte each fit in 'available' bytes; guards
an attacker-controlled count driving a large allocation. */
static HTS_INLINE HTS_UNUSED hts_boolean hts_count_fits(size_t count,
LLint available) {
return (available >= 0 && (LLint) count <= available) ? HTS_TRUE : HTS_FALSE;
}
/* Thin aliases over the libc allocator/memcpy (historical "t" suffix); no
added bounds checking. freet() also NULLs the freed pointer and tolerates
NULL. memcpybuff() despite the name is a raw memcpy: the caller owns the

View File

@@ -54,12 +54,18 @@ Please visit our Website: http://www.httrack.com
#include "htsftp.h"
#include "htsmd5.h"
#include "htssniff.h"
#include "htscodec.h"
#include "htsproxy.h"
#if HTS_USEZLIB
#include "htszlib.h"
#endif
#if HTS_USEZSTD
#include <zstd.h>
#endif
#include "coucal/coucal.h"
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -67,6 +73,9 @@ Please visit our Website: http://www.httrack.com
#ifndef _WIN32
#include <sys/socket.h>
#include <unistd.h>
#else
#include <io.h> /* _get_osfhandle, for the sparse-file hint */
#include <winioctl.h> /* FSCTL_SET_SPARSE */
#endif
/* very minimalistic internal tests */
@@ -740,6 +749,47 @@ static int st_filterdual(httrackp *opt, int argc, char **argv) {
return 0;
}
/* Length/work caps stop a hostile pattern stack-overflowing or hanging the
process (OSS-Fuzz 5060751291908096 / 5745936014573568). */
static int st_filterbounds(httrackp *opt, int argc, char **argv) {
const size_t big = 100000; /* well past the length cap */
const size_t stars = 1023; /* pattern len 2047, under the length cap */
const size_t subjlen = 2048;
char *subj = malloct(big + 1);
char *pat = malloct(2 * stars + 2);
size_t steps = 0, maxsteps = 0, depth = 0, maxdepth = 0, i;
(void) opt;
(void) argc;
(void) argv;
memset(subj, 'a', big);
subj[big] = '\0';
/* '*' matches anything, but an over-length subject trips the length cap */
assertf(strjoker(subj, "*", NULL, NULL) == NULL);
assertf(strjokerfind(subj, "*") == NULL);
/* Star-heavy dead-end at the length cap: unbounded it runs ~1.26e9 memo-steps
(~6s). */
for (i = 0; i < stars; i++) {
pat[2 * i] = '*';
pat[2 * i + 1] = 'a';
}
pat[2 * stars] = 'b'; /* never matches an all-'a' subject */
pat[2 * stars + 1] = '\0';
subj[subjlen] = '\0';
/* Budget must fire and hold: steps > cap (deleting the budget zeroes the
counter that is the enforcement), steps < 10*cap (unbudgeted ~1.26e9). */
assertf(strjoker_bounds(subj, pat, &steps, &maxsteps, &depth, &maxdepth) ==
NULL);
assertf(steps > maxsteps && steps < 10 * maxsteps);
/* Depth caps the stack: uncapped this recurses 2046 frames, ~900KB (#574). */
assertf(depth == maxdepth);
assertf(strjokerfind(subj, pat) == NULL);
freet(pat);
freet(subj);
printf("filterbounds: OK\n");
return 0;
}
static int st_simplify(httrackp *opt, int argc, char **argv) {
(void) opt;
if (argc < 1) {
@@ -770,7 +820,11 @@ static int st_mime(httrackp *opt, int argc, char **argv) {
return 0;
}
static size_t st_decode_body(const char *arg, char *buf, size_t size);
static int st_charset(httrackp *opt, int argc, char **argv) {
char buf[512];
size_t len;
char *s;
(void) opt;
@@ -778,7 +832,8 @@ static int st_charset(httrackp *opt, int argc, char **argv) {
fprintf(stderr, "charset: needs a charset and a string\n");
return 1;
}
s = hts_convertStringToUTF8(argv[1], strlen(argv[1]), argv[0]);
len = st_decode_body(argv[1], buf, sizeof(buf));
s = hts_convertStringToUTF8(buf, len, argv[0]);
if (s != NULL) {
printf("%s\n", s);
freet(s);
@@ -803,16 +858,22 @@ static int st_metacharset(httrackp *opt, int argc, char **argv) {
}
static int st_isutf8(httrackp *opt, int argc, char **argv) {
char buf[512];
size_t len;
(void) opt;
if (argc < 1) {
fprintf(stderr, "isutf8: needs a string\n");
return 1;
}
printf("%d\n", hts_isStringUTF8(argv[0], strlen(argv[0])) ? 1 : 0);
len = st_decode_body(argv[0], buf, sizeof(buf));
printf("%d\n", hts_isStringUTF8(buf, len) ? 1 : 0);
return 0;
}
static int st_idna_encode(httrackp *opt, int argc, char **argv) {
char buf[512];
size_t len;
char *s;
(void) opt;
@@ -820,7 +881,8 @@ static int st_idna_encode(httrackp *opt, int argc, char **argv) {
fprintf(stderr, "idna-encode: needs a hostname\n");
return 1;
}
s = hts_convertStringUTF8ToIDNA(argv[0], strlen(argv[0]));
len = st_decode_body(argv[0], buf, sizeof(buf));
s = hts_convertStringUTF8ToIDNA(buf, len);
if (s != NULL) {
printf("%s\n", s);
freet(s);
@@ -982,7 +1044,7 @@ static int st_hashtable(httrackp *opt, int argc, char **argv) {
/* produce random patterns, or read from a file */
if (sscanf(snum, "%lu", &count) != 1) {
const off_t size = fsize(snum);
const LLint size = fsize(snum);
FILE *fp = fopen(snum, "rb");
if (fp != NULL) {
buff = malloct(size);
@@ -1082,7 +1144,10 @@ static int st_strsafe(httrackp *opt, int argc, char **argv) {
comes from argv so its length is opaque to the compiler (no static
-Wstringop-overflow, genuine runtime check). "overflow-buff" exercises
htsbuff. */
char small[4];
/* Not sizeof(char*): on ILP32 a char[4] equals the pointer size, and the
MSVC array-vs-pointer heuristic (sizeof(A) != sizeof(char*)) then reads
it as a pointer and silently skips the bound. */
char small[6];
const char *const src = (argc >= 2) ? argv[1] : "overflowing";
if (strcmp(argv[0], "overflow-buff") == 0) {
@@ -1266,6 +1331,186 @@ static int st_identurl(httrackp *opt, int argc, char **argv) {
return 0;
}
static int st_proxyurl(httrackp *opt, int argc, char **argv) {
char BIGSTK name[HTS_URLMAXSIZE * 2];
int port = -1;
(void) opt;
if (argc < 1) {
fprintf(stderr, "proxyurl: needs a proxy argument\n");
return 1;
}
hts_parse_proxy(argv[0], name, sizeof(name), &port);
// host= is what the connect actually resolves (scheme + user:pass stripped)
printf("name=%s port=%d host=%s\n", name, port,
jump_identification_const(name));
return 0;
}
/* Scripted SOCKS5 server: no-auth method reply, then a success CONNECT reply
carrying the given ATYP address, then a sentinel origin byte the handshake
must leave unread (an off-by-one in the reply framing eats it). */
static size_t socks5_reply(unsigned char *buf, unsigned char atyp,
const unsigned char *addr, size_t addrlen) {
size_t n = 0;
buf[n++] = 0x05;
buf[n++] = 0x00; /* method: no authentication */
buf[n++] = 0x05;
buf[n++] = 0x00; /* REP: succeeded */
buf[n++] = 0x00;
buf[n++] = atyp;
memcpy(buf + n, addr, addrlen);
n += addrlen;
buf[n++] = 0x1f;
buf[n++] = 0x90; /* BND.PORT */
buf[n++] = 0xAA; /* sentinel */
return n;
}
#define SOCKS5_SENTINEL_LEFT(io, len) ((io).consumed == (len) - 1)
static int st_socks5(httrackp *opt, int argc, char **argv) {
static const unsigned char v4[4] = {127, 0, 0, 1};
static const unsigned char v6[16] = {0};
static const unsigned char domain[6] = {5, 'p', 'r', 'o', 'x', 'y'};
const char *const proxy = "socks5://127.0.0.1";
unsigned char script[64];
socks5_test_io io;
size_t len;
(void) argc;
(void) argv;
/* each reply address type is drained exactly, sentinel untouched */
len = socks5_reply(script, 0x01, v4, sizeof(v4));
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 1);
assertf(SOCKS5_SENTINEL_LEFT(io, len));
/* the greeting offers no-auth only, and the origin goes out by name, port 80
*/
assertf(io.sent_len == 3 + 7 + 11);
assertf(memcmp(io.sent, "\x05\x01\x00", 3) == 0);
assertf(memcmp(io.sent + 3, "\x05\x01\x00\x03\x0borigin.test\x00\x50", 18) ==
0);
len = socks5_reply(script, 0x04, v6, sizeof(v6));
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 1);
assertf(SOCKS5_SENTINEL_LEFT(io, len));
len = socks5_reply(script, 0x03, domain, sizeof(domain));
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 1);
assertf(SOCKS5_SENTINEL_LEFT(io, len));
/* an unknown address type has no known length: fail, never guess */
len = socks5_reply(script, 0x02, v4, sizeof(v4));
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 0);
/* truncated frames (here: no BND.PORT) fail instead of over-reading */
len = socks5_reply(script, 0x01, v4, sizeof(v4)) - 3;
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 0);
/* explicit origin port is encoded big-endian (8443 = 0x20fb) */
len = socks5_reply(script, 0x01, v4, sizeof(v4));
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test:8443", proxy, &io) == 1);
assertf(memcmp(io.sent + io.sent_len - 2, "\x20\xfb", 2) == 0);
/* credentials: split on the first colon of the escaped userinfo, so %3a stays
inside the username and a colon in the password is not a delimiter */
{
static const unsigned char auth_script[] = {
0x05, 0x02, /* method: user/pass */
0x01, 0x00, /* auth: success */
0x05, 0x00, 0x00, 0x01, /* REP: succeeded, ATYP ipv4 */
127, 0, 0, 1, 0x1f, 0x90, 0xAA};
io.reply = auth_script;
io.reply_len = sizeof(auth_script);
assertf(socks5_handshake_scripted(opt, "origin.test",
"socks5://us%3aer:p:ass@127.0.0.1",
&io) == 1);
assertf(SOCKS5_SENTINEL_LEFT(io, sizeof(auth_script)));
assertf(memcmp(io.sent, "\x05\x02\x00\x02", 4) == 0);
assertf(memcmp(io.sent + 4, "\x01\x05us:er\x05p:ass", 13) == 0);
}
/* a proxy demanding auth we cannot provide, and one refusing every method */
io.reply = (const unsigned char *) "\x05\x02";
io.reply_len = 2;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 0);
io.reply = (const unsigned char *) "\x05\xff";
io.reply_len = 2;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 0);
/* a refused CONNECT is an error, not a tunnel */
io.reply = (const unsigned char
*) "\x05\x00\x05\x05\x00\x01\x7f\x00\x00\x01\x1f\x90";
io.reply_len = 12;
assertf(socks5_handshake_scripted(opt, "origin.test", proxy, &io) == 0);
/* over-long host or credentials are rejected before anything is sent */
{
char host[512];
char name[1024];
size_t i;
host[0] = '\0';
for (i = 0; i < 256; i++)
strcatbuff(host, "a");
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, host, proxy, &io) == 0);
assertf(io.sent_len == 0);
strcpybuff(name, "socks5://user:");
for (i = 0; i < 256; i++)
strcatbuff(name, "p");
strcatbuff(name, "@127.0.0.1");
io.reply = script;
io.reply_len = len;
assertf(socks5_handshake_scripted(opt, "origin.test", name, &io) == 0);
assertf(io.sent_len == 0);
}
printf("socks5 self-test OK\n");
return 0;
}
/* Regression for the one-byte fil[] overflow: a 2047-byte hostless "?"-URL used
to abort in strncat_safe_ when the missing leading '/' pushed fil to 2048. */
static int st_identabs(httrackp *opt, int argc, char **argv) {
lien_adrfil af;
const size_t len =
sizeof(af.fil) - 1; /* 2047: max URL the top guard admits */
char *url = malloct(len + 1);
(void) opt;
(void) argc;
(void) argv;
url[0] = '?';
memset(url + 1, 'a', len - 1);
url[len] = '\0';
assertf(ident_url_absolute(url, &af) == -1);
freet(url);
/* valid URLs still parse, so the guard is not over-rejecting */
assertf(ident_url_absolute("http://www.example.com/a/b/c.html?x=1", &af) ==
0);
assertf(ident_url_absolute("www.foo.com?bar=1", &af) == 0);
printf("identabs 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). */
@@ -1310,7 +1555,84 @@ static int st_headerlong(httrackp *opt, int argc, char **argv) {
return 0;
}
/* Decode a body argument ("hex:FFD8.." or literal text) into buf. */
/* http_xfread1 must refuse an in-memory buffer whose size would exceed a 32-bit
index (hostile Content-Length or endless stream) rather than allocate it.
The guard returns before any socket read, so no real connection is needed. */
static int st_xfread_limit(httrackp *opt, int argc, char **argv) {
htsblk r;
(void) opt;
(void) argc;
(void) argv;
// Content-Length just over 2 GiB.
memset(&r, 0, sizeof(r));
r.soc = INVALID_SOCKET;
r.totalsize = (LLint) INT32_MAX + 1;
printf("bylen: refused=%d adr=%s msg=%s\n",
http_xfread1(&r, 8192) == READ_ERROR, r.adr != NULL ? "alloc" : "null",
r.msg);
if (r.adr != NULL)
freet(r.adr);
// Unknown length, buffer already at the limit: the next read would exceed it.
memset(&r, 0, sizeof(r));
r.soc = INVALID_SOCKET;
r.totalsize = -1;
r.size = (LLint) INT32_MAX;
printf("bygrow: refused=%d adr=%s msg=%s\n",
http_xfread1(&r, 8192) == READ_ERROR, r.adr != NULL ? "alloc" : "null",
r.msg);
if (r.adr != NULL)
freet(r.adr);
// Exactly at the 2 GiB index (size + bufl == INT32_MAX): must also be
// refused, since the reallocs below add 1 (a `> INT32_MAX` check would let
// this through and overflow the int realloc size).
memset(&r, 0, sizeof(r));
r.soc = INVALID_SOCKET;
r.totalsize = -1;
r.size = (LLint) INT32_MAX - 8192;
http_xfread1(&r, 8192);
printf("boundary: msg=%s\n", r.msg);
// A legitimate small size must NOT be refused by the guard (the read then
// fails on the invalid socket, but the size-too-large msg must not be set).
memset(&r, 0, sizeof(r));
r.soc = INVALID_SOCKET;
r.totalsize = 1000;
http_xfread1(&r, 8192);
printf("accept: msg=%s\n", r.msg);
if (r.adr != NULL)
freet(r.adr);
return 0;
}
/* Parse a Content-Range header and print the sanitized triple. A hostile value
(negative or INT64 extreme) must clamp to 0 without signed-overflow UB. */
static int st_crange(httrackp *opt, int argc, char **argv) {
int i;
(void) opt;
if (argc < 1) {
fprintf(stderr, "crange: needs at least one raw Content-Range line\n");
return 1;
}
for (i = 0; i < argc; i++) {
htsblk r;
char BIGSTK line[HTS_URLMAXSIZE * 2];
memset(&r, 0, sizeof(r));
strcpybuff(line, argv[i]);
treathead(NULL, "www.example.com", "/", &r, line);
printf("crange_start=" LLintP " crange_end=" LLintP " crange=" LLintP "\n",
(LLint) r.crange_start, (LLint) r.crange_end, (LLint) r.crange);
}
return 0;
}
/* Decode an argument ("hex:FFD8.." or literal text) into buf. Raw non-UTF-8
bytes cannot survive a Windows command line, so hostile inputs go as hex. */
static size_t st_decode_body(const char *arg, char *buf, size_t size) {
size_t n = 0;
@@ -1350,6 +1672,94 @@ static int st_sniff(httrackp *opt, int argc, char **argv) {
return 0;
}
/* fsize()/fsize_utf8()/fpsize() must report a size past 4GB: 32-bit wraps both
ways there (MSVC's off_t and struct _stat st_size are long, 32-bit even on
x64), and a size under 4GB would survive an *unsigned* 32-bit truncation. */
static int st_fsize(httrackp *opt, int argc, char **argv) {
const LLint expected = 5 * 1024 * 1024 * 1024LL;
char BIGSTK path[HTS_URLMAXSIZE * 2];
char BIGSTK absent[HTS_URLMAXSIZE * 2];
/* both variants: only fsize() feeds the >2GB readers (file://, --list) */
const int width = (int) sizeof(fsize(""));
const int width_utf8 = (int) sizeof(fsize_utf8(""));
FILE *fp;
LLint got, got_utf8, gotp, gone;
int rc = 0;
(void) opt;
if (argc < 1) {
fprintf(stderr, "fsize: needs a directory\n");
return 1;
}
concat(path, sizeof(path), argv[0], "/sparse-5g.bin");
concat(absent, sizeof(absent), argv[0], "/no-such-file.bin");
/* sparse: seek past 4GB and write the last byte */
fp = FOPEN(path, "wb");
if (fp == NULL) {
fprintf(stderr, "fsize: cannot create '%s': %s\n", path, strerror(errno));
return 1;
}
#ifdef _WIN32
{
/* NTFS allocates the hole unless asked not to; POSIX gives it for free.
Best-effort: a non-sparse file still measures the same, just costs 5GB.
*/
HANDLE h = (HANDLE) _get_osfhandle(_fileno(fp));
DWORD ret;
if (h != INVALID_HANDLE_VALUE)
(void) DeviceIoControl(h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &ret, NULL);
}
#endif
if (fseeko(fp, expected - 1, SEEK_SET) != 0 || fputc(0, fp) == EOF ||
fclose(fp) != 0) {
fprintf(stderr, "fsize: cannot extend '%s' to " LLintP ": %s\n", path,
expected, strerror(errno));
UNLINK(path);
return 1;
}
got = fsize(path);
got_utf8 = fsize_utf8(path);
fp = FOPEN(path, "rb");
if (fp == NULL) {
fprintf(stderr, "fsize: cannot reopen '%s': %s\n", path, strerror(errno));
gotp = -1;
} else {
gotp = fpsize(fp);
fclose(fp);
}
UNLINK(path);
gone = fsize(absent); /* contract: -1, not 0, when absent */
printf("fsize: width=%d,%d size=" LLintP "," LLintP " psize=" LLintP
" absent=" LLintP "\n",
width, width_utf8, got, got_utf8, gotp, gone);
if (width != 8 || width_utf8 != 8) {
fprintf(stderr, "fsize: return types are %d/%d bytes, expected 8\n", width,
width_utf8);
rc = 1;
}
if (got != expected || got_utf8 != expected) {
fprintf(stderr,
"fsize: fsize/fsize_utf8 are " LLintP "/" LLintP
", expected " LLintP "\n",
got, got_utf8, expected);
rc = 1;
}
if (gotp != expected) {
fprintf(stderr, "fsize: fpsize is " LLintP ", expected " LLintP "\n", gotp,
expected);
rc = 1;
}
if (gone != -1) {
fprintf(stderr, "fsize: absent file is " LLintP ", expected -1\n", gone);
rc = 1;
}
return rc;
}
static int st_savename(httrackp *opt, int argc, char **argv) {
lien_adrfilsave afs;
cache_back cache;
@@ -1679,9 +2089,30 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
return 1;
}
http_cookie_header_selftest(&cookie, dom, "/", hdr, sizeof(hdr));
http_cookie_header(&cookie, dom, "/", hdr, sizeof(hdr));
if (strcmp(hdr, expected) != 0)
err = 1;
/* A hostile over-long request host must not overflow domain[256] in
treathead's default-domain copy (that would abort the mirror). */
{
static t_cookie ck2;
htsblk r;
char host[600];
memset(&r, 0, sizeof(r));
memset(host, 'a', sizeof(host) - 1);
host[sizeof(host) - 1] = '\0';
ck2.max_len = (int) sizeof(ck2.data);
ck2.data[0] = '\0';
treathead(&ck2, host, "/", &r, "Set-Cookie: SID=1; path=/");
if (strnotempty(ck2.data)) // oversize-host cookie was not dropped
err = 1;
/* control: a normal host still yields a cookie through treathead */
treathead(&ck2, dom, "/", &r, "Set-Cookie: SID=1; path=/");
if (strstr(ck2.data, "SID") == NULL) // guard wrongly dropped a valid cookie
err = 1;
}
if (strstr(hdr, "$Version") != NULL || strstr(hdr, "$Path") != NULL)
err = 1;
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
@@ -2146,6 +2577,7 @@ static int ae_write_collision(const char *path, const unsigned char *src,
freet(buf);
return ok ? 0 : 1;
}
#endif
/* Write src[0..len) to path as-is; 0 on success. */
static int ae_write_raw(const char *path, const unsigned char *src,
@@ -2179,17 +2611,22 @@ static int ae_check_decoded(const char *path, const unsigned char *expect,
fclose(f);
return (off == len) ? 0 : 1;
}
#endif
/* Accept-Encoding (#450): advertise gzip+deflate; both decode (hts_zunpack) */
static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
const char *off = hts_acceptencoding(HTS_FALSE);
const char *on = hts_acceptencoding(HTS_TRUE);
const char *off = hts_acceptencoding(HTS_FALSE, HTS_TRUE);
const char *on = hts_acceptencoding(HTS_TRUE, HTS_FALSE);
const char *tls = hts_acceptencoding(HTS_TRUE, HTS_TRUE);
(void) opt;
assertf(strcmp(off, "identity") == 0);
assertf(strstr(on, "gzip") != NULL);
assertf(strstr(on, "deflate") != NULL); /* fails on the old gzip-only list */
/* br and zstd ride on TLS only, so a cleartext proxy can not be handed a
coding it may try to rewrite */
assertf(strstr(on, "br") == NULL && strstr(on, "zstd") == NULL);
assertf((strstr(tls, ", br") != NULL) == (HTS_USEBROTLI != 0));
assertf((strstr(tls, "zstd") != NULL) == (HTS_USEZSTD != 0));
#if HTS_USEZLIB
if (argc >= 1) {
static const int windowBits[] = {16 + MAX_WBITS, MAX_WBITS, -MAX_WBITS};
@@ -2277,6 +2714,115 @@ static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
return 0;
}
#if HTS_USEBROTLI
/* No brotli encoder is linked, so the coded bytes are canned: brotli quality 9
over cc_text, and over 4 MiB of 'A' (14 bytes, ~300000x). */
static const unsigned char cc_br_text[] = {
0x1b, 0x43, 0x00, 0x00, 0x44, 0xdd, 0x96, 0xea, 0xe8, 0x22, 0xdd, 0x90,
0xa4, 0x1b, 0x8a, 0xf7, 0x47, 0x0e, 0xc2, 0xc5, 0x3d, 0x09, 0x1b, 0x70,
0xe0, 0x1e, 0x60, 0xa0, 0x8b, 0xcc, 0xbe, 0xcb, 0xb0, 0x31, 0x76, 0x9e,
0xcf, 0x6e, 0x41, 0xb5, 0xe8, 0x2e, 0x56, 0x78, 0x08, 0x1b, 0xfa, 0x08,
0x8a, 0x50, 0x83, 0x4e, 0x62, 0x7f, 0xbf, 0x05, 0xf2, 0x22, 0x8f, 0xdf,
0x28, 0xdc, 0x9f, 0xa9, 0x90, 0x50, 0x37, 0x62, 0x56, 0x4f, 0xa8};
static const unsigned char cc_br_bomb[] = {0x9b, 0xff, 0xff, 0x3f, 0x00,
0x24, 0x82, 0xe2, 0xb1, 0x40,
0x72, 0xef, 0x7f, 0x00};
#endif
static const unsigned char cc_text[] =
"content codings: HTTrack decodes gzip, brotli and zstd bodies alike.";
/* Content codings: br and zstd decode, junk tokens stay identity, a coding we
can not undo fails the fetch, and a bomb never lands on disk. */
static int st_contentcodings(httrackp *opt, int argc, char **argv) {
const size_t tlen = sizeof(cc_text) - 1;
char inpath[HTS_URLMAXSIZE], outpath[HTS_URLMAXSIZE];
(void) opt;
assertf(hts_codec_parse("gzip") == HTS_CODEC_DEFLATE);
assertf(hts_codec_parse("x-deflate") == HTS_CODEC_DEFLATE);
assertf(hts_codec_parse("") == HTS_CODEC_IDENTITY);
assertf(hts_codec_parse("identity") == HTS_CODEC_IDENTITY);
/* servers do label plain bodies with junk; the page must survive that */
assertf(hts_codec_parse("utf-8") == HTS_CODEC_IDENTITY);
/* a real coding with no decoder here: fail, never save the coded bytes */
assertf(hts_codec_parse("compress") == HTS_CODEC_UNSUPPORTED);
assertf(hts_codec_parse("br") ==
(HTS_USEBROTLI ? HTS_CODEC_BROTLI : HTS_CODEC_UNSUPPORTED));
assertf(hts_codec_parse("zstd") ==
(HTS_USEZSTD ? HTS_CODEC_ZSTD : HTS_CODEC_UNSUPPORTED));
assertf(hts_codec_is_archive_ext(HTS_CODEC_DEFLATE, "tgz"));
assertf(!hts_codec_is_archive_ext(HTS_CODEC_DEFLATE, "html"));
assertf(hts_codec_is_archive_ext(HTS_CODEC_BROTLI, "br"));
assertf(hts_codec_is_archive_ext(HTS_CODEC_ZSTD, "zst"));
/* decoded-size budget: 4096x, floor 1 MiB, ceiling INT_MAX */
assertf(hts_codec_maxout(1) == 1024 * 1024);
assertf(hts_codec_maxout(1024) == 4096 * 1024);
assertf(hts_codec_maxout(1024 * 1024) == INT_MAX);
if (argc >= 1) {
snprintf(inpath, sizeof(inpath), "%s/cc-in", argv[0]);
snprintf(outpath, sizeof(outpath), "%s/cc-out", argv[0]);
#if HTS_USEBROTLI
{
unsigned char head[16];
assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text)) == 0);
assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) ==
(int) tlen);
assertf(ae_check_decoded(outpath, cc_text, tlen) == 0);
assertf(hts_codec_head(HTS_CODEC_BROTLI, cc_br_text, sizeof(cc_br_text),
head, sizeof(head)) == sizeof(head));
assertf(memcmp(head, cc_text, sizeof(head)) == 0);
/* truncated: must fail, not fall back to a verbatim copy */
assertf(ae_write_raw(inpath, cc_br_text, sizeof(cc_br_text) - 4) == 0);
assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) < 0);
/* cc_br_bomb is a valid stream that expands to 4 MiB; the budget, not a
corrupt frame, is what must reject it. */
assertf((LLint) (4 * 1024 * 1024) >
hts_codec_maxout((LLint) sizeof(cc_br_bomb)));
assertf(ae_write_raw(inpath, cc_br_bomb, sizeof(cc_br_bomb)) == 0);
assertf(hts_codec_unpack(HTS_CODEC_BROTLI, inpath, outpath) < 0);
}
#endif
#if HTS_USEZSTD
{
const size_t bomblen = 4 * 1024 * 1024;
const size_t bound = ZSTD_compressBound(bomblen);
unsigned char *bomb = malloct(bomblen);
unsigned char *zbuf = malloct(bound);
unsigned char head[16];
size_t zlen;
assertf(bomb != NULL && zbuf != NULL);
zlen = ZSTD_compress(zbuf, bound, cc_text, tlen, 6);
assertf(!ZSTD_isError(zlen));
assertf(ae_write_raw(inpath, zbuf, zlen) == 0);
assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) == (int) tlen);
assertf(ae_check_decoded(outpath, cc_text, tlen) == 0);
assertf(hts_codec_head(HTS_CODEC_ZSTD, zbuf, zlen, head, sizeof(head)) ==
sizeof(head));
assertf(memcmp(head, cc_text, sizeof(head)) == 0);
assertf(ae_write_raw(inpath, zbuf, zlen - 4) == 0);
assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) < 0);
memset(bomb, 'A', bomblen);
zlen = ZSTD_compress(zbuf, bound, bomb, bomblen, 6);
assertf(!ZSTD_isError(zlen));
/* the fixture must really be past the budget, whatever the ratio is */
assertf((LLint) bomblen > hts_codec_maxout((LLint) zlen));
assertf(ae_write_raw(inpath, zbuf, zlen) == 0);
assertf(hts_codec_unpack(HTS_CODEC_ZSTD, inpath, outpath) < 0);
freet(bomb);
freet(zbuf);
}
#endif
/* a coding we can not undo yields no file at all */
assertf(hts_codec_unpack(HTS_CODEC_UNSUPPORTED, inpath, outpath) < 0);
}
printf("contentcodings self-test OK\n");
return 0;
}
/* Each call parses `txt` under a fresh host, then checkrobots() for `path`. */
static int rb_decide(robots_wizard *r, const char *txt, const char *path) {
static int n = 0;
@@ -2360,11 +2906,43 @@ static int st_robots(httrackp *opt, int argc, char **argv) {
return 0;
}
/* Connected stream pair over loopback; Windows has no socketpair(). */
static int st_socketpair(T_SOC sv[2]) {
struct sockaddr_in sa;
socklen_t len = sizeof(sa);
T_SOC srv, cli, acc;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if ((srv = (T_SOC) socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
return -1;
if (bind(srv, (struct sockaddr *) &sa, sizeof(sa)) != 0 ||
listen(srv, 1) != 0 ||
getsockname(srv, (struct sockaddr *) &sa, &len) != 0) {
deletesoc(srv);
return -1;
}
if ((cli = (T_SOC) socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
deletesoc(srv);
return -1;
}
if (connect(cli, (struct sockaddr *) &sa, sizeof(sa)) != 0 ||
(acc = (T_SOC) accept(srv, NULL, NULL)) == INVALID_SOCKET) {
deletesoc(cli);
deletesoc(srv);
return -1;
}
deletesoc(srv);
sv[0] = acc;
sv[1] = cli;
return 0;
}
/* get_ftp_line must bound a hostile, CRLF-less reply into its internal
1024-byte buffer; ASan turns the pre-fix overflow into an abort here. */
#ifndef _WIN32
static int st_ftpline(httrackp *opt, int argc, char **argv) {
int sv[2];
T_SOC sv[2];
char line[2048];
char flood[4096];
@@ -2372,19 +2950,19 @@ static int st_ftpline(httrackp *opt, int argc, char **argv) {
(void) argc;
(void) argv;
memset(flood, 'x', sizeof(flood));
assertf(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == 0);
assertf(write(sv[1], "220 ", 4) == 4); // valid 3-digit code
assertf(write(sv[1], flood, sizeof(flood)) == (ssize_t) sizeof(flood));
assertf(write(sv[1], "\r\n", 2) == 2); // end the line so we return
close(sv[1]);
assertf(st_socketpair(sv) == 0);
// the 4102-byte reply fits the loopback send buffer, so no reader is needed
assertf(send(sv[1], "220 ", 4, 0) == 4); // valid 3-digit code
assertf(send(sv[1], flood, (int) sizeof(flood), 0) == (int) sizeof(flood));
assertf(send(sv[1], "\r\n", 2, 0) == 2); // end the line so we return
deletesoc(sv[1]);
line[0] = '\0';
get_ftp_line(sv[0], line, sizeof(line), 5);
close(sv[0]);
deletesoc(sv[0]);
printf("ftp-line self-test OK (bounded %d-byte reply)\n",
(int) sizeof(flood));
return 0;
}
#endif
/* ftp_split_userpass: well-formed split, plus a hostile over-long userinfo
that pre-fix overran user[256]/pass[256]. */
@@ -2425,21 +3003,6 @@ static int st_ftpuser(httrackp *opt, int argc, char **argv) {
return 0;
}
/* hts_count_fits caps the .class constant-pool entry count to the file size,
rejecting the ~68 MB-per-file calloc DoS. */
static int st_java(httrackp *opt, int argc, char **argv) {
(void) opt;
(void) argc;
(void) argv;
assertf(hts_count_fits(10, 1000) == HTS_TRUE);
assertf(hts_count_fits(0, 10) == HTS_TRUE);
assertf(hts_count_fits(65535, 10) == HTS_FALSE);
assertf(hts_count_fits(1, 0) == HTS_FALSE);
assertf(hts_count_fits(1, -1) == HTS_FALSE);
printf("java constant-pool cap self-test OK\n");
return 0;
}
/* ------------------------------------------------------------ */
/* Registry: name -> handler, with a usage hint and a one-line description. */
/* ------------------------------------------------------------ */
@@ -2461,6 +3024,8 @@ static const struct selftest_entry {
"memoized vs unmemoized matcher differential", st_filtermemo},
{"filterdual", "<string1> <string2> <filter>...",
"merged two-form filter verdict (fa_strjoker_dual)", st_filterdual},
{"filterbounds", "", "matcher length/work caps reject hostile patterns",
st_filterbounds},
{"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify},
{"stripquery", "", "--strip-query pattern/key stripping self-test",
st_stripquery},
@@ -2469,11 +3034,11 @@ static const struct selftest_entry {
{"redirect-samefile", "", "same-file redirect detection self-test (#159)",
st_redirect_samefile},
{"mime", "<filename>", "MIME type for a filename", st_mime},
{"charset", "<charset> <string>",
{"charset", "<charset> <hex:..|string>",
"convert a string to UTF-8 from a charset", st_charset},
{"metacharset", "<html>", "extract the <meta> charset from an HTML page",
st_metacharset},
{"isutf8", "<string>", "is the string valid UTF-8 (1/0)", st_isutf8},
{"isutf8", "<hex:..|string>", "is the string valid UTF-8 (1/0)", st_isutf8},
{"idna-encode", "<host>", "encode a hostname to IDNA/punycode",
st_idna_encode},
{"idna-decode", "<host>", "decode an IDNA/punycode hostname",
@@ -2491,15 +3056,26 @@ static const struct selftest_entry {
{"resolve", "<link> <adr> <fil>", "resolve a link against an origin",
st_resolve},
{"identurl", "<url>", "split an absolute URL into (adr, fil)", st_identurl},
{"proxyurl", "<proxy-arg>", "parse a -P proxy URL into host/port",
st_proxyurl},
{"socks5", "", "SOCKS5 handshake framing and credential self-test",
st_socks5},
{"identabs", "", "ident_url_absolute one-byte fil[] overflow self-test",
st_identabs},
{"header", "<raw-header-line> ...", "response header-line parsing",
st_header},
{"headerlong", "[header-name:]",
"over-long header value must not overflow the parse scratch",
st_headerlong},
{"crange", "<raw-content-range-line> ...",
"Content-Range parse integer safety", st_crange},
{"xfread-limit", "", "in-memory receive buffer size bound",
st_xfread_limit},
{"savename", "<fil> <content-type> [key=value ...]",
"local save-name for a URL", st_savename},
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
st_sniff},
{"fsize", "<dir>", "file size past the 2GB signed-32-bit wrap", st_fsize},
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
st_cacheindex},
@@ -2525,14 +3101,14 @@ static const struct selftest_entry {
{"status", "", "HTTP status code -> reason phrase self-test", st_status},
{"acceptencoding", "[dir]",
"Accept-Encoding advertises gzip+deflate, both decode", st_acceptencoding},
{"contentcodings", "[dir]",
"brotli and zstd bodies decode; bombs and unknown codings are refused",
st_contentcodings},
{"robots", "", "robots.txt RFC 9309 Allow/Disallow precedence self-test",
st_robots},
#ifndef _WIN32
{"ftp-line", "", "get_ftp_line bounds a hostile FTP reply line",
st_ftpline},
#endif
{"ftp-userpass", "", "ftp_split_userpass bounds URL userinfo", st_ftpuser},
{"java", "", "java .class constant-pool count cap self-test", st_java},
};
static void list_selftests(void) {

View File

@@ -37,6 +37,7 @@ Please visit our Website: http://www.httrack.com
/* specific definitions */
#include "htsbase.h"
#include "htscore.h"
#include "htscodec.h"
#include "htszlib.h"
#if HTS_USEZLIB
@@ -46,13 +47,8 @@ Please visit our Website: http://www.httrack.com
#include "htszlib.h"
*/
/*
Unpack file into a new file (gzip, zlib RFC1950 or raw deflate RFC1951).
A body provably in no deflate framing is copied verbatim (identity).
Return value: size of the new file, or -1 if an error occurred
*/
/* Note: utf-8 */
int hts_zunpack(char *filename, char *newfile) {
int hts_zunpack(const char *filename, const char *newfile) {
int ret = -1;
if (filename != NULL && newfile != NULL && filename[0] && newfile[0]) {
@@ -61,6 +57,7 @@ int hts_zunpack(char *filename, char *newfile) {
if (in != NULL) {
unsigned char BIGSTK inbuf[8192];
const LLint maxout = hts_codec_maxout(hts_codec_coded_size(in));
size_t navail = fread(inbuf, 1, sizeof(inbuf), in);
/* gzip/zlib headers -> +32 windowBits; else raw deflate (RFC1951) */
const hts_boolean wrapped =
@@ -73,9 +70,10 @@ int hts_zunpack(char *filename, char *newfile) {
deflate framing at all. env_error: local I/O or memory failure. */
hts_boolean not_deflate = HTS_FALSE;
hts_boolean env_error = HTS_FALSE;
hts_boolean bomb = HTS_FALSE;
/* deflate is ambiguous; on failure retry with the other windowBits */
for (attempt = 0; attempt < 2 && ret < 0; attempt++) {
for (attempt = 0; attempt < 2 && ret < 0 && !bomb; attempt++) {
const int windowBits =
(attempt == 0 ? wrapped : !wrapped) ? (32 + MAX_WBITS) : -MAX_WBITS;
FILE *fpout;
@@ -102,7 +100,7 @@ int hts_zunpack(char *filename, char *newfile) {
}
{
hts_boolean ok = HTS_TRUE;
int size = 0;
LLint size = 0;
int zerr = Z_OK;
/* chunked inflate; first chunk in inbuf, single member */
@@ -126,27 +124,33 @@ int hts_zunpack(char *filename, char *newfile) {
break;
}
produced = sizeof(outbuf) - strm.avail_out;
size += (LLint) produced;
if (size > maxout) { /* decompression bomb: no retry, no copy */
bomb = HTS_TRUE;
ok = HTS_FALSE;
break;
}
if (produced > 0 &&
fwrite(outbuf, 1, produced, fpout) != produced) {
env_error = HTS_TRUE;
ok = HTS_FALSE;
break;
}
size += (int) produced;
} while (strm.avail_out == 0);
if (!ok || zerr == Z_STREAM_END)
break;
navail = fread(inbuf, 1, sizeof(inbuf), in);
} while (navail > 0);
if (ok && zerr == Z_STREAM_END)
ret = size;
ret = (int) size;
}
inflateEnd(&strm);
fclose(fpout);
}
/* keep a mislabeled identity body verbatim only when provably not
deflate; truncation or a local failure must keep failing (#47) */
if (ret < 0 && !wrapped && not_deflate && !env_error && !ferror(in)) {
if (ret < 0 && !bomb && !wrapped && not_deflate && !env_error &&
!ferror(in)) {
FILE *const fpout =
FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
@@ -172,6 +176,25 @@ int hts_zunpack(char *filename, char *newfile) {
return ret;
}
size_t hts_zhead(const void *in, size_t in_len, void *out, size_t out_len) {
z_stream zs;
size_t n = 0;
int err;
memset(&zs, 0, sizeof(zs));
if (inflateInit2(&zs, 47) != Z_OK) /* 47: gzip or zlib, autodetected */
return 0;
zs.next_in = (const Bytef *) in;
zs.avail_in = (uInt) in_len;
zs.next_out = (Bytef *) out;
zs.avail_out = (uInt) out_len;
err = inflate(&zs, Z_SYNC_FLUSH);
if (err == Z_OK || err == Z_STREAM_END || err == Z_BUF_ERROR)
n = out_len - zs.avail_out;
inflateEnd(&zs);
return n;
}
int hts_extract_meta(const char *path) {
char catbuff[CATBUFF_SIZE];
unzFile zFile = unzOpen(fconcat(catbuff, sizeof(catbuff), path, "hts-cache/new.zip"));

View File

@@ -44,7 +44,13 @@ Please visit our Website: http://www.httrack.com
/* Library internal definictions */
#ifdef HTS_INTERNAL_BYTECODE
extern int hts_zunpack(char *filename, char *newfile);
/* Inflate filename into newfile (gzip, zlib or raw deflate); decoded size, or
-1 on a corrupt/truncated stream, an over-budget body, or an I/O failure. A
body provably in no deflate framing is copied verbatim. */
extern int hts_zunpack(const char *filename, const char *newfile);
/* Inflate the head of a gzip/zlib stream, out_len max; 0 if undecodable */
extern size_t hts_zhead(const void *in, size_t in_len, void *out,
size_t out_len);
extern int hts_extract_meta(const char *path);
extern const char *hts_get_zerror(int err);
#endif

View File

@@ -743,7 +743,8 @@ HTSEXT_API hts_boolean hts_findissystem(find_handle find);
HTSEXT_API FILE *hts_fopen_utf8(const char *path, const char *mode);
#define STAT hts_stat_utf8
typedef struct _stat STRUCT_STAT;
/* _stat64: _stat's st_size is a 32-bit long, even in x64 builds */
typedef struct _stat64 STRUCT_STAT;
HTSEXT_API int hts_stat_utf8(const char *path, STRUCT_STAT *buf);

View File

@@ -45,6 +45,7 @@ Please visit our Website: http://www.httrack.com
#include "htsdefines.h"
#include "httrack.h"
#include "htslib.h"
#include "htscharset.h" // after htslib.h: winsock2.h must precede windows.h
/* Static definitions */
static int fexist(const char *s);
@@ -206,6 +207,7 @@ int main(int argc, char **argv) {
httrackp *opt;
#ifdef _WIN32
hts_argv_utf8(&argc, &argv);
{
WORD wVersionRequested; // requested version WinSock API
WSADATA wsadata; // Windows Sockets API data
@@ -919,9 +921,6 @@ static void signal_handlers(void) {
#ifdef SIGSEGV
signal(SIGSEGV, sig_fatal); // segmentation violation
#endif
#ifdef SIGSTKFLT
signal(SIGSTKFLT, sig_fatal); // stack fault
#endif
}
// fin routines de détournement de SIGHUP & co

View File

@@ -1,127 +0,0 @@
# Microsoft Developer Studio Project File - Name="httrack" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Console Application" 0x0103
CFG=httrack - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "httrack.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "httrack.mak" CFG="httrack - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "httrack - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "httrack - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE "httrack - Win32 Release avec debug" (based on "Win32 (x86) Console Application")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "httrack - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "c:\temp\vcpp"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MD /W3 /Gi /O2 /Op /Ob2 /I "C:\Dev\IPv6Kit\inc\\" /I "C:\Dev\zlib\\" /I "C:\Dev\openssl\include" /I "C:\Dev\Winhttrack" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "HTS_ANALYSTE_CONSOLE" /YX /FD /Zm200 /c
# SUBTRACT CPP /Fr
# ADD BASE RSC /l 0x40c /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 wsock32.lib libhttrack.lib /nologo /subsystem:console /machine:I386 /out:"L:\HTTrack\httrack\httrack.exe" /libpath:"C:\Dev\openssl\lib" /libpath:"C:\Dev\zlib\dll32" /libpath:"C:\Dev\openssl\lib\out32dll" /libpath:"C:\temp\Releaselib"
# SUBTRACT LINK32 /verbose
!ELSEIF "$(CFG)" == "httrack - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "c:\temp\vcpp"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MDd /W3 /Gm /GR /ZI /Od /I "C:\Dev\IPv6Kit\inc\\" /I "C:\Dev\zlib\\" /I "C:\Dev\openssl\include" /I "C:\Dev\Winhttrack" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "HTS_ANALYSTE_CONSOLE" /FAcs /Fr /FD /Zm200 /c
# ADD BASE RSC /l 0x40c /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 wsock32.lib libhttrack.lib /nologo /subsystem:console /debug /debugtype:both /machine:I386 /out:"C:\temp\httrack.exe" /pdbtype:sept /libpath:"C:\Dev\openssl\lib" /libpath:"C:\Dev\zlib\dll32" /libpath:"C:\Dev\openssl\lib\out32dll" /libpath:"C:\temp\Debuglib"
# SUBTRACT LINK32 /profile /map
!ELSEIF "$(CFG)" == "httrack - Win32 Release avec debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "httrack___Win32_Release_avec_debug"
# PROP BASE Intermediate_Dir "httrack___Win32_Release_avec_debug"
# PROP BASE Ignore_Export_Lib 0
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release_avec_debug"
# PROP Intermediate_Dir "c:\temp\vcpp"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MT /W3 /GX /Ot /Oi /Oy /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# SUBTRACT BASE CPP /Ox /Oa /Ow /Og /Os
# ADD CPP /nologo /MD /W3 /GX /Zi /Ot /Oi /Oy /Ob2 /I "C:\Dev\IPv6Kit\inc\\" /I "C:\Dev\zlib\\" /I "C:\Dev\openssl\include" /I "C:\Dev\Winhttrack" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "HTS_ANALYSTE_CONSOLE" /FAcs /FR /YX /FD /Zm200 /c
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 wsock32.lib /nologo /subsystem:console /machine:I386 /out:"c:\temp\httrack.exe"
# SUBTRACT BASE LINK32 /verbose
# ADD LINK32 wsock32.lib libhttrack.lib /nologo /subsystem:console /debug /machine:I386 /out:"L:\HTTrack\httrack\httrack.exe" /libpath:"C:\Dev\openssl\lib" /libpath:"C:\Dev\zlib\dll32" /libpath:"C:\Dev\openssl\lib\out32dll"
# SUBTRACT LINK32 /verbose
!ENDIF
# Begin Target
# Name "httrack - Win32 Release"
# Name "httrack - Win32 Debug"
# Name "httrack - Win32 Release avec debug"
# Begin Source File
SOURCE=httrack.c
# End Source File
# Begin Source File
SOURCE=httrack.h
# End Source File
# End Target
# End Project

View File

@@ -1,44 +0,0 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "httrack"=.\httrack.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
Begin Project Dependency
Project_Dep_Name libhttrack
End Project Dependency
}}}
###############################################################################
Project: "libhttrack"=.\libhttrack\libhttrack.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

5
src/httrack.rc Normal file
View File

@@ -0,0 +1,5 @@
// Version resource for httrack.exe, the command line program. See version.rc.
#define VER_FILE_DESCRIPTION "HTTrack Website Copier (command line)"
#define VER_ORIGINAL_FILENAME "httrack.exe"
#define VER_FILETYPE VFT_APP
#include "version.rc"

View File

@@ -1,677 +0,0 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Name="httrack"
ProjectGUID="{D0E894E7-F64C-4722-9807-9ABB1054D43A}"
RootNamespace="httrack"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Release avec debug|Win32"
OutputDirectory=".\Release_avec_debug"
IntermediateDirectory="c:\temp\vcpp"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TypeLibraryName=".\Release_avec_debug/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="4"
InlineFunctionExpansion="2"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE;_CRT_SECURE_NO_WARNINGS"
RuntimeLibrary="2"
UsePrecompiledHeader="0"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerOutput="2"
AssemblerListingLocation="c:\temp\vcpp/"
ObjectFile="c:\temp\vcpp/"
ProgramDataBaseFileName="c:\temp\vcpp/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib,C:\Dev\zlib\dll32,C:\Dev\openssl\lib\out32dll"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Release_avec_debug/httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release avec debug|x64"
OutputDirectory="$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
TypeLibraryName=".\Release_avec_debug/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="4"
InlineFunctionExpansion="2"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE"
RuntimeLibrary="2"
UsePrecompiledHeader="0"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerOutput="2"
AssemblerListingLocation="c:\temp\vcpp/"
ObjectFile="c:\temp\vcpp/"
ProgramDataBaseFileName="c:\temp\vcpp/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib,C:\Dev\zlib\dll32,C:\Dev\openssl\lib\out32dll"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Release_avec_debug/httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|Win32"
OutputDirectory="C:\temp\httrackcmd\Debug"
IntermediateDirectory="C:\temp\httrackcmd\Debug"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TypeLibraryName=".\Debug/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="0"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE;_CRT_SECURE_NO_WARNINGS"
RuntimeLibrary="3"
BufferSecurityCheck="true"
RuntimeTypeInfo="true"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerOutput="2"
AssemblerListingLocation="c:\temp\vcpp/"
ObjectFile="c:\temp\vcpp/"
ProgramDataBaseFileName="c:\temp\vcpp/"
BrowseInformation="1"
BrowseInformationFile="c:\temp\vcpp/"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="_DEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="C:\temp\httrack\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib;C:\temp\release_libz;C:\Dev\openssl\lib\out32dll;C:\temp\Debuglib"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Debug/httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="C:\temp\httrackcmd\Debug"
IntermediateDirectory="C:\temp\httrackcmd\Debug"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
TypeLibraryName=".\Debug/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="0"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="_DEBUG;WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE;_CRT_SECURE_NO_WARNINGS"
RuntimeLibrary="3"
BufferSecurityCheck="true"
RuntimeTypeInfo="true"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerOutput="2"
AssemblerListingLocation="c:\temp\vcpp/"
ObjectFile="c:\temp\vcpp/"
ProgramDataBaseFileName="c:\temp\vcpp/"
BrowseInformation="1"
BrowseInformationFile="c:\temp\vcpp/"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="_DEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="C:\temp\httrack64\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib,C:\temp\zlib,C:\Dev\openssl\lib\out32dll,C:\temp\Debuglib"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Debug/httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="C:\temp\httrackcmd\Release"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TypeLibraryName=".\Release/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="3"
InlineFunctionExpansion="2"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE;_CRT_SECURE_NO_WARNINGS"
StringPooling="true"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerListingLocation="c:\temp\vcpp/"
ObjectFile="c:\temp\vcpp/"
ProgramDataBaseFileName="c:\temp\vcpp/"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib,C:\temp\zlib,C:\Dev\openssl\lib\out32dll,C:\temp\Releaselib"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
TypeLibraryName=".\Release/httrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions="/Zm200 "
Optimization="3"
InlineFunctionExpansion="2"
AdditionalIncludeDirectories="&quot;C:\Dev\zlib-1.2.8\&quot;;&quot;C:\Dev\openssl-1.0.1j\include&quot;;coucal"
PreprocessorDefinitions="NDEBUG;WIN32;_CONSOLE;HTS_ANALYSTE_CONSOLE;_CRT_SECURE_NO_WARNINGS"
StringPooling="true"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
UsePrecompiledHeader="0"
PrecompiledHeaderFile="c:\temp\vcpp/httrack.pch"
AssemblerListingLocation="c:\temp\vcpp64/"
ObjectFile="c:\temp\vcpp64/"
ProgramDataBaseFileName="c:\temp\vcpp64/"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1033"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\x64\httrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib,C:\temp\zlib,C:\Dev\openssl\lib\out32dll,C:\temp\Releaselib"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\x64\httrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<File
RelativePath="httrack.c"
>
<FileConfiguration
Name="Release avec debug|Win32"
>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
BrowseInformation="1"
/>
</FileConfiguration>
<FileConfiguration
Name="Release avec debug|x64"
>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
BrowseInformation="1"
/>
</FileConfiguration>
<FileConfiguration
Name="Debug|Win32"
>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
BrowseInformation="1"
/>
</FileConfiguration>
<FileConfiguration
Name="Debug|x64"
>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
BrowseInformation="1"
/>
</FileConfiguration>
<FileConfiguration
Name="Release|Win32"
>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
/>
</FileConfiguration>
<FileConfiguration
Name="Release|x64"
>
<Tool
Name="VCCLCompilerTool"
Optimization="2"
AdditionalIncludeDirectories=""
PreprocessorDefinitions=""
/>
</FileConfiguration>
</File>
<File
RelativePath=".\httrack.h"
>
</File>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

116
src/httrack.vcxproj Normal file
View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{2F9E0E4C-2B5B-4B2E-9F6D-1C0A5E7B3D01}</ProjectGuid>
<RootNamespace>httrack</RootNamespace>
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)'==''">10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<!-- No direct vcpkg dependency, but the triplet drives applocal deployment:
without it the OpenSSL and zlib DLLs libhttrack needs never land beside
httrack.exe. -->
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows</VcpkgTriplet>
<VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows</VcpkgTriplet>
<OutDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\obj\httrack\</IntDir>
<TargetName>httrack</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- No LIBHTTRACK_EXPORTS: HTSEXT_API is dllimport on this side. -->
<PreprocessorDefinitions>WIN32;_CONSOLE;_MBCS;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<!-- main() calls WSAStartup/WSACleanup itself. -->
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="httrack.c" />
</ItemGroup>
<!-- Pulls in libhttrack.lib and builds it first. -->
<ItemGroup>
<ProjectReference Include="libhttrack.vcxproj">
<Project>{e76ad871-54c1-45e8-a657-6117adeffb46}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="httrack.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -1,406 +0,0 @@
# Microsoft Developer Studio Project File - Name="libhttrack" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
CFG=libhttrack - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "libhttrack.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "libhttrack.mak" CFG="libhttrack - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "libhttrack - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
!MESSAGE "libhttrack - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
MTL=midl.exe
RSC=rc.exe
!IF "$(CFG)" == "libhttrack - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "C:\temp\Releaselib"
# PROP Intermediate_Dir "C:\temp\Releaselib"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LIBHTTRACK_EXPORTS" /Yu"stdafx.h" /FD /c
# ADD CPP /nologo /MD /W3 /GX /O2 /Ob2 /I "C:\Dev\IPv6Kit\inc" /I "C:\Dev\zlib\include" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LIBHTTRACK_EXPORTS" /D "ZLIB_DLL" /FD /c
# SUBTRACT CPP /YX /Yc /Yu
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x40c /d "NDEBUG"
# ADD RSC /l 0x40c /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
# ADD LINK32 wsock32.lib zdll.lib /nologo /dll /map /machine:I386 /out:"L:\HTTrack\httrack\libhttrack.dll" /libpath:"C:\Dev\zlib\lib"
# SUBTRACT LINK32 /pdb:none
!ELSEIF "$(CFG)" == "libhttrack - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "C:\temp\Debuglib"
# PROP Intermediate_Dir "C:\temp\Debuglib"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LIBHTTRACK_EXPORTS" /Yu"stdafx.h" /FD /GZ /c
# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "C:\Dev\IPv6Kit\inc" /I "C:\Dev\zlib\include" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LIBHTTRACK_EXPORTS" /D "ZLIB_DLL" /FD /GZ /c
# SUBTRACT CPP /YX /Yc /Yu
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x40c /d "_DEBUG"
# ADD RSC /l 0x40c /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
# ADD LINK32 wsock32.lib zdll.lib /nologo /dll /map /debug /machine:I386 /out:"C:\temp\libhttrack.dll" /pdbtype:sept /libpath:"C:\Dev\zlib\lib"
# SUBTRACT LINK32 /pdb:none
!ENDIF
# Begin Target
# Name "libhttrack - Win32 Release"
# Name "libhttrack - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=minizip\crypt.h
# End Source File
# Begin Source File
SOURCE="hts-indextmpl.h"
# End Source File
# Begin Source File
SOURCE=htsalias.c
# End Source File
# Begin Source File
SOURCE=htsalias.h
# End Source File
# Begin Source File
SOURCE=htsback.c
# End Source File
# Begin Source File
SOURCE=htsback.h
# End Source File
# Begin Source File
SOURCE=htsbase.h
# End Source File
# Begin Source File
SOURCE=htsbasenet.h
# End Source File
# Begin Source File
SOURCE=htsbauth.c
# End Source File
# Begin Source File
SOURCE=htsbauth.h
# End Source File
# Begin Source File
SOURCE=htscache.c
# End Source File
# Begin Source File
SOURCE=htscache.h
# End Source File
# Begin Source File
SOURCE=htscatchurl.c
# End Source File
# Begin Source File
SOURCE=htscatchurl.h
# End Source File
# Begin Source File
SOURCE=htsconfig.h
# End Source File
# Begin Source File
SOURCE=htscore.c
# End Source File
# Begin Source File
SOURCE=htscore.h
# End Source File
# Begin Source File
SOURCE=htscoremain.c
# End Source File
# Begin Source File
SOURCE=htscoremain.h
# End Source File
# Begin Source File
SOURCE=htsdefines.h
# End Source File
# Begin Source File
SOURCE=htsfilters.c
# End Source File
# Begin Source File
SOURCE=htsfilters.h
# End Source File
# Begin Source File
SOURCE=htsftp.c
# End Source File
# Begin Source File
SOURCE=htsftp.h
# End Source File
# Begin Source File
SOURCE=htsglobal.h
# End Source File
# Begin Source File
SOURCE=htshash.c
# End Source File
# Begin Source File
SOURCE=htshash.h
# End Source File
# Begin Source File
SOURCE=htshelp.c
# End Source File
# Begin Source File
SOURCE=htshelp.h
# End Source File
# Begin Source File
SOURCE=htsindex.c
# End Source File
# Begin Source File
SOURCE=htsindex.h
# End Source File
# Begin Source File
SOURCE=htsinthash.c
# End Source File
# Begin Source File
SOURCE=htsjava.c
# End Source File
# Begin Source File
SOURCE=htsjava.h
# End Source File
# Begin Source File
SOURCE=htslib.c
# End Source File
# Begin Source File
SOURCE=htslib.h
# End Source File
# Begin Source File
SOURCE=htsmd5.c
# End Source File
# Begin Source File
SOURCE=htsmd5.h
# End Source File
# Begin Source File
SOURCE=htsmodules.c
# End Source File
# Begin Source File
SOURCE=htsmodules.h
# End Source File
# Begin Source File
SOURCE=htsname.c
# End Source File
# Begin Source File
SOURCE=htsname.h
# End Source File
# Begin Source File
SOURCE=htsnet.h
# End Source File
# Begin Source File
SOURCE=htsnostatic.c
# End Source File
# Begin Source File
SOURCE=htsnostatic.h
# End Source File
# Begin Source File
SOURCE=htsopt.h
# End Source File
# Begin Source File
SOURCE=htsparse.c
# End Source File
# Begin Source File
SOURCE=htsparse.h
# End Source File
# Begin Source File
SOURCE=htsrobots.c
# End Source File
# Begin Source File
SOURCE=htsrobots.h
# End Source File
# Begin Source File
SOURCE=htsstrings.h
# End Source File
# Begin Source File
SOURCE=htssystem.h
# End Source File
# Begin Source File
SOURCE=htsthread.c
# End Source File
# Begin Source File
SOURCE=htsthread.h
# End Source File
# Begin Source File
SOURCE=htstools.c
# End Source File
# Begin Source File
SOURCE=htstools.h
# End Source File
# Begin Source File
SOURCE=htswizard.c
# End Source File
# Begin Source File
SOURCE=htswizard.h
# End Source File
# Begin Source File
SOURCE=htswrap.c
# End Source File
# Begin Source File
SOURCE=htswrap.h
# End Source File
# Begin Source File
SOURCE=htszlib.c
# End Source File
# Begin Source File
SOURCE=htszlib.h
# End Source File
# Begin Source File
SOURCE=minizip\ioapi.c
# End Source File
# Begin Source File
SOURCE=minizip\ioapi.h
# End Source File
# Begin Source File
SOURCE=minizip\iowin32.c
# End Source File
# Begin Source File
SOURCE=minizip\iowin32.h
# End Source File
# Begin Source File
SOURCE=md5.c
# End Source File
# Begin Source File
SOURCE=md5.h
# End Source File
# Begin Source File
SOURCE=minizip\mztools.c
# End Source File
# Begin Source File
SOURCE=minizip\mztools.h
# End Source File
# Begin Source File
SOURCE=minizip\unzip.c
# End Source File
# Begin Source File
SOURCE=minizip\unzip.h
# End Source File
# Begin Source File
SOURCE=minizip\zip.c
# End Source File
# Begin Source File
SOURCE=minizip\zip.h
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
# End Group
# Begin Source File
SOURCE=.\ReadMe.txt
# End Source File
# End Target
# End Project

View File

@@ -1,29 +0,0 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "libhttrack"=.\libhttrack.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

5
src/libhttrack.rc Normal file
View File

@@ -0,0 +1,5 @@
// Version resource for libhttrack.dll. See version.rc.
#define VER_FILE_DESCRIPTION "HTTrack Website Copier engine"
#define VER_ORIGINAL_FILENAME "libhttrack.dll"
#define VER_FILETYPE VFT_DLL
#include "version.rc"

File diff suppressed because it is too large Load Diff

150
src/libhttrack.vcxproj Normal file
View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{E76AD871-54C1-45E8-A657-6117ADEFFB46}</ProjectGuid>
<RootNamespace>libhttrack</RootNamespace>
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)'==''">10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<!-- OpenSSL 3.x and zlib come from vcpkg. Dynamic triplets: the DLLs ship with
the app, so an OpenSSL CVE is a DLL swap rather than a rebuild. -->
<PropertyGroup>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows</VcpkgTriplet>
<VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows</VcpkgTriplet>
<!-- WinHTTrack links ..\httrack\src\$(Platform)\$(Configuration)\libhttrack.lib -->
<OutDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\obj\</IntDir>
<TargetName>libhttrack</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- LIBHTTRACK_EXPORTS turns HTSEXT_API into __declspec(dllexport); ZLIB_DLL
imports zlib from its DLL. HTS_USEBROTLI/HTS_USEZSTD enable the br and
zstd content codings. Windows 7 floor, matching WinHTTrack. -->
<PreprocessorDefinitions>WIN32;_WINDOWS;_MBCS;_USRDLL;LIBHTTRACK_EXPORTS;ZLIB_DLL;HTS_USEBROTLI=1;HTS_USEZSTD=1;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<!-- vcpkg auto-links libssl/libcrypto/zlib/brotli/zstd; only the OS import
lib is explicit. -->
<AdditionalDependencies>Ws2_32.lib;Shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="coucal\coucal.c" />
<ClCompile Include="htsalias.c" />
<ClCompile Include="htsback.c" />
<ClCompile Include="htsbauth.c" />
<ClCompile Include="htscache.c" />
<ClCompile Include="htscache_selftest.c" />
<ClCompile Include="htscatchurl.c" />
<ClCompile Include="htscharset.c" />
<ClCompile Include="htscodec.c" />
<ClCompile Include="htsconcat.c" />
<ClCompile Include="htscore.c" />
<ClCompile Include="htscoremain.c" />
<ClCompile Include="htsdns_selftest.c" />
<ClCompile Include="htsencoding.c" />
<ClCompile Include="htsfilters.c" />
<ClCompile Include="htsftp.c" />
<ClCompile Include="htshash.c" />
<ClCompile Include="htshelp.c" />
<ClCompile Include="htsindex.c" />
<ClCompile Include="htslib.c" />
<ClCompile Include="htsmd5.c" />
<ClCompile Include="htsmodules.c" />
<ClCompile Include="htsname.c" />
<ClCompile Include="htsparse.c" />
<ClCompile Include="htsproxy.c" />
<ClCompile Include="htsrobots.c" />
<ClCompile Include="htsselftest.c" />
<ClCompile Include="htssniff.c" />
<ClCompile Include="htsthread.c" />
<ClCompile Include="htstools.c" />
<ClCompile Include="htswizard.c" />
<ClCompile Include="htswrap.c" />
<ClCompile Include="htszlib.c" />
<ClCompile Include="md5.c" />
<ClCompile Include="minizip\ioapi.c" />
<ClCompile Include="minizip\iowin32.c" />
<ClCompile Include="minizip\mztools.c" />
<ClCompile Include="minizip\unzip.c" />
<ClCompile Include="minizip\zip.c" />
<ClCompile Include="punycode.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="libhttrack.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -1,438 +0,0 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Name="proxytrack"
ProjectGUID="{4C31F79A-9DAB-44CE-88D6-0FF961599AAB}"
RootNamespace="proxytrack"
Keyword="Win32Proj"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="C:\temp\proxytrack\Debug"
IntermediateDirectory="C:\temp\proxytrack\Debug\int"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\httrack\src;&quot;C:\Dev\zlib-1.2.8&quot;;C:\Dev\minizip;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="NO_MALLOCT;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib zlib1.lib"
OutputFile="c:\temp\httrack\proxytrack.exe"
LinkIncremental="2"
AdditionalLibraryDirectories="C:\temp\debug_libz"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
ProgramDatabaseFile="$(OutDir)/proxytrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\httrack\src;&quot;C:\Dev\zlib-1.2.8&quot;;C:\Dev\minizip;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="NO_MALLOCT;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib zlib1.lib"
OutputFile="c:\temp\httrack64\proxytrack.exe"
LinkIncremental="2"
AdditionalLibraryDirectories="C:\temp\debug_libz64"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
ProgramDatabaseFile="$(OutDir)/proxytrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="C:\temp\proxytrack\Release"
IntermediateDirectory="C:\temp\proxytrack\Release\int"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories="C:\Dev\httrack\src;&quot;C:\Dev\zlib-1.2.8&quot;;C:\Dev\minizip;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="NO_MALLOCT;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib zlib1.lib"
OutputFile="O:\HTTrack\httrack\proxytrack.exe"
LinkIncremental="1"
AdditionalLibraryDirectories="C:\temp\release_libz;C:\temp\Releaselib"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\proxytrack.pdb"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
OptimizeForWindows98="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
Description="Copying proxytrack to separate package"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;copy /Y &quot;O:\HTTrack\httrack\proxytrack.exe&quot; &quot;O:\ProxyTrack\proxytrack\proxytrack.exe&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.c&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.h&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.vcproj&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.txt&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories="C:\Dev\httrack\src;&quot;C:\Dev\zlib-1.2.8&quot;;C:\Dev\minizip;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="NO_MALLOCT;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib zlib1.lib"
OutputFile="O:\HTTrack\httrack\x64\proxytrack.exe"
LinkIncremental="1"
AdditionalLibraryDirectories="C:\temp\release_libz64;C:\temp\Releaselib64"
IgnoreAllDefaultLibraries="false"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\x64\proxytrack.pdb"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
Description="Copying proxytrack to separate package"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;copy /Y &quot;O:\HTTrack\httrack\proxytrack.exe&quot; &quot;O:\ProxyTrack\proxytrack\proxytrack.exe&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.c&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.h&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.vcproj&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;copy /Y &quot;C:\Dev\httrack\src\proxy\*.txt&quot; &quot;O:\ProxyTrack\proxytrack\src\&quot;&#x0D;&#x0A;"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath="..\coucal\coucal.c"
>
</File>
<File
RelativePath="..\htsmd5.c"
>
</File>
<File
RelativePath="..\minizip\ioapi.c"
>
</File>
<File
RelativePath="..\minizip\iowin32.c"
>
</File>
<File
RelativePath="main.c"
>
</File>
<File
RelativePath="..\md5.c"
>
</File>
<File
RelativePath="..\minizip\mztools.c"
>
</File>
<File
RelativePath="proxytrack.c"
>
</File>
<File
RelativePath="store.c"
>
</File>
<File
RelativePath="..\minizip\unzip.c"
>
</File>
<File
RelativePath="..\minizip\zip.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
<File
RelativePath="proxystrings.h"
>
</File>
<File
RelativePath="proxytrack.h"
>
</File>
<File
RelativePath="store.h"
>
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
<File
RelativePath=".\changelog.txt"
>
</File>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View File

@@ -577,9 +577,11 @@ PT_Index PT_LoadCache(const char *filename) {
chain = coucal_enum_next(&en);
}
if (chain != NULL) {
if (!link_has_authority(chain->name))
strcat(index->slots.common.startUrl, "http://");
strcat(index->slots.common.startUrl, chain->name);
const char *scheme = link_has_authority(chain->name) ? "" : "http://";
snprintf(index->slots.common.startUrl,
sizeof(index->slots.common.startUrl), "%s%s", scheme,
(const char *) chain->name);
}
}
}
@@ -632,7 +634,7 @@ int PT_EnumCache(PT_Indexes indexes,
const long int index_id = (long int) chain->value.intg;
const char *const url = chain->name;
if (index_id >= 0 && index_id <= indexes->index_size) {
if (index_id >= 0 && index_id < indexes->index_size) {
PT_Element item =
PT_ReadCache(indexes->index[index_id], url,
FETCH_HEADERS | FETCH_BODY);
@@ -757,7 +759,7 @@ PT_Element PT_ReadIndex(PT_Indexes indexes, const char *url, int flags) {
if (strncmp(url, "http://", 7) == 0)
url += 7;
if (coucal_read(indexes->cil, url, &index_id)) {
if (index_id >= 0 && index_id <= indexes->index_size) {
if (index_id >= 0 && index_id < indexes->index_size) {
PT_Element item = PT_ReadCache(indexes->index[index_id], url, flags);
if (item != NULL) {
@@ -780,7 +782,7 @@ int PT_LookupIndex(PT_Indexes indexes, const char *url) {
if (strncmp(url, "http://", 7) == 0)
url += 7;
if (coucal_read(indexes->cil, url, &index_id)) {
if (index_id >= 0 && index_id <= indexes->index_size) {
if (index_id >= 0 && index_id < indexes->index_size) {
return 1;
} else {
proxytrack_print_log(CRITICAL,
@@ -967,10 +969,12 @@ int PT_LoadCache__New(PT_Index index_, const char *filename) {
/* First link as starting URL */
if (!firstSeen) {
if (strstr(filenameIndex, "/robots.txt") == NULL) {
const char *scheme =
link_has_authority(filenameIndex) ? "" : "http://";
firstSeen = 1;
if (!link_has_authority(filenameIndex))
strcat(index->startUrl, "http://");
strcat(index->startUrl, filenameIndex);
snprintf(index->startUrl, sizeof(index->startUrl), "%s%s",
scheme, filenameIndex);
}
}
} else {
@@ -1102,7 +1106,8 @@ static PT_Element PT_ReadCache__New_u(PT_Index index_, const char *url,
&& (!isalpha(previous_save_[0]) || previous_save_[1] != ':')) // c:/home/foo/bar.gif
) {
index->safeCache = 1;
sprintf(previous_save, "%s%s", index->path, previous_save_);
snprintf(previous_save, sizeof(previous_save), "%s%s",
index->path, previous_save_);
}
// bogus format (includes buggy absolute path)
else {
@@ -1127,15 +1132,16 @@ static PT_Element PT_ReadCache__New_u(PT_Index index_, const char *url,
int saveLen = (int) strlen(previous_save_);
if (index->fixedPath < saveLen) {
sprintf(previous_save, "%s%s", index->path,
previous_save_ + index->fixedPath);
snprintf(previous_save, sizeof(previous_save), "%s%s",
index->path, previous_save_ + index->fixedPath);
} else {
snprintf(r->msg, sizeof(r->msg), "Bogus fixePath prefix for %s (prefixLen=%d)",
previous_save_, (int) index->fixedPath);
r->statuscode = STATUSCODE_INVALID;
}
} else {
sprintf(previous_save, "%s%s", index->path, previous_save_);
snprintf(previous_save, sizeof(previous_save), "%s%s",
index->path, previous_save_);
}
}
}
@@ -1348,45 +1354,71 @@ static int PT_SaveCache__New(PT_Indexes indexes, const char *filename) {
/* Old HTTrack cache (dat/ndx) format */
/* ------------------------------------------------------------ */
static int cache_brstr(char *adr, char *s) {
static int cache_brstr(char *adr, char *s, size_t s_size) {
int i;
int off;
char buff[256 + 1];
char buff[256 + 4];
off = binput(adr, buff, 256);
adr += off;
sscanf(buff, "%d", &i);
if (i > 0)
strncpy(s, adr, i);
*(s + i) = '\0';
off += i;
/* binput stops at adr's terminating NUL; a value only follows a real line
terminator, so never step past end-of-buffer. */
if (adr[off - 1] == '\0') {
s[0] = '\0';
return off - 1;
}
/* an empty/non-numeric field leaves i unset: treat as length 0 */
if (sscanf(buff, "%d", &i) != 1 || i < 0 || i > 32768)
i = 0;
if (i > 0) {
/* a corrupt cache may declare a length past the buffer end; bound the copy
and the advance to the bytes actually present */
const size_t avail = strnlen(adr + off, (size_t) i);
const size_t store = avail < s_size ? avail : s_size - 1;
memcpy(s, adr + off, store);
s[store] = '\0';
off += (int) avail;
} else {
s[0] = '\0';
}
return off;
}
static void cache_rstr(FILE * fp, char *s) {
INTsys i;
static void cache_rstr(FILE *fp, char *s, size_t s_size) {
INTsys i = 0;
char buff[256 + 4];
linput(fp, buff, 256);
sscanf(buff, INTsysP, &i);
if (i < 0 || i > 32768) /* error, something nasty happened */
/* an unmatched sscanf leaves i untouched: length 0, not a stack value */
if (sscanf(buff, INTsysP, &i) != 1 || i < 0 || i > 32768)
i = 0;
if (i > 0) {
if ((int) fread(s, 1, i, fp) != i) {
/* store at most s_size-1 bytes, but consume all i bytes so the next field
stays aligned when a tampered cache declares more than the destination
holds */
const size_t want = (size_t) i;
const size_t store = want < s_size ? want : s_size - 1;
if (fread(s, 1, store, fp) != store) {
assertf(! "fread_cache_failed");
}
if (want > store && fseek(fp, (long) (want - store), SEEK_CUR) != 0) {
assertf(!"fseek_cache_failed");
}
s[store] = '\0';
} else {
s[0] = '\0';
}
*(s + i) = '\0';
}
static char *cache_rstr_addr(FILE * fp) {
INTsys i;
INTsys i = 0;
char *addr = NULL;
char buff[256 + 4];
linput(fp, buff, 256);
sscanf(buff, "%d", &i);
if (i < 0 || i > 32768) /* error, something nasty happened */
/* an unmatched sscanf leaves i untouched: length 0, not a stack value */
if (sscanf(buff, INTsysP, &i) != 1 || i < 0 || i > 32768)
i = 0;
if (i > 0) {
addr = malloc(i + 1);
@@ -1400,19 +1432,31 @@ static char *cache_rstr_addr(FILE * fp) {
return addr;
}
/* Allocate n+1 bytes for an n-byte cache body, rejecting a hostile/corrupt
length that would demand an absurd allocation or wrap n+1. NULL on refuse. */
static char *cache_alloc_body(size_t n) {
if (n >= (size_t) INT32_MAX)
return NULL;
return (char *) malloc(n + 1);
}
static void cache_rint(FILE * fp, int *i) {
char s[256];
cache_rstr(fp, s);
sscanf(s, "%d", i);
cache_rstr(fp, s, sizeof(s));
if (sscanf(s, "%d", i) != 1)
*i = 0;
}
static void cache_rLLint(FILE * fp, unsigned long *i) {
int l;
char s[256];
cache_rstr(fp, s);
sscanf(s, "%d", &l);
cache_rstr(fp, s, sizeof(s));
/* reject a non-numeric or negative field: a negative length cast to unsigned
would wrap a later malloc(size+1) to a tiny buffer */
if (sscanf(s, "%d", &l) != 1 || l < 0)
l = 0;
*i = (unsigned long) l;
}
@@ -1472,12 +1516,12 @@ static int PT_LoadCache__Old(PT_Index index_, const char *filename) {
char *a = use;
use[ndxSize] = '\0';
a += cache_brstr(a, firstline);
a += cache_brstr(a, firstline, sizeof(firstline));
if (strncmp(firstline, "CACHE-", 6) == 0) { // Nouvelle version du cache
if (strncmp(firstline, "CACHE-1.", 8) == 0) { // Version 1.1x
cache->version = (int) (firstline[8] - '0'); // cache 1.x
if (cache->version <= 5) {
a += cache_brstr(a, firstline);
a += cache_brstr(a, firstline, sizeof(firstline));
strcpy(cache->lastmodified, firstline);
} else {
fclose(cache->dat);
@@ -1523,11 +1567,12 @@ static int PT_LoadCache__Old(PT_Index index_, const char *filename) {
if (!firstSeen) {
if (strstr(line, "/robots.txt") == NULL) {
PT_Index__Old index = cache;
const char *scheme =
link_has_authority(line) ? "" : "http://";
firstSeen = 1;
if (!link_has_authority(line))
strcat(index->startUrl, "http://");
strcat(index->startUrl, line);
snprintf(index->startUrl, sizeof(index->startUrl), "%s%s",
scheme, line);
}
}
@@ -1623,9 +1668,13 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
String urlDecoded;
r->statuscode = old_r.statuscode;
r->size = old_r.size; // taille fichier
strcpy(r->msg, old_r.msg);
strcpy(r->contenttype, old_r.contenttype);
r->size = old_r.size > 0 ? (size_t) old_r.size : 0; // taille fichier
/* these fixed fields come straight from fread and carry no guaranteed
NUL; terminate them before the bounded copy */
old_r.msg[sizeof(old_r.msg) - 1] = '\0';
old_r.contenttype[sizeof(old_r.contenttype) - 1] = '\0';
strcpybuff(r->msg, old_r.msg);
strcpybuff(r->contenttype, old_r.contenttype);
/* Guess the destination filename.. this sucks, because this method is not reliable.
Yes, the old 1.0 cache format was *that* bogus. /rx */
@@ -1666,26 +1715,27 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
cache_rint(cache->dat, &r->statuscode);
cache_rLLint(cache->dat, &size_);
r->size = (size_t) size_;
cache_rstr(cache->dat, r->msg);
cache_rstr(cache->dat, r->contenttype);
cache_rstr(cache->dat, r->msg, sizeof(r->msg));
cache_rstr(cache->dat, r->contenttype, sizeof(r->contenttype));
if (cache->version >= 3)
cache_rstr(cache->dat, r->charset);
cache_rstr(cache->dat, r->lastmodified);
cache_rstr(cache->dat, r->etag);
cache_rstr(cache->dat, r->location);
cache_rstr(cache->dat, r->charset, sizeof(r->charset));
cache_rstr(cache->dat, r->lastmodified, sizeof(r->lastmodified));
cache_rstr(cache->dat, r->etag, sizeof(r->etag));
cache_rstr(cache->dat, r->location, sizeof(location_default));
if (cache->version >= 2)
cache_rstr(cache->dat, r->cdispo);
cache_rstr(cache->dat, r->cdispo, sizeof(r->cdispo));
if (cache->version >= 4) {
cache_rstr(cache->dat, previous_save_); // adr
cache_rstr(cache->dat, previous_save_); // fil
cache_rstr(cache->dat, previous_save_, sizeof(previous_save_)); // adr
cache_rstr(cache->dat, previous_save_, sizeof(previous_save_)); // fil
previous_save[0] = '\0';
cache_rstr(cache->dat, previous_save_); // save
cache_rstr(cache->dat, previous_save_,
sizeof(previous_save_)); // save
}
if (cache->version >= 5) {
r->headers = cache_rstr_addr(cache->dat);
}
//
cache_rstr(cache->dat, check);
cache_rstr(cache->dat, check, sizeof(check));
if (strcmp(check, "HTS") == 0) { /* intégrité OK */
ok = 1;
}
@@ -1714,7 +1764,8 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
&& (!isalpha(previous_save_[0]) || previous_save_[1] != ':')) // c:/home/foo/bar.gif
) {
index->safeCache = 1;
sprintf(previous_save, "%s%s", index->path, previous_save_);
snprintf(previous_save, sizeof(previous_save), "%s%s", index->path,
previous_save_);
}
// bogus format (includes buggy absolute path)
else {
@@ -1739,15 +1790,16 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
int saveLen = (int) strlen(previous_save_);
if (index->fixedPath < saveLen) {
sprintf(previous_save, "%s%s", index->path,
previous_save_ + index->fixedPath);
snprintf(previous_save, sizeof(previous_save), "%s%s",
index->path, previous_save_ + index->fixedPath);
} else {
snprintf(r->msg, sizeof(r->msg), "Bogus fixePath prefix for %s (prefixLen=%d)",
previous_save_, (int) index->fixedPath);
r->statuscode = STATUSCODE_INVALID;
}
} else {
sprintf(previous_save, "%s%s", index->path, previous_save_);
snprintf(previous_save, sizeof(previous_save), "%s%s",
index->path, previous_save_);
}
}
}
@@ -1764,7 +1816,7 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
FILE *fp = fopen(previous_save, "rb");
if (fp != NULL) {
r->adr = (char *) malloc(r->size + 1);
r->adr = cache_alloc_body(r->size);
if (r->adr != NULL) {
if (r->size > 0 && fread(r->adr, 1, r->size, fp) != r->size) {
r->statuscode = STATUSCODE_INVALID;
@@ -1784,7 +1836,7 @@ static PT_Element PT_ReadCache__Old_u(PT_Index index_, const char *url,
} else {
// lire fichier (d'un coup)
if (flags & FETCH_BODY) {
r->adr = (char *) malloc(r->size + 1);
r->adr = cache_alloc_body(r->size);
if (r->adr != NULL) {
if (fread(r->adr, 1, r->size, cache->dat) != r->size) { // erreur
free(r->adr);
@@ -1938,6 +1990,9 @@ static time_t getGMT(struct tm *tm) { /* hey, time_t is local! */
#if (defined(BSD) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD_kernel__))
time_t now = time(NULL);
time_t timezone = -localtime(&now)->tm_gmtoff;
#elif defined(_MSC_VER)
/* MSVC spells it _timezone */
const time_t timezone = _timezone;
#endif
return (time_t) (t - timezone);
}

113
src/proxytrack.vcxproj Normal file
View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{2F9E0E4C-2B5B-4B2E-9F6D-1C0A5E7B3D03}</ProjectGuid>
<RootNamespace>proxytrack</RootNamespace>
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)'==''">10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows</VcpkgTriplet>
<VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows</VcpkgTriplet>
<OutDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\obj\proxytrack\</IntDir>
<TargetName>proxytrack</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- Matches proxytrack_CFLAGS in Makefile.am. Standalone: it does not link
libhttrack, it only borrows headers (hts_effective_mime is a macro). -->
<PreprocessorDefinitions>WIN32;_CONSOLE;_MBCS;NO_MALLOCT;ZLIB_CONST;HTS_INTHASH_USES_MD5;ZLIB_DLL;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;$(MSBuildThisFileDirectory)proxy;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="proxy\main.c" />
<ClCompile Include="proxy\proxytrack.c" />
<ClCompile Include="proxy\store.c" />
<ClCompile Include="coucal\coucal.c" />
<ClCompile Include="htsmd5.c" />
<ClCompile Include="md5.c" />
<ClCompile Include="minizip\ioapi.c" />
<ClCompile Include="minizip\iowin32.c" />
<ClCompile Include="minizip\mztools.c" />
<ClCompile Include="minizip\unzip.c" />
<ClCompile Include="minizip\zip.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

12
src/vcpkg.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema":
"https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "libhttrack",
"version-string": "3.49",
"dependencies": [
"brotli",
"openssl",
"zlib",
"zstd"
]
}

50
src/version.rc Normal file
View File

@@ -0,0 +1,50 @@
// Version resource for libhttrack.dll and httrack.exe. Signing enforces that every
// binary in a release names the same product and version, so both need one.
// Spelled out here because a VERSIONINFO cannot take a version string apart;
// tests/01_engine-version-macros.test fails if this drifts from htsglobal.h.
// The per-binary parts come from libhttrack.rc / httrack.rc.
#include <winver.h>
#ifndef VER_FILE_DESCRIPTION
#define VER_FILE_DESCRIPTION "HTTrack Website Copier"
#endif
#ifndef VER_ORIGINAL_FILENAME
#define VER_ORIGINAL_FILENAME ""
#endif
#ifndef VER_FILETYPE
#define VER_FILETYPE VFT_APP
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3, 49, 12, 0
PRODUCTVERSION 3, 49, 12, 0
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VER_FILETYPE
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // U.S. English, Unicode
BEGIN
VALUE "CompanyName", "Xavier Roche"
VALUE "FileDescription", VER_FILE_DESCRIPTION
VALUE "FileVersion", "3.49.12"
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"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -12,10 +12,9 @@ if test -n "${BROWSER}"; then
fi
# Patch for Darwin/Mac by Ross Williams
if test "$(uname -s)" == "Darwin"; then
# Darwin/Mac OS X uses a system 'open' command to find
# the default browser. The -W flag causes it to wait for
# the browser to exit
BROWSEREXE="/usr/bin/open -W"
# Darwin's 'open -W' launches the default browser and waits for it to quit.
# Keep it only as a fallback so a configured $BROWSER still wins the search.
BROWSEREXEFALLBACK="/usr/bin/open -W"
fi
BINWD=$(dirname "$0")
SRCHPATH=("$BINWD" /usr/local/bin /usr/share/bin /usr/bin /usr/lib/httrack /usr/local/lib/httrack /usr/local/share/httrack /opt/local/bin /sw/bin "${HOME}/usr/bin" "${HOME}/bin")
@@ -81,6 +80,7 @@ for i in "${SRCHBROWSEREXE[@]}"; do
done
test -n "$BROWSEREXE" && break
done
test -n "$BROWSEREXE" || BROWSEREXE="${BROWSEREXEFALLBACK}"
test -n "$BROWSEREXE" || ! log "Could not find any suitable browser" || exit 1
# "browse" command
@@ -132,12 +132,12 @@ function cleanup {
}
# Cleanup in case of emergency
trap "cleanup now; exit" HUP INT QUIT ILL TRAP ABRT BUS FPE SEGV PIPE ALRM TERM STKFLT XCPU XFSZ
trap "cleanup now; exit" HUP INT QUIT PIPE TERM
# Got SRVURL, launch browser
launch_browser "${BROWSEREXE}" "${SRVURL}"
# That's all, folks!
trap "" HUP INT QUIT ILL TRAP ABRT BUS FPE SEGV PIPE ALRM TERM STKFLT XCPU XFSZ
trap "" HUP INT QUIT PIPE TERM
cleanup
exit 0

View File

@@ -1,120 +0,0 @@
# Microsoft Developer Studio Project File - Name="webhttrack" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Console Application" 0x0103
CFG=webhttrack - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "webhttrack.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "webhttrack.mak" CFG="webhttrack - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "webhttrack - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "webhttrack - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "webhttrack - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c
# ADD CPP /nologo /MD /W3 /GX /O2 /I "C:\Dev\\" /I "C:\Dev\IPv6Kit\inc\\" /I "C:\Dev\zlib\\" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /FD /c
# SUBTRACT CPP /YX /Yc /Yu
# ADD BASE RSC /l 0x40c /d "NDEBUG"
# ADD RSC /l 0x40c /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 wsock32.lib libhttrack.lib /nologo /subsystem:console /pdb:none /machine:I386 /force /out:"L:\HTTrack\httrack\webhttrack.exe" /libpath:"C:\Dev\openssl\lib" /libpath:"C:\Dev\zlib\dll32" /libpath:"C:\Dev\openssl\lib\out32dll" /libpath:"C:\temp\Releaselib"
!ELSEIF "$(CFG)" == "webhttrack - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c
# ADD CPP /nologo /MDd /W3 /Gm /GR /GX /ZI /Od /I "C:\Dev\\" /I "C:\Dev\IPv6Kit\inc\\" /I "C:\Dev\zlib\\" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FAcs /FR /FD /GZ /c
# SUBTRACT CPP /YX /Yc /Yu
# ADD BASE RSC /l 0x40c /d "_DEBUG"
# ADD RSC /l 0x40c /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 wsock32.lib libhttrack.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\temp\webhttrack.exe" /pdbtype:sept /libpath:"C:\Dev\openssl\lib" /libpath:"C:\Dev\zlib\dll32" /libpath:"C:\Dev\openssl\lib\out32dll" /libpath:"C:\temp\Releaselib"
!ENDIF
# Begin Target
# Name "webhttrack - Win32 Release"
# Name "webhttrack - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\htsserver.c
# End Source File
# Begin Source File
SOURCE=.\htsweb.c
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\htsserver.h
# End Source File
# Begin Source File
SOURCE=.\htsweb.h
# End Source File
# End Group
# Begin Group "Resource Files"
# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
# End Group
# Begin Source File
SOURCE=.\ReadMe.txt
# End Source File
# End Target
# End Project

View File

@@ -1,44 +0,0 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "libhttrack"=..\libhttrack\libhttrack.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Project: "webhttrack"=.\webhttrack.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
Begin Project Dependency
Project_Dep_Name libhttrack
End Project Dependency
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

View File

@@ -1,443 +0,0 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9,00"
Name="webhttrack"
ProjectGUID="{B2A68D1B-3EB3-4B16-B634-8D36AC4266EC}"
RootNamespace="webhttrack"
TargetFrameworkVersion="131072"
>
<Platforms>
<Platform
Name="Win32"
/>
<Platform
Name="x64"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="C:\temp\webhttrack\Debug"
IntermediateDirectory="C:\temp\webhttrack\Debug"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TypeLibraryName=".\Debug/webhttrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\httrack\src;C:\Dev\zlib\;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
RuntimeTypeInfo="true"
PrecompiledHeaderFile=".\Debug/webhttrack.pch"
AssemblerOutput="2"
AssemblerListingLocation=".\Debug/"
ObjectFile=".\Debug/"
ProgramDataBaseFileName=".\Debug/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="_DEBUG"
Culture="1036"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="C:\temp\httrack\webhttrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib;C:\temp\debug_libz;C:\Dev\openssl\lib\out32dll;C:\temp\Debuglib"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Debug/webhttrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Debug|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
TypeLibraryName=".\Debug/webhttrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="C:\Dev\httrack\src;C:\Dev\zlib\;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
RuntimeTypeInfo="true"
PrecompiledHeaderFile=".\Debug/webhttrack.pch"
AssemblerOutput="2"
AssemblerListingLocation=".\Debug/"
ObjectFile=".\Debug/"
ProgramDataBaseFileName=".\Debug/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="_DEBUG"
Culture="1036"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="C:\temp\httrack64\webhttrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib;C:\temp\debug_libz64;C:\Dev\openssl\lib\out32dll;C:\temp\Debuglib"
GenerateDebugInformation="true"
ProgramDatabaseFile=".\Debug/webhttrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="C:\temp\webhttrack\Release"
IntermediateDirectory="C:\temp\webhttrack\Release"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TypeLibraryName=".\Release/webhttrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
Optimization="3"
InlineFunctionExpansion="1"
AdditionalIncludeDirectories="C:\Dev\httrack\src;C:\Dev\zlib\;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
StringPooling="true"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
PrecompiledHeaderFile=".\Release/webhttrack.pch"
AssemblerListingLocation="c:\temp\VCRelease/"
ObjectFile="c:\temp\VCRelease/"
ProgramDataBaseFileName="c:\temp\VCRelease/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1036"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\webhttrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib;C:\temp\release_libz;C:\Dev\openssl\lib\out32dll;C:\temp\Releaselib"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\webhttrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;"
/>
</Configuration>
<Configuration
Name="Release|x64"
OutputDirectory="$(SolutionDir)$(PlatformName)\$(ConfigurationName)"
IntermediateDirectory="$(PlatformName)\$(ConfigurationName)"
ConfigurationType="1"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
UseOfMFC="0"
ATLMinimizesCRunTimeLibraryUsage="false"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
TargetEnvironment="3"
TypeLibraryName=".\Release/webhttrack.tlb"
HeaderFileName=""
/>
<Tool
Name="VCCLCompilerTool"
Optimization="3"
InlineFunctionExpansion="1"
AdditionalIncludeDirectories="C:\Dev\httrack\src;C:\Dev\zlib\;&quot;C:\Dev\openssl-1.0.1j\include&quot;;C:\Dev\httrack\src\coucal"
PreprocessorDefinitions="_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE"
StringPooling="true"
RuntimeLibrary="2"
EnableFunctionLevelLinking="true"
PrecompiledHeaderFile=".\Release/webhttrack.pch"
AssemblerListingLocation="c:\temp\VCRelease64/"
ObjectFile="c:\temp\VCRelease64/"
ProgramDataBaseFileName="c:\temp\VCRelease64/"
BrowseInformation="1"
WarningLevel="3"
SuppressStartupBanner="true"
Detect64BitPortabilityProblems="true"
CompileAs="0"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
PreprocessorDefinitions="NDEBUG"
Culture="1036"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="Ws2_32.lib libhttrack.lib"
OutputFile="O:\HTTrack\httrack\x64\webhttrack.exe"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="C:\Dev\openssl\lib;C:\temp\release_libz64;C:\Dev\openssl\lib\out32dll;C:\temp\Releaselib64"
GenerateDebugInformation="true"
ProgramDatabaseFile="O:\HTTrack\httrack\x64\webhttrack.pdb"
SubSystem="1"
RandomizedBaseAddress="1"
DataExecutionPrevention="0"
TargetMachine="17"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
CommandLine="&quot;C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\signtool.exe&quot; sign /f &quot;Z:\Mes Documents\certificats\httrack-com\httrack-signing-certificate.pfx&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll $(TargetPath)&#x0D;&#x0A;"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
>
<File
RelativePath="htsserver.c"
>
</File>
<File
RelativePath="htsweb.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl"
>
</Filter>
<Filter
Name="Resource Files"
Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
>
<File
RelativePath="htsserver.h"
>
</File>
<File
RelativePath="htsweb.h"
>
</File>
</Filter>
<File
RelativePath="ReadMe.txt"
>
</File>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

109
src/webhttrack.vcxproj Normal file
View File

@@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{2F9E0E4C-2B5B-4B2E-9F6D-1C0A5E7B3D02}</ProjectGuid>
<RootNamespace>webhttrack</RootNamespace>
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)'==''">10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows</VcpkgTriplet>
<VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows</VcpkgTriplet>
<OutDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\obj\webhttrack\</IntDir>
<TargetName>webhttrack</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- Matches htsserver_CFLAGS in Makefile.am. -->
<PreprocessorDefinitions>WIN32;_CONSOLE;_MBCS;ZLIB_CONST;HTS_INTHASH_USES_MD5;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="htsserver.c" />
<ClCompile Include="htsweb.c" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="libhttrack.vcxproj">
<Project>{e76ad871-54c1-45e8-a657-6117adeffb46}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@@ -23,7 +23,7 @@ conv 'iso-8859-1' 'café' 'café'
# windows-1252 is NOT latin-1: 0x80 is the euro sign, not U+0080. This is the
# case that actually exercises the cp1252 table (the 0x80-0x9F range).
conv 'windows-1252' $'\x80' '€'
conv 'windows-1252' 'hex:80' '€'
# pure ASCII is charset-invariant
conv 'us-ascii' 'hello' 'hello'
@@ -34,8 +34,8 @@ conv 'no-such-charset-xyz' 'abc' 'abc'
test "$(httrack -O /dev/null -#test=charset 'no-such-charset-xyz' 'café' 2>/dev/null)" == "" || exit 1
# malformed UTF-8 (lone continuation byte, truncated lead byte) must not crash
runs 'utf-8' $'\x80'
runs 'utf-8' $'\xc3'
runs 'utf-8' 'hex:80'
runs 'utf-8' 'hex:c3'
# hts_getCharsetFromMeta: <meta> charset extraction, HTML5 and legacy forms.
# -#test=metacharset <html> prints the charset, or "(none)".
@@ -89,11 +89,11 @@ utf8() {
utf8 'plain' '1'
utf8 'café' '1'
utf8 '统计' '1'
utf8 "$(printf 'caf\xe9')" '0' # latin-1 accent
utf8 "$(printf '\xc3')" '0' # truncated lead byte
utf8 "$(printf '\xa9')" '0' # lone continuation byte
utf8 "$(printf '\xf0\x9f\x98\x80')" '1' # 4-byte emoji
utf8 "$(printf '\xc0\xaf')" '0' # overlong (latin-1 "À¯" must stay convertible)
utf8 "$(printf '\xed\xa0\x80')" '0' # UTF-16 surrogate
utf8 "$(printf '\xf4\x90\x80\x80')" '0' # above U+10FFFF
utf8 "$(printf '\xf8\x88\x80\x80\x80')" '0' # legacy 5-byte form
utf8 'hex:636166e9' '0' # latin-1 accent
utf8 'hex:c3' '0' # truncated lead byte
utf8 'hex:a9' '0' # lone continuation byte
utf8 'hex:f09f9880' '1' # 4-byte emoji
utf8 'hex:c0af' '0' # overlong (latin-1 "À¯" must stay convertible)
utf8 'hex:eda080' '0' # UTF-16 surrogate
utf8 'hex:f4908080' '0' # above U+10FFFF
utf8 'hex:f888808080' '0' # legacy 5-byte form

34
tests/01_engine-crange.test Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
#
set -euo pipefail
# Content-Range parse (treathead via -#test=crange). Hostile values must clamp
# to 0, and the crange +/- 1 arithmetic must not sign-overflow (UBSan aborts on
# the pre-fix binary at the INT64_MIN cases).
cr() {
local want="$1"
shift
out="$(httrack -O /dev/null -#test=crange "$@")"
test "$out" == "$want" || {
echo "FAIL: $* -> '$out' (want '$want')"
exit 1
}
}
# Normal range and the '*/N' fallback both parse through unchanged.
cr 'crange_start=0 crange_end=70870 crange=70871' 'Content-Range: bytes 0-70870/70871'
cr 'crange_start=0 crange_end=70870 crange=70871' 'Content-Range: bytes */70871'
# INT64_MIN total in the fallback would make crange-1 overflow: clamp to 0.
cr 'crange_start=0 crange_end=0 crange=0' 'Content-Range: bytes */-9223372036854775808'
# Any negative field in the 3-field form clamps the whole triple to 0 (each
# field independently: a leading negative start/end alone still zeroes all).
cr 'crange_start=0 crange_end=0 crange=0' 'Content-Range: bytes -1--1/-1'
cr 'crange_start=0 crange_end=0 crange=0' 'Content-Range: bytes -5-10/20'
# INT64_MAX is non-negative: it passes through, the parse must not mangle it.
cr 'crange_start=0 crange_end=9223372036854775807 crange=9223372036854775807' \
'Content-Range: bytes 0-9223372036854775807/9223372036854775807'

View File

@@ -52,14 +52,15 @@ run "$tmp/dir" "$tmp/adir"
loghas 'Could not include URL list "[^"]+": not a regular file'
# unreadable regular file: the fopen() errno arm fires, distinct from the
# directory branch. Root bypasses mode 000, so skip it there.
if test "$(id -u)" -ne 0; then
: >"$tmp/noperm.txt"
chmod 000 "$tmp/noperm.txt"
# directory branch. Needs an OS that enforces mode 000: root bypasses it, and
# Windows ignores chmod entirely, so probe rather than assume.
: >"$tmp/noperm.txt"
chmod 000 "$tmp/noperm.txt"
if ! cat "$tmp/noperm.txt" >/dev/null 2>&1; then
run "$tmp/perm" "$tmp/noperm.txt"
chmod 644 "$tmp/noperm.txt"
loghas 'Could not include URL list "[^"]+": .+'
lognot 'not a regular file'
fi
chmod 644 "$tmp/noperm.txt"
exit 0

View File

@@ -192,3 +192,14 @@ test "$(with_timeout httrack -O /dev/null -#test=filter "$pat" "$subj")" == "$su
# (guards against a single-backtrack-point matcher rewrite)
match '*[aX]X*[a]Y' 'aXaXaY'
nomatch '*[aX]X*[a]Y' 'aXbXaY'
# hostile patterns must not stack-overflow or hang: length + depth + work caps
test "$(with_timeout httrack -O /dev/null -#test=filterbounds)" == "filterbounds: OK" || exit 1
# and the depth cap must hold on the 1MB stack a Windows thread gets (#574):
# uncapped, the same pattern recursed ~900KB deep. Skipped where 512K is too
# small to even start the binary.
if (ulimit -s 512 && httrack --version >/dev/null 2>&1); then
out=$(ulimit -s 512 && with_timeout httrack -O /dev/null -#test=filterbounds)
test "$out" == "filterbounds: OK" || exit 1
fi

View File

@@ -0,0 +1,18 @@
#!/bin/bash
#
# File sizes past the 32-bit wrap ('httrack -#test=fsize <dir>'). 5GB, so a
# signed *and* an unsigned truncation both show up; sparse, so it costs no disk.
set -euo pipefail
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
out=$(httrack -#test=fsize "$dir")
echo "$out"
want="fsize: width=8,8 size=5368709120,5368709120 psize=5368709120 absent=-1"
test "$out" == "$want" || {
echo "FAIL: unexpected size report (want '$want')"
exit 1
}

8
tests/01_engine-identabs.test Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
#
set -euo pipefail
# A 2047-byte hostless "?"-URL used to overflow fil[] by one byte and abort in
# strncat_safe_; the guard now returns -1 while valid URLs still parse.
test "$(httrack -O /dev/null -#test=identabs)" == "identabs self-test OK" || exit 1

View File

@@ -38,4 +38,7 @@ dec 'xn--!!!' '㚯'
dec 'xn--already-encoded.com' 'ǮalǭǮreaǭǫdy.com'
# second label has a truncated UTF-8 sequence: used to leak the segment buffer.
enc_bad "$(printf 'b\xc3\xbcchev.\xe4\xbe\x8b\xe5\xad\x62\xc3\xbccheple')"
# Fed as hex: the raw bytes must reach the encoder intact, but invalid UTF-8
# cannot survive a Windows command line (it round-trips through UTF-16 to valid
# U+FFFD, which then encodes fine instead of being rejected).
enc_bad 'hex:62c3bc636865762ee4be8be5ad62c3bc636865706c65'

View File

@@ -1,7 +0,0 @@
#!/bin/bash
#
set -euo pipefail
# .class constant-pool count is capped to the file size (calloc DoS).
httrack -O /dev/null -#test=java run | grep -q "java constant-pool cap self-test OK"

44
tests/01_engine-proxyurl.test Executable file
View File

@@ -0,0 +1,44 @@
#!/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=..", where host= is what the connect resolves.
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'
p 'proxy.example.com' 'name=proxy.example.com port=8080 host=proxy.example.com'
# 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'
p 'user:pass@proxy' 'name=user:pass@proxy port=8080 host=proxy'
# 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'
p 'http://proxy' 'name=http://proxy port=8080 host=proxy'
# socks5/socks5h default to 1080 and resolve to the bare host
p 'socks5://host:1080' 'name=socks5://host port=1080 host=host'
p 'socks5://host' 'name=socks5://host port=1080 host=host'
p 'socks5h://host' 'name=socks5h://host port=1080 host=host'
# explicit port wins over the 1080 default, with and without userinfo
p 'socks5://host:9050' 'name=socks5://host port=9050 host=host'
p 'socks5://user:pass@host:9050' 'name=socks5://user:pass@host port=9050 host=host'
# 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'
p 'socks5://us%3Aer:pass@host:1080' 'name=socks5://us%3Aer:pass@host port=1080 host=host'
p 'socks5://a@b:c@host:1080' 'name=socks5://a@b:c@host port=1080 host=host'
p 'socks5://naïve:pass@héllo:1080' 'name=socks5://naïve:pass@héllo port=1080 host=héllo'
# a lone ':' must not underflow the backward scan
p ':' 'name= port=8080 host='

View File

@@ -32,6 +32,13 @@ esac
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_rcfile.XXXXXX") || exit 1
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
# HTS_HTTRACKRC is ".httrackrc" on POSIX but "httrackrc" on Windows: write both,
# each platform reads the one it knows.
write_rc() {
cat >"$1/.httrackrc"
cp "$1/.httrackrc" "$1/httrackrc"
}
# --- 1. alias token survives the bound intact -------------------------------
d1="$tmp/intact"
mkdir -p "$d1"
@@ -40,7 +47,7 @@ echo '<html><body>hello</body></html>' >"$d1/index.html"
# optinclude_file() lowercases each config line, so the marker is lowercase to
# survive the comparison verbatim.
marker='zzz_rcfile_marker_0123456789_abcdefghijklmnopqrstuvwxyz_intact'
printf 'user-agent=%s\n' "$marker" >"$d1/.httrackrc"
printf 'user-agent=%s\n' "$marker" | write_rc "$d1"
# Run with no -O so the working-directory .httrackrc is loaded (an -O path makes
# the engine skip the rc files). Output lands in the temp dir. Guard the run so a
@@ -73,7 +80,7 @@ echo '<html><body>hi</body></html>' >"$d2/index.html"
val=$(printf 'a%.0s' $(seq 1 200))
for _ in $(seq 1 400); do
printf 'user-agent=%s\n' "$val"
done >"$d2/.httrackrc"
done | write_rc "$d2"
# The process aborts (httrack turns the fatal signal into exit 134 either way),
# so the exit code does not distinguish the bounded abort from a heap overflow;

View File

@@ -35,7 +35,8 @@ chk image/avif hex:0000001C6674797061766966 "$yes"
chk image/avif hex:0000001C6674797068656963 "$no" # heic brand is not avif
chk image/heic hex:0000001C6674797068656963 "$yes"
chk image/svg+xml '<svg xmlns="x">' "$yes"
chk image/svg+xml $'\xef\xbb\xbf <?xml version="1.0"?>' "$yes" # BOM+ws skip
# BOM+ws skip; hex, as Windows argv cannot carry the raw BOM through the ANSI codepage
chk image/svg+xml hex:EFBBBF20203C3F786D6C2076657273696F6E3D22312E30223F3E "$yes"
# audio / video
chk audio/mpeg 'ID3xxx' "$yes"

View File

@@ -0,0 +1,9 @@
#!/bin/bash
#
set -euo pipefail
# The SOCKS5 handshake framing and credential split, driven against scripted
# server replies (frame draining, oversize rejects, RFC 1929 fields).
httrack -O /dev/null '-#test=socks5' | grep -q "socks5 self-test OK"

View File

@@ -32,10 +32,10 @@ case "$err" in
esac
# Same guarantee for the htsbuff builder. The source is exactly the buffer
# capacity (4 bytes into a 4-byte buffer), so this also pins the boundary: a
# capacity (6 bytes into a 6-byte buffer), so this also pins the boundary: a
# '<=' off-by-one in the capacity check would let it through (and print "NOT
# aborted"). Match the specific htsbuff abort message, not just any assert.
err=$(httrack -#test=strsafe overflow-buff "abcd" 2>&1) || true
err=$(httrack -#test=strsafe overflow-buff "abcdef" 2>&1) || true
case "$err" in
*"strsafe: NOT aborted"*)
echo "htsbuff over-capacity write was NOT caught" >&2

View File

@@ -0,0 +1,64 @@
#!/bin/bash
#
# version.rc repeats the version that htsglobal.h declares. Signing enforces that
# every binary in a release reports the same one, so a drift fails the signing
# request on release day rather than the build. Assert the two agree.
set -euo pipefail
src="${top_srcdir:-..}/src"
h="$src/htsglobal.h"
rc="$src/version.rc"
for f in "$h" "$rc"; do
[ -f "$f" ] || {
echo "cannot find $f"
exit 1
}
done
# 3.49-12 (display) and 3.49.12 (dotted).
version=$(sed -n 's/^#define HTTRACK_VERSION[[:space:]][[:space:]]*"\([^"]*\)".*/\1/p' "$h")
versionid=$(sed -n 's/^#define HTTRACK_VERSIONID[[:space:]][[:space:]]*"\([^"]*\)".*/\1/p' "$h")
if [ -z "$version" ] || [ -z "$versionid" ]; then
echo "could not read the version from $h"
exit 1
fi
# The same version, as version.rc states it.
fileversion=$(sed -n 's/^[[:space:]]*FILEVERSION[[:space:]][[:space:]]*\(.*\)$/\1/p' "$rc" | tr -d ' \r')
productversion=$(sed -n 's/^[[:space:]]*PRODUCTVERSION[[:space:]][[:space:]]*\(.*\)$/\1/p' "$rc" | tr -d ' \r')
rc_fileversion=$(sed -n 's/.*VALUE "FileVersion",[[:space:]]*"\([^"]*\)".*/\1/p' "$rc")
rc_productversion=$(sed -n 's/.*VALUE "ProductVersion",[[:space:]]*"\([^"]*\)".*/\1/p' "$rc")
rc_productname=$(sed -n 's/.*VALUE "ProductName",[[:space:]]*"\([^"]*\)".*/\1/p' "$rc")
# 3.49.12 -> 3,49,12,0
expected_numeric="$(echo "$versionid" | tr '.' ','),0"
fail=0
check() { # what expected actual
if [ "$2" != "$3" ]; then
echo "version.rc $1 is \"$3\", but htsglobal.h says it should be \"$2\""
fail=1
fi
}
check FILEVERSION "$expected_numeric" "$fileversion"
check PRODUCTVERSION "$expected_numeric" "$productversion"
check FileVersion "$versionid" "$rc_fileversion"
check ProductVersion "$version" "$rc_productversion"
# Signing pins the product name too.
check ProductName "HTTrack Website Copier" "$rc_productname"
[ "$fail" -eq 0 ] || exit 1
# And the shipped binary must agree with the header it was built from.
out=$(httrack --version)
case "$out" in
*"$version"*) ;;
*)
echo "httrack --version says \"$out\", which does not mention $version"
exit 1
;;
esac
echo "version resource agrees with htsglobal.h: $version ($expected_numeric)"

29
tests/01_engine-xfread.test Executable file
View File

@@ -0,0 +1,29 @@
#!/bin/bash
#
set -euo pipefail
# http_xfread1 must refuse an in-memory receive buffer that would exceed a
# 32-bit index (hostile Content-Length, or an endless stream) rather than
# allocate it, while still accepting a normal small size. -#test=xfread-limit
# drives both refusal paths and the accept path.
out="$(httrack -O /dev/null -#test=xfread-limit)"
for case in bylen bygrow; do
echo "$out" | grep -q "${case}: refused=1 adr=null msg=In-memory content too large" || {
echo "FAIL ${case}: $out"
exit 1
}
done
# Exactly INT32_MAX must be refused too (the reallocs add 1).
echo "$out" | grep -q 'boundary: msg=In-memory content too large' || {
echo "FAIL boundary: $out"
exit 1
}
# The guard must NOT fire for a legitimate small size.
if echo "$out" | grep -q 'accept: msg=In-memory content too large'; then
echo "FAIL accept (guard fired on a legit size): $out"
exit 1
fi

View File

@@ -0,0 +1,11 @@
#!/bin/bash
#
set -euo pipefail
# brotli/zstd decode, unknown codings, and the decoded-size budget.
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
httrack -O /dev/null -#test=contentcodings "$dir" run |
grep -q "contentcodings self-test OK"

View File

@@ -66,9 +66,12 @@ test -n "$port" || {
}
out="$tmpdir/crawl"
# macOS has only 127.0.0.1, so the dead 127.0.0.x connects block to the timeout
# instead of refusing instantly (as on Linux); a small --timeout + --retries=0
# bound the stall without changing what the fallback exercises.
HTTRACK_DEBUG_RESOLVE="deadhost:127.0.0.2,127.0.0.1" \
httrack "http://deadhost:$port/simple/basic.html" -O "$out" \
-c1 --robots=0 --timeout=30 --quiet -Z >"$tmpdir/log" 2>&1
-c1 --robots=0 --timeout=5 --retries=0 --quiet -Z >"$tmpdir/log" 2>&1
log="$out/hts-log.txt"
@@ -92,12 +95,12 @@ test -f "$out/deadhost_$port/simple/basic.html" || {
exit 1
}
# every address refused: the slot exhausts the list, then errors out (the
# harness timeout would catch a hang/loop; refused connects are instant)
# every address dead: the slot exhausts the list, then errors out (the harness
# timeout would catch a hang/loop)
out2="$tmpdir/crawl2"
HTTRACK_DEBUG_RESOLVE="alldead:127.0.0.2,127.0.0.3" \
httrack "http://alldead:$port/simple/basic.html" -O "$out2" \
-c1 --robots=0 --timeout=30 --quiet -Z >"$tmpdir/log2" 2>&1
-c1 --robots=0 --timeout=5 --retries=0 --quiet -Z >"$tmpdir/log2" 2>&1
log2="$out2/hts-log.txt"
grep -q "trying next address" "$log2" || {

View File

@@ -1,26 +0,0 @@
#!/bin/bash
# The java plugin must load (versioned dlopen name) and parse a .class
# constant pool: a resource named only inside Foo.class gets crawled.
set -e
: "${top_srcdir:=..}"
tmproot=$(mktemp -d)
trap 'rm -rf "$tmproot"' EXIT
mkdir "$tmproot/javaclass"
cat >"$tmproot/javaclass/index.html" <<'EOF'
<html><body><a href="Foo.class">applet</a></body></html>
EOF
printf 'GIF89a' >"$tmproot/javaclass/hello.gif"
# magic/minor/major, count=2, one CONSTANT_Utf8 "hello.gif", class/superclass
printf '\xCA\xFE\xBA\xBE\x00\x00\x00\x32\x00\x02\x01\x00\x09hello.gif\x00\x00\x00\x00' \
>"$tmproot/javaclass/Foo.class"
# --log-found first: a plugin-load failure (wrong soname, shadowing system
# libhtsjava) trips here on the banner suffix, not as an opaque "not found".
bash "$top_srcdir/tests/local-crawl.sh" --root "$tmproot" --errors 0 \
--log-found '\+libhtsjava' \
--found 'javaclass/Foo.class' \
--found 'javaclass/hello.gif' \
httrack 'BASEURL/javaclass/index.html'

View File

@@ -53,4 +53,4 @@ bash "$top_srcdir/tests/local-crawl.sh" --rerun \
--log-found ', no files updated' \
--max-mirror-bytes 700000 \
--min-mirror-bytes 500000 \
httrack 'BASEURL/big/index.html' --retries=0 -c8 -%c100 -A100000000
httrack 'BASEURL/big/index.html' --retries=0 -c16 -%c100 -A100000000

View File

@@ -0,0 +1,16 @@
#!/bin/bash
#
# -M caps received volume, not saved 200-only bytes (#520): links to large 404
# bodies pump received >> saved; the cap must trip though little lands on disk.
set -euo pipefail
: "${top_srcdir:=..}"
# The mirror bound is the load-bearing half: it fires while <100 KB is saved,
# so the cap can only have tripped on received volume, not saved bytes (which
# would exceed the cap and fetch all 16 links).
bash "$top_srcdir/tests/local-crawl.sh" \
--log-found 'More than 500000 bytes have been transferred.. giving up' \
--max-mirror-bytes 100000 \
httrack 'BASEURL/maxrecv/index.html' -M500000 -c4

View File

@@ -0,0 +1,118 @@
#!/bin/bash
# C7: a server 304 answering a resume's Range request is out of protocol; httrack
# must drop the partial and refetch, not finalize it as complete at the partial
# byte count. Pass 1 leaves a partial + temp-ref; pass 2 resumes, gets a bogus
# 304, and must recover the whole file.
set -u
: "${top_srcdir:=..}"
testdir=$(cd "$(dirname "$0")" && pwd)
server="${testdir}/local-server.py"
command -v python3 >/dev/null || ! echo "python3 not found; skipping" || exit 77
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_c7.XXXXXX") || exit 1
serverpid=
crawlpid=
cleanup() {
test -n "$crawlpid" && kill -9 "$crawlpid" 2>/dev/null
if test -n "$serverpid"; then
kill "$serverpid" 2>/dev/null
wait "$serverpid" 2>/dev/null
fi
rm -rf "$tmpdir"
}
trap cleanup EXIT HUP INT QUIT PIPE TERM
serverlog="${tmpdir}/server.log"
counter="${tmpdir}/reqcount"
mark="${tmpdir}/got304"
RESUME304_COUNTER="$counter" RESUME304_MARK="$mark" python3 "$server" --root "${testdir}/server-root" >"$serverlog" 2>&1 &
serverpid=$!
port=
for _ in $(seq 1 50); do
line=$(head -n1 "$serverlog" 2>/dev/null)
if test "${line%% *}" == "PORT"; then
port="${line#PORT }"
break
fi
kill -0 "$serverpid" 2>/dev/null || {
echo "server exited early: $(cat "$serverlog")"
exit 1
}
sleep 0.1
done
test -n "$port" || {
echo "could not discover server port"
exit 1
}
base="http://127.0.0.1:${port}"
which httrack >/dev/null || {
echo "could not find httrack"
exit 1
}
out="${tmpdir}/crawl"
mkdir "$out"
common=(-O "$out" --quiet --disable-security-limits --robots=0 --timeout=30 --retries=1 -c1)
refdir="${out}/hts-cache/ref"
# --- pass 1: crawl, interrupt once the blob download is underway -------------
printf '[pass 1: interrupt mid-download] ..\t'
httrack "${common[@]}" "${base}/resume304/index.html" >"${tmpdir}/log1" 2>&1 &
crawlpid=$!
for _ in $(seq 1 300); do
test -s "$counter" && break
kill -0 "$crawlpid" 2>/dev/null || break
sleep 0.1
done
sleep 0.5
kill -TERM "$crawlpid" 2>/dev/null
wait "$crawlpid" 2>/dev/null
crawlpid=
test -n "$(find "$refdir" -name '*.ref' 2>/dev/null)" || {
echo "FAIL: no temp-ref survived pass 1; cannot drive C7"
exit 1
}
echo "OK (temp-ref present)"
before=$(wc -c <"$counter" 2>/dev/null || echo 0)
# --- pass 2: --continue -> resume Range -> bogus 304 -> drop + refetch --------
printf '[pass 2: resume gets 304, must refetch] ..\t'
httrack "${common[@]}" --continue "${base}/resume304/index.html" >"${tmpdir}/log2" 2>&1
echo "OK (terminated)"
blob=$(find "$out" -name blob.bin 2>/dev/null | head -1)
full=8200 # len(RESUME304_BODY) = len("C7DATA--") + 8192
printf '[file recovered whole] ..\t'
test -s "$blob" || {
echo "FAIL: blob.bin missing after the 304 resume (partial dropped, not refetched)"
exit 1
}
got=$(wc -c <"$blob")
test "$got" -eq "$full" || {
echo "FAIL: blob.bin is ${got} bytes, expected ${full} (partial finalized as complete)"
exit 1
}
echo "OK (${got} bytes)"
# Pass 2 must issue at least two requests: the resume (Range -> 304) and the
# range-less refetch (-> 200). Exactly one means the partial was trusted.
after=$(wc -c <"$counter" 2>/dev/null || echo 0)
hits=$((after - before))
printf '[resume then refetch] ..\t'
test "$hits" -ge 2 || {
echo "FAIL: only ${hits} pass-2 request(s); the 304 was trusted, no refetch"
exit 1
}
echo "OK (${hits} requests)"
# The recovery must go through the resume path: a Range request that drew the
# bogus 304 (a fresh re-crawl sends no Range, so this marker stays empty).
printf '[resume Range drew a 304] ..\t'
test -s "$mark" || {
echo "FAIL: no Range request got a 304; the resume path was not exercised"
exit 1
}
echo "OK"

View File

@@ -0,0 +1,96 @@
#!/bin/bash
# C2: a resume whose 206 carries a Content-Range end of INT64_MAX must not
# sign-overflow the crange+1 range check (UBSan aborts on the pre-fix binary).
# Pass 1 leaves a partial + temp-ref; pass 2 resumes, gets the hostile 206, and
# must reject the range and refetch the whole file rather than overflow. Teeth
# are UBSan-only: a normal build wraps to the same reject+restart.
set -u
: "${top_srcdir:=..}"
testdir=$(cd "$(dirname "$0")" && pwd)
server="${testdir}/local-server.py"
command -v python3 >/dev/null || ! echo "python3 not found; skipping" || exit 77
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_crange.XXXXXX") || exit 1
serverpid=
crawlpid=
cleanup() {
test -n "$crawlpid" && kill -9 "$crawlpid" 2>/dev/null
if test -n "$serverpid"; then
kill "$serverpid" 2>/dev/null
wait "$serverpid" 2>/dev/null
fi
rm -rf "$tmpdir"
}
trap cleanup EXIT HUP INT QUIT PIPE TERM
serverlog="${tmpdir}/server.log"
python3 "$server" --root "${testdir}/server-root" >"$serverlog" 2>&1 &
serverpid=$!
port=
for _ in $(seq 1 50); do
line=$(head -n1 "$serverlog" 2>/dev/null)
if test "${line%% *}" == "PORT"; then
port="${line#PORT }"
break
fi
kill -0 "$serverpid" 2>/dev/null || {
echo "server exited early: $(cat "$serverlog")"
exit 1
}
sleep 0.1
done
test -n "$port" || {
echo "could not discover server port"
exit 1
}
base="http://127.0.0.1:${port}"
which httrack >/dev/null || {
echo "could not find httrack"
exit 1
}
out="${tmpdir}/crawl"
mkdir "$out"
common=(-O "$out" --quiet --disable-security-limits --robots=0 --timeout=30 --retries=1 -c1)
refdir="${out}/hts-cache/ref"
# --- pass 1: crawl, interrupt once the blob download is underway -------------
printf '[pass 1: interrupt mid-download] ..\t'
httrack "${common[@]}" "${base}/crange206/index.html" >"${tmpdir}/log1" 2>&1 &
crawlpid=$!
for _ in $(seq 1 300); do
test -n "$(find "$refdir" -name '*.ref' 2>/dev/null)" && break
kill -0 "$crawlpid" 2>/dev/null || break
sleep 0.1
done
sleep 0.3
kill -TERM "$crawlpid" 2>/dev/null
wait "$crawlpid" 2>/dev/null
crawlpid=
test -n "$(find "$refdir" -name '*.ref' 2>/dev/null)" || {
echo "FAIL: no temp-ref survived pass 1; cannot drive the resume"
exit 1
}
echo "OK (temp-ref present)"
# --- pass 2: --continue -> resume -> hostile INT64_MAX Content-Range ----------
printf '[pass 2: hostile 206, no overflow, refetch] ..\t'
httrack "${common[@]}" --continue "${base}/crange206/index.html" >"${tmpdir}/log2" 2>&1
echo "OK (terminated)"
blob=$(find "$out" -name blob.bin 2>/dev/null | head -1)
full=6008 # len(CRANGE206_BODY) = len("CR206DAT") + 6000
printf '[file recovered whole] ..\t'
test -s "$blob" || {
echo "FAIL: blob.bin missing after the hostile 206"
exit 1
}
got=$(wc -c <"$blob")
test "$got" -eq "$full" || {
echo "FAIL: blob.bin is ${got} bytes, expected ${full}"
exit 1
}
echo "OK (${got} bytes)"

View File

@@ -0,0 +1,96 @@
#!/bin/bash
# C2 (memory branch): a resume whose 206 lies text/html with a matching
# INT64_MAX Content-Length must not overflow the resume buffer-size add
# (UBSan aborts on the pre-fix binary at alloc_mem += totalsize). Pass 1 leaves
# a partial + temp-ref; pass 2 resumes and must reject and refetch the whole
# file. Teeth are UBSan-only: a normal build wraps to the same reject+restart.
set -u
: "${top_srcdir:=..}"
testdir=$(cd "$(dirname "$0")" && pwd)
server="${testdir}/local-server.py"
command -v python3 >/dev/null || ! echo "python3 not found; skipping" || exit 77
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_crangemem.XXXXXX") || exit 1
serverpid=
crawlpid=
cleanup() {
test -n "$crawlpid" && kill -9 "$crawlpid" 2>/dev/null
if test -n "$serverpid"; then
kill "$serverpid" 2>/dev/null
wait "$serverpid" 2>/dev/null
fi
rm -rf "$tmpdir"
}
trap cleanup EXIT HUP INT QUIT PIPE TERM
serverlog="${tmpdir}/server.log"
python3 "$server" --root "${testdir}/server-root" >"$serverlog" 2>&1 &
serverpid=$!
port=
for _ in $(seq 1 50); do
line=$(head -n1 "$serverlog" 2>/dev/null)
if test "${line%% *}" == "PORT"; then
port="${line#PORT }"
break
fi
kill -0 "$serverpid" 2>/dev/null || {
echo "server exited early: $(cat "$serverlog")"
exit 1
}
sleep 0.1
done
test -n "$port" || {
echo "could not discover server port"
exit 1
}
base="http://127.0.0.1:${port}"
which httrack >/dev/null || {
echo "could not find httrack"
exit 1
}
out="${tmpdir}/crawl"
mkdir "$out"
common=(-O "$out" --quiet --disable-security-limits --robots=0 --timeout=30 --retries=1 -c1)
refdir="${out}/hts-cache/ref"
# --- pass 1: crawl, interrupt once the blob download is underway -------------
printf '[pass 1: interrupt mid-download] ..\t'
httrack "${common[@]}" "${base}/crange206mem/index.html" >"${tmpdir}/log1" 2>&1 &
crawlpid=$!
for _ in $(seq 1 300); do
test -n "$(find "$refdir" -name '*.ref' 2>/dev/null)" && break
kill -0 "$crawlpid" 2>/dev/null || break
sleep 0.1
done
sleep 0.3
kill -TERM "$crawlpid" 2>/dev/null
wait "$crawlpid" 2>/dev/null
crawlpid=
test -n "$(find "$refdir" -name '*.ref' 2>/dev/null)" || {
echo "FAIL: no temp-ref survived pass 1; cannot drive the resume"
exit 1
}
echo "OK (temp-ref present)"
# --- pass 2: --continue -> resume -> hostile INT64_MAX Content-Range ----------
printf '[pass 2: hostile 206, no overflow, refetch] ..\t'
httrack "${common[@]}" --continue "${base}/crange206mem/index.html" >"${tmpdir}/log2" 2>&1
echo "OK (terminated)"
blob=$(find "$out" -name blob.bin 2>/dev/null | head -1)
full=6008 # len(CRANGE206_BODY) = len("CR206DAT") + 6000
printf '[file recovered whole] ..\t'
test -s "$blob" || {
echo "FAIL: blob.bin missing after the hostile 206"
exit 1
}
got=$(wc -c <"$blob")
test "$got" -eq "$full" || {
echo "FAIL: blob.bin is ${got} bytes, expected ${full}"
exit 1
}
echo "OK (${got} bytes)"

34
tests/49_local-cookiewall.test Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
#
# Cookie-wall self-redirect (#15): httrack replays a Set-Cookie to mirror the
# real page, gives up when nothing changes, and stays bounded when it never does.
set -euo pipefail
: "${top_srcdir:=..}"
# Unknown-ext (wall.php) and known-ext (wall.html): the wall sets the cookie once,
# then serves real content; httrack must replay it and mirror that content.
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
--found 'cookiewall/wall.html' \
--file-matches 'cookiewall/wall.html' 'REAL-CONTENT-BEHIND-COOKIE-WALL' \
httrack 'BASEURL/cookiewall/index.html'
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
--found 'cookiewall2/wall.html' \
--file-matches 'cookiewall2/wall.html' 'REAL-CONTENT-BEHIND-COOKIE-WALL' \
httrack 'BASEURL/cookiewall2/index.html'
# No Set-Cookie: the jar never changes, so httrack must give up at once (retry
# stays gated) and not mirror the wall. A dropped gate would skip this log line.
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
--not-found 'cookiewall3/wall.html' \
--log-found 'loop to same filename' \
httrack 'BASEURL/cookiewall3/index.html'
# Ever-changing cookie never satisfies the wall: httrack must stop at the retry
# cap (bounded, no early give-up) and not mirror it.
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
--not-found 'cookiewall4/wall.html' \
--log-not-found 'loop to same filename' \
httrack 'BASEURL/cookiewall4/index.html'

View File

@@ -0,0 +1,38 @@
#!/bin/bash
#
# br and zstd on the wire: both decode, a junk coding leaves the body alone, an
# undecodable one fails the fetch, and br/zstd are advertised over TLS only.
set -euo pipefail
: "${top_srcdir:=..}"
# Both decoders are needed for the wire fixtures; the codec-off matrix is
# covered by the fenced 01_zlib-contentcodings self-test.
if test "${BROTLI_ENABLED:-yes}" != "yes" || test "${ZSTD_ENABLED:-yes}" != "yes"; then
echo "brotli/zstd not both compiled, skipping"
exit 77
fi
# bad.html (in memory) and bin.dat (direct-to-disk) both carry an undecodable
# coding: the fetch fails and neither leaves coded bytes on disk.
bash "$top_srcdir/tests/local-crawl.sh" --errors 2 --files 5 \
--file-matches 'codec/br.html' 'coded body' \
--file-matches 'codec/zstd.html' 'coded body' \
--file-matches 'codec/junk.html' 'junk coding' \
--file-matches 'codec/ae.html' 'AE=gzip, deflate,' \
--file-not-matches 'codec/ae.html' ' br' \
--file-not-matches 'codec/ae.html' 'zstd' \
--not-found 'codec/bad.html' \
--not-found 'codec/bin.dat' \
--log-found 'Unsupported Content-Encoding' \
httrack 'BASEURL/codec/index.html'
if test "${HTTPS_SUPPORT:-}" == "no"; then
echo "no https support compiled, skipping the TLS leg"
exit 0
fi
bash "$top_srcdir/tests/local-crawl.sh" --tls --errors 2 --files 5 \
--file-matches 'codec/ae.html' 'AE=gzip, deflate, br, zstd,' \
httrack 'BASEURL/codec/index.html'

View File

@@ -0,0 +1,38 @@
#!/bin/bash
#
# A coded re-fetch that fails to decode must not destroy the mirrored file (#557).
# Pass 1 mirrors from valid gzip; pass 2 (--update) serves an undecodable body for
# mem.html (in-memory), disk.bin (direct-to-disk) and unsup.html (a coding we have
# no decoder for). All three must keep their pass-1 content. fresh.html and
# freshdisk.bin decode on both passes: they are the controls that a good update
# still lands, in memory and direct-to-disk (the latter renaming the decoded temp
# over an existing file). Unfixed, the decode target is the mirror itself, so the
# first three are lost.
set -euo pipefail
: "${top_srcdir:=..}"
# A restrictive umask makes the decoded direct-to-disk file's mode observable:
# it is committed by renaming a temp, so it needs the chmod filecreate() does.
umask 077
bash "$top_srcdir/tests/local-crawl.sh" \
--rerun-args '--update' \
--errors 3 \
--log-found 'decompressing.*upcodec/mem\.html' \
--log-found 'decompressing.*upcodec/disk\.bin' \
--log-found 'Unsupported Content-Encoding.*upcodec/unsup\.html' \
--file-matches 'upcodec/mem.html' 'MIRRORED-MEM-V1' \
--file-matches 'upcodec/disk.bin' 'MIRRORED-DISK-V1' \
--file-min-bytes 'upcodec/disk.bin' 32768 \
--file-mode 'upcodec/disk.bin' 644 \
--file-matches 'upcodec/unsup.html' 'MIRRORED-UNSUP-V1' \
--file-matches 'upcodec/fresh.html' 'FRESH-V2' \
--file-not-matches 'upcodec/fresh.html' 'FRESH-V1' \
--file-mode 'upcodec/fresh.html' 644 \
--file-matches 'upcodec/freshdisk.bin' 'FRESHDISK-V2' \
--file-not-matches 'upcodec/freshdisk.bin' 'FRESHDISK-V1' \
--file-min-bytes 'upcodec/freshdisk.bin' 32768 \
--file-mode 'upcodec/freshdisk.bin' 644 \
httrack 'BASEURL/upcodec/index.html'

211
tests/52_local-socks5.test Normal file
View File

@@ -0,0 +1,211 @@
#!/bin/bash
#
# Issue #563: a SOCKS5 proxy must carry both http and https, resolve the origin
# name REMOTELY (ATYP=domain, never a local lookup), and authenticate per RFC
# 1929 without leaking the credentials to the origin.
set -euo pipefail
: "${top_srcdir:=..}"
if test "${HTTPS_SUPPORT:-}" == "no"; then
echo "no https support compiled, skipping"
exit 77
fi
if ! command -v python3 >/dev/null 2>&1 || ! command -v openssl >/dev/null 2>&1; then
echo "python3/openssl missing, skipping"
exit 77
fi
# a .invalid name never resolves (RFC 6761): reaching the page at all proves the
# proxy did the DNS
host="socks-origin.invalid"
server="$top_srcdir/tests/socks5-server.py"
tmpdir=$(mktemp -d)
pids=
cleanup() {
for pid in $pids; do
kill "$pid" 2>/dev/null || true
done
rm -rf "$tmpdir"
}
trap cleanup EXIT
openssl req -x509 -newkey rsa:2048 -keyout "$tmpdir/key.pem" \
-out "$tmpdir/cert.pem" -days 2 -nodes -subj "/CN=127.0.0.1" \
>/dev/null 2>&1
cat "$tmpdir/key.pem" "$tmpdir/cert.pem" >"$tmpdir/both.pem"
# start_server <logdir> <mode>: sets $tls_port/$http_port/$socks_port
start_server() {
local dir="$1" mode="$2" ports
mkdir -p "$dir"
ports="$dir/ports.txt"
python3 "$server" "$tmpdir/both.pem" "$dir" "$mode" \
>"$ports" 2>"$dir/server.err" &
pids="$pids $!"
for _ in $(seq 1 100); do
grep -q "^ready" "$ports" 2>/dev/null && break
sleep 0.1
done
grep -q "^ready" "$ports" 2>/dev/null || {
echo "server ($mode) did not start" >&2
cat "$dir/server.err" >&2
exit 1
}
tls_port=$(awk '/^TLS/{print $2}' "$ports")
http_port=$(awk '/^HTTP/{print $2}' "$ports")
socks_port=$(awk '/^SOCKS/{print $2}' "$ports")
}
# kill a hung httrack so a missing read bound surfaces as $HANG_RC instead of
# stalling the job (macOS has no `timeout`)
HANG_RC=137 # 128 + SIGKILL
run_crawl() {
local out="$1" url="$2" proxy="$3"
shift 3
local extra="$*"
test -n "$extra" || extra="-r1 -s0"
rm -rf "$out"
# shellcheck disable=SC2086
httrack "$url" --proxy "$proxy" \
-O "$out" $extra --timeout=10 >"$out.log" 2>&1 &
local pid=$!
(sleep 60 && kill -9 "$pid" 2>/dev/null) &
local guard=$!
local rc=0
wait "$pid" 2>/dev/null || rc=$?
kill "$guard" 2>/dev/null || true
wait "$guard" 2>/dev/null || true
return "$rc"
}
# --- https over socks, remote DNS -------------------------------------------
ok="$tmpdir/ok"
start_server "$ok" ok
run_crawl "$ok/out" "https://${host}:${tls_port}/" "socks5://127.0.0.1:${socks_port}"
grep -rq "ORIGIN-PAGE-563" "$ok/out" || {
echo "FAIL: origin page not downloaded through the socks proxy" >&2
cat "$ok/out.log" "$ok/server.err" >&2
exit 1
}
grep -q "^ATYP domain ${host}\$" "$ok/socks.log" || {
echo "FAIL: proxy did not receive ATYP=domain ${host} (no remote DNS)" >&2
cat "$ok/socks.log" >&2
exit 1
}
# a client that resolved locally would have sent an address, not a name
grep -q "^ATYP ipv4" "$ok/socks.log" && {
echo "FAIL: httrack resolved locally and sent an IPv4 to the proxy" >&2
cat "$ok/socks.log" >&2
exit 1
}
echo "OK: https tunneled through socks5 with remote DNS"
# --- plain http over socks --------------------------------------------------
: >"$ok/origin-headers.log" # the https crawl above also logged a request line
run_crawl "$ok/outh" "http://${host}:${http_port}/" "socks5://127.0.0.1:${socks_port}"
grep -rq "ORIGIN-PAGE-563" "$ok/outh" || {
echo "FAIL: plain http not tunneled through the socks proxy" >&2
cat "$ok/outh.log" "$ok/socks.log" >&2
exit 1
}
# the tunneled request is origin-form, as on a direct connection: the http-proxy
# absolute URI would reach the origin as "GET http://host/"
grep -q "^REQUEST GET / " "$ok/origin-headers.log" || {
echo "FAIL: tunneled request is not origin-form" >&2
cat "$ok/origin-headers.log" >&2
exit 1
}
grep -q "^REQUEST GET http" "$ok/origin-headers.log" && {
echo "FAIL: absolute-URI request sent through the socks tunnel" >&2
cat "$ok/origin-headers.log" >&2
exit 1
}
echo "OK: plain http tunneled through socks5"
# --- keep-alive socket reuse must NOT re-run the handshake ------------------
# A reused keep-alive socket is already tunneled; re-injecting the SOCKS
# greeting corrupts the origin stream. -c1 + the origin's max>1 advert makes
# httrack serve the whole plain-http crawl over one tunnel: exactly one
# handshake, every subpage intact.
ka="$tmpdir/ka"
start_server "$ka" ok
run_crawl "$ka/out" "http://${host}:${http_port}/" "socks5://127.0.0.1:${socks_port}" -r3 -c1
missing=0
for i in 1 2 3 4 5; do
grep -rq "SUBPAGE-563 /p${i}.html" "$ka/out" || missing=1
done
test "$missing" -eq 0 || {
echo "FAIL: a keep-alive subpage was lost (re-handshake corrupted the reused socket)" >&2
cat "$ka/out.log" "$ka/socks.log" >&2
exit 1
}
handshakes=$(grep -c "^CMD " "$ka/socks.log" || true)
test "$handshakes" -eq 1 || {
echo "FAIL: expected 1 SOCKS handshake for the reused socket, got $handshakes" >&2
cat "$ka/socks.log" >&2
exit 1
}
echo "OK: keep-alive socket reused over one socks tunnel, no re-handshake"
# --- authenticated socks (RFC 1929) -----------------------------------------
# over plain http: that is the request an http proxy would decorate with
# Proxy-Authorization, so the no-leak check below has teeth
auth="$tmpdir/auth"
start_server "$auth" ok
run_crawl "$auth/out" "http://${host}:${http_port}/" \
"socks5://user:secret@127.0.0.1:${socks_port}"
grep -rq "ORIGIN-PAGE-563" "$auth/out" || {
echo "FAIL: origin not downloaded through the authenticated socks proxy" >&2
cat "$auth/out.log" "$auth/socks.log" >&2
exit 1
}
grep -q "^METHOD userpass\$" "$auth/socks.log" || {
echo "FAIL: user/pass method not negotiated (auth ignored)" >&2
cat "$auth/socks.log" >&2
exit 1
}
grep -q "^AUTH user secret\$" "$auth/socks.log" || {
echo "FAIL: wrong socks credentials sent" >&2
cat "$auth/socks.log" >&2
exit 1
}
grep -qi "secret\|proxy-authorization" "$auth/origin-headers.log" && {
echo "FAIL: socks credentials leaked into the origin request" >&2
cat "$auth/origin-headers.log" >&2
exit 1
}
echo "OK: socks5 user/pass negotiated, creds not leaked to origin"
# --- hostile: refusing proxy ------------------------------------------------
ref="$tmpdir/refuse"
start_server "$ref" refuse
rc=0
run_crawl "$ref/out" "https://${host}:${tls_port}/" "socks5://127.0.0.1:${socks_port}" || rc=$?
test "$rc" -ne "$HANG_RC" || {
echo "FAIL: crawl hung on a refusing socks proxy" >&2
exit 1
}
grep -rq "ORIGIN-PAGE-563" "$ref/out" 2>/dev/null && {
echo "FAIL: refusing proxy unexpectedly served the page" >&2
exit 1
}
echo "OK: refusing socks proxy fails cleanly, no hang"
# --- hostile: truncated handshake reply -------------------------------------
trunc="$tmpdir/trunc"
start_server "$trunc" truncate
rc=0
run_crawl "$trunc/out" "https://${host}:${tls_port}/" "socks5://127.0.0.1:${socks_port}" || rc=$?
test "$rc" -ne "$HANG_RC" || {
echo "FAIL: crawl hung on a truncated socks reply (bounded read missing)" >&2
exit 1
}
grep -rq "ORIGIN-PAGE-563" "$trunc/out" 2>/dev/null && {
echo "FAIL: truncated-reply proxy unexpectedly served the page" >&2
exit 1
}
echo "OK: bounded socks handshake, no hang on a truncated reply"

Some files were not shown because too many files have changed in this diff Show More