Compare commits

..

2 Commits

Author SHA1 Message Date
Xavier Roche
cf483a24e4 Remove commented-out code across the engine
Sweep of ~650 lines of //-disabled statements, declarations, and calls
(plus three /*-wrapped dead blocks in htscoremain.c and htscore.c and
the MFC pApp remnant in htsserver.c). Prose comments stay, including
labels that describe live code; usage examples and config-knob docs
(HTS_TRACE_MALLOC, the htsweb.c DEBUG toggle) were kept. Insertions are
clang-format rewraps of live lines that became touched by the deletions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-06 10:42:28 +02:00
Xavier Roche
abaea566bd Remove dead #if 0 blocks and their orphaned satellites
All #if 0 code that has been disabled for years goes away: the sig_ask
interactive signal handlers, check_rate, HTS_TOTAL_RECV_CHECK, the
make_empty_index no-op body (function removed with its only call site),
the htscharset sample main(), the dead FTP CWD sequence, and assorted
disabled fragments in the parser and engine. Blocks with a live #else
keep that branch. The htsweb.c DEBUG toggle stays: its #else arm is the
live definition and the macro is used.

The htsparse.c 3.43 note about not post-excluding authorized links is
kept as a plain comment; commented-out declarations and calls that only
served the removed blocks go with them. French comments rewrapped by
the formatter were translated in passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-06 10:16:21 +02:00
93 changed files with 1548 additions and 2865 deletions

View File

@@ -55,9 +55,7 @@ jobs:
run: make -j"$(nproc)" run: make -j"$(nproc)"
- name: Test - name: Test
run: | run: make check
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
@@ -101,8 +99,7 @@ jobs:
sudo find /usr/bin /usr/local/bin -maxdepth 1 -name 'python3*' \ sudo find /usr/bin /usr/local/bin -maxdepth 1 -name 'python3*' \
-exec mv {} {}.hidden \; -exec mv {} {}.hidden \;
! command -v python3 ! command -v python3
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16 make check
make check -j"$jobs"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
@@ -136,15 +133,7 @@ jobs:
run: make -j"$(sysctl -n hw.ncpu)" run: make -j"$(sysctl -n hw.ncpu)"
- name: Test - name: Test
# bigcrawl's sustained -c8 crawl drops fetches on macOS's loopback when run: make check
# it competes with other crawls, flaking its exact file count (the #527
# macOS drop). Run everything else in parallel, then bigcrawl alone (its
# serial-safe condition). Linux tolerates the full parallel run.
run: |
jobs=$(( $(sysctl -n hw.ncpu) * 2 )); [ "$jobs" -le 16 ] || jobs=16
rest=$(cd tests && ls *.test | grep -v '^36_local-bigcrawl\.test$' | tr '\n' ' ')
make check -j"$jobs" TESTS="$rest"
make check TESTS=36_local-bigcrawl.test
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
@@ -182,9 +171,7 @@ jobs:
run: make -j"$(nproc)" run: make -j"$(nproc)"
- name: Test - name: Test
run: | run: make check
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
@@ -239,9 +226,7 @@ jobs:
env: 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 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 UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: | run: make check
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
@@ -287,52 +272,12 @@ jobs:
# 01_engine-* only; zlib-dependent self-tests are named 01_zlib-* and # 01_engine-* only; zlib-dependent self-tests are named 01_zlib-* and
# skipped here (uninstrumented libz floods MSan with false positives). # skipped here (uninstrumented libz floods MSan with false positives).
tests="$(cd tests && ls 01_engine-*.test | tr '\n' ' ')" tests="$(cd tests && ls 01_engine-*.test | tr '\n' ' ')"
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16 make check TESTS="$tests"
make check -j"$jobs" TESTS="$tests"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()
run: cat tests/test-suite.log 2>/dev/null || true run: cat tests/test-suite.log 2>/dev/null || true
# libFuzzer smoke: build the fuzz/ harnesses over the pure hostile-input
# parsers and replay each seed corpus under ASan+UBSan+LeakSanitizer. Replay
# (not open-ended mutation) keeps CI deterministic -- it can't hit strjoker's
# catastrophic backtracking -- and pins the regression seeds for the bugs the
# fuzzers found. Deep discovery is a maintainer / OSS-Fuzz activity.
fuzz:
name: fuzz (libFuzzer corpus replay, clang)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential clang autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
- name: Configure (fuzzers, static)
run: |
set -euo pipefail
autoreconf -fi
./configure CC=clang \
CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g -O1 -fno-omit-frame-pointer" \
LDFLAGS="-fsanitize=address,undefined" \
--enable-fuzzers --disable-shared
- name: Build
run: make -j"$(nproc)"
- name: Replay corpora
env:
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1:halt_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
run: bash fuzz/run-fuzzers.sh fuzz check
# Optional-dependency build: compile and test with HTTPS/OpenSSL disabled -- # Optional-dependency build: compile and test with HTTPS/OpenSSL disabled --
# the configuration users on minimal systems build, and one libssl is not even # the configuration users on minimal systems build, and one libssl is not even
# installed here so configure cannot silently re-enable it. The matrix above # installed here so configure cannot silently re-enable it. The matrix above
@@ -363,9 +308,7 @@ jobs:
run: make -j"$(nproc)" run: make -j"$(nproc)"
- name: Test - name: Test
run: | run: make check
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure - name: Print the test log on failure
if: failure() if: failure()

View File

@@ -1,63 +0,0 @@
# CodeQL static analysis (C). The security-extended suite covers the classes
# this codebase actually fights: overflows, tainted-size allocs, format bugs.
name: CodeQL
on:
push:
branches: [master]
pull_request:
schedule:
# Weekly re-scan of master so new/updated queries land without a push.
- cron: "17 4 * * 1"
permissions:
contents: read
concurrency:
group: codeql-${{ github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: analyze (c-cpp)
runs-on: ubuntu-24.04
permissions:
contents: read
# Upload findings to the repo's code-scanning dashboard.
security-events: write
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
- uses: github/codeql-action/init@v4
with:
languages: c-cpp
build-mode: manual
queries: security-extended
# fopen's umask-controlled 0666 is intended for mirror/cache/log
# output; the one credential file (cookies.txt) is kept 0600 on Unix.
config: |
query-filters:
- exclude:
id: cpp/world-writable-file-creation
# Manual build: CodeQL traces the compiler, so build exactly what ships.
- name: Build
run: |
set -euo pipefail
autoreconf -fi
./configure
make -j"$(nproc)"
- uses: github/codeql-action/analyze@v4
with:
category: "/language:c-cpp"

3
.gitignore vendored
View File

@@ -34,9 +34,8 @@ Makefile
*.so.* *.so.*
*.a *.a
# make dist output; dist/ holds httrack-gh-release staging artifacts. # make dist output.
/httrack-*.tar.gz /httrack-*.tar.gz
/dist/
# Editor / autotools backup files. # Editor / autotools backup files.
*~ *~

View File

@@ -6,19 +6,8 @@ the operational checklist: toolchain, invariants, and how to ship a change.
## Build & test ## Build & test
- Fresh clone first: `git submodule update --init src/coucal` - Fresh clone first: `git submodule update --init src/coucal`
- `./bootstrap` (regenerates `configure` via `autoreconf`; needs autoconf, - `./bootstrap` (regenerates `configure` via `autoreconf`; needs autoconf,
automake, libtool), then `bash configure && make -j"$(nproc)" && make check automake, libtool), then `bash configure && make && make check`. Or run
-j"$(nproc)"`. Always pass `-j` to `make check`: the suite runs under `sh build.sh` to do bootstrap + configure + make in one shot.
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)`. macOS runs
36_local-bigcrawl alone in a second pass: its sustained `-c8` crawl overloads
the macOS loopback when it competes with other crawls and flakes its exact
file count (Linux tolerates the full parallel run).
Or run `sh build.sh` to do bootstrap + configure + make in one shot.
## Hard invariants ## Hard invariants
- **Generated autotools files are NOT in git.** `configure`, every - **Generated autotools files are NOT in git.** `configure`, every

View File

@@ -1,5 +1,5 @@
SUBDIRS = src man m4 libtest templates lang html tests fuzz SUBDIRS = src man m4 libtest templates lang html tests
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4

View File

@@ -1,6 +1,6 @@
AC_PREREQ([2.71]) AC_PREREQ([2.71])
AC_INIT([httrack], [3.49.12], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/]) AC_INIT([httrack], [3.49.11], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
AC_COPYRIGHT([ AC_COPYRIGHT([
HTTrack Website Copier, Offline Browser for Windows and Unix HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 1998-2015 Xavier Roche and other contributors Copyright (C) 1998-2015 Xavier Roche and other contributors
@@ -29,11 +29,11 @@ AC_CONFIG_SRCDIR(src/httrack.c)
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS(config.h) AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE([subdir-objects]) AM_INIT_AUTOMAKE([subdir-objects])
# 3:4:0: 3.49.12 tail-appends one field (why_url) to httrackp and otherwise only # 3:3:0: 3.49.11 only adds enum values, macros and inline helpers to the
# deletes dead comments; no struct layout or exported signature broke vs 3.49.11, # installed headers (no struct layout or exported signature changed vs
# so it stays soname .so.3; bump revision. # 3.49.10), so it stays soname .so.3; bump revision.
# (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.) # (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.)
VERSION_INFO="3:4:0" VERSION_INFO="3:3:0"
AM_MAINTAINER_MODE AM_MAINTAINER_MODE
AC_USE_SYSTEM_EXTENSIONS AC_USE_SYSTEM_EXTENSIONS
@@ -102,8 +102,7 @@ AX_CHECK_COMPILE_FLAG([-Wignored-qualifiers], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -
AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"]) AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"])
AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"]) AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"])
AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"]) AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"])
AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector-strong"], AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])
[AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])])
AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"]) AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"])
AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"]) AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"])
AX_CHECK_LINK_FLAG([-Wl,--discard-all], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--discard-all"]) AX_CHECK_LINK_FLAG([-Wl,--discard-all], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--discard-all"])
@@ -111,13 +110,6 @@ AX_CHECK_LINK_FLAG([-Wl,--no-undefined], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,
AX_CHECK_LINK_FLAG([-Wl,-z,relro,-z,now], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,relro,-z,now"]) AX_CHECK_LINK_FLAG([-Wl,-z,relro,-z,now], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,relro,-z,now"])
AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,noexecstack"]) AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,noexecstack"])
# Fortify libc calls (=3, else =2) unless the toolchain predefines it; skip
# sanitizer builds, whose interceptors want the unfortified calls.
case "$CFLAGS" in
*-fsanitize=*) ;;
*) AX_ADD_FORTIFY_SOURCE ;;
esac
# Force libc back into DT_NEEDED for libraries that reach it only through # Force libc back into DT_NEEDED for libraries that reach it only through
# libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style # libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style
# linker; Apple ld rejects these flags and links libSystem unconditionally. # linker; Apple ld rejects these flags and links libSystem unconditionally.
@@ -318,37 +310,6 @@ AC_ARG_ENABLE([online-unit-tests],
]) ])
AC_SUBST(ONLINE_UNIT_TESTS,$online_unit_tests) AC_SUBST(ONLINE_UNIT_TESTS,$online_unit_tests)
## libFuzzer harnesses (fuzz/); requires clang
AC_MSG_CHECKING(whether to build fuzzers)
AC_ARG_ENABLE([fuzzers],
[AS_HELP_STRING([--enable-fuzzers],[Build libFuzzer harnesses in fuzz/ (requires clang) @<:@default=no@:>@])],
[
case "${enableval}" in
no|yes)
fuzzers=$enableval
AC_MSG_RESULT($enableval)
;;
*)
AC_MSG_ERROR(bad value for fuzzers, expected yes/no)
;;
esac
],
[
fuzzers=no
AC_MSG_RESULT(no)
])
if test x"$fuzzers" = x"yes"; then
# Instrument the whole build for coverage; harnesses link -fsanitize=fuzzer.
AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer-no-link],
[DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fsanitize=fuzzer-no-link"],
[AC_MSG_ERROR([--enable-fuzzers requires libFuzzer support (clang)])])
# clang's static sanitizer runtimes clash with -Wl,--no-undefined on the .so.
if test x"$enable_shared" != x"no"; then
AC_MSG_ERROR([--enable-fuzzers requires --disable-shared])
fi
fi
AM_CONDITIONAL([FUZZERS], [test x"$fuzzers" = x"yes"])
# Final output # Final output
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
@@ -360,6 +321,5 @@ lang/Makefile
html/Makefile html/Makefile
libtest/Makefile libtest/Makefile
tests/Makefile tests/Makefile
fuzz/Makefile
]) ])
AC_OUTPUT AC_OUTPUT

10
debian/changelog vendored
View File

@@ -1,13 +1,3 @@
httrack (3.49.12-1) unstable; urgency=medium
* New upstream release: security and crawl-correctness fixes (remote stack
overflow in Content-Type/-Encoding parsing, fuzzer-found parser over-reads,
world-readable cookies.txt, filter-pattern denial of service) plus a new
--why filter diagnostic; full list in history.txt.
* Build with _FORTIFY_SOURCE and -fstack-protector-strong.
-- Xavier Roche <xavier@debian.org> Fri, 10 Jul 2026 21:11:09 +0200
httrack (3.49.11-1) unstable; urgency=medium httrack (3.49.11-1) unstable; urgency=medium
* New upstream release: crawl correctness and security fixes (network-facing * New upstream release: crawl correctness and security fixes (network-facing

View File

@@ -1,45 +0,0 @@
# libFuzzer harnesses; built only with --enable-fuzzers (requires clang).
if FUZZERS
noinst_PROGRAMS = fuzz-charset fuzz-meta fuzz-idna fuzz-entities \
fuzz-unescape fuzz-filters fuzz-url fuzz-header fuzz-cachendx
endif
AM_CPPFLAGS = \
@DEFAULT_CFLAGS@ \
@THREADS_CFLAGS@ \
@V6_FLAG@ \
@LFS_FLAG@ \
-I$(top_srcdir)/src \
-I$(top_srcdir)/src/coucal
# Static-link libhttrack.la: the internal symbols are hidden in the .so.
AM_LDFLAGS = @DEFAULT_LDFLAGS@ -fsanitize=fuzzer -static-libtool-libs
LDADD = $(top_builddir)/src/libhttrack.la $(THREADS_LIBS)
fuzz_charset_SOURCES = fuzz-charset.c fuzz.h
fuzz_meta_SOURCES = fuzz-meta.c fuzz.h
fuzz_idna_SOURCES = fuzz-idna.c fuzz.h
fuzz_entities_SOURCES = fuzz-entities.c fuzz.h
fuzz_unescape_SOURCES = fuzz-unescape.c fuzz.h
fuzz_filters_SOURCES = fuzz-filters.c fuzz.h
fuzz_url_SOURCES = fuzz-url.c fuzz.h
fuzz_header_SOURCES = fuzz-header.c fuzz.h
fuzz_cachendx_SOURCES = fuzz-cachendx.c fuzz.h
# List corpus files explicitly: automake does not expand EXTRA_DIST globs.
EXTRA_DIST = README.md run-fuzzers.sh \
corpus/charset/utf8.txt corpus/charset/latin1.txt corpus/charset/sjis.txt \
corpus/meta/meta-charset.html corpus/meta/meta-http-equiv.html \
corpus/idna/idna.txt corpus/idna/unicode.txt \
corpus/idna/regress-multilabel-leak.txt \
corpus/entities/entities.txt \
corpus/unescape/percent.txt \
corpus/filters/filter.bin corpus/filters/filter-size.bin \
corpus/filters/regress-empty-subject-unique.bin \
corpus/filters/redos-star-classes.bin \
corpus/url/http-url.txt corpus/url/relative-path.txt \
corpus/url/regress-file-empty-path.txt corpus/url/regress-long-path-abort.txt \
corpus/header/full-response.txt corpus/header/redirect.txt \
corpus/cachendx/new-format.txt corpus/cachendx/old-format.txt \
corpus/cachendx/regress-overadvance.bin \
corpus/cachendx/regress-truncated-entry.bin

View File

@@ -1,15 +0,0 @@
# Fuzzing httrack
libFuzzer harnesses for the pure hostile-input parsers (charset/UTF-8/IDNA codecs, entity and percent decoders, wildcard filters, URL splitter). Off by default; needs clang.
```sh
./bootstrap
mkdir /var/tmp/bld-fuzz && cd /var/tmp/bld-fuzz
CC=clang CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all -g -O1" \
LDFLAGS="-fsanitize=address,undefined" \
bash /path/to/httrack/configure --enable-fuzzers --disable-shared
make
bash /path/to/httrack/fuzz/run-fuzzers.sh fuzz 60 # 60s per target
```
Run one target by hand: `fuzz/fuzz-url -max_total_time=300 corpusdir fuzz/corpus/url`. Seed corpora live in `corpus/<target>/`; a crash reproducer is replayed with `fuzz/fuzz-url crash-file`.

View File

@@ -1,7 +0,0 @@
8
CACHE-1.1
28
Mon, 01 Jan 2024 00:00:00 GMT
www.example.com
/index.html
123

View File

@@ -1,5 +0,0 @@
3
1.0
www.example.com
/page
5

View File

@@ -1,2 +0,0 @@
32768
CACHE-1.1

View File

@@ -1,5 +0,0 @@
8
CACHE-1.1
1
x
www.example.com

View File

@@ -1 +0,0 @@
café naďve ¤

View File

@@ -1 +0,0 @@
コンピュータ

Binary file not shown.

View File

@@ -1 +0,0 @@
&amp;&lt;&gt;&#65;&#x2603;&eacute;&notarealentity;&#xFFFFFFFF;

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
**((

View File

@@ -1,10 +0,0 @@
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Content-Encoding: gzip
Last-Modified: Mon, 01 Jan 2024 00:00:00 GMT
Etag: "abc"
Location: http://example.com/x
Set-Cookie: ID=42; path=/; domain=.example.com
Content-Range: bytes 0-99/100
Transfer-Encoding: chunked

View File

@@ -1,3 +0,0 @@
HTTP/1.0 301 Moved
Location: /elsewhere
Content-Disposition: attachment; filename="a.pdf"

View File

@@ -1 +0,0 @@
xn--bcher-kva.example.com

View File

@@ -1 +0,0 @@
büchev.ä¾å­bücheple

View File

@@ -1 +0,0 @@
bücher.例子.example

View File

@@ -1 +0,0 @@
<html><head><meta charset="utf-8"></head><body>x</body></html>

View File

@@ -1 +0,0 @@
<meta content="text/html; charset=gb2312" http-equiv="content-type">

View File

@@ -1 +0,0 @@
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

View File

@@ -1 +0,0 @@
<meta charset='utf-8

View File

@@ -1 +0,0 @@
<meta charset = utf-8 />

View File

@@ -1 +0,0 @@
%41%zz%%20%c3%a9+%2e%2e%2f

View File

@@ -1 +0,0 @@
http://user:pass@www.example.com:8080/a/b/../c/./d.html?q=1#frag

View File

@@ -1 +0,0 @@
file://

View File

@@ -1 +0,0 @@
ftpumŠ[/../e0O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/.../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W//O/../f9O_i../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/.../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W//O/../f9O_i<5F>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._f9O_i../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/.../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W//O/../f9O_i<5F>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/i<>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_im<69>W/.¿ù../O/../9O_imÙ<6D>Š<EFBFBD>W/../_/../../e0O/../f9O_./f9O_i<5F>W/../O/../90O/./e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_imÙ<6D>Š<EFBFBD>W/../O/.._i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9O_i/../O/../f9O_i<5F>W/../O/../90O/../itpumÙ/ftpumŠ[/../e0O/../itpumÙ<6D>Š[/../O/../f9O_i<5F>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/../f9OmÙ<6D>Š<EFBFBD>W/../O/../9O_imÙ<6D>Š<EFBFBD>W/../O/..O_imÙ<6D>Š<EFBFBD>W/../O/../../e0O/9O_imÙ<6D>Š<EFBFBD>W/../O/../

View File

@@ -1 +0,0 @@
ftp://ftp.example.com/pub/../file.txt

View File

@@ -1,65 +0,0 @@
/* ------------------------------------------------------------ */
/*
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
*/
/* Fuzz the cache-index (.ndx) parser: a corrupt or truncated index must not
walk the length-prefixed cache_brstr/cache_binput scan past the buffer.
Mirrors the -#C cache-listing scan (htscoremain.c). */
#include "fuzz.h"
#include "htscache.h"
#include "htslib.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *buf = fuzz_strdup(data, size);
const char *const end = buf + size;
char firstline[256];
char *a = buf;
/* header: two length-prefixed fields (version, last-modified) */
a += cache_brstr(a, firstline, sizeof(firstline));
a += cache_brstr(a, firstline, sizeof(firstline));
/* body: newline-delimited host/file/position triples; the length-prefixed
scan must stay inside the buffer */
while (a != NULL && a < end) {
char BIGSTK line[HTS_URLMAXSIZE * 2];
char linepos[256];
int pos;
a = strchr(a + 1, '\n');
if (a == NULL)
break;
a++;
a += cache_binput(a, end, line, HTS_URLMAXSIZE);
a += cache_binput(a, end, line + strlen(line), HTS_URLMAXSIZE);
a += cache_binput(a, end, linepos, 200);
sscanf(linepos, "%d", &pos);
(void) pos;
}
freet(buf);
return 0;
}

View File

@@ -1,84 +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
*/
/* Fuzz the charset codecs: hts_convertStringToUTF8/FromUTF8 and the
UTF-8/UCS4 primitives (htscharset.c). First input byte picks the charset. */
#include "fuzz.h"
#include "htscharset.h"
static const char *const charsets[] = {
"utf-8", "iso-8859-1", "iso-8859-2", "iso-8859-15", "windows-1252",
"us-ascii", "shift_jis", "euc-jp", "iso-2022-jp", "gb2312",
"big5", "euc-kr", "koi8-r", "utf-16", "unknown-charset",
};
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
const char *charset;
char *s;
if (size == 0)
return 0;
charset = charsets[data[0] % (sizeof(charsets) / sizeof(charsets[0]))];
data++, size--;
s = fuzz_strdup(data, size);
{
char *utf8 = hts_convertStringToUTF8(s, size, charset);
freet(utf8);
}
{
char *enc = hts_convertStringFromUTF8(s, size, charset);
freet(enc);
}
{
size_t nChars = 0;
hts_UCS4 *ucs = hts_convertUTF8StringToUCS4(s, size, &nChars);
if (ucs != NULL) {
char *back = hts_convertUCS4StringToUTF8(ucs, nChars);
freet(back);
freet(ucs);
}
}
{
size_t i = 0;
while (i < size) {
hts_UCS4 uc = 0;
const size_t nr = hts_readUTF8(s + i, size - i, &uc);
char out[8];
if (nr == 0)
break;
hts_writeUTF8(uc, out, sizeof(out));
i += nr;
}
}
freet(s);
return 0;
}

View File

@@ -1,51 +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
*/
/* Fuzz the HTML entity decoder (htsencoding.c). First input byte picks the
destination size, so truncation bounds get exercised too. */
#include "fuzz.h"
#include "htsencoding.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
static const size_t dsizes[] = {1, 2, 8, 64, 4096};
size_t dsize;
char *src, *dest;
if (size == 0)
return 0;
dsize = dsizes[data[0] % (sizeof(dsizes) / sizeof(dsizes[0]))];
data++, size--;
src = fuzz_strdup(data, size);
dest = malloct(dsize);
(void) hts_unescapeEntities(src, dest, dsize);
(void) hts_unescapeEntitiesWithCharset(src, dest, dsize, "iso-8859-1");
freet(dest);
freet(src);
return 0;
}

View File

@@ -1,65 +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
*/
/* Fuzz the wildcard filter matcher (htsfilters.c; #148 bracket-range OOB was
here). Input splits on the first NUL: pattern, then subject string. */
#include "fuzz.h"
#include "htsfilters.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *buf = fuzz_strdup(data, size);
const char *joker = buf;
const uint8_t *sep = memchr(data, '\0', size);
/* subject in its own allocation so ASan bounds it apart from the pattern */
char *nom = sep != NULL ? fuzz_strdup(sep + 1, (data + size) - (sep + 1))
: fuzz_strdup(data + size, 0);
(void) strjoker(nom, joker, NULL, NULL);
{
LLint sz = (LLint) size;
int size_flag = 0;
(void) strjoker(nom, joker, &sz, &size_flag);
}
(void) strjokerfind(nom, joker);
{
char *filter = malloct(strlen(joker) + 2);
char *filters[1];
LLint sz = (LLint) size;
int size_flag = 0, depth = 0;
filter[0] = '-';
memcpy(filter + 1, joker, strlen(joker) + 1);
filters[0] = filter;
(void) fa_strjoker(0, filters, 1, nom, &sz, &size_flag, &depth);
freet(filter);
}
freet(nom);
freet(buf);
return 0;
}

View File

@@ -1,74 +0,0 @@
/* ------------------------------------------------------------ */
/*
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
*/
/* Fuzz the HTTP response-header parser (htslib.c): treatfirstline on the
status line, treathead on each following header. Both consume raw bytes
off the wire and copy fields into fixed htsblk buffers; treathead also
mutates its line in place and drives cookie parsing. */
#include "fuzz.h"
#include "htslib.h"
#include "htsbauth.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *buf = fuzz_strdup(data, size);
htsblk r;
t_cookie *cookie = calloct(1, sizeof(*cookie));
char *line = malloct(size + 1);
char *p = buf;
int first = 1;
memset(&r, 0, sizeof(r));
cookie->max_len = (int) sizeof(cookie->data);
r.location = malloct(HTS_URLMAXSIZE * 2);
r.location[0] = '\0';
/* feed one header line at a time, as the receive loop does */
while (p != NULL && *p != '\0') {
char *nl = strchr(p, '\n');
size_t n = (nl != NULL) ? (size_t) (nl - p) : strlen(p);
size_t i, len = 0;
/* binput drops every '\r' on the wire; mirror it */
for (i = 0; i < n; i++)
if (p[i] != '\r')
line[len++] = p[i];
line[len] = '\0';
if (first) {
treatfirstline(&r, line);
first = 0;
} else {
treathead(cookie, "www.example.com", "/", &r, line);
}
p = (nl != NULL) ? nl + 1 : NULL;
}
freet(r.location);
freet(line);
freet(cookie);
freet(buf);
return 0;
}

View File

@@ -1,47 +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
*/
/* Fuzz the IDNA/punycode codec (htscharset.c, CVE-prone lineage). */
#include "fuzz.h"
#include "htscharset.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *s = fuzz_strdup(data, size);
{
char *idna = hts_convertStringUTF8ToIDNA(s, size);
freet(idna);
}
{
char *utf8 = hts_convertStringIDNAToUTF8(s, size);
freet(utf8);
}
(void) hts_isStringIDNA(s, size);
freet(s);
return 0;
}

View File

@@ -1,40 +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
*/
/* Fuzz hts_getCharsetFromMeta (htscharset.c): scans raw attacker HTML for a
<meta> charset declaration. */
#include "fuzz.h"
#include "htscharset.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *html = fuzz_strdup(data, size);
char *charset = hts_getCharsetFromMeta(html, size);
freet(charset);
freet(html);
return 0;
}

View File

@@ -1,53 +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
*/
/* Fuzz the URL percent-decoders (htslib.c). First input byte picks the
output buffer size, so the bounded-copy contract is exercised. */
#include "fuzz.h"
#include "httrack-library.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
static const size_t bsizes[] = {1, 2, 16, 256, 8192};
size_t bsize;
char *s, *catbuff;
if (size == 0)
return 0;
bsize = bsizes[data[0] % (sizeof(bsizes) / sizeof(bsizes[0]))];
data++, size--;
s = fuzz_strdup(data, size);
catbuff = malloct(bsize);
(void) unescape_http(catbuff, bsize, s);
(void) unescape_http_unharm(catbuff, bsize, s, 0);
(void) unescape_http_unharm(catbuff, bsize, s, 1);
unescape_amp(s);
freet(catbuff);
freet(s);
return 0;
}

View File

@@ -1,49 +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
*/
/* Fuzz the URL splitter and path normalizer (htslib.c): ident_url_absolute
is the first parser to touch a raw URL; fil_simplifie collapses ./ and ../
in place. */
#include "fuzz.h"
#include "htscore.h"
#include "htslib.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
char *s = fuzz_strdup(data, size);
lien_adrfil *af = calloct(1, sizeof(*af));
/* fil_simplifie rewrites in place and may grow an empty path to "./" */
char *path = malloct(size + 3);
(void) ident_url_absolute(s, af);
memcpy(path, s, size + 1);
fil_simplifie(path);
freet(path);
freet(af);
freet(s);
return 0;
}

View File

@@ -1,51 +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
*/
/* Shared helpers for the libFuzzer harnesses. */
#ifndef FUZZ_H
#define FUZZ_H
#define HTS_INTERNAL_BYTECODE
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "htsbase.h"
/* Heap NUL-terminated copy of the fuzzer input, so ASan bounds every read. */
static char *fuzz_strdup(const uint8_t *data, size_t size) {
char *s = malloct(size + 1);
memcpy(s, data, size);
s[size] = '\0';
return s;
}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
#endif

View File

@@ -1,41 +0,0 @@
#!/bin/bash
# Drive every built harness against its seed corpus.
# run-fuzzers.sh <build-fuzz-dir> check deterministic replay (CI smoke)
# run-fuzzers.sh <build-fuzz-dir> [seconds] timed mutation run (discovery)
# Replay is crash/leak-only and never mutates; the per-unit -timeout guards
# both modes against pathological slowdowns (e.g. pre-#501 strjoker).
set -euo pipefail
srcdir=$(cd "$(dirname "$0")" && pwd)
bld=${1:?usage: run-fuzzers.sh <build-fuzz-dir> [check|seconds]}
mode=${2:-20}
status=0
for f in "$bld"/fuzz-*; do
if [ ! -f "$f" ] || [ ! -r "$f" ]; then continue; fi
case "$f" in *.o | *.c | *.dSYM) continue ;; esac
name=$(basename "$f")
corpus="$srcdir/corpus/${name#fuzz-}"
if [ "$mode" = "check" ]; then
echo "=== $name (replay) ==="
[ -d "$corpus" ] || continue
if ! "$f" -runs=0 -timeout=25 -rss_limit_mb=2048 "$corpus"; then
echo "*** $name FAILED on its corpus" >&2
status=1
fi
continue
fi
work=$(mktemp -d)
args=("$work")
[ -d "$corpus" ] && args+=("$corpus")
echo "=== $name (${mode}s) ==="
if ! "$f" -max_total_time="$mode" -timeout=25 -rss_limit_mb=2048 \
-artifact_prefix="$work/" -print_final_stats=1 "${args[@]}"; then
echo "*** $name FAILED; artifacts:" >&2
ls -l "$work" >&2
status=1
else
rm -rf "$work"
fi
done
exit $status

View File

@@ -4,23 +4,6 @@ HTTrack Website Copier release history:
This file lists all changes and fixes that have been made for HTTrack This file lists all changes and fixes that have been made for HTTrack
3.49-12
+ New: --why explains which filter rule accepts or rejects a given URL, then exits (#505)
+ Fixed: links carrying raw UTF-8 bytes were fetched double-encoded and 404'd (#516)
+ Fixed: an uncompressed body mislabeled as gzip no longer loses the page (#515)
+ Fixed: remote stack overflow and uninitialized read in Content-Type/-Encoding parsing (#506)
+ Fixed: several over-reads and a leak in the filter, URL and IDNA parsers (#499)
+ Fixed: bound the cache-index (.ndx) parser to its buffer (#507)
+ Fixed: catastrophic backtracking on '*'-heavy filter patterns (#513)
+ Fixed: cookies.txt was created world-readable (#511)
+ Fixed: a single corrupt cache entry no longer aborts the whole mirror (#494)
+ Fixed: cache-reconcile policy was broken for zip caches (#491, #493, #495)
+ Fixed: cancelling a crawl mid type-check no longer orphans .delayed placeholders (#496)
+ Fixed: detect URLs after the first inline script and in mid-tag attributes (#497)
+ Changed: build with _FORTIFY_SOURCE and -fstack-protector-strong (#504)
+ Changed: removed the pre-3.31 (.dat/.ndx) cache import (#512)
+ Changed: multiple internal hardening and build improvements (libFuzzer harnesses, CodeQL, dead-code removal)
3.49-11 3.49-11
+ New: parse robots.txt Allow rules and path wildcards per RFC 9309 (#452) + New: parse robots.txt Allow rules and path wildcards per RFC 9309 (#452)
+ New: advertise deflate in Accept-Encoding and decode deflate responses (#450) + New: advertise deflate in Accept-Encoding and decode deflate responses (#450)

View File

@@ -1,119 +0,0 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_add_fortify_source.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_ADD_FORTIFY_SOURCE
#
# DESCRIPTION
#
# Check whether -D_FORTIFY_SOURCE=2 can be added to CPPFLAGS without macro
# redefinition warnings, other cpp warnings or linker. Some distributions
# (such as Ubuntu or Gentoo Linux) enable _FORTIFY_SOURCE globally in
# their compilers, leading to unnecessary warnings in the form of
#
# <command-line>:0:0: error: "_FORTIFY_SOURCE" redefined [-Werror]
# <built-in>: note: this is the location of the previous definition
#
# which is a problem if -Werror is enabled. This macro checks whether
# _FORTIFY_SOURCE is already defined, and if not, adds -D_FORTIFY_SOURCE=2
# to CPPFLAGS.
#
# Newer mingw-w64 msys2 package comes with a bug in
# headers-git-7.0.0.5546.d200317d-1. It broke -D_FORTIFY_SOURCE support,
# and would need -lssp or -fstack-protector. See
# https://github.com/msys2/MINGW-packages/issues/5803. Try to actually
# link it.
#
# LICENSE
#
# Copyright (c) 2017 David Seifert <soap@gentoo.org>
# Copyright (c) 2019, 2023 Reini Urban <rurban@cpan.org>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 10
AC_DEFUN([AX_ADD_FORTIFY_SOURCE],[
ac_save_cflags=$CFLAGS
ac_cwerror_flag=yes
AX_CHECK_COMPILE_FLAG([-Werror],[CFLAGS="$CFLAGS -Werror"])
ax_add_fortify_3_failed=
AC_MSG_CHECKING([whether to add -D_FORTIFY_SOURCE=3 to CPPFLAGS])
AC_LINK_IFELSE([
AC_LANG_PROGRAM([],
[[
#ifndef _FORTIFY_SOURCE
return 0;
#else
_FORTIFY_SOURCE_already_defined;
#endif
]]
)],
AC_LINK_IFELSE([
AC_LANG_SOURCE([[
#define _FORTIFY_SOURCE 3
#include <string.h>
int main(void) {
char *s = " ";
strcpy(s, "x");
return strlen(s)-1;
}
]]
)],
[
AC_MSG_RESULT([yes])
CFLAGS=$ac_save_cflags
CPPFLAGS="$CPPFLAGS -D_FORTIFY_SOURCE=3"
], [
AC_MSG_RESULT([no])
ax_add_fortify_3_failed=1
],
),
[
AC_MSG_RESULT([no])
ax_add_fortify_3_failed=1
])
if test -n "$ax_add_fortify_3_failed"
then
AC_MSG_CHECKING([whether to add -D_FORTIFY_SOURCE=2 to CPPFLAGS])
AC_LINK_IFELSE([
AC_LANG_PROGRAM([],
[[
#ifndef _FORTIFY_SOURCE
return 0;
#else
_FORTIFY_SOURCE_already_defined;
#endif
]]
)],
AC_LINK_IFELSE([
AC_LANG_SOURCE([[
#define _FORTIFY_SOURCE 2
#include <string.h>
int main(void) {
char *s = " ";
strcpy(s, "x");
return strlen(s)-1;
}
]]
)],
[
AC_MSG_RESULT([yes])
CFLAGS=$ac_save_cflags
CPPFLAGS="$CPPFLAGS -D_FORTIFY_SOURCE=2"
], [
AC_MSG_RESULT([no])
CFLAGS=$ac_save_cflags
],
),
[
AC_MSG_RESULT([no])
CFLAGS=$ac_save_cflags
])
fi
])

View File

@@ -3,7 +3,7 @@
.\" .\"
.\" This file is generated by man/makeman.sh; do not edit by hand. .\" This file is generated by man/makeman.sh; do not edit by hand.
.\" SPDX-License-Identifier: GPL-3.0-or-later .\" SPDX-License-Identifier: GPL-3.0-or-later
.TH httrack 1 "07 July 2026" "httrack website copier" .TH httrack 1 "27 June 2026" "httrack website copier"
.SH NAME .SH NAME
httrack \- offline browser : copy websites to a local directory httrack \- offline browser : copy websites to a local directory
.SH SYNOPSIS .SH SYNOPSIS
@@ -51,7 +51,6 @@ httrack \- offline browser : copy websites to a local directory
[ \fB\-%T, \-\-utf8\-conversion\fR ] [ \fB\-%T, \-\-utf8\-conversion\fR ]
[ \fB\-bN, \-\-cookies[=N]\fR ] [ \fB\-bN, \-\-cookies[=N]\fR ]
[ \fB\-%K, \-\-cookies\-file\fR ] [ \fB\-%K, \-\-cookies\-file\fR ]
[ \fB\-%Y, \-\-why\fR ]
[ \fB\-u, \-\-check\-type[=N]\fR ] [ \fB\-u, \-\-check\-type[=N]\fR ]
[ \fB\-j, \-\-parse\-java[=N]\fR ] [ \fB\-j, \-\-parse\-java[=N]\fR ]
[ \fB\-sN, \-\-robots[=N]\fR ] [ \fB\-sN, \-\-robots[=N]\fR ]
@@ -219,8 +218,6 @@ links conversion to UTF\-8 (\-\-utf8\-conversion)
accept cookies in cookies.txt (0=do not accept,* 1=accept) (\-\-cookies[=N]) accept cookies in cookies.txt (0=do not accept,* 1=accept) (\-\-cookies[=N])
.IP \-%K .IP \-%K
load extra cookies from a Netscape cookies.txt (\-\-cookies\-file <param>) load extra cookies from a Netscape cookies.txt (\-\-cookies\-file <param>)
.IP \-%Y
explain which filter rule accepts or rejects a URL, then exit (\-\-why <param>)
.IP \-u .IP \-u
check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N]) check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N])
.IP \-j .IP \-j

View File

@@ -114,8 +114,6 @@ const char *hts_optalias[][4] = {
"strip [host/pattern=]key1,key2,... from URLs"}, "strip [host/pattern=]key1,key2,... from URLs"},
{"cookies-file", "-%K", "param1", {"cookies-file", "-%K", "param1",
"load extra cookies from a Netscape cookies.txt"}, "load extra cookies from a Netscape cookies.txt"},
{"why", "-%Y", "param1",
"explain which filter rule accepts or rejects a URL, then exit"},
{"pause", "-%G", "param1", {"pause", "-%G", "param1",
"random pause of MIN[:MAX] seconds between files"}, "random pause of MIN[:MAX] seconds between files"},
{"generate-errors", "-o", "single", ""}, {"generate-errors", "-o", "single", ""},

View File

@@ -68,15 +68,6 @@ static int slot_can_be_cached_on_disk(const lien_back * back);
static int slot_can_be_cleaned(const lien_back * back); static int slot_can_be_cleaned(const lien_back * back);
static int slot_can_be_finalized(httrackp * opt, const lien_back * back); static int slot_can_be_finalized(httrackp * opt, const lien_back * back);
/* Which hard quota, if any, is currently aborting the mirror. */
typedef enum {
HTS_MIRROR_LIMIT_NONE = 0,
HTS_MIRROR_LIMIT_SIZE,
HTS_MIRROR_LIMIT_TIME,
} hts_mirror_limit;
static hts_mirror_limit back_mirror_limit(httrackp *opt);
struct_back *back_new(httrackp *opt, int back_max) { struct_back *back_new(httrackp *opt, int back_max) {
int i; int i;
struct_back *sback = calloct(1, sizeof(struct_back)); struct_back *sback = calloct(1, sizeof(struct_back));
@@ -533,13 +524,12 @@ int back_nsoc_overall(const struct_back * sback) {
/* generate temporary file on lien_back */ /* generate temporary file on lien_back */
/* Note: utf-8 */ /* Note: utf-8 */
static int create_back_tmpfile(httrackp *opt, lien_back *const back, static int create_back_tmpfile(httrackp * opt, lien_back *const back) {
const char *ext) {
// do not use tempnam() but a regular filename // do not use tempnam() but a regular filename
back->tmpfile_buffer[0] = '\0'; back->tmpfile_buffer[0] = '\0';
if (back->url_sav != NULL && back->url_sav[0] != '\0') { if (back->url_sav != NULL && back->url_sav[0] != '\0') {
snprintf(back->tmpfile_buffer, sizeof(back->tmpfile_buffer), "%s.%s", snprintf(back->tmpfile_buffer, sizeof(back->tmpfile_buffer), "%s.z",
back->url_sav, ext); back->url_sav);
back->tmpfile = back->tmpfile_buffer; back->tmpfile = back->tmpfile_buffer;
if (structcheck(back->tmpfile) != 0) { if (structcheck(back->tmpfile) != 0) {
hts_log_print(opt, LOG_WARNING, "can not create directory to %s", hts_log_print(opt, LOG_WARNING, "can not create directory to %s",
@@ -547,8 +537,9 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
return -1; return -1;
} }
} else { } else {
snprintf(back->tmpfile_buffer, sizeof(back->tmpfile_buffer), "%s/tmp%d.%s", snprintf(back->tmpfile_buffer, sizeof(back->tmpfile_buffer),
StringBuff(opt->path_html_utf8), opt->state.tmpnameid++, ext); "%s/tmp%d.z", StringBuff(opt->path_html_utf8),
opt->state.tmpnameid++);
back->tmpfile = back->tmpfile_buffer; back->tmpfile = back->tmpfile_buffer;
} }
/* OK */ /* OK */
@@ -556,32 +547,6 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
return 0; return 0;
} }
/* 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
transfer leaves the previous copy intact. Skips the zlib .z temp. */
static void back_finalize_backup(httrackp *opt, lien_back *const back,
const hts_boolean commit) {
if (back->tmpfile == NULL || back->r.compressed)
return;
if (commit) {
(void) UNLINK(back->tmpfile); /* new copy is good; drop the backup */
} else {
if (back->r.out != NULL) {
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)
hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
"could not restore %s; previous copy kept as %s",
back->url_sav, back->tmpfile);
}
back->tmpfile = NULL;
}
// objet (lien) téléchargé ou transféré depuis le cache // objet (lien) téléchargé ou transféré depuis le cache
// //
// fermer les paramètres de transfert, // fermer les paramètres de transfert,
@@ -617,7 +582,6 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
LLintP " got " LLintP "): %s%s", back[p].r.totalsize, LLintP " got " LLintP "): %s%s", back[p].r.totalsize,
back[p].r.size, back[p].url_adr, back[p].url_fil); back[p].r.size, back[p].url_adr, back[p].url_fil);
} }
back_finalize_backup(opt, &back[p], HTS_FALSE);
return -1; return -1;
} }
@@ -636,7 +600,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
// en mémoire -> passage sur disque // en mémoire -> passage sur disque
if (!back[p].r.is_write) { if (!back[p].r.is_write) {
// do not use tempnam() but a regular filename // do not use tempnam() but a regular filename
if (create_back_tmpfile(opt, &back[p], "z") == 0) { if (create_back_tmpfile(opt, &back[p]) == 0) {
assertf(back[p].tmpfile != NULL); assertf(back[p].tmpfile != NULL);
/* note: tmpfile is utf-8 */ /* note: tmpfile is utf-8 */
back[p].r.out = FOPEN(back[p].tmpfile, "wb"); back[p].r.out = FOPEN(back[p].tmpfile, "wb");
@@ -709,9 +673,6 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
} }
} }
#endif #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);
/* Write mode to disk */ /* Write mode to disk */
if (back[p].r.is_write && back[p].r.adr != NULL) { if (back[p].r.is_write && back[p].r.adr != NULL) {
freet(back[p].r.adr); freet(back[p].r.adr);
@@ -940,9 +901,6 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
} }
} }
} }
/* Aborted, error, or not ready: url_sav (if written) is broken; restore the
previous copy from the backup. */
back_finalize_backup(opt, &back[p], HTS_FALSE);
return -1; return -1;
} }
@@ -1688,104 +1646,138 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
} }
} }
// tester cache // tester cache
if ((strcmp(adr, "file://")) /* pas fichier */ if ((strcmp(adr, "file://")) /* pas fichier */
&& ((!test) || &&((!test) || (cache->type == 1)) /* cache prioritaire, laisser passer en test! */
(cache->type == 1)) /* cache prioritaire, laisser passer en test! */ &&((strnotempty(save)) || (strcmp(fil, "/robots.txt") == 0))) { // si en test on ne doit pas utiliser le cache sinon telescopage avec le 302..
&& #if HTS_FAST_CACHE
((strnotempty(save)) || (strcmp(fil, "/robots.txt") ==
0))) { // si en test on ne doit pas utiliser le
// cache sinon telescopage avec le 302..
intptr_t hash_pos; intptr_t hash_pos;
int hash_pos_return = 0; int hash_pos_return = 0;
#else
char *a = NULL;
#endif
#if HTS_FAST_CACHE
if (cache->hashtable) { if (cache->hashtable) {
#else
if (cache->use) {
#endif
char BIGSTK buff[HTS_URLMAXSIZE * 4]; char BIGSTK buff[HTS_URLMAXSIZE * 4];
#if HTS_FAST_CACHE
strcpybuff(buff, adr); strcpybuff(buff, adr);
strcatbuff(buff, fil); strcatbuff(buff, fil);
hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos); hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos);
#else
buff[0] = '\0';
strcatbuff(buff, "\n");
strcatbuff(buff, adr);
strcatbuff(buff, "\n");
strcatbuff(buff, fil);
strcatbuff(buff, "\n");
a = strstr(cache->use, buff);
#endif
// Ok, noté en cache->. mais bien présent dans le cache ou sur disque?
#if HTS_FAST_CACHE
// negative values when data is not in cache // negative values when data is not in cache
if (hash_pos_return < 0) { if (hash_pos_return < 0) {
if (!test) { // not test mode #else
/* note: no check with IS_DELAYED_EXT() enabled - postcheck by if (a) {
* client please! */ #endif
if (save[0] != '\0' && !IS_DELAYED_EXT(save) && if (!test) { // non mode test
fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <= #if HTS_FAST_CACHE==0
0) { // final file missing or empty int pos = -1;
int found = 0;
/* It is possible that the file has been moved due to changes in a += strlen(buff);
* build structure */ sscanf(a, "%d", &pos); // lire position
{ #endif
char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
htsblk r;
previous_save[0] = '\0'; #if HTS_FAST_CACHE==0
r = cache_readex(opt, cache, adr, fil, /*head */ NULL, if (pos < 0) { // pas de mise en cache data, vérifier existence
#endif
/* note: no check with IS_DELAYED_EXT() enabled - postcheck by client please! */
if (save[0] != '\0' && !IS_DELAYED_EXT(save) && fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <= 0) { // fichier final n'existe pas ou est vide!
int found = 0;
/* It is possible that the file has been moved due to changes in build structure */
{
char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
htsblk r;
previous_save[0] = '\0';
r =
cache_readex(opt, cache, adr, fil, /*head */ NULL,
/*bound to back[p] (temporary) */ /*bound to back[p] (temporary) */
back[p].location_buffer, previous_save, /*ro */ back[p].location_buffer, previous_save, /*ro */
1); 1);
/* Is supposed to be on disk only */ /* Is supposed to be on disk only */
if (r.is_write && previous_save[0] != '\0') { if (r.is_write && previous_save[0] != '\0') {
/* Exists, but with another (old) filename: rename (almost) /* Exists, but with another (old) filename: rename (almost) silently */
* silently */ if (strcmp(previous_save, save) != 0
if (strcmp(previous_save, save) != 0 && && fexist_utf8(fconv(catbuff, sizeof(catbuff), previous_save))) {
fexist_utf8( rename(fconv(catbuff, sizeof(catbuff), previous_save),
fconv(catbuff, sizeof(catbuff), previous_save))) { fconv(catbuff2, sizeof(catbuff2), save));
rename(fconv(catbuff, sizeof(catbuff), previous_save), if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
fconv(catbuff2, sizeof(catbuff2), save)); found = 1;
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) { hts_log_print(opt, LOG_DEBUG,
found = 1; "File '%s' has been renamed since last mirror to '%s' ; applying changes",
hts_log_print(opt, LOG_DEBUG, previous_save, save);
"File '%s' has been renamed since last " } else {
"mirror to '%s' ; applying changes", hts_log_print(opt, LOG_ERROR,
previous_save, save); "Could not rename '%s' to '%s' ; will have to retransfer it",
} else { previous_save, save);
hts_log_print(opt, LOG_ERROR, }
"Could not rename '%s' to '%s' ; will have "
"to retransfer it",
previous_save, save);
} }
} }
back[p].location_buffer[0] = '\0';
} }
back[p].location_buffer[0] = '\0';
}
/* Not found ? */ /* Not found ? */
if (!found) { if (!found) {
// invalidate: gone from disk, force a refetch #if HTS_FAST_CACHE
hash_pos_return = 0; hash_pos_return = 0;
if (opt->norecatch) { #else
if (!fexist_utf8(fconv( a = NULL;
catbuff, sizeof(catbuff), #endif
save))) { // declared but missing: user erased it // dévalider car non présent sur disque dans structure originale!!!
FILE *fp = // sinon, le fichier est ok à priori, mais on renverra un if-modified-since pour
FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb"); // en être sûr
if (opt->norecatch) { // tester norecatch
if (!fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) { // fichier existe pas mais déclaré: on l'a effacé
FILE *fp = FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb");
if (fp) if (fp)
fclose(fp); fclose(fp);
hts_log_print(opt, LOG_WARNING,
"Previous file '%s' not found (erased by user ?), ignoring: %s%s",
save, back[p].url_adr, back[p].url_fil);
}
} else {
hts_log_print(opt, LOG_WARNING, hts_log_print(opt, LOG_WARNING,
"Previous file '%s' not found (erased by " "Previous file '%s' not found (erased by user ?), recatching: %s%s",
"user ?), ignoring: %s%s",
save, back[p].url_adr, back[p].url_fil); save, back[p].url_adr, back[p].url_fil);
} }
} else {
hts_log_print(opt, LOG_WARNING,
"Previous file '%s' not found (erased by user "
"?), recatching: %s%s",
save, back[p].url_adr, back[p].url_fil);
} }
} } // fsize() <= 0
} // fsize() <= 0 #if HTS_FAST_CACHE==0
}
#endif
} }
} }
// //
} else { } else {
#if HTS_FAST_CACHE
hash_pos_return = 0; hash_pos_return = 0;
#else
a = NULL;
#endif
} }
if (hash_pos_return) { // in cache, with data // Existe pas en cache, ou bien pas de cache présent
#if HTS_FAST_CACHE
if (hash_pos_return) { // OK existe en cache (et données aussi)!
#else
if (a != NULL) { // OK existe en cache (et données aussi)!
#endif
const int cache_is_prioritary = cache->type == 1 const int cache_is_prioritary = cache->type == 1
|| opt->state.stop != 0; || opt->state.stop != 0;
if (cache_is_prioritary) { // cache prioritaire (pas de test if-modified..) if (cache_is_prioritary) { // cache prioritaire (pas de test if-modified..)
@@ -1906,8 +1898,7 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
#endif #endif
} }
} }
/* Not in cache ; maybe in temporary cache ? Warning: non-movable /* Not in cache ; maybe in temporary cache ? Warning: non-movable "url_sav" */
"url_sav" */
else if (back_unserialize_ref(opt, adr, fil, &itemback) == 0) { else if (back_unserialize_ref(opt, adr, fil, &itemback) == 0) {
const off_t file_size = fsize_utf8(itemback->url_sav); const off_t file_size = fsize_utf8(itemback->url_sav);
@@ -2092,9 +2083,13 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
if (!back_trylive(opt, cache, sback, p)) { if (!back_trylive(opt, cache, sback, p)) {
#if HTS_XGETHOST #if HTS_XGETHOST
back[p].status = STATUS_WAIT_DNS; // host name resolution attempt #if HDEBUG
soc = INVALID_SOCKET; // not opened yet printf("back_solve..\n");
if (host_wait(opt, &back[p])) { // ready (file, or cached dns) #endif
back[p].status = STATUS_WAIT_DNS; // tentative de résolution du nom de host
soc = INVALID_SOCKET; // pas encore ouverte
back_solve(opt, &back[p]); // préparer
if (host_wait(opt, &back[p])) { // prêt, par ex fichier ou dispo dans dns
#if HDEBUG #if HDEBUG
printf("ok, dns cache ready..\n"); printf("ok, dns cache ready..\n");
#endif #endif
@@ -2237,9 +2232,37 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
} }
#if HTS_XGETHOST #if HTS_XGETHOST
// Resolution is synchronous inside the connect path; no pre-resolve step, so // attendre que le host (ou celui du proxy) ait été résolu
// the host is always immediately ready. // si c'est un fichier, la résolution est immédiate
int host_wait(httrackp *opt, lien_back *back) { return 1; } // idem pour ftp://
void back_solve(httrackp * opt, lien_back * back) {
assertf(opt != NULL);
assertf(back != NULL);
if ((!strfield(back->url_adr, "file://"))
&& !strfield(back->url_adr, "ftp://")
) {
const char *a;
if (!(back->r.req.proxy.active))
a = back->url_adr;
else
a = back->r.req.proxy.name;
assertf(a != NULL);
a = jump_protocol_const(a);
if (check_hostname_dns(a)) {
hts_log_print(opt, LOG_DEBUG, "resolved: %s", a);
} else {
hts_log_print(opt, LOG_DEBUG, "failed to resolve: %s", a);
}
}
}
// détermine si le host a pu être résolu
int host_wait(httrackp * opt, lien_back * back) {
// Always synchronous. No more background DNS resolution
// (does not really improve performances)
return 1;
}
#endif #endif
// élimine les fichiers non html en backing (anticipation) // élimine les fichiers non html en backing (anticipation)
@@ -2426,15 +2449,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
back_clean(opt, cache, sback); back_clean(opt, cache, sback);
#endif #endif
/* Time/size limit exceeded past grace: abort in-flight transfers so no wait /* Time limit exceeded past grace: abort in-flight transfers so no wait loop
loop starves (#481, #77). FTP slots stay, their thread owns the socket. */ starves (#481). FTP slots stay, their thread owns the socket. */
if (!back_checkmirror(opt)) { if (!back_checkmirror(opt)) {
const hts_mirror_limit limit = back_mirror_limit(opt);
const char *const reason =
(limit == HTS_MIRROR_LIMIT_SIZE) ? "size limit" : "time limit";
const char *const slotmsg = (limit == HTS_MIRROR_LIMIT_SIZE)
? "Mirror Size Limit"
: "Mirror Time Out";
int aborted = 0; int aborted = 0;
unsigned int i; unsigned int i;
@@ -2448,15 +2465,15 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
if (back[i].r.is_write && IS_DELAYED_EXT(back[i].url_sav)) if (back[i].r.is_write && IS_DELAYED_EXT(back[i].url_sav))
back_delayed_discard(opt, &back[i]); back_delayed_discard(opt, &back[i]);
back[i].r.statuscode = STATUSCODE_TIMEOUT; back[i].r.statuscode = STATUSCODE_TIMEOUT;
strcpybuff(back[i].r.msg, slotmsg); strcpybuff(back[i].r.msg, "Mirror Time Out");
back[i].status = STATUS_READY; back[i].status = STATUS_READY;
back_set_finished(sback, i); back_set_finished(sback, i);
aborted++; aborted++;
} }
} }
if (aborted > 0) if (aborted > 0)
hts_log_print(opt, LOG_WARNING, "%s reached, %d transfer(s) aborted", hts_log_print(opt, LOG_WARNING,
reason, aborted); "time limit reached, %d transfer(s) aborted", aborted);
return; return;
} }
@@ -2946,8 +2963,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "gz") == 0 strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "gz") == 0
&& strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "tgz") == 0 && strfield(get_ext(catbuff, sizeof(catbuff), back[i].url_sav), "tgz") == 0
) { ) {
if (create_back_tmpfile(opt, &back[i], "z") == if (create_back_tmpfile(opt, &back[i]) == 0) {
0) {
assertf(back[i].tmpfile != NULL); assertf(back[i].tmpfile != NULL);
/* note: tmpfile is utf-8 */ /* note: tmpfile is utf-8 */
if ((back[i].r.out = if ((back[i].r.out =
@@ -2960,20 +2976,6 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
back[i].url_sav, 1, 1, back[i].url_sav, 1, 1,
back[i].r.notmodified); back[i].r.notmodified);
back[i].r.compressed = 0; back[i].r.compressed = 0;
/* Re-fetch over an existing file (#77 follow-up):
move the good copy aside before truncating it
so an aborted transfer can restore it. url_sav
is still written normally (file list intact).
*/
back[i].tmpfile = NULL;
if (fexist_utf8(back[i].url_sav)) {
if (create_back_tmpfile(opt, &back[i], "bak") !=
0 ||
RENAME(back[i].url_sav, back[i].tmpfile) !=
0) {
back[i].tmpfile = NULL;
}
}
if ((back[i].r.out = if ((back[i].r.out =
filecreate(&opt->state.strc, filecreate(&opt->state.strc,
back[i].url_sav)) == NULL) { back[i].url_sav)) == NULL) {
@@ -3540,20 +3542,22 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
if (back[i].r.statuscode == 406) { // 'Not Acceptable' if (back[i].r.statuscode == 406) { // 'Not Acceptable'
back[i].r.statuscode = HTTP_OK; back[i].r.statuscode = HTTP_OK;
} }
// On update, keep the good copy on error (mask as 304); skip // 'do not erase already downloaded file'
// resume paths so a stale-partial 416 still re-fetches. // on an updated file
if (HTTP_IS_ERROR(back[i].r.statuscode) && // with an error : consider a 304 error
back[i].is_update && !back[i].testmode && if (!opt->delete_old) {
back[i].range_req_size == 0 && back[i].url_sav[0] && if (HTTP_IS_ERROR(back[i].r.statuscode) && back[i].is_update
fexist_utf8(back[i].url_sav)) { && !back[i].testmode) {
hts_log_print(opt, LOG_DEBUG, if (back[i].url_sav[0] && fexist_utf8(back[i].url_sav)) {
"Error %d (%s) ignored on update, keeping " hts_log_print(opt, LOG_DEBUG,
"existing copy: %s%s", "Error ignored %d (%s) because of 'no purge' option for %s%s",
back[i].r.statuscode, back[i].r.msg, back[i].r.statuscode, back[i].r.msg,
back[i].url_adr, back[i].url_fil); back[i].url_adr, back[i].url_fil);
back[i].r.statuscode = HTTP_NOT_MODIFIED; back[i].r.statuscode = HTTP_NOT_MODIFIED;
deletehttp(&back[i].r); deletehttp(&back[i].r);
back[i].r.soc = INVALID_SOCKET; back[i].r.soc = INVALID_SOCKET;
}
}
} }
// Various hacks to limit re-transfers when updating a mirror // Various hacks to limit re-transfers when updating a mirror
// Force update if same size detected // Force update if same size detected
@@ -4174,43 +4178,38 @@ static int back_maxtime_grace(const int maxtime) {
return maximum(5, minimum(30, maxtime / 10)); return maximum(5, minimum(30, maxtime / 10));
} }
/* Bytes the smooth stop may overrun before in-flight transfers are aborted. int back_checkmirror(httrackp * opt) {
No floor (unlike the time grace): a size overrun should abort promptly. */ // Check max size
static LLint back_maxsize_grace(const LLint maxsite) { return maxsite / 10; } if ((opt->maxsite > 0) && (HTS_STAT.stat_bytes >= opt->maxsite)) {
if (!opt->state.stop) { /* not yet stopped */
/* Which cap has overrun its grace and must hard-stop the mirror (#77, #481). */ hts_log_print(opt, LOG_ERROR,
static hts_mirror_limit back_mirror_limit(httrackp *opt) { "More than " LLintP
if (opt->maxsite > 0 && HTS_STAT.stat_bytes >= opt->maxsite && " bytes have been transferred.. giving up",
HTS_STAT.stat_bytes - opt->maxsite >= back_maxsize_grace(opt->maxsite)) (LLint) opt->maxsite);
return HTS_MIRROR_LIMIT_SIZE; /* cancel mirror smoothly */
hts_request_stop(opt, 0);
}
return 1; /* don'k break mirror too sharply for size limits, but stop requested */
/*return 0;
*/
}
// Check max time
if (opt->maxtime > 0) { if (opt->maxtime > 0) {
const TStamp elapsed = time_local() - HTS_STAT.stat_timestart; const TStamp elapsed = time_local() - HTS_STAT.stat_timestart;
if (elapsed >= opt->maxtime && if (elapsed >= opt->maxtime) {
elapsed - opt->maxtime >= back_maxtime_grace(opt->maxtime)) if (!opt->state.stop) { /* not yet stopped */
return HTS_MIRROR_LIMIT_TIME; hts_log_print(opt, LOG_ERROR, "More than %d seconds passed.. giving up",
opt->maxtime);
/* cancel mirror smoothly */
hts_request_stop(opt, 0);
}
/* smooth stop starved past the grace period: stop waiting (#481) */
if (elapsed - opt->maxtime >= back_maxtime_grace(opt->maxtime))
return 0;
}
} }
return HTS_MIRROR_LIMIT_NONE; return 1; /* Ok, go on */
}
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 &&
!opt->state.stop) {
hts_log_print(opt, LOG_ERROR,
"More than " LLintP
" bytes have been transferred.. giving up",
(LLint) opt->maxsite);
hts_request_stop(opt, 0);
}
if (opt->maxtime > 0 && !opt->state.stop &&
(time_local() - HTS_STAT.stat_timestart) >= opt->maxtime) {
hts_log_print(opt, LOG_ERROR, "More than %d seconds passed.. giving up",
opt->maxtime);
hts_request_stop(opt, 0);
}
/* hard stop once a cap overruns its grace (callers must stop waiting) */
return back_mirror_limit(opt) == HTS_MIRROR_LIMIT_NONE;
} }
// octets transférés + add // octets transférés + add

View File

@@ -138,11 +138,12 @@ LLint back_transferred(LLint add, struct_back * sback);
// hostback // hostback
#if HTS_XGETHOST #if HTS_XGETHOST
void back_solve(httrackp * opt, lien_back * sback);
int host_wait(httrackp * opt, lien_back * sback); int host_wait(httrackp * opt, lien_back * sback);
#endif #endif
int back_checksize(httrackp * opt, lien_back * eback, int check_only_totalsize); int back_checksize(httrackp * opt, lien_back * eback, int check_only_totalsize);
/* Enforce -M/-E quotas: smooth-stops when reached; returns 0 once the -M cap /* Enforce -M/-E quotas: requests a smooth stop when reached; returns 0 once
or -E deadline overruns its grace period (callers must stop waiting). */ the -E deadline overran its grace period (callers must stop waiting). */
int back_checkmirror(httrackp * opt); int back_checkmirror(httrackp * opt);
#endif #endif

View File

@@ -307,25 +307,14 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
return -1; return -1;
} }
/* Write cookies.txt; returns 0 on success. The jar holds live session // écrire cookies.txt
cookies, so keep it owner-only on Unix (Windows inherits folder ACLs). */ // !=0 : erreur
int cookie_save(t_cookie * cookie, const char *name) { int cookie_save(t_cookie * cookie, const char *name) {
char catbuff[CATBUFF_SIZE]; char catbuff[CATBUFF_SIZE];
if (strnotempty(cookie->data)) { if (strnotempty(cookie->data)) {
char BIGSTK line[8192]; char BIGSTK line[8192];
#ifdef _WIN32
FILE *fp = fopen(fconv(catbuff, sizeof(catbuff), name), "wb"); FILE *fp = fopen(fconv(catbuff, sizeof(catbuff), name), "wb");
#else
const int fd = open(fconv(catbuff, sizeof(catbuff), name),
O_WRONLY | O_CREAT | O_TRUNC, HTS_PROTECT_FILE);
FILE *fp = fd != -1 ? fdopen(fd, "wb") : NULL;
if (fd != -1 && fp == NULL)
close(fd); /* fdopen failed: don't leak the descriptor */
if (fp != NULL) /* O_CREAT's mode skips pre-existing jars: tighten those */
(void) fchmod(fd, HTS_PROTECT_FILE);
#endif
if (fp) { if (fp) {
char *a = cookie->data; char *a = cookie->data;

File diff suppressed because it is too large Load Diff

View File

@@ -89,11 +89,14 @@ typedef enum {
the involved files are absent. */ the involved files are absent. */
void hts_cache_reconcile(httrackp *opt, hts_cache_reconcile_mode mode); void hts_cache_reconcile(httrackp *opt, hts_cache_reconcile_mode mode);
int cache_writedata(FILE * cache_ndx, FILE * cache_dat, const char *str1,
const char *str2, char *outbuff, int len);
int cache_readdata(cache_back * cache, const char *str1, const char *str2,
char **inbuff, int *len);
void cache_rstr(FILE *fp, char *s, size_t s_size); void cache_rstr(FILE *fp, char *s, size_t s_size);
char *cache_rstr_addr(FILE * fp); char *cache_rstr_addr(FILE * fp);
int cache_brstr(char *adr, char *s, size_t s_size); int cache_brstr(char *adr, char *s, size_t s_size);
/* binput over a NUL-terminated buffer, bounded: no read starts at/past end. */
int cache_binput(char *adr, const char *end, char *s, int max);
int cache_brint(char *adr, int *i); int cache_brint(char *adr, int *i);
void cache_rint(FILE * fp, int *i); void cache_rint(FILE * fp, int *i);
void cache_rLLint(FILE * fp, LLint * i); void cache_rLLint(FILE * fp, LLint * i);

View File

@@ -78,6 +78,14 @@ static void selftest_open_for_read(cache_back *cache, httrackp *opt) {
} }
static void selftest_close(cache_back *cache) { static void selftest_close(cache_back *cache) {
if (cache->dat != NULL) {
fclose(cache->dat);
cache->dat = NULL;
}
if (cache->ndx != NULL) {
fclose(cache->ndx);
cache->ndx = NULL;
}
if (cache->zipOutput != NULL) { if (cache->zipOutput != NULL) {
zipClose(cache->zipOutput, zipClose(cache->zipOutput,
"Created by HTTrack Website Copier (cache self-test)"); "Created by HTTrack Website Copier (cache self-test)");
@@ -981,15 +989,26 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
failures += failures +=
reconcile_expect(opt, "hts-cache/old.zip", SOLID, "promote-zip-noop"); reconcile_expect(opt, "hts-cache/old.zip", SOLID, "promote-zip-noop");
/* PROMOTE: the removed pre-3.31 legacy pair is left alone */ /* PROMOTE: a pure-legacy old generation is promoted too (was dead when no
zip cache existed) */
reconcile_wipe(opt); reconcile_wipe(opt);
reconcile_put(opt, "hts-cache/old.dat", SOLID); reconcile_put(opt, "hts-cache/old.dat", SOLID);
reconcile_put(opt, "hts-cache/old.ndx", TINY); reconcile_put(opt, "hts-cache/old.ndx", TINY);
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE); hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
failures += reconcile_expect(opt, "hts-cache/new.dat", -1, "promote-dat"); failures += reconcile_expect(opt, "hts-cache/new.dat", SOLID, "promote-dat");
failures += reconcile_expect(opt, "hts-cache/new.ndx", -1, "promote-dat"); failures += reconcile_expect(opt, "hts-cache/new.ndx", TINY, "promote-dat");
failures += reconcile_expect(opt, "hts-cache/old.dat", SOLID, "promote-dat"); failures += reconcile_expect(opt, "hts-cache/old.dat", -1, "promote-dat");
failures += reconcile_expect(opt, "hts-cache/old.ndx", TINY, "promote-dat");
/* PROMOTE: a half-written legacy new pair is replaced by the old pair */
reconcile_wipe(opt);
reconcile_put(opt, "hts-cache/new.dat", TINY);
reconcile_put(opt, "hts-cache/old.dat", SOLID);
reconcile_put(opt, "hts-cache/old.ndx", TINY);
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
failures +=
reconcile_expect(opt, "hts-cache/new.dat", SOLID, "promote-dat-partial");
failures +=
reconcile_expect(opt, "hts-cache/new.ndx", TINY, "promote-dat-partial");
/* INTERRUPTED: no lock file, no action */ /* INTERRUPTED: no lock file, no action */
reconcile_wipe(opt); reconcile_wipe(opt);
@@ -1039,7 +1058,7 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
failures += failures +=
reconcile_expect(opt, "hts-cache/new.zip", MID, "interrupted-bignew"); reconcile_expect(opt, "hts-cache/new.zip", MID, "interrupted-bignew");
/* INTERRUPTED: the removed pre-3.31 legacy pair is left alone */ /* INTERRUPTED: the legacy pair follows the same size rule (was dead code) */
reconcile_wipe(opt); reconcile_wipe(opt);
reconcile_put(opt, "hts-in_progress.lock", 0); reconcile_put(opt, "hts-in_progress.lock", 0);
reconcile_put(opt, "hts-cache/new.dat", TINY); reconcile_put(opt, "hts-cache/new.dat", TINY);
@@ -1048,13 +1067,9 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
reconcile_put(opt, "hts-cache/old.ndx", MID); reconcile_put(opt, "hts-cache/old.ndx", MID);
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED); hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
failures += failures +=
reconcile_expect(opt, "hts-cache/new.dat", TINY, "interrupted-dat"); reconcile_expect(opt, "hts-cache/new.dat", SOLID, "interrupted-dat");
failures += failures +=
reconcile_expect(opt, "hts-cache/new.ndx", TINY, "interrupted-dat"); reconcile_expect(opt, "hts-cache/new.ndx", MID, "interrupted-dat");
failures +=
reconcile_expect(opt, "hts-cache/old.dat", SOLID, "interrupted-dat");
failures +=
reconcile_expect(opt, "hts-cache/old.ndx", MID, "interrupted-dat");
/* ROLLBACK: the old zip generation is restored (a zip cache used to lose /* ROLLBACK: the old zip generation is restored (a zip cache used to lose
its only good generation here) */ its only good generation here) */
@@ -1074,18 +1089,17 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-lst"); failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-lst");
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-txt"); failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-txt");
/* ROLLBACK: sidecars restored, the removed pre-3.31 pair left alone */ /* ROLLBACK: full legacy generation incl. sidecars (historical behavior) */
reconcile_wipe(opt); reconcile_wipe(opt);
reconcile_put(opt, "hts-cache/new.dat", TINY); reconcile_put(opt, "hts-cache/new.dat", TINY);
reconcile_put(opt, "hts-cache/new.ndx", TINY);
reconcile_put(opt, "hts-cache/old.dat", SOLID); reconcile_put(opt, "hts-cache/old.dat", SOLID);
reconcile_put(opt, "hts-cache/old.ndx", MID); reconcile_put(opt, "hts-cache/old.ndx", MID);
reconcile_put(opt, "hts-cache/old.lst", MID); reconcile_put(opt, "hts-cache/old.lst", MID);
reconcile_put(opt, "hts-cache/old.txt", MID); reconcile_put(opt, "hts-cache/old.txt", MID);
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK); hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
failures += reconcile_expect(opt, "hts-cache/new.dat", TINY, "rollback-dat"); failures += reconcile_expect(opt, "hts-cache/new.dat", SOLID, "rollback-dat");
failures += reconcile_expect(opt, "hts-cache/new.ndx", -1, "rollback-dat"); failures += reconcile_expect(opt, "hts-cache/new.ndx", MID, "rollback-dat");
failures += reconcile_expect(opt, "hts-cache/old.dat", SOLID, "rollback-dat");
failures += reconcile_expect(opt, "hts-cache/old.ndx", MID, "rollback-dat");
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-dat"); failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-dat");
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-dat"); failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-dat");
@@ -1099,101 +1113,6 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
return failures; return failures;
} }
/* The pre-3.31 .dat/.ndx import was removed: cache_init must refuse the
legacy pair, leave the files in place, and not flag an update run. */
int cache_legacy_refused_selftest(httrackp *opt, const char *dir) {
int failures = 0;
int variant;
selftest_tag = "cache-legacy";
golden_setup(opt, dir);
#ifdef _WIN32
mkdir(reconcile_st_path(opt, "hts-cache"));
#else
mkdir(reconcile_st_path(opt, "hts-cache"), HTS_PROTECT_FOLDER);
#endif
/* variant 0: old.dat/old.ndx (--update layout); 1: new.dat/new.ndx (ro) */
for (variant = 0; variant < 2; variant++) {
cache_back cache;
const char *const dat =
variant == 0 ? "hts-cache/old.dat" : "hts-cache/new.dat";
const char *const ndx =
variant == 0 ? "hts-cache/old.ndx" : "hts-cache/new.ndx";
reconcile_wipe(opt);
reconcile_put(opt, dat, 4096);
reconcile_put(opt, ndx, 4096);
opt->is_update = 0;
selftest_open(&cache, opt, /*ro*/ variant == 1);
if (cache_readable(&cache)) {
printf("cache-legacy: variant %d imported a removed format\n", variant);
failures++;
}
if (coucal_nitems(cache.hashtable) != 0) { /* no phantom index entries */
printf("cache-legacy: variant %d imported index entries\n", variant);
failures++;
}
if (opt->is_update) {
printf("cache-legacy: variant %d flagged an update\n", variant);
failures++;
}
if (fsize(reconcile_st_path(opt, dat)) != 4096 ||
fsize(reconcile_st_path(opt, ndx)) != 4096) {
printf("cache-legacy: variant %d touched the legacy files\n", variant);
failures++;
}
if (cache.lst != NULL) {
fclose(cache.lst);
cache.lst = NULL;
}
if (cache.txt != NULL) {
fclose(cache.txt);
cache.txt = NULL;
}
selftest_close(&cache);
}
/* a healthy zip must win over stray legacy files, with no refusal */
{
cache_back cache;
const char *const body = "<html>zip wins</html>";
reconcile_wipe(opt);
selftest_open_for_write(&cache, opt);
store_entry(opt, &cache, "example.com", "/", "example.com/index.html", 200,
"OK", "text/html", "utf-8", "", "", "", "", body, strlen(body));
selftest_close(&cache);
reconcile_put(opt, "hts-cache/old.ndx", 4096);
reconcile_put(opt, "hts-cache/old.dat", 4096);
selftest_open_for_read(&cache, opt);
if (!cache_readable(&cache)) {
printf("cache-legacy: stray legacy files shadowed the zip cache\n");
failures++;
}
failures +=
check_entry(opt, &cache, "example.com", "/", 200, "OK", "text/html",
"utf-8", "", "", "", "", body, strlen(body));
if (fsize(reconcile_st_path(opt, "hts-cache/old.ndx")) != 4096 ||
fsize(reconcile_st_path(opt, "hts-cache/old.dat")) != 4096) {
printf("cache-legacy: zip-wins pass touched the legacy files\n");
failures++;
}
if (cache.lst != NULL) {
fclose(cache.lst);
cache.lst = NULL;
}
if (cache.txt != NULL) {
fclose(cache.txt);
cache.txt = NULL;
}
selftest_close(&cache);
}
reconcile_wipe(opt);
return failures;
}
/* --- read-side corruption injection --------------------------------------- */ /* --- read-side corruption injection --------------------------------------- */
/* canary read back intact after each corruption; victim gets the byte surgery /* canary read back intact after each corruption; victim gets the byte surgery

View File

@@ -60,10 +60,6 @@ int cache_write_failure_selftest(httrackp *opt, const char *dir);
under <dir>. Returns the failed-check count. */ under <dir>. Returns the failed-check count. */
int cache_reconcile_selftest(httrackp *opt, const char *dir); int cache_reconcile_selftest(httrackp *opt, const char *dir);
/* Verify cache_init refuses a pre-3.31 .dat/.ndx cache without touching it.
Returns the number of failed checks (0 = pass). */
int cache_legacy_refused_selftest(httrackp *opt, const char *dir);
/* Inject read-side corruption (zip byte surgery: bad size, header, deflate) /* Inject read-side corruption (zip byte surgery: bad size, header, deflate)
under <dir> and assert every case degrades to STATUSCODE_INVALID without under <dir> and assert every case degrades to STATUSCODE_INVALID without
tainting a sibling entry. */ tainting a sibling entry. */

View File

@@ -84,7 +84,7 @@ static int hts_equalsAlphanum(const char *a, const char *b) {
/* Copy the memory region [s .. s + size - 1 ] as a \0-terminated string. */ /* Copy the memory region [s .. s + size - 1 ] as a \0-terminated string. */
static char *hts_stringMemCopy(const char *s, size_t size) { static char *hts_stringMemCopy(const char *s, size_t size) {
char *dest = malloct(size + 1); char *dest = malloc(size + 1);
if (dest != NULL) { if (dest != NULL) {
memcpy(dest, s, size); memcpy(dest, s, size);
@@ -549,6 +549,42 @@ char *hts_convertStringFromUTF8(const char *s, size_t size, const char *charset)
#endif #endif
HTS_STATIC char *hts_getCharsetFromContentType(const char *mime) {
/* text/html; charset=utf-8 */
const char *const charset = "charset";
char *pos = strstr(mime, charset);
if (pos != NULL) {
/* Skip spaces */
int eq = 0;
for(pos += strlen(charset);
*pos == ' ' || *pos == '=' || *pos == '"' || *pos == '\''; pos++) {
if (*pos == '=') {
eq = 1;
}
}
if (eq == 1) {
int len;
for(len = 0;
pos[len] == ' ' || pos[len] == ';' || pos[len] == '"' || *pos == '\'';
pos++) ;
if (len != 0) {
char *const s = malloc(len + 1);
int i;
for(i = 0; i < len; i++) {
s[i] = pos[i];
}
s[len] = '\0';
return s;
}
}
}
return NULL;
}
#ifdef _WIN32 #ifdef _WIN32
#define strcasecmp(a,b) stricmp(a,b) #define strcasecmp(a,b) stricmp(a,b)
#define strncasecmp(a,b,n) strnicmp(a,b,n) #define strncasecmp(a,b,n) strnicmp(a,b,n)
@@ -608,106 +644,58 @@ int hts_isCharsetUTF8(const char *charset) {
|| strcasecmp(charset, "utf8") == 0 ); || strcasecmp(charset, "utf8") == 0 );
} }
/* Extract X from a "text/html; charset=X" content= attribute value. */
static char *charset_from_content(const char *s, size_t len) {
size_t i;
for (i = 0; i + 7 < len; i++) {
if ((i == 0 || is_space(s[i - 1]) || s[i - 1] == ';') &&
strncasecmp(&s[i], "charset", 7) == 0 && is_space_or_equal(s[i + 7])) {
size_t j, val;
for (j = i + 7; j < len && is_space_or_equal_or_quote(s[j]); j++)
;
for (val = j; j < len && s[j] != '"' && s[j] != '\'' && s[j] != ';' &&
!is_space(s[j]);
j++)
;
if (j != val) {
return hts_stringMemCopy(&s[val], j - val);
}
}
}
return NULL;
}
char *hts_getCharsetFromMeta(const char *html, size_t size) { char *hts_getCharsetFromMeta(const char *html, size_t size) {
size_t i; int i;
/* HTML5 <meta charset=X> or legacy /* <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8" > */
<meta http-equiv="Content-Type" content="text/html; charset=X">, for(i = 0; i < size; i++) {
attributes in any order; first resolvable tag wins. */ if (html[i] == '<' && strncasecmp(&html[i + 1], "meta", 4) == 0
for (i = 0; i + 6 < size; i++) { && is_space(html[i + 5])) {
size_t j; /* Skip spaces */
const char *equiv = NULL, *content = NULL; for(i += 5; is_space(html[i]); i++) ;
size_t equiv_len = 0, content_len = 0; if (strncasecmp(&html[i], "HTTP-EQUIV", 10) == 0
&& is_space_or_equal(html[i + 10])) {
for(i += 10; is_space_or_equal_or_quote(html[i]); i++) ;
if (strncasecmp(&html[i], "CONTENT-TYPE", 12) == 0) {
for(i += 12; is_space_or_equal_or_quote(html[i]); i++) ;
if (strncasecmp(&html[i], "CONTENT", 7) == 0
&& is_space_or_equal(html[i + 7])) {
for(i += 7; is_space_or_equal_or_quote(html[i]); i++) ;
/* Skip content-type */
for(;
i < size && html[i] != ';' && html[i] != '"' && html[i] != '\'';
i++) ;
/* Expect charset attribute here */
if (html[i] == ';') {
for(i++; is_space(html[i]); i++) ;
/* Look for charset */
if (strncasecmp(&html[i], "charset", 7) == 0
&& is_space_or_equal(html[i + 7])) {
int len;
if (html[i] != '<' || strncasecmp(&html[i + 1], "meta", 4) != 0 || for(i += 7; is_space_or_equal(html[i]) || html[i] == '\'';
!is_space(html[i + 5])) { i++) ;
continue; /* Charset */
} for(len = 0;
/* Attribute scan, strictly bounded by size. */ i + len < size && html[i + len] != '"'
for (j = i + 6; j < size && html[j] != '>';) { && html[i + len] != '\'' && html[i + len] != ' '; len++) ;
size_t name, name_len, val = 0, val_len = 0; /* No error ? */
if (len != 0 && i < size) {
char *const s = malloc(len + 1);
int j;
if (is_space(html[j]) || html[j] == '/') { for(j = 0; j < len; j++) {
j++; s[j] = html[i + j];
continue; }
} s[len] = '\0';
for (name = j; j < size && html[j] != '=' && html[j] != '>' && return s;
html[j] != '/' && !is_space(html[j]); }
j++) }
; }
name_len = j - name;
for (; j < size && is_space(html[j]); j++)
;
if (j < size && html[j] == '=') {
for (j++; j < size && is_space(html[j]); j++)
;
if (j < size && (html[j] == '"' || html[j] == '\'')) {
const char quote = html[j++];
for (val = j; j < size && html[j] != quote; j++)
;
if (j >= size) /* unterminated quote: drop the tag */
break;
val_len = j++ - val;
} else {
/* unquoted: ends at whitespace or '>' only (HTML5 prescan); '/'
belongs to the value except as the tail of a self-close */
for (val = j; j < size && !is_space(html[j]) && html[j] != '>'; j++)
;
val_len = j - val;
if (val_len != 0 && j < size && html[j] == '>' &&
html[val + val_len - 1] == '/') {
val_len--;
} }
} }
} }
/* first occurrence of each attribute wins, as in browsers */
if (val_len != 0) {
if (name_len == 7 && strncasecmp(&html[name], "charset", 7) == 0) {
return hts_stringMemCopy(&html[val], val_len);
} else if (equiv == NULL && name_len == 10 &&
strncasecmp(&html[name], "http-equiv", 10) == 0) {
equiv = &html[val];
equiv_len = val_len;
} else if (content == NULL && name_len == 7 &&
strncasecmp(&html[name], "content", 7) == 0) {
content = &html[val];
content_len = val_len;
}
}
} }
if (equiv_len == 12 && strncasecmp(equiv, "content-type", 12) == 0 &&
content != NULL) {
char *const s = charset_from_content(content, content_len);
if (s != NULL) {
return s;
}
}
i = j;
} }
return NULL; return NULL;
} }
@@ -922,19 +910,19 @@ char *hts_convertStringUTF8ToIDNA(const char *s, size_t size) {
/* Reader: can read bytes up to j */ /* Reader: can read bytes up to j */
#define RD ( utfSeq < j ? segData[utfSeq++] : -1 ) #define RD ( utfSeq < j ? segData[utfSeq++] : -1 )
/* Writer: a malformed sequence abandons the encode (NULL) */ /* Writer: upon error, return FFFD (replacement character) */
#define WR(C) \ #define WR(C) do { \
do { \ if ((C) != -1) { \
if ((C) != -1) { \ /* copy character */ \
/* copy character */ \ assertf(segOutputSize < segSize); \
assertf(segOutputSize < segSize); \ segInt[segOutputSize++] = (C); \
segInt[segOutputSize++] = (C); \ } \
} else { \ /* In case of error, abort. */ \
freet(segInt); \ else { \
FREE_BUFFER(); \ FREE_BUFFER(); \
return NULL; \ return NULL; \
} \ } \
} while (0) } while(0)
/* Read/Write Unicode character. */ /* Read/Write Unicode character. */
READ_UNICODE(RD, WR); READ_UNICODE(RD, WR);
@@ -1152,45 +1140,17 @@ int hts_isStringUTF8(const char *s, size_t size) {
const unsigned char *const data = (const unsigned char*) s; const unsigned char *const data = (const unsigned char*) s;
size_t i; size_t i;
/* Strict RFC 3629: reject overlongs, surrogates, > U+10FFFF, 5/6-byte. */
for(i = 0 ; i < size ; ) { for(i = 0 ; i < size ; ) {
const unsigned char c = data[i]; /* Reader: can read bytes up to j */
size_t len, k; #define RD ( i < size ? data[i++] : -1 )
hts_UCS4 uc, min;
if (c < 0x80) { /* Writer: upon error, return FFFD (replacement character) */
i++; #define WR(C) if ((C) == -1) { return 0; }
continue;
} else if (c >= 0xc2 && c <= 0xdf) {
len = 2;
min = 0x80;
uc = c & 0x1f;
} else if (c >= 0xe0 && c <= 0xef) {
len = 3;
min = 0x800;
uc = c & 0x0f;
} else if (c >= 0xf0 && c <= 0xf4) {
len = 4;
min = 0x10000;
uc = c & 0x07;
} else { /* continuation byte, overlong C0/C1, or F5..FF lead */
return 0;
}
if (size - i < len) {
return 0;
}
for (k = 1; k < len; k++) {
const unsigned char cc = data[i + k];
if ((cc & 0xc0) != 0x80) { /* Read Unicode character. */
return 0; READ_UNICODE(RD, WR);
} #undef RD
uc = (uc << 6) | (cc & 0x3f); #undef WR
}
if (uc < min || uc > 0x10FFFF || (uc >= 0xD800 && uc <= 0xDFFF)) {
return 0;
}
i += len;
} }
return 1; return 1;

View File

@@ -76,8 +76,7 @@ extern char *hts_convertStringIDNAToUTF8(const char *s, size_t size);
extern int hts_isStringIDNA(const char *s, size_t size); extern int hts_isStringIDNA(const char *s, size_t size);
/** /**
* Extract the <meta> charset from the HTML buffer "html" (HTML5 charset= or * Extract the charset from the HTML buffer "html"
* legacy http-equiv form). Returns a malloc'ed string, or NULL if none.
**/ **/
extern char *hts_getCharsetFromMeta(const char *html, size_t size); extern char *hts_getCharsetFromMeta(const char *html, size_t size);
@@ -87,8 +86,7 @@ extern char *hts_getCharsetFromMeta(const char *html, size_t size);
extern int hts_isStringAscii(const char *s, size_t size); extern int hts_isStringAscii(const char *s, size_t size);
/** /**
* Is the given string valid UTF-8 ? Strict RFC 3629: overlong forms, * Is the given string an UTF-8 string ?
* surrogates, codepoints above U+10FFFF and 5/6-byte sequences are rejected.
**/ **/
extern int hts_isStringUTF8(const char *s, size_t size); extern int hts_isStringUTF8(const char *s, size_t size);

View File

@@ -76,6 +76,9 @@ Please visit our Website: http://www.httrack.com
// always direct-to-disk (0/1) // always direct-to-disk (0/1)
#define HTS_DIRECTDISK_ALWAYS 1 #define HTS_DIRECTDISK_ALWAYS 1
// fast cache (build hash table)
#define HTS_FAST_CACHE 1
// le > peut être considéré comme un tag de fermeture de commentaire (<!-- > est // le > peut être considéré comme un tag de fermeture de commentaire (<!-- > est
// valide) // valide)
#define GT_ENDS_COMMENT 1 #define GT_ENDS_COMMENT 1

View File

@@ -134,6 +134,18 @@ RUN_CALLBACK0(opt, end); \
back_delete_all(opt, &cache, sback); \ back_delete_all(opt, &cache, sback); \
back_free(&sback); \ back_free(&sback); \
checkrobots_free(&robots); \ checkrobots_free(&robots); \
if (cache.use) { \
freet(cache.use); \
cache.use = NULL; \
} \
if (cache.dat) { \
fclose(cache.dat); \
cache.dat = NULL; \
} \
if (cache.ndx) { \
fclose(cache.ndx); \
cache.ndx = NULL; \
} \
if (cache.zipOutput) { \ if (cache.zipOutput) { \
zipClose(cache.zipOutput, \ zipClose(cache.zipOutput, \
"Created by HTTrack Website Copier/" HTTRACK_VERSION); \ "Created by HTTrack Website Copier/" HTTRACK_VERSION); \
@@ -143,6 +155,10 @@ RUN_CALLBACK0(opt, end); \
unzClose(cache.zipInput); \ unzClose(cache.zipInput); \
cache.zipInput = NULL; \ cache.zipInput = NULL; \
} \ } \
if (cache.olddat) { \
fclose(cache.olddat); \
cache.olddat = NULL; \
} \
if (cache.lst) { \ if (cache.lst) { \
fclose(cache.lst); \ fclose(cache.lst); \
cache.lst = opt->state.strc.lst = NULL; \ cache.lst = opt->state.strc.lst = NULL; \
@@ -472,11 +488,32 @@ void hts_finish_makeindex(httrackp *opt, int *makeindex_done,
*makeindex_done = 1; *makeindex_done = 1;
} }
/* Flush the parsed HTML output buffer to disk. */ /* Flush the parsed HTML output buffer to disk, skipping the rewrite when the
* on-disk MD5 is unchanged. */
void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r, void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
FILE **fp, const char *ht_buff, size_t ht_len, FILE **fp, const char *ht_buff, size_t ht_len,
const char *adr, const char *fil, const char *save) { const char *adr, const char *fil, const char *save) {
{ char digest[32 + 2];
off_t fsize_old =
fsize(fconv(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), save));
int ok = 0;
digest[0] = '\0';
domd5mem(ht_buff, ht_len, digest, 1);
if (fsize_old == (off_t) ht_len) {
int mlen = 0;
char *mbuff;
cache_readdata(cache, "//[HTML-MD5]//", save, &mbuff, &mlen);
if (mlen)
mbuff[mlen] = '\0';
if ((mlen == 32) && (strcmp(((mbuff != NULL) ? mbuff : ""), digest) == 0)) {
ok = 1;
hts_log_print(opt, LOG_DEBUG, "File not re-written (md5): %s", save);
}
freet(mbuff);
}
if (!ok) {
file_notify(opt, adr, fil, save, 1, 1, r->notmodified); file_notify(opt, adr, fil, save, 1, 1, r->notmodified);
*fp = filecreate(&opt->state.strc, save); *fp = filecreate(&opt->state.strc, save);
if (*fp) { if (*fp) {
@@ -508,7 +545,13 @@ void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
if (fcheck) if (fcheck)
hts_log_print(opt, LOG_ERROR, "* * Fatal write error, giving up"); hts_log_print(opt, LOG_ERROR, "* * Fatal write error, giving up");
} }
} else {
file_notify(opt, adr, fil, save, 0, 0, r->notmodified);
filenote(&opt->state.strc, save, NULL);
} }
if (cache->ndx)
cache_writedata(cache->ndx, cache->dat, "//[HTML-MD5]//", save, digest,
(int) strlen(digest));
} }
/* does it look like XML ? (SVG et al.) */ /* does it look like XML ? (SVG et al.) */
@@ -803,52 +846,6 @@ int httpmirror(char *url1, httrackp * opt) {
} }
} // while } // while
/* --why: print which filter rule decides for this URL, then stop */
if (StringNotEmpty(opt->why_url)) {
char BIGSTK url[HTS_URLMAXSIZE * 2];
lien_adrfil af;
if (strstr(StringBuff(opt->why_url), ":/") == NULL)
snprintf(url, sizeof(url), "http://%s", StringBuff(opt->why_url));
else
strcpybuff(url, StringBuff(opt->why_url));
if (ident_url_absolute(url, &af) < 0) {
printf("--why: unable to parse URL %s" LF, StringBuff(opt->why_url));
} else {
char BIGSTK l[HTS_URLMAXSIZE * 2], lfull[HTS_URLMAXSIZE * 2];
int jok, jokDepth = 0;
/* the two forms the wizard tests */
strcpybuff(l, jump_identification_const(af.adr));
if (*af.fil != '/')
strcatbuff(l, "/");
strcatbuff(l, af.fil);
if (!link_has_authority(af.adr))
strcpybuff(lfull, "http://");
else
lfull[0] = '\0';
strcatbuff(lfull, af.adr);
if (*af.fil != '/')
strcatbuff(lfull, "/");
strcatbuff(lfull, af.fil);
jok = fa_strjoker_dual(/*url */ 0, filters, filptr, lfull, l, NULL,
NULL, &jokDepth);
if (jok > 0)
printf("%s: accepted by rule #%d (%s)" LF, url, jokDepth + 1,
filters[jokDepth]);
else if (jok < 0)
printf("%s: rejected by rule #%d (%s)" LF, url, jokDepth + 1,
filters[jokDepth]);
else
printf("%s: no filter rule matches; the wizard decides "
"(same-site links are followed by default)" LF,
url);
}
freet(primary);
XH_extuninit;
return 1;
}
/* load URL file list */ /* load URL file list */
/* OPTIMIZED for fast load */ /* OPTIMIZED for fast load */
if (StringNotEmpty(opt->filelist)) { if (StringNotEmpty(opt->filelist)) {
@@ -1905,10 +1902,7 @@ int httpmirror(char *url1, httrackp * opt) {
} }
// ATTENTION C'EST ICI QU'ON SAUVE LE FICHIER!! // ATTENTION C'EST ICI QU'ON SAUVE LE FICHIER!!
// An empty body must not overwrite the file when the transfer failed if (r.adr != NULL || r.size == 0) {
// (statuscode <= 0, e.g. an -M hard-stop): it would truncate a good
// copy to 0 (#77 follow-up).
if (r.adr != NULL || (r.size == 0 && r.statuscode > 0)) {
file_notify(opt, urladr(), urlfil(), savename(), 1, 1, r.notmodified); file_notify(opt, urladr(), urlfil(), savename(), 1, 1, r.notmodified);
if (filesave(opt, r.adr, (int) r.size, savename(), urladr(), urlfil()) != if (filesave(opt, r.adr, (int) r.size, savename(), urladr(), urlfil()) !=
0) { 0) {
@@ -1929,6 +1923,7 @@ int httpmirror(char *url1, httrackp * opt) {
*/ */
} }
} }
} }
/* Parsing of other media types (java, ram..) */ /* Parsing of other media types (java, ram..) */

View File

@@ -193,7 +193,8 @@ struct cache_back {
/* */ /* */
int type; int type;
int ro; /**< read-only: no new cache is written */ int ro; /**< read-only: no new cache is written */
FILE *dat, *ndx, *olddat; /**< new data, new index, old data files */
char *use; /**< in-memory list of cached adr+fil keys */
FILE *lst; /**< file list, used for purge */ FILE *lst; /**< file list, used for purge */
FILE *txt; /**< human-readable file list (info) */ FILE *txt; /**< human-readable file list (info) */
char lastmodified[256]; char lastmodified[256];
@@ -287,12 +288,12 @@ struct filecreate_params {
/* True if a new cache is being written (plain or zip backend). */ /* True if a new cache is being written (plain or zip backend). */
HTS_STATIC int cache_writable(cache_back * cache) { HTS_STATIC int cache_writable(cache_back * cache) {
return (cache != NULL && cache->zipOutput != NULL); return (cache != NULL && (cache->dat != NULL || cache->zipOutput != NULL));
} }
/* True if an old cache is available to read (plain or zip backend). */ /* True if an old cache is available to read (plain or zip backend). */
HTS_STATIC int cache_readable(cache_back * cache) { HTS_STATIC int cache_readable(cache_back * cache) {
return (cache != NULL && cache->zipInput != NULL); return (cache != NULL && (cache->olddat != NULL || cache->zipInput != NULL));
} }
#endif #endif

View File

@@ -1803,23 +1803,6 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
StringCopy(opt->cookies_file, argv[na]); StringCopy(opt->cookies_file, argv[na]);
} }
break; break;
case 'Y': // why: explain the filter verdict for a URL, no crawl
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
HTS_PANIC_PRINTF("Option why needs a blank space and a URL");
printf(
"Example: --why \"http://www.example.com/file.zip\"\n");
htsmain_free();
return -1;
} else {
na++;
if (strlen(argv[na]) >= HTS_URLMAXSIZE) {
HTS_PANIC_PRINTF("why URL too long");
htsmain_free();
return -1;
}
StringCopy(opt->why_url, argv[na]);
}
break;
case 'G': // pause: randomized inter-file delay MIN[:MAX] seconds case 'G': // pause: randomized inter-file delay MIN[:MAX] seconds
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) { if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
HTS_PANIC_PRINTF("Option pause needs a blank space and a " HTS_PANIC_PRINTF("Option pause needs a blank space and a "
@@ -1953,20 +1936,18 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
char BIGSTK url[HTS_URLMAXSIZE * 2]; char BIGSTK url[HTS_URLMAXSIZE * 2];
char linepos[256]; char linepos[256];
int pos; int pos;
LLint cacheNdxLen = 0; char *cacheNdx =
char *cacheNdx = readfile2( readfile(fconcat
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), (OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
StringBuff(opt->path_log), "hts-cache/new.ndx"), "hts-cache/new.ndx"));
&cacheNdxLen);
cache_init(&cache, opt); /* load cache */ cache_init(&cache, opt); /* load cache */
if (cacheNdx != NULL) { if (cacheNdx != NULL) {
char firstline[256]; char firstline[256];
char *a = cacheNdx; char *a = cacheNdx;
const char *const end = cacheNdx + cacheNdxLen;
a += cache_brstr(a, firstline, sizeof(firstline)); a += cache_brstr(a, firstline, sizeof(firstline));
a += cache_brstr(a, firstline, sizeof(firstline)); a += cache_brstr(a, firstline, sizeof(firstline));
while (a != NULL && a < end) { while(a != NULL) {
a = strchr(a + 1, '\n'); /* start of line */ a = strchr(a + 1, '\n'); /* start of line */
if (a) { if (a) {
htsblk r; htsblk r;
@@ -1974,15 +1955,15 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
/* */ /* */
a++; a++;
/* read "host/file" */ /* read "host/file" */
a += cache_binput(a, end, afs.af.adr, HTS_URLMAXSIZE); a += binput(a, afs.af.adr, HTS_URLMAXSIZE);
a += cache_binput(a, end, afs.af.fil, HTS_URLMAXSIZE); a += binput(a, afs.af.fil, HTS_URLMAXSIZE);
url[0] = '\0'; url[0] = '\0';
if (!link_has_authority(afs.af.adr)) if (!link_has_authority(afs.af.adr))
strcatbuff(url, "http://"); strcatbuff(url, "http://");
strcatbuff(url, afs.af.adr); strcatbuff(url, afs.af.adr);
strcatbuff(url, afs.af.fil); strcatbuff(url, afs.af.fil);
/* read position */ /* read position */
a += cache_binput(a, end, linepos, 200); a += binput(a, linepos, 200);
sscanf(linepos, "%d", &pos); sscanf(linepos, "%d", &pos);
if (!hasFilter if (!hasFilter
|| (strjoker(url, filter, NULL, NULL) != NULL) || (strjoker(url, filter, NULL, NULL) != NULL)

View File

@@ -297,26 +297,6 @@ int dns_selftests(httrackp *opt) {
IPV6_resolver = 0; IPV6_resolver = 0;
} }
/* "host:port" resolves as the bare host: the cache/connect path strips
":port" before both the backend and the cache key (#181). */
mock_reset_calls();
{
SOCaddr a;
char ip[64];
const char *err = NULL;
/* cache miss: the backend is hit with the stripped host, not "host:port" */
CHECK(hts_dns_resolve2(opt, "v6only.test:8080", &a, &err) != NULL);
CHECK(mock_find("v6only.test")->calls == 1);
/* the bare host shares that one cache entry (stripped key) */
CHECK(hts_dns_resolve2(opt, "v6only.test", &a, &err) != NULL);
CHECK(mock_find("v6only.test")->calls == 1);
/* a port variant of an already-cached host also hits, right address */
CHECK(hts_dns_resolve2(opt, "v4only.test:1234", &a, &err) != NULL);
SOCaddr_inetntoa(ip, sizeof(ip), a);
CHECK(strcmp(ip, "1.2.3.4") == 0);
}
/* newhttp_addr() must connect to the addr_index-th address, not always the /* newhttp_addr() must connect to the addr_index-th address, not always the
first: this is what back_connect_next relies on to reach the fallback. */ first: this is what back_connect_next relies on to reach the fallback. */
{ {

View File

@@ -48,7 +48,6 @@ Please visit our Website: http://www.httrack.com
#include "htsbase.h" #include "htsbase.h"
#include "htslib.h" #include "htslib.h"
#include <ctype.h> #include <ctype.h>
#include <stdint.h>
/* END specific definitions */ /* END specific definitions */
// à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé // à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé
@@ -95,107 +94,16 @@ int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * siz
return verdict; return verdict;
} }
int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1, // supercomparateur joker (tm)
const char *nom2, LLint *size, int *size_flag, // compare a et b (b=avec joker dedans), case insensitive [voir CI]
int *depth) { // renvoi l'adresse de la première lettre de la chaine
int depth1 = 0, depth2 = 0; // (càd *[..]toto.. renvoi adresse de toto dans la chaine)
int flag1 = 0, flag2 = 0; // accepte les délires du genre www.*.*/ * / * truc*.*
LLint sz1 = 0, sz2 = 0; // cet algo est 'un peu' récursif mais ne consomme pas trop de tm
int jok1, jok2, use1; // * = toute lettre
// --?-- : spécifique à HTTrack et aux ?
if (size) {
sz1 = *size;
sz2 = *size;
}
jok1 = fa_strjoker(type, filters, nfil, nom1, size ? &sz1 : NULL,
size_flag ? &flag1 : NULL, &depth1);
jok2 = fa_strjoker(type, filters, nfil, nom2, size ? &sz2 : NULL,
size_flag ? &flag2 : NULL, &depth2);
use1 = jok2 == 0 || (jok1 != 0 && depth1 >= depth2);
if (size)
*size = use1 ? sz1 : sz2;
if (size_flag)
*size_flag = use1 ? flag1 : flag2;
if (depth)
*depth = use1 ? depth1 : depth2;
return use1 ? jok1 : jok2;
}
/* 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 */
} strjoker_memo;
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size,
int *size_flag);
/* 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) {
size_t bit = 0;
const char *adr;
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)
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, HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
LLint *size, int *size_flag) { LLint *size, int *size_flag) {
strjoker_memo memo = {chaine, joker, 0, NULL};
unsigned char stackbits[2048];
hts_boolean onheap = HTS_FALSE;
const char *adr;
if (chaine != NULL && joker != NULL) {
const size_t n1 = strlen(chaine) + 1;
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 (bytes <= sizeof(stackbits)) {
memset(stackbits, 0, bytes);
memo.failed = stackbits;
} else {
memo.failed = (unsigned char *) calloct(bytes, 1);
onheap = memo.failed != NULL ? HTS_TRUE : HTS_FALSE;
}
}
}
adr = strjoker_rec(&memo, chaine, joker, size, size_flag);
if (onheap)
freet(memo.failed);
return adr;
}
/* 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};
return strjoker_rec(&memo, chaine, joker, size, size_flag);
}
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
const char *joker, LLint *size,
int *size_flag) {
if (strnotempty(joker) == 0) { // fin de chaine joker if (strnotempty(joker) == 0) { // fin de chaine joker
if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok
return chaine; return chaine;
@@ -369,7 +277,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// tester sans le joker (pas ()+ mais ()*) // tester sans le joker (pas ()+ mais ()*)
if (!unique) { if (!unique) {
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag))) { if ((adr = strjoker(chaine, joker + jmp, size, size_flag))) {
return adr; return adr;
} }
} }
@@ -378,11 +286,10 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
if (!unique) if (!unique)
max = (int) strlen(chaine); max = (int) strlen(chaine);
else /* *(a) only match a (not aaaaa) */ else /* *(a) only match a (not aaaaa) */
max = strnotempty(chaine) ? 1 : 0; /* empty chaine: no char to eat */ max = 1;
while(i < (int) max) { while(i < (int) max) {
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
if ((adr = strjoker_rec(memo, chaine + i + 1, joker + jmp, size, if ((adr = strjoker(chaine + i + 1, joker + jmp, size, size_flag))) {
size_flag))) {
return adr; return adr;
} }
i++; i++;
@@ -392,8 +299,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// tester chaîne vide // tester chaîne vide
if (i != max + 2) // avant c'est ok if (i != max + 2) // avant c'est ok
if ((adr = strjoker_rec(memo, chaine + max, joker + jmp, size, if ((adr = strjoker(chaine + max, joker + jmp, size, size_flag)))
size_flag)))
return adr; return adr;
return NULL; // perdu return NULL; // perdu
@@ -415,7 +321,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
// comparaison ok? // comparaison ok?
if (ok) { if (ok) {
// continuer la comparaison. // continuer la comparaison.
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag)) if (strjoker(chaine + jmp, joker + jmp, size, size_flag))
return chaine; // retourner 1e lettre return chaine; // retourner 1e lettre
} }

View File

@@ -41,17 +41,8 @@ Please visit our Website: http://www.httrack.com
int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * size, int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * size,
int *size_flag, int *depth); int *size_flag, int *depth);
/* fa_strjoker() on both URL forms the engine builds (nom1 full, nom2 without
scheme); the match latest in the list wins, a "don't know" verdict defers.
Returns the merged verdict; the out-params carry the winner's values. */
int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
const char *nom2, LLint *size, int *size_flag, int *depth);
HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * size, HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * size,
int *size_flag); int *size_flag);
/* strjoker() without the failure memo (exponential worst case); test-only
oracle for the memoized matcher. */
const char *strjoker_nomemo(const char *chaine, const char *joker, LLint *size,
int *size_flag);
const char *strjokerfind(const char *chaine, const char *joker); const char *strjokerfind(const char *chaine, const char *joker);
#endif #endif

View File

@@ -43,8 +43,8 @@ Please visit our Website: http://www.httrack.com
configure.ac, decoupled from these). VERSION is the display form, VERSIONID configure.ac, decoupled from these). VERSION is the display form, VERSIONID
the dotted numeric form, AFF_VERSION the short form shown in footers, the dotted numeric form, AFF_VERSION the short form shown in footers,
LIB_VERSION the data/cache format generation. */ LIB_VERSION the data/cache format generation. */
#define HTTRACK_VERSION "3.49-12" #define HTTRACK_VERSION "3.49-11"
#define HTTRACK_VERSIONID "3.49.12" #define HTTRACK_VERSIONID "3.49.11"
#define HTTRACK_AFF_VERSION "3.x" #define HTTRACK_AFF_VERSION "3.x"
#define HTTRACK_LIB_VERSION "2.0" #define HTTRACK_LIB_VERSION "2.0"
@@ -56,10 +56,6 @@ Please visit our Website: http://www.httrack.com
// Platform detection (sizes, feature macros) // Platform detection (sizes, feature macros)
#include "htsconfig.h" #include "htsconfig.h"
// Fixed-width integer types + PRI* format macros for the LLint/TStamp typedefs
#include <stdint.h>
#include <inttypes.h>
// WIN32 types // WIN32 types
#ifdef _WIN32 #ifdef _WIN32
#ifndef SIZEOF_LONG #ifndef SIZEOF_LONG
@@ -115,6 +111,29 @@ Please visit our Website: http://www.httrack.com
#define HTS_DO_NOT_USE_UID #define HTS_DO_NOT_USE_UID
#endif #endif
#ifndef HTS_LONGLONG
#ifdef SIZEOF_LONG_LONG
#if SIZEOF_LONG_LONG == 8
#define HTS_LONGLONG 1
#endif
#endif
#ifndef HTS_LONGLONG
#ifdef __sun
#define HTS_LONGLONG 0
#endif
#ifdef __osf__
#define HTS_LONGLONG 0
#endif
#ifdef __linux
#define HTS_LONGLONG 1
#endif
#ifdef _WIN32
#define HTS_LONGLONG 1
#endif
#endif
#endif
#ifdef DLLIB #ifdef DLLIB
#define HTS_DLOPEN 1 #define HTS_DLOPEN 1
#else #else
@@ -307,11 +326,59 @@ typedef int hts_tristate;
#define HTS_DEPRECATED(msg) #define HTS_DEPRECATED(msg)
#endif #endif
/* LLint/TStamp: signed exact-width 64-bit; -1 is a sentinel engine-wide. */ #ifndef HTS_LONGLONG
typedef int64_t LLint; #ifdef HTS_NO_64_BIT
typedef int64_t TStamp; #define HTS_LONGLONG 0
/* Full printf conversion, '%' included (PRId64 has none): "X: " LLintP. */ #else
#define LLintP "%" PRId64 #define HTS_LONGLONG 1
#endif
#endif
/* Wide integer types, chosen per platform.
LLint: signed 64-bit counter for byte counts and large sizes (falls back to
plain int where 64-bit is unavailable).
TStamp: timestamp/duration in the same width (a double in the no-64-bit
fallback).
LLintP: the printf conversion for an LLint. */
#if HTS_LONGLONG
#ifdef LLINT_FORMAT
typedef LLINT_TYPE LLint;
typedef LLINT_TYPE TStamp;
#define LLintP LLINT_FORMAT
#else
#ifdef _WIN32
typedef __int64 LLint;
typedef __int64 TStamp;
#define LLintP "%I64d"
#elif (defined(_LP64) || defined(__x86_64__) || defined(__powerpc64__) || \
defined(__64BIT__))
typedef long int LLint;
typedef long int TStamp;
#define LLintP "%ld"
#else
typedef long long int LLint;
typedef long long int TStamp;
#define LLintP "%lld"
#endif
#endif /* HTS_LONGLONG */
#else
typedef int LLint;
#define LLintP "%d"
typedef double TStamp;
#endif
/* Integer type for file offsets/sizes passed to the C library. Widens to /* 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 LLint (with HTS_FSEEKO for fseeko/ftello) under large-file support, plain
@@ -355,10 +422,9 @@ typedef int T_SOC;
#endif #endif
/* Permission bits for created folders and files (mkdir and chmod). /* Permission bits for created folders and files (mkdir and chmod).
PROTECT_FOLDER/FILE are owner-only. With HTS_ACCESS set (the default) the PROTECT_FOLDER is owner-only. With HTS_ACCESS set (the default) the ACCESS_
ACCESS_ modes also grant group/other read; otherwise they stay owner-only. */ modes also grant group/other read; otherwise they stay owner-only. */
#define HTS_PROTECT_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR) #define HTS_PROTECT_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR)
#define HTS_PROTECT_FILE (S_IRUSR | S_IWUSR)
#if HTS_ACCESS #if HTS_ACCESS
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

View File

@@ -543,7 +543,6 @@ void help(const char *app, int more) {
infomsg("Spider options:"); infomsg("Spider options:");
infomsg(" bN accept cookies in cookies.txt (0=do not accept,* 1=accept)"); infomsg(" bN accept cookies in cookies.txt (0=do not accept,* 1=accept)");
infomsg(" %K load extra cookies from a Netscape cookies.txt"); infomsg(" %K load extra cookies from a Netscape cookies.txt");
infomsg(" %Y explain which filter rule accepts or rejects a URL, then exit");
infomsg infomsg
(" u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)"); (" u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)");
infomsg infomsg

View File

@@ -1562,7 +1562,7 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
} }
// An empty/whitespace Content-Type value yields no token: keep the // An empty/whitespace Content-Type value yields no token: keep the
// sentinel default rather than reading an uninitialized tempo. // sentinel default rather than reading an uninitialized tempo.
if (sscanf(rcvd + p, "%1099s", tempo) == 1) { // tempo[1100], server data if (sscanf(rcvd + p, "%s", tempo) == 1) {
if (strlen(tempo) < sizeof(retour->contenttype) - 2) // pas trop long!! if (strlen(tempo) < sizeof(retour->contenttype) - 2) // pas trop long!!
strcpybuff(retour->contenttype, tempo); strcpybuff(retour->contenttype, tempo);
else else
@@ -1657,11 +1657,11 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
if (a) if (a)
*a = '\0'; *a = '\0';
} }
// Bound to tempo[1100]; no token => leave empty, don't read uninit tempo. sscanf(a, "%s", tempo);
if (sscanf(a, "%1099s", tempo) == 1 && strlen(tempo) < 64) if (strlen(tempo) < 64) // pas trop long!!
strcpybuff(retour->contentencoding, tempo); strcpybuff(retour->contentencoding, tempo);
else else
retour->contentencoding[0] = '\0'; retour->contentencoding[0] = '\0'; // erreur
#if HTS_USEZLIB #if HTS_USEZLIB
/* Check known encodings */ /* Check known encodings */
if (retour->contentencoding[0]) { if (retour->contentencoding[0]) {
@@ -2497,10 +2497,6 @@ int ident_url_absolute(const char *url, lien_adrfil *adrfil) {
// effacer adrfil->adr et adrfil->fil // effacer adrfil->adr et adrfil->fil
adrfil->adr[0] = adrfil->fil[0] = '\0'; adrfil->adr[0] = adrfil->fil[0] = '\0';
// Reject an over-long URL: a root-relative path is copied whole into fil[].
if (strlen(url) >= HTS_URLMAXSIZE * 2)
return -1;
#if HDEBUG #if HDEBUG
printf("protocol: %s\n", url); printf("protocol: %s\n", url);
#endif #endif
@@ -2576,7 +2572,7 @@ int ident_url_absolute(const char *url, lien_adrfil *adrfil) {
if (*p == '/' || *p == '\\') { /* adrfil->file:///.. */ if (*p == '/' || *p == '\\') { /* adrfil->file:///.. */
strcatbuff(adrfil->fil, p); // fichier local ; adrfil->adr="#" strcatbuff(adrfil->fil, p); // fichier local ; adrfil->adr="#"
} else { } else {
if (*p == '\0' || p[1] != ':') { /* empty path: not a DOS drive letter */ if (p[1] != ':') {
strcatbuff(adrfil->fil, "//"); /* adrfil->file://server/foo */ strcatbuff(adrfil->fil, "//"); /* adrfil->file://server/foo */
strcatbuff(adrfil->fil, p); strcatbuff(adrfil->fil, p);
} else { } else {
@@ -3038,11 +3034,12 @@ HTSEXT_API char *int2char(strc_int2bytes2 * strc, int n) {
/* See http://physics.nist.gov/cuu/Units/binary.html */ /* See http://physics.nist.gov/cuu/Units/binary.html */
#define ToLLint(a) ((LLint)(a)) #define ToLLint(a) ((LLint)(a))
#define ToLLintKiB (ToLLint(1024)) #define ToLLintKiB (ToLLint(1024))
#define ToLLintMiB (ToLLintKiB * ToLLintKiB) #define ToLLintMiB (ToLLintKiB*ToLLintKiB)
#define ToLLintGiB (ToLLintKiB * ToLLintKiB * ToLLintKiB) #ifdef HTS_LONGLONG
#define ToLLintTiB (ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB) #define ToLLintGiB (ToLLintKiB*ToLLintKiB*ToLLintKiB)
#define ToLLintPiB \ #define ToLLintTiB (ToLLintKiB*ToLLintKiB*ToLLintKiB*ToLLintKiB)
(ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB * ToLLintKiB) #define ToLLintPiB (ToLLintKiB*ToLLintKiB*ToLLintKiB*ToLLintKiB*ToLLintKiB)
#endif
HTSEXT_API char **int2bytes2(strc_int2bytes2 * strc, LLint n) { HTSEXT_API char **int2bytes2(strc_int2bytes2 * strc, LLint n) {
if (n < ToLLintKiB) { if (n < ToLLintKiB) {
sprintf(strc->buff1, "%d", (int) (LLint) n); sprintf(strc->buff1, "%d", (int) (LLint) n);
@@ -3051,7 +3048,9 @@ HTSEXT_API char **int2bytes2(strc_int2bytes2 * strc, LLint n) {
sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / ToLLintKiB)), sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / ToLLintKiB)),
(int) ((LLint) ((n % ToLLintKiB) * 100) / ToLLintKiB)); (int) ((LLint) ((n % ToLLintKiB) * 100) / ToLLintKiB));
strcpybuff(strc->buff2, "KiB"); strcpybuff(strc->buff2, "KiB");
} else if (n < ToLLintGiB) { }
#ifdef HTS_LONGLONG
else if (n < ToLLintGiB) {
sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintMiB))), sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintMiB))),
(int) ((LLint) (((n % (ToLLintMiB)) * 100) / (ToLLintMiB)))); (int) ((LLint) (((n % (ToLLintMiB)) * 100) / (ToLLintMiB))));
strcpybuff(strc->buff2, "MiB"); strcpybuff(strc->buff2, "MiB");
@@ -3068,6 +3067,13 @@ HTSEXT_API char **int2bytes2(strc_int2bytes2 * strc, LLint n) {
(int) ((LLint) (((n % (ToLLintPiB)) * 100) / (ToLLintPiB)))); (int) ((LLint) (((n % (ToLLintPiB)) * 100) / (ToLLintPiB))));
strcpybuff(strc->buff2, "PiB"); strcpybuff(strc->buff2, "PiB");
} }
#else
else {
sprintf(strc->buff1, "%d,%02d", (int) ((LLint) (n / (ToLLintMiB))),
(int) ((LLint) (((n % (ToLLintMiB)) * 100) / (ToLLintMiB))));
strcpybuff(strc->buff2, "MiB");
}
#endif
strc->buffadr[0] = strc->buff1; strc->buffadr[0] = strc->buff1;
strc->buffadr[1] = strc->buff2; strc->buffadr[1] = strc->buff2;
return strc->buffadr; return strc->buffadr;
@@ -5996,7 +6002,6 @@ HTSEXT_API httrackp *hts_create_opt(void) {
StringCopy(opt->footer, HTS_DEFAULT_FOOTER); StringCopy(opt->footer, HTS_DEFAULT_FOOTER);
StringCopy(opt->strip_query, ""); StringCopy(opt->strip_query, "");
StringCopy(opt->cookies_file, ""); StringCopy(opt->cookies_file, "");
StringCopy(opt->why_url, "");
opt->pause_min_ms = 0; opt->pause_min_ms = 0;
opt->pause_max_ms = 0; opt->pause_max_ms = 0;
opt->ftp_proxy = HTS_TRUE; opt->ftp_proxy = HTS_TRUE;
@@ -6144,7 +6149,6 @@ HTSEXT_API void hts_free_opt(httrackp * opt) {
StringFree(opt->mod_blacklist); StringFree(opt->mod_blacklist);
StringFree(opt->strip_query); StringFree(opt->strip_query);
StringFree(opt->cookies_file); StringFree(opt->cookies_file);
StringFree(opt->why_url);
StringFree(opt->path_html); StringFree(opt->path_html);
StringFree(opt->path_html_utf8); StringFree(opt->path_html_utf8);

View File

@@ -536,8 +536,6 @@ struct httrackp {
(--cookies-file) */ (--cookies-file) */
int pause_min_ms; /**< inter-file pause lower bound, ms (0=off, #185) */ int pause_min_ms; /**< inter-file pause lower bound, ms (0=off, #185) */
int pause_max_ms; /**< inter-file pause upper bound, ms */ int pause_max_ms; /**< inter-file pause upper bound, ms */
String why_url; /**< URL to diagnose (--why): print the deciding filter rule
and exit without crawling */
}; };
/* Running statistics for a mirror. */ /* Running statistics for a mirror. */

View File

@@ -2007,7 +2007,7 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
/* Unescape/escape %20 and other &nbsp; */ /* Unescape/escape %20 and other &nbsp; */
{ {
// NULL when UTF-8 conversion is off (-%T0) // Note: always true (iso-8859-1 as default)
const char *const charset = str->page_charset_; const char *const charset = str->page_charset_;
const int hasCharset = charset != NULL const int hasCharset = charset != NULL
&& *charset != '\0'; && *charset != '\0';
@@ -2031,11 +2031,10 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
// Force to encode non-printable chars (should never happend) // Force to encode non-printable chars (should never happend)
escape_remove_control(lien); escape_remove_control(lien);
// charset conversion for the URI filename (not the query // charset conversion for the URI filename,
// string), unless the bytes already are valid UTF-8: // and not already UTF-8
// converting those would double-encode them (#180) // (note: not for the query string!)
if (hasCharset && !hts_isCharsetUTF8(charset) && if (hasCharset && !hts_isCharsetUTF8(charset)) {
!hts_isStringUTF8(lien, strlen(lien))) {
char *const s = hts_convertStringToUTF8(lien, strlen(lien), charset); char *const s = hts_convertStringToUTF8(lien, strlen(lien), charset);
if (s != NULL) { if (s != NULL) {
hts_log_print(opt, LOG_DEBUG, hts_log_print(opt, LOG_DEBUG,

View File

@@ -46,7 +46,6 @@ Please visit our Website: http://www.httrack.com
#include "htsdefines.h" #include "htsdefines.h"
#include "htslib.h" #include "htslib.h"
#include "htsparse.h" #include "htsparse.h"
#include "htscache.h"
#include "htscache_selftest.h" #include "htscache_selftest.h"
#include "htsdns_selftest.h" #include "htsdns_selftest.h"
#include "htscharset.h" #include "htscharset.h"
@@ -580,16 +579,15 @@ static int st_filter(httrackp *opt, int argc, char **argv) {
int matched; int matched;
(void) opt; (void) opt;
if (argc < 1) { if (argc < 2) {
fprintf(stderr, "filter: needs a filter pattern and a string\n"); fprintf(stderr, "filter: needs a filter pattern and a string\n");
return 1; return 1;
} }
/* exact-size heap copies so a sanitizer traps an over-read; a missing /* exact-size heap copies so a sanitizer traps any over-read of the pattern */
subject means "" (not reachable as a CLI arg) */ str = strdupt(argv[1]);
str = strdupt(argc >= 2 ? argv[1] : "");
pat = strdupt(argv[0]); pat = strdupt(argv[0]);
matched = strjoker(str, pat, NULL, NULL) != NULL; matched = strjoker(str, pat, NULL, NULL) != NULL;
printf("%s does %s %s\n", str, matched ? "match" : "NOT match", argv[0]); printf("%s does %s %s\n", argv[1], matched ? "match" : "NOT match", argv[0]);
freet(str); freet(str);
freet(pat); freet(pat);
return 0; return 0;
@@ -621,125 +619,6 @@ static int st_filtersize(httrackp *opt, int argc, char **argv) {
return 0; return 0;
} }
/* Mime-type filter verdict via fa_strjoker(type=1): only mime: rules apply. */
static int st_filtermime(httrackp *opt, int argc, char **argv) {
int verdict;
(void) opt;
if (argc < 2) {
fprintf(stderr, "filtermime: needs <mime> <filter> [filter...]\n");
return 1;
}
verdict = fa_strjoker(1, &argv[1], argc - 1, argv[0], NULL, NULL, NULL);
printf("verdict=%s\n", verdict > 0 ? "allowed"
: verdict < 0 ? "forbidden"
: "unknown");
return 0;
}
/* SplitMix64: deterministic, platform-independent case generator. */
static uint64_t st_mix64(uint64_t *state) {
uint64_t z = (*state += UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4B5B9);
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
return z ^ (z >> 31);
}
/* Differential test: memoized strjoker vs the no-memo oracle on seeded random
pattern/subject/size cases must agree on result, *size and *size_flag. */
static int st_filtermemo(httrackp *opt, int argc, char **argv) {
static const char *const pieces[] = {
"a", "b", "A", "c", ".", "/",
"?", "*", "*[a]", "*[ab]", "*[a-c]", "*[A-Z]",
"*[<5]", "*[>5]", "*(a)", "*(a,b)", "*[file]", "*[path]",
"*[name]", "*[param]", "*[]", "*[\\a]", "*[a-"};
static const char subject_chars[] = "abAc./?";
uint64_t rng = UINT64_C(0x501);
int iters = 20000, matched = 0, unmatched = 0;
int it;
(void) opt;
if (argc >= 1)
sscanf(argv[0], "%d", &iters);
for (it = 0; it < iters; it++) {
char pat[64], str[16];
size_t pl = 0;
const int npieces = (int) (st_mix64(&rng) % 5);
const int slen = (int) (st_mix64(&rng) % 13);
const int szsel = (int) (st_mix64(&rng) % 4); /* none/unknown/3/20 */
LLint sz1, sz2, off1, off2;
int i, flag1 = 0, flag2 = 0;
char *hpat, *hstr;
const char *adr;
for (i = 0; i < npieces; i++) {
const char *p =
pieces[st_mix64(&rng) % (sizeof(pieces) / sizeof(pieces[0]))];
const size_t len = strlen(p);
if (len >= sizeof(pat) - pl)
break;
memcpy(pat + pl, p, len);
pl += len;
}
pat[pl] = '\0';
for (i = 0; i < slen; i++)
str[i] = subject_chars[st_mix64(&rng) % (sizeof(subject_chars) - 1)];
str[slen] = '\0';
sz1 = sz2 = (szsel <= 1) ? -1 : (szsel == 2) ? 3 : 20;
/* exact-size heap copies per run so a sanitizer traps any over-read */
hpat = strdupt(pat);
hstr = strdupt(str);
adr = strjoker(hstr, hpat, szsel ? &sz1 : NULL, szsel ? &flag1 : NULL);
off1 = adr != NULL ? (LLint) (adr - hstr) : -1;
freet(hpat);
freet(hstr);
hpat = strdupt(pat);
hstr = strdupt(str);
adr =
strjoker_nomemo(hstr, hpat, szsel ? &sz2 : NULL, szsel ? &flag2 : NULL);
off2 = adr != NULL ? (LLint) (adr - hstr) : -1;
freet(hpat);
freet(hstr);
if (off1 != off2 || sz1 != sz2 || flag1 != flag2) {
printf("filtermemo MISMATCH pat=[%s] str=[%s] szsel=%d: "
"off %d/%d size %d/%d flag %d/%d\n",
pat, str, szsel, (int) off1, (int) off2, (int) sz1, (int) sz2,
flag1, flag2);
return 1;
}
if (off1 >= 0)
matched++;
else
unmatched++;
}
/* both polarities must actually occur or the test proves nothing */
assertf(matched > 0 && unmatched > 0);
printf("filtermemo: %d cases OK\n", iters);
return 0;
}
/* Merged two-form filter verdict via fa_strjoker_dual (see htsfilters.h). */
static int st_filterdual(httrackp *opt, int argc, char **argv) {
int depth = -1, verdict;
(void) opt;
if (argc < 3) {
fprintf(stderr,
"filterdual: needs <string1> <string2> <filter> [filter...]\n");
return 1;
}
verdict = fa_strjoker_dual(0, &argv[2], argc - 2, argv[0], argv[1], NULL,
NULL, &depth);
printf("verdict=%s rule=%d\n",
verdict > 0 ? "allowed"
: verdict < 0 ? "forbidden"
: "unknown",
depth);
return 0;
}
static int st_simplify(httrackp *opt, int argc, char **argv) { static int st_simplify(httrackp *opt, int argc, char **argv) {
(void) opt; (void) opt;
if (argc < 1) { if (argc < 1) {
@@ -788,30 +667,6 @@ static int st_charset(httrackp *opt, int argc, char **argv) {
return 0; return 0;
} }
static int st_metacharset(httrackp *opt, int argc, char **argv) {
char *s;
(void) opt;
if (argc < 1) {
fprintf(stderr, "metacharset: needs an html string\n");
return 1;
}
s = hts_getCharsetFromMeta(argv[0], strlen(argv[0]));
printf("%s\n", s != NULL ? s : "(none)");
freet(s);
return 0;
}
static int st_isutf8(httrackp *opt, int argc, char **argv) {
(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);
return 0;
}
static int st_idna_encode(httrackp *opt, int argc, char **argv) { static int st_idna_encode(httrackp *opt, int argc, char **argv) {
char *s; char *s;
@@ -1239,33 +1094,6 @@ static int st_resolve(httrackp *opt, int argc, char **argv) {
return 0; return 0;
} }
/* Split a URL into (adr, fil), or print "error" if rejected. A second arg pads
the URL with that many 'a's to reach lengths a CLI arg can't. */
static int st_identurl(httrackp *opt, int argc, char **argv) {
lien_adrfil af;
char *url;
size_t len, pad = 0;
(void) opt;
if (argc < 1) {
fprintf(stderr, "identurl: needs a URL\n");
return 1;
}
if (argc >= 2)
pad = (size_t) atoi(argv[1]);
len = strlen(argv[0]);
url = malloct(len + pad + 1);
memcpy(url, argv[0], len);
memset(url + len, 'a', pad);
url[len + pad] = '\0';
if (ident_url_absolute(url, &af) >= 0)
printf("adr=%s fil=%s\n", af.adr, af.fil);
else
printf("error\n");
freet(url);
return 0;
}
/* Extra args are key=value: adr= cdispo= statuscode= status= strip= urlhack= /* 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 no-www= no-slash= no-query= n83= type=, plus repeatable prior=adr|fil|sav
registering an already-crawled link (dedup/collision paths). */ registering an already-crawled link (dedup/collision paths). */
@@ -1287,26 +1115,6 @@ static int st_header(httrackp *opt, int argc, char **argv) {
treathead(NULL, "www.example.com", "/", &r, line); treathead(NULL, "www.example.com", "/", &r, line);
} }
printf("contenttype=%s cdispo=%s\n", r.contenttype, r.cdispo); printf("contenttype=%s cdispo=%s\n", r.contenttype, r.cdispo);
printf("contentencoding=%s\n", r.contentencoding);
return 0;
}
/* An over-long header value must not overflow treathead's tempo[1100]. */
static int st_headerlong(httrackp *opt, int argc, char **argv) {
htsblk r;
char BIGSTK line[HTS_URLMAXSIZE * 2];
const char *const name = argc >= 1 ? argv[0] : "Content-Type:";
const int pad = 1500; /* > tempo[1100] */
size_t n;
(void) opt;
memset(&r, 0, sizeof(r));
n = (size_t) snprintf(line, sizeof(line), "%s ", name);
memset(line + n, 'a', pad);
line[n + pad] = '\0';
treathead(NULL, "www.example.com", "/", &r, line);
printf("contenttype_len=%d contentencoding_len=%d\n",
(int) strlen(r.contenttype), (int) strlen(r.contentencoding));
return 0; return 0;
} }
@@ -1514,78 +1322,6 @@ static int st_cache(httrackp *opt, int argc, char **argv) {
return err; return err;
} }
/* A corrupt cache index (.ndx) must not walk the length-prefixed scan past
the buffer. Checks the two primitives the loader is built from. */
static int st_cacheindex(httrackp *opt, int argc, char **argv) {
int fail = 0;
(void) opt;
(void) argc;
(void) argv;
/* A length prefix that overstates the bytes present must bound the advance
to the buffer, not trust the declared length. */
{
static const char src[] = "32768\nCACHE-1.1";
const size_t len = sizeof(src) - 1;
char *buf = malloct(len + 1);
char s[256];
int off;
memcpy(buf, src, len + 1);
off = cache_brstr(buf, s, sizeof(s));
if (off > (int) len) {
printf("cacheindex: over-advance off=%d len=%d\n", off, (int) len);
fail = 1;
}
if (strcmp(s, "CACHE-1.1") != 0) {
printf("cacheindex: value=%s\n", s);
fail = 1;
}
freet(buf);
}
/* cache_binput reads a field while in bounds, but refuses one starting at
or past end-of-buffer. */
{
char buf[8] = "ab\ncd";
const char *const end = buf + 5;
char s[16];
if (cache_binput(buf, end, s, sizeof(s)) != 3 || strcmp(s, "ab") != 0)
fail = 1; /* normal read: "ab" then the '\n', 3 bytes consumed */
if (cache_binput(end, end, s, sizeof(s)) != 0 || s[0] != '\0')
fail = 1;
}
/* Drive the full loader scan over a truncated index: a declared length that
overshoots plus a half-written entry. ASan aborts here on the pre-fix
scan; the cursor must never leave the buffer. */
{
static const char src[] = "9\nCACHE-1.1\n99\nwww.example.com\n/a";
const size_t len = sizeof(src) - 1;
char *buf = malloct(len + 1);
const char *const end = buf + len;
char line[256];
char *a = buf;
memcpy(buf, src, len + 1);
a += cache_brstr(a, line, sizeof(line));
a += cache_brstr(a, line, sizeof(line));
while (a != NULL && a < end) {
a = strchr(a + 1, '\n');
if (a == NULL)
break;
a++;
a += cache_binput(a, end, line, sizeof(line));
}
freet(buf);
}
printf("cacheindex: %s\n", fail ? "FAIL" : "OK");
return fail;
}
static int st_cache_golden(httrackp *opt, int argc, char **argv) { static int st_cache_golden(httrackp *opt, int argc, char **argv) {
int regen, err; int regen, err;
@@ -1623,18 +1359,6 @@ static int st_cache_corrupt(httrackp *opt, int argc, char **argv) {
return err; return err;
} }
static int st_cache_legacy(httrackp *opt, int argc, char **argv) {
int err;
if (argc < 1) {
fprintf(stderr, "cache-legacy: needs a directory\n");
return 1;
}
err = cache_legacy_refused_selftest(opt, argv[0]);
printf("cache-legacy: %s\n", err ? "FAIL" : "OK");
return err;
}
static int st_reconcile(httrackp *opt, int argc, char **argv) { static int st_reconcile(httrackp *opt, int argc, char **argv) {
int err; int err;
@@ -1686,26 +1410,6 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
err = 1; err = 1;
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
err = 1; err = 1;
#ifndef _WIN32
/* the jar holds live session cookies: cookie_save must keep it 0600 */
{
const char *jar = "st-cookies-jar.txt";
struct stat st;
(void) UNLINK(jar);
assertf(cookie_save(&cookie, jar) == 0);
assertf(stat(jar, &st) == 0);
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
assertf(st.st_size > 0); /* mode-only checks would pass an empty jar */
/* a pre-existing world-readable jar must be tightened, not kept */
assertf(chmod(jar, 0644) == 0);
assertf(cookie_save(&cookie, jar) == 0);
assertf(stat(jar, &st) == 0);
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
assertf(st.st_size > 0);
(void) UNLINK(jar);
}
#endif
printf("cookie-header: %s\n", err ? "FAIL" : "OK"); printf("cookie-header: %s\n", err ? "FAIL" : "OK");
if (err) if (err)
printf(" got: %s\n", hdr); printf(" got: %s\n", hdr);
@@ -2147,19 +1851,6 @@ static int ae_write_collision(const char *path, const unsigned char *src,
return ok ? 0 : 1; return ok ? 0 : 1;
} }
/* Write src[0..len) to path as-is; 0 on success. */
static int ae_write_raw(const char *path, const unsigned char *src,
size_t len) {
FILE *const f = FOPEN(path, "wb");
int ok;
if (f == NULL)
return 1;
ok = fwrite(src, 1, len, f) == len;
fclose(f);
return ok ? 0 : 1;
}
/* Compare path's bytes to expect[0..len); 0 if equal. Streams (large files). */ /* Compare path's bytes to expect[0..len); 0 if equal. Streams (large files). */
static int ae_check_decoded(const char *path, const unsigned char *expect, static int ae_check_decoded(const char *path, const unsigned char *expect,
size_t len) { size_t len) {
@@ -2227,46 +1918,6 @@ static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
assertf(ae_write_collision(inpath, body, 64) == 0); assertf(ae_write_collision(inpath, body, 64) == 0);
assertf(hts_zunpack(inpath, outpath) == 64); assertf(hts_zunpack(inpath, outpath) == 64);
assertf(ae_check_decoded(outpath, body, 64) == 0); assertf(ae_check_decoded(outpath, body, 64) == 0);
/* Identity fallback (#47): a plain body mislabeled as compressed is kept
verbatim, small and multi-chunk (> one 8 KiB fread). */
assertf(ae_write_raw(inpath, small, slen) == 0);
assertf(hts_zunpack(inpath, outpath) == (int) slen);
assertf(ae_check_decoded(outpath, small, slen) == 0);
{
const size_t ilen = 16 * 1024;
unsigned char *idbody = malloct(ilen);
assertf(idbody != NULL);
for (i = 0; i < ilen; i++)
idbody[i] = small[i % slen];
assertf(ae_write_raw(inpath, idbody, ilen) == 0);
assertf(hts_zunpack(inpath, outpath) == (int) ilen);
assertf(ae_check_decoded(outpath, idbody, ilen) == 0);
freet(idbody);
}
/* Truncated gzip (CRC+ISIZE cut), zlib (ADLER32 cut) and raw deflate
must all still fail, not fall back to a verbatim copy. */
{
static const struct {
int wb;
size_t cut;
} tr[] = {{16 + MAX_WBITS, 8}, {MAX_WBITS, 4}, {-MAX_WBITS, 5}};
for (i = 0; i < sizeof(tr) / sizeof(tr[0]); i++) {
unsigned char z[512];
size_t zlen;
FILE *f;
assertf(ae_write_packed(inpath, tr[i].wb, small, slen) == 0);
f = FOPEN(inpath, "rb");
assertf(f != NULL);
zlen = fread(z, 1, sizeof(z), f);
fclose(f);
assertf(zlen > tr[i].cut && zlen < sizeof(z));
assertf(ae_write_raw(inpath, z, zlen - tr[i].cut) == 0);
assertf(hts_zunpack(inpath, outpath) < 0);
}
}
freet(body); freet(body);
} }
#else #else
@@ -2455,12 +2106,6 @@ static const struct selftest_entry {
{"filtersize", "<size> <string> <filter>...", {"filtersize", "<size> <string> <filter>...",
"size-aware filter verdict (negative size = unknown/scan time)", "size-aware filter verdict (negative size = unknown/scan time)",
st_filtersize}, st_filtersize},
{"filtermime", "<mime> <filter>...",
"mime-type filter verdict (fa_strjoker type=1)", st_filtermime},
{"filtermemo", "[iterations]",
"memoized vs unmemoized matcher differential", st_filtermemo},
{"filterdual", "<string1> <string2> <filter>...",
"merged two-form filter verdict (fa_strjoker_dual)", st_filterdual},
{"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify}, {"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify},
{"stripquery", "", "--strip-query pattern/key stripping self-test", {"stripquery", "", "--strip-query pattern/key stripping self-test",
st_stripquery}, st_stripquery},
@@ -2471,9 +2116,6 @@ static const struct selftest_entry {
{"mime", "<filename>", "MIME type for a filename", st_mime}, {"mime", "<filename>", "MIME type for a filename", st_mime},
{"charset", "<charset> <string>", {"charset", "<charset> <string>",
"convert a string to UTF-8 from a charset", st_charset}, "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},
{"idna-encode", "<host>", "encode a hostname to IDNA/punycode", {"idna-encode", "<host>", "encode a hostname to IDNA/punycode",
st_idna_encode}, st_idna_encode},
{"idna-decode", "<host>", "decode an IDNA/punycode hostname", {"idna-decode", "<host>", "decode an IDNA/punycode hostname",
@@ -2490,27 +2132,19 @@ static const struct selftest_entry {
st_relative}, st_relative},
{"resolve", "<link> <adr> <fil>", "resolve a link against an origin", {"resolve", "<link> <adr> <fil>", "resolve a link against an origin",
st_resolve}, st_resolve},
{"identurl", "<url>", "split an absolute URL into (adr, fil)", st_identurl},
{"header", "<raw-header-line> ...", "response header-line parsing", {"header", "<raw-header-line> ...", "response header-line parsing",
st_header}, st_header},
{"headerlong", "[header-name:]",
"over-long header value must not overflow the parse scratch",
st_headerlong},
{"savename", "<fil> <content-type> [key=value ...]", {"savename", "<fil> <content-type> [key=value ...]",
"local save-name for a URL", st_savename}, "local save-name for a URL", st_savename},
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency", {"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
st_sniff}, st_sniff},
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache}, {"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
st_cacheindex},
{"cache-golden", "<dir> [regen]", "frozen cache-format read self-test", {"cache-golden", "<dir> [regen]", "frozen cache-format read self-test",
st_cache_golden}, st_cache_golden},
{"cache-writefail", "<dir>", "cache write-failure handling self-test", {"cache-writefail", "<dir>", "cache write-failure handling self-test",
st_cache_writefail}, st_cache_writefail},
{"reconcile", "<dir>", "cache generation reconcile policy self-test", {"reconcile", "<dir>", "cache generation reconcile policy self-test",
st_reconcile}, st_reconcile},
{"cache-legacy", "<dir>", "pre-3.31 legacy cache refusal self-test",
st_cache_legacy},
{"cache-corrupt", "<dir>", "cache read-side corruption self-test", {"cache-corrupt", "<dir>", "cache read-side corruption self-test",
st_cache_corrupt}, st_cache_corrupt},
{"dns", "", "DNS resolver/cache self-test", st_dns}, {"dns", "", "DNS resolver/cache self-test", st_dns},

View File

@@ -524,11 +524,28 @@ static int hts_acceptlink_(httrackp * opt, int ptr,
// filters, 0=sait pas 1=ok -1=interdit // filters, 0=sait pas 1=ok -1=interdit
{ {
int jokDepth = 0; int jokDepth1 = 0, jokDepth2 = 0;
int jok1 = 0, jok2 = 0;
jok = fa_strjoker_dual(/*url */ 0, _FILTERS, *_FILTERS_PTR, lfull, l, jok1 =
NULL, NULL, &jokDepth); fa_strjoker( /*url */ 0, _FILTERS, *_FILTERS_PTR, lfull, NULL, NULL,
mdepth = _FILTERS[jokDepth]; &jokDepth1);
jok2 =
fa_strjoker( /*url */ 0, _FILTERS, *_FILTERS_PTR, l, NULL, NULL,
&jokDepth2);
if (jok2 == 0) { // #2 doesn't know
jok = jok1; // then, use #1
mdepth = _FILTERS[jokDepth1];
} else if (jok1 == 0) { // #1 doesn't know
jok = jok2; // then, use #2
mdepth = _FILTERS[jokDepth2];
} else if (jokDepth1 >= jokDepth2) { // #1 matching rule is "after" #2, then it is prioritary
jok = jok1;
mdepth = _FILTERS[jokDepth1];
} else { // #2 matching rule is "after" #1, then it is prioritary
jok = jok2;
mdepth = _FILTERS[jokDepth2];
}
} }
if (jok == 1) { // autorisé if (jok == 1) { // autorisé
@@ -948,10 +965,34 @@ int hts_testlinksize(httrackp * opt, const char *adr, const char *fil, LLint siz
// filters, 0=sait pas 1=ok -1=interdit // filters, 0=sait pas 1=ok -1=interdit
{ {
sz = size; int jokDepth1 = 0, jokDepth2 = 0;
jok = fa_strjoker_dual(/*url */ 0, *opt->filters.filters, int jok1 = 0, jok2 = 0;
*opt->filters.filptr, lfull, l, &sz, &size_flag, LLint sz1 = size, sz2 = size;
NULL); int size_flag1 = 0, size_flag2 = 0;
jok1 =
fa_strjoker( /*url */ 0, *opt->filters.filters, *opt->filters.filptr,
lfull, &sz1, &size_flag1, &jokDepth1);
jok2 =
fa_strjoker( /*url */ 0, *opt->filters.filters, *opt->filters.filptr,
l, &sz2, &size_flag2, &jokDepth2);
if (jok2 == 0) { // #2 doesn't know
jok = jok1; // then, use #1
sz = sz1;
size_flag = size_flag1;
} else if (jok1 == 0) { // #1 doesn't know
jok = jok2; // then, use #2
sz = sz2;
size_flag = size_flag2;
} else if (jokDepth1 >= jokDepth2) { // #1 matching rule is "after" #2, then it is prioritary
jok = jok1;
sz = sz1;
size_flag = size_flag1;
} else { // #2 matching rule is "after" #1, then it is prioritary
jok = jok2;
sz = sz2;
size_flag = size_flag2;
}
} }
// log // log

View File

@@ -48,7 +48,6 @@ Please visit our Website: http://www.httrack.com
/* /*
Unpack file into a new file (gzip, zlib RFC1950 or raw deflate RFC1951). 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 Return value: size of the new file, or -1 if an error occurred
*/ */
/* Note: utf-8 */ /* Note: utf-8 */
@@ -69,10 +68,6 @@ int hts_zunpack(char *filename, char *newfile) {
((inbuf[0] & 0x0f) == Z_DEFLATED && ((inbuf[0] & 0x0f) == Z_DEFLATED &&
(((unsigned) inbuf[0] << 8 | inbuf[1]) % 31) == 0))); (((unsigned) inbuf[0] << 8 | inbuf[1]) % 31) == 0)));
int attempt; int attempt;
/* not_deflate: the raw attempt hit a data error, so the body is in no
deflate framing at all. env_error: local I/O or memory failure. */
hts_boolean not_deflate = HTS_FALSE;
hts_boolean env_error = HTS_FALSE;
/* deflate is ambiguous; on failure retry with the other windowBits */ /* 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; attempt++) {
@@ -83,20 +78,15 @@ int hts_zunpack(char *filename, char *newfile) {
if (attempt > 0) { if (attempt > 0) {
/* rewind input; reopening fpout "wb" discards the partial output */ /* rewind input; reopening fpout "wb" discards the partial output */
if (fseek(in, 0, SEEK_SET) != 0) { if (fseek(in, 0, SEEK_SET) != 0)
env_error = HTS_TRUE;
break; break;
}
navail = fread(inbuf, 1, sizeof(inbuf), in); navail = fread(inbuf, 1, sizeof(inbuf), in);
} }
fpout = FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb"); fpout = FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
if (fpout == NULL) { if (fpout == NULL)
env_error = HTS_TRUE;
break; break;
}
memset(&strm, 0, sizeof(strm)); memset(&strm, 0, sizeof(strm));
if (inflateInit2(&strm, windowBits) != Z_OK) { if (inflateInit2(&strm, windowBits) != Z_OK) {
env_error = HTS_TRUE;
fclose(fpout); fclose(fpout);
break; break;
} }
@@ -118,17 +108,12 @@ int hts_zunpack(char *filename, char *newfile) {
zerr = inflate(&strm, Z_NO_FLUSH); zerr = inflate(&strm, Z_NO_FLUSH);
if (zerr == Z_NEED_DICT || zerr == Z_DATA_ERROR || if (zerr == Z_NEED_DICT || zerr == Z_DATA_ERROR ||
zerr == Z_MEM_ERROR || zerr == Z_STREAM_ERROR) { zerr == Z_MEM_ERROR || zerr == Z_STREAM_ERROR) {
if (zerr == Z_MEM_ERROR || zerr == Z_STREAM_ERROR)
env_error = HTS_TRUE;
else if (windowBits < 0)
not_deflate = HTS_TRUE;
ok = HTS_FALSE; ok = HTS_FALSE;
break; break;
} }
produced = sizeof(outbuf) - strm.avail_out; produced = sizeof(outbuf) - strm.avail_out;
if (produced > 0 && if (produced > 0 &&
fwrite(outbuf, 1, produced, fpout) != produced) { fwrite(outbuf, 1, produced, fpout) != produced) {
env_error = HTS_TRUE;
ok = HTS_FALSE; ok = HTS_FALSE;
break; break;
} }
@@ -144,28 +129,6 @@ int hts_zunpack(char *filename, char *newfile) {
inflateEnd(&strm); inflateEnd(&strm);
fclose(fpout); 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)) {
FILE *const fpout =
FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
if (fpout != NULL && fseek(in, 0, SEEK_SET) == 0) {
int size = 0;
while ((navail = fread(inbuf, 1, sizeof(inbuf), in)) > 0) {
if (fwrite(inbuf, 1, navail, fpout) != navail) {
size = -1;
break;
}
size += (int) navail;
}
if (size >= 0 && !ferror(in))
ret = size;
}
if (fpout != NULL)
fclose(fpout);
}
fclose(in); fclose(in);
} }
} }

View File

@@ -1,10 +0,0 @@
#!/bin/bash
#
set -euo pipefail
# Cache-index (.ndx) parse must stay inside the buffer on a corrupt length
# prefix (ASan aborts here on the pre-fix binary; the OK check gates the
# non-sanitized build too).
test "$(httrack -O /dev/null -#test=cacheindex)" == 'cacheindex: OK'

View File

@@ -36,64 +36,3 @@ test "$(httrack -O /dev/null -#test=charset 'no-such-charset-xyz' 'café' 2>/dev
# malformed UTF-8 (lone continuation byte, truncated lead byte) must not crash # malformed UTF-8 (lone continuation byte, truncated lead byte) must not crash
runs 'utf-8' $'\x80' runs 'utf-8' $'\x80'
runs 'utf-8' $'\xc3' runs 'utf-8' $'\xc3'
# hts_getCharsetFromMeta: <meta> charset extraction, HTML5 and legacy forms.
# -#test=metacharset <html> prints the charset, or "(none)".
meta() {
test "$(httrack -O /dev/null -#test=metacharset "$1")" == "$2" || exit 1
}
# HTML5 form, quoting/spacing flavors
meta '<meta charset="utf-8">' 'utf-8'
meta "<meta charset='utf-8'>" 'utf-8'
meta '<meta charset=utf-8>' 'utf-8'
meta '<meta charset=utf-8 />' 'utf-8'
meta '<meta charset=utf-8/>' 'utf-8'
# charset labels are case-insensitive downstream; only require case-insensitive match
test "$(httrack -O /dev/null -#test=metacharset '<META CHARSET=UTF-8>' | tr '[:upper:]' '[:lower:]')" == 'utf-8' || exit 1
meta '<html><head><meta charset = "utf-8" ></head>' 'utf-8'
# legacy http-equiv form, attributes in any order
meta '<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">' 'iso-8859-1'
meta '<meta content="text/html; charset=gb2312" http-equiv="content-type">' 'gb2312'
meta '<meta http-equiv=Content-Type content="text/html;charset=euc-jp">' 'euc-jp'
meta '<meta http-equiv="Content-Type" content="text/html; charset = koi8-r ">' 'koi8-r'
meta '<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">' 'utf-8'
meta '<meta http-equiv=content-type content=text/html;charset=utf-8 />' 'utf-8'
meta '<meta http-equiv=content-type content=text/html;charset=utf-8>' 'utf-8'
# first resolvable meta wins; unrelated metas are skipped
meta '<meta name="viewport" content="width=1"><meta charset="koi8-r">' 'koi8-r'
meta '<meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=gb2312">' 'utf-8'
# duplicate attribute: first wins, as in browsers
meta '<meta http-equiv="Content-Type" content="text/html; charset=big5" http-equiv="refresh">' 'big5'
# negatives: wrong tag/attribute, empty value
meta '<p>x</p>' '(none)'
meta '<meta name="desc" content="charset=bogus">' '(none)'
meta '<meta http-equiv="refresh" content="0; url=x.html">' '(none)'
meta '<body charset="utf-8">' '(none)'
meta '<meta charset="">' '(none)'
# hostile truncations: no crash, no match
meta '<meta charset="utf-8' '(none)'
meta '<meta charset=' '(none)'
meta '<meta ' '(none)'
meta '<meta' '(none)'
# hts_isStringUTF8: valid-UTF-8 probe (guards the #180 link conversion).
utf8() {
test "$(httrack -O /dev/null -#test=isutf8 "$1")" == "$2" || exit 1
}
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

View File

@@ -12,11 +12,6 @@ match() {
nomatch() { nomatch() {
test "$(httrack -O /dev/null -#test=filter "$1" "$2")" == "$2 does NOT match $1" || exit 1 test "$(httrack -O /dev/null -#test=filter "$1" "$2")" == "$2 does NOT match $1" || exit 1
} }
# single-arg call means an empty subject (unreachable as a CLI arg); '*(' used
# to force max=1 and over-read past it.
nomatch_empty() {
test "$(httrack -O /dev/null -#test=filter "$1")" == " does NOT match $1" || exit 1
}
# bare star matches everything # bare star matches everything
match '*' 'anything/at/all' match '*' 'anything/at/all'
@@ -127,68 +122,3 @@ nomatch '*[file]end' 'foo?xend'
nomatch '*[name]X' 'abc?X' nomatch '*[name]X' 'abc?X'
match '*[file]' 'foo?x=1' # trailing query: tolerated, as for *.aspx match '*[file]' 'foo?x=1' # trailing query: tolerated, as for *.aspx
match '*.aspx' 'page.aspx?y=2' match '*.aspx' 'page.aspx?y=2'
# empty subject against a unique-match allow-all pattern (heap over-read regress)
nomatch_empty '*(('
nomatch_empty '**(('
# near-miss negatives: word order, exact tails, range boundaries, class case
nomatch 'foo*bar' 'foobaz'
nomatch '*foo*bar' 'barfoo' # both words present, wrong order
nomatch '*.gif' 'a.gifx' # trailing junk after the extension
nomatch '*.gif' 'a.gi'
nomatch '*[b-y]' 'a'
nomatch '*[b-y]' 'z'
nomatch '*[a-z].txt' 'B.txt' # classes are case-sensitive...
match 'FoO*' 'foobar' # ...literals are not
# a star may match an empty run, but never skip a required literal
match '*[0-9].gif' '.gif'
nomatch '*[0-9].gif' 'x.gif'
# a descending range *[c-a] is an empty class, not c..a reversed
match '*[c-a]x' 'x'
nomatch '*[c-a]x' 'cx'
# *[param] is all-or-nothing on '?': spans everything after one, else nothing
match 'a*[param]' 'a?x=1'
nomatch 'a*[param]' 'ab?x=1'
# the latest matching rule wins, regardless of sign
fsize 'verdict=forbidden size_flag=0' -1 a.gif '+*.gif' '-*.gif'
fsize 'verdict=allowed size_flag=0' -1 a.gif '-*.gif' '+*.gif'
# a failed size test sets size_flag but leaves the rule unmatched
fsize 'verdict=unknown size_flag=1' 5 foo.jpg '-*.jpg*[>10]'
# mime filters (-#test=filtermime = fa_strjoker type=1): only mime: rules
# apply to a mime type, and mime: rules never apply to a URL
fmime() {
local want="$1"
shift
test "$(httrack -O /dev/null -#test=filtermime "$@")" == "verdict=$want" || exit 1
}
fmime allowed 'image/png' '+mime:image/*'
fmime unknown 'text/html' '+mime:image/*'
fmime forbidden 'image/png' '+mime:image/*' '-mime:image/png' # last wins
fmime allowed 'image/png' '-mime:image/png' '+mime:image/*'
fmime unknown 'image/png' '+image/*' # URL rule: skipped
fsize 'verdict=unknown size_flag=0' -1 'image/png' '+mime:image/*' # and vice versa
# memoized vs no-memo differential over seeded random patterns and subjects,
# matching and non-matching (the self-test asserts both polarities occurred)
test "$(httrack -O /dev/null -#test=filtermemo)" == "filtermemo: 20000 cases OK" || exit 1
# star-heavy non-match must stay polynomial (#501); 400 chars also exercises
# the heap-allocated memo. timeout (where available) turns a hang into a FAIL.
with_timeout() {
if command -v timeout >/dev/null; then timeout 60 "$@"; else "$@"; fi
}
pat=$(printf '*[a]%.0s' $(seq 1 12))b
subj=$(printf 'a%.0s' $(seq 1 400))
test "$(with_timeout httrack -O /dev/null -#test=filter "$pat" "$subj")" == "$subj does NOT match $pat" || exit 1
# a later star's class dead-end must re-extend the earlier star, not give up
# (guards against a single-backtrack-point matcher rewrite)
match '*[aX]X*[a]Y' 'aXaXaY'
nomatch '*[aX]X*[a]Y' 'aXbXaY'

View File

@@ -1,20 +0,0 @@
#!/bin/bash
#
set -euo pipefail
# fa_strjoker_dual: merged verdict on the two URL forms the engine tests;
# the match latest in the filter list wins, an unknown verdict defers.
fdual() {
local want="$1"
shift
test "$(httrack -O /dev/null -#test=filterdual "$@")" == "$want" || exit 1
}
fdual 'verdict=unknown rule=0' zzz yyy '+a*' # neither form matches
fdual 'verdict=forbidden rule=0' aaa zzz '-a*' # only form 1 matches
fdual 'verdict=forbidden rule=0' zzz aaa '-a*' # only form 2 matches
fdual 'verdict=forbidden rule=1' a b '+a*' '-b*' # later rule wins (form 2)
fdual 'verdict=forbidden rule=1' b a '+a*' '-b*' # later rule wins (form 1)
fdual 'verdict=allowed rule=0' a aa '+a*' # tie: form 1 wins

View File

@@ -27,16 +27,3 @@ hdr 'contenttype= cdispo=report.pdf' \
# Path components in the filename are dropped on the wire (RFC 2616). # Path components in the filename are dropped on the wire (RFC 2616).
hdr 'contenttype= cdispo=evil.pdf' \ hdr 'contenttype= cdispo=evil.pdf' \
'Content-Disposition: attachment; filename="../../evil.pdf"' 'Content-Disposition: attachment; filename="../../evil.pdf"'
# An over-long header value must not overflow the parse scratch (ASan aborts
# here on the pre-fix binary).
test "$(httrack -O /dev/null -#test=headerlong 'Content-Type:')" \
== 'contenttype_len=32 contentencoding_len=0'
test "$(httrack -O /dev/null -#test=headerlong 'Content-Encoding:')" \
== 'contenttype_len=0 contentencoding_len=0'
# An empty Content-Encoding yields no token: leave it empty, don't read the
# uninitialized scratch (MSan aborts here on the pre-fix binary).
enc() { httrack -O /dev/null -#test=header "$1" | grep '^contentencoding='; }
test "$(enc 'Content-Encoding:')" == 'contentencoding='
test "$(enc 'Content-Encoding: gzip')" == 'contentencoding=gzip'

View File

@@ -1,27 +0,0 @@
#!/bin/bash
#
set -euo pipefail
# ident_url_absolute splits a raw URL into (adr, fil), the first parser to touch
# it. -#test=identurl <url> [pad] prints "adr=.. fil=.." or "error"; pad appends
# N 'a's, reaching lengths a CLI arg can't.
split() {
test "$(httrack -O /dev/null -#test=identurl "$1")" == "$2" || exit 1
}
split 'http://www.example.com/a/b/../c.html' 'adr=www.example.com fil=/a/c.html'
split 'ftp://ftp.example.com/pub/file.txt' 'adr=ftp://ftp.example.com fil=/pub/file.txt'
split 'file:///etc/hostname' 'adr=file:// fil=/etc/hostname'
# empty-path file:// used to read one byte past the URL (p[1] with *p=0)
split 'file://' 'adr=file:// fil=//'
# a root-relative path is copied whole into fil[HTS_URLMAXSIZE*2], so a URL at
# the buffer size is the tightest overflow; it must be rejected, not abort.
split_pad() {
test "$(httrack -O /dev/null -#test=identurl "$1" "$2")" == "$3" || exit 1
}
split_pad '/' 2047 'error' # 1 + 2047 = 2048 = HTS_URLMAXSIZE*2
split_pad 'http://h/' 3000 'error'

View File

@@ -8,8 +8,8 @@ set -euo pipefail
enc() { test "$(httrack -O /dev/null -#test=idna-encode "$1")" == "$2" || exit 1; } enc() { test "$(httrack -O /dev/null -#test=idna-encode "$1")" == "$2" || exit 1; }
dec() { test "$(httrack -O /dev/null -#test=idna-decode "$1")" == "$2" || exit 1; } dec() { test "$(httrack -O /dev/null -#test=idna-decode "$1")" == "$2" || exit 1; }
# a malformed encode input must be rejected (empty output), not encoded or leaked. # crash probe: malformed ACE input must exit cleanly, not abort.
enc_bad() { test -z "$(httrack -O /dev/null -#test=idna-encode "$1" 2>/dev/null)" || exit 1; } runs() { httrack -O /dev/null -#test=idna-decode "$1" >/dev/null 2>&1 || exit 1; }
# encode # encode
enc 'www.café.com' 'www.xn--caf-dma.com' enc 'www.café.com' 'www.xn--caf-dma.com'
@@ -33,9 +33,6 @@ enc 'xn--already-encoded.com' 'xn--already-encoded.com'
# an empty punycode payload decodes back to the bare xn-- label # an empty punycode payload decodes back to the bare xn-- label
dec 'xn--' 'xn--' dec 'xn--' 'xn--'
# malformed ACE payloads decode to a stable (garbage) string, not a crash # malformed ACE payloads (invalid base-36, garbage) must not crash
dec 'xn--!!!' '㚯' runs 'xn--!!!'
dec 'xn--already-encoded.com' 'ǮalǭǮreaǭǫdy.com' runs 'xn--already-encoded.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')"

View File

@@ -1,23 +0,0 @@
#!/bin/bash
#
# The pre-3.31 .dat/.ndx cache import was removed: cache_init must refuse the
# legacy pair and leave the files untouched (httrack -#test=cache-legacy <dir>).
set -eu
dir=$(mktemp -d)
trap 'rm -rf "$dir"' EXIT
# the refusal errors land on stdout (no log file); pin them and the verdict
out=$(httrack -#test=cache-legacy "$dir" 2>/dev/null)
cnt=$(printf '%s\n' "$out" | grep -c 'no longer supported' || true)
test "$cnt" = 2 || {
echo "expected 2 refusal messages, got $cnt" >&2
exit 1
}
out=$(printf '%s\n' "$out" | tail -n1)
test "$out" = "cache-legacy: OK" || {
echo "expected 'cache-legacy: OK', got: $out" >&2
exit 1
}

View File

@@ -22,9 +22,9 @@ bash crawl-test.sh \
httrack http://ut.httrack.com/unicode-links/utf8.html httrack http://ut.httrack.com/unicode-links/utf8.html
bash crawl-test.sh \ bash crawl-test.sh \
--errors 2 --files 9 \ --errors 4 --files 7 \
--found ut.httrack.com/unicode-links/café3860.html \ --found ut.httrack.com/unicode-links/café3860.html \
--found ut.httrack.com/unicode-links/café9fa8.html \ --found ut.httrack.com/unicode-links/café9fa8.html \
--found ut.httrack.com/unicode-links/café30f4.html \ --found ut.httrack.com/unicode-links/café30f4.html \
--found ut.httrack.com/unicode-links/café5e1f.html \ --found ut.httrack.com/unicode-links/café5e1f.html \
--found ut.httrack.com/unicode-links/café7b30.html \ --found ut.httrack.com/unicode-links/café7b30.html \

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# #
# Diverse seeded /big/ crawl: 12 pattern families, decoy absence, update pass # Diverse seeded /big/ crawl: 12 pattern families, decoy absence, update pass
# must 304-revalidate. 361 = 1 index + 96 pages + 192 imgs + 5 shared + 61 # must 304-revalidate. 360 = 1 index + 96 pages + 192 imgs + 5 shared + 60
# family + 6 singles; the 4 planted errors write -o1 pages, not counted. # family + 6 singles; the 4 planted errors write -o1 pages, not counted.
set -euo pipefail set -euo pipefail
@@ -9,7 +9,7 @@ set -euo pipefail
: "${top_srcdir:=..}" : "${top_srcdir:=..}"
bash "$top_srcdir/tests/local-crawl.sh" --rerun \ bash "$top_srcdir/tests/local-crawl.sh" --rerun \
--errors-content 4 --files 361 \ --errors 4 --files 360 \
--found 'big/p/95.html' \ --found 'big/p/95.html' \
--found 'big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png' \ --found 'big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png' \
--found 'big/a/f2-2x.png' \ --found 'big/a/f2-2x.png' \
@@ -40,7 +40,6 @@ bash "$top_srcdir/tests/local-crawl.sh" --rerun \
--not-found 'big/x/bj.jar' \ --not-found 'big/x/bj.jar' \
--not-found 'big/x/is1.png' \ --not-found 'big/x/is1.png' \
--not-found 'big/x/concat.html' \ --not-found 'big/x/concat.html' \
--file-matches 'big/f1/gzid.html' '<p>gzid</p>' \
--file-matches 'big/p/2.html' 'srcset="\.\./a/f2-1x\.png 1x, \.\./a/f2-2x\.png 2x"' \ --file-matches 'big/p/2.html' 'srcset="\.\./a/f2-1x\.png 1x, \.\./a/f2-2x\.png 2x"' \
--file-matches 'big/a/blk2.css' 'url\(blk2-bg\.png\)' \ --file-matches 'big/a/blk2.css' 'url\(blk2-bg\.png\)' \
--file-matches 'big/p/5.html' "document\\.write\\('<a href=\"\\.\\./f5/dw\\.html\"" \ --file-matches 'big/p/5.html' "document\\.write\\('<a href=\"\\.\\./f5/dw\\.html\"" \

View File

@@ -1,35 +0,0 @@
#!/bin/bash
#
set -euo pipefail
# --why: report which +/- filter rule decides for a URL, without crawling.
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
why() {
local want="$1" out
shift
out="$(httrack -O "$tmpdir/p" -q "$@" |
grep -E 'accepted|rejected|no filter|unable')"
test "$out" == "$want" || {
echo "want: $want"
echo "got : $out"
exit 1
}
}
base=http://www.example.com
why "$base/x.zip: rejected by rule #2 (-*.zip)" \
--why "$base/x.zip" "$base/" '+*.png' '-*.zip'
why "$base/a.png: accepted by rule #1 (+*.png)" \
--why "$base/a.png" "$base/" '+*.png' '-*.zip'
why "$base/i.html: no filter rule matches; the wizard decides (same-site links are followed by default)" \
--why "$base/i.html" "$base/" '+*.png' '-*.zip'
# scheme-less input gets the same http:// default as primary URLs
why "$base/x.zip: rejected by rule #2 (-*.zip)" \
--why "www.example.com/x.zip" "$base/" '+*.png' '-*.zip'
# the rule latest in the list decides
why "$base/x.zip: accepted by rule #3 (+*x.zip)" \
--why "$base/x.zip" "$base/" '+*.png' '-*.zip' '+*x.zip'

View File

@@ -1,21 +0,0 @@
#!/bin/bash
#
# #180: raw non-ASCII hrefs must reach the wire UTF-8 percent-encoded whatever
# the declared page charset; each PDF exists only at its exact UTF-8 path.
set -euo pipefail
: "${top_srcdir:=..}"
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --rerun \
--found 'charset/header/统计大数据服务平台.pdf' \
--found 'charset/meta5/统计大数据服务平台.pdf' \
--found 'charset/metaeq/统计大数据服务平台.pdf' \
--found 'charset/none/统计大数据服务平台.pdf' \
--found 'charset/latin1hdr/统计大数据服务平台.pdf' \
--found 'charset/latin1real/café.pdf' \
--found 'charset/metalatin1/déjà.pdf' \
--found 'charset/latin1ovl/À¡x.pdf' \
--found 'charset/priority/nuée.pdf' \
--found 'charset/preenc/统计大数据服务平台.pdf' \
--found 'charset/bom/统计大数据服务平台.pdf' \
httrack 'BASEURL/charset/index.html'

View File

@@ -1,21 +0,0 @@
#!/bin/bash
#
# -M byte cap under a slow server (#77): p0 overruns -M at once while p1..p3
# trickle for a minute; the cap must abort the in-flight transfers, not wait
# them out. Unfixed, the smooth stop drains the trickle at server pace.
set -euo pipefail
: "${top_srcdir:=..}"
start=$(date +%s)
bash "$top_srcdir/tests/local-crawl.sh" \
--log-found 'More than 400000 bytes have been transferred.. giving up' \
httrack 'BASEURL/bigtrickle/index.html' -M400000 -c4
wall=$(($(date +%s) - start))
# trickle runs 60s; a smooth-only stop waits it out, the hard stop lands near 8s.
# Wall clock is the discriminating check: the log line above fires either way.
if [ "$wall" -ge 30 ]; then
echo "crawl took ${wall}s, -M hard stop did not engage" >&2
exit 1
fi

View File

@@ -1,24 +0,0 @@
#!/bin/bash
#
# -M hard-abort must not destroy already-complete files (#77 follow-up).
# Pass 1 mirrors bigtrunc fully. Pass 2 re-fetches under --update -M: fast.bin
# overruns the cap, its abort tears down slow.bin's stalled re-fetch. Both were
# complete on disk from pass 1, and both must survive the aborted update:
# - slow.bin's re-fetch is incomplete; the direct-to-disk write must not be
# left truncated (the previous copy is restored).
# - fast.bin fully arrives but is torn down; the aborted slot must not overwrite
# it with an empty body.
# Unfixed, slow.bin is truncated and fast.bin is zeroed while the cache still
# records them complete.
set -euo pipefail
: "${top_srcdir:=..}"
bash "$top_srcdir/tests/local-crawl.sh" \
--rerun-args '--update -M400000' \
--log-found 'More than 400000 bytes have been transferred.. giving up' \
--found bigtrunc/slow.bin \
--file-min-bytes bigtrunc/slow.bin 655360 \
--file-min-bytes bigtrunc/fast.bin 655360 \
httrack 'BASEURL/bigtrunc/index.html' -c2

View File

@@ -1,13 +0,0 @@
#!/bin/bash
# Issue #176: on --update a page that 200'd on the first crawl but now 403s must
# keep its good local copy, not be overwritten by the error body nor purged.
set -euo pipefail
: "${top_srcdir:=..}"
bash "$top_srcdir/tests/local-crawl.sh" --rerun \
--found 'errmask/keep.dat' \
--file-min-bytes 'errmask/keep.dat' 1024 \
--file-matches 'errmask/keep.dat' '^KEEP' \
--file-not-matches 'errmask/keep.dat' 'error 403' \
httrack 'BASEURL/errmask/index.html'

View File

@@ -35,14 +35,11 @@ TESTS = \
01_engine-entities.test \ 01_engine-entities.test \
01_engine-filelist.test \ 01_engine-filelist.test \
01_engine-filter.test \ 01_engine-filter.test \
01_engine-filterdual.test \
01_engine-ftp-line.test \ 01_engine-ftp-line.test \
01_engine-ftp-userpass.test \ 01_engine-ftp-userpass.test \
01_engine-cacheindex.test \
01_engine-hashtable.test \ 01_engine-hashtable.test \
01_engine-header.test \ 01_engine-header.test \
01_engine-idna.test \ 01_engine-idna.test \
01_engine-identurl.test \
01_engine-escape-room.test \ 01_engine-escape-room.test \
01_engine-inplace-escape.test \ 01_engine-inplace-escape.test \
01_engine-java.test \ 01_engine-java.test \
@@ -68,7 +65,6 @@ TESTS = \
01_zlib-acceptencoding.test \ 01_zlib-acceptencoding.test \
01_zlib-cache.test \ 01_zlib-cache.test \
01_zlib-cache-corrupt.test \ 01_zlib-cache-corrupt.test \
01_zlib-cache-legacy.test \
01_zlib-cache-golden.test \ 01_zlib-cache-golden.test \
01_zlib-cache-writefail.test \ 01_zlib-cache-writefail.test \
01_zlib-savename-cached.test \ 01_zlib-savename-cached.test \
@@ -108,11 +104,6 @@ TESTS = \
36_local-bigcrawl.test \ 36_local-bigcrawl.test \
37_local-cache-outage.test \ 37_local-cache-outage.test \
38_local-update-304.test \ 38_local-update-304.test \
39_local-delayed-cancel.test \ 39_local-delayed-cancel.test
40_local-why.test \
41_local-utf8-link.test \
42_local-maxsize-slow.test \
43_local-update-truncate.test \
44_local-update-errormask.test
CLEANFILES = check-network_sh.cache CLEANFILES = check-network_sh.cache

View File

@@ -13,21 +13,15 @@
# #
# Usage: # Usage:
# bash local-crawl.sh [--tls] [--root DIR] [--cookie NAME=VALUE ...] \ # bash local-crawl.sh [--tls] [--root DIR] [--cookie NAME=VALUE ...] \
# [--rerun-args 'ARGS'] \ # --errors N --files N --found PATH ... --directory PATH ... \
# --errors N --errors-content N --files N --found PATH ... --directory PATH ... \
# --log-found REGEX ... --log-not-found REGEX ... \ # --log-found REGEX ... --log-not-found REGEX ... \
# --file-matches PATH REGEX ... --file-not-matches PATH REGEX ... \ # --file-matches PATH REGEX ... --file-not-matches PATH REGEX ... \
# --file-min-bytes PATH N --max-mirror-bytes N \ # --max-mirror-bytes N \
# httrack BASEURL/some/path [httrack-args...] # httrack BASEURL/some/path [httrack-args...]
# --errors counts every "Error:" log line; --errors-content drops transient
# network failures (codes -2..-6) that flake on busy loopback under -c8.
# --log-found/--log-not-found grep (ERE) the crawl's hts-log.txt. # --log-found/--log-not-found grep (ERE) the crawl's hts-log.txt.
# --max/--min-mirror-bytes bound the mirrored content bytes (host root). # --max/--min-mirror-bytes bound the mirrored content bytes (host root).
# --file-matches/--file-not-matches grep (ERE) a mirrored file (PATH under the # --file-matches/--file-not-matches grep (ERE) a mirrored file (PATH under the
# host root), to assert rewritten link/content survived the crawl. # host root), to assert rewritten link/content survived the crawl.
# --file-min-bytes asserts a mirrored file (PATH) is at least N bytes.
# --rerun-args runs a second pass (same server and mirror dir) with the given
# extra httrack args appended, e.g. an --update run under a cap.
# --cookie writes a Netscape cookies.txt (scoped to the discovered host:port, # --cookie writes a Netscape cookies.txt (scoped to the discovered host:port,
# which the ephemeral port forces into the cookie domain) and passes it to # which the ephemeral port forces into the cookie domain) and passes it to
# httrack via --cookies-file, to exercise preloaded cookies. # httrack via --cookies-file, to exercise preloaded cookies.
@@ -45,7 +39,6 @@ key="${testdir}/server.key"
tls= tls=
verbose= verbose=
rerun= rerun=
rerun_args=
rerun_dead= rerun_dead=
tmpdir= tmpdir=
serverpid= serverpid=
@@ -129,11 +122,7 @@ while test "$pos" -lt "$nargs"; do
pos=$((pos + 1)) pos=$((pos + 1))
cookies+=("${args[$pos]}") cookies+=("${args[$pos]}")
;; ;;
--rerun-args) --errors | --files)
pos=$((pos + 1))
rerun_args="${args[$pos]}"
;;
--errors | --errors-content | --files)
audit+=("${args[$pos]}" "${args[$((pos + 1))]}") audit+=("${args[$pos]}" "${args[$((pos + 1))]}")
pos=$((pos + 1)) pos=$((pos + 1))
;; ;;
@@ -141,7 +130,7 @@ while test "$pos" -lt "$nargs"; do
audit+=("${args[$pos]}" "${args[$((pos + 1))]}") audit+=("${args[$pos]}" "${args[$((pos + 1))]}")
pos=$((pos + 1)) pos=$((pos + 1))
;; ;;
--file-matches | --file-not-matches | --file-min-bytes) --file-matches | --file-not-matches)
audit+=("${args[$pos]}" "${args[$((pos + 1))]}" "${args[$((pos + 2))]}") audit+=("${args[$pos]}" "${args[$((pos + 1))]}" "${args[$((pos + 2))]}")
pos=$((pos + 2)) pos=$((pos + 2))
;; ;;
@@ -252,25 +241,6 @@ if test -n "$rerun"; then
fi fi
fi fi
# --- optional second pass with extra args (e.g. an --update run under a cap) ---
# Same server and mirror dir as the first pass, so the second pass sees the
# cache the first pass wrote. Used to exercise re-fetch/update behaviour.
if test -n "$rerun_args"; then
read -ra extra <<<"$rerun_args"
info "re-running httrack with ${rerun_args}"
httrack -O "$out" --user-agent="httrack $ver local ($(uname -omrs))" \
"${moreargs[@]}" "${hts[@]}" "${extra[@]}" >"${log}.2" 2>&1 &
crawlpid=$!
wait "$crawlpid"
crawlres=$?
crawlpid=
test "$crawlres" -eq 0 || ! result "second pass exited $crawlres" || {
cat "${log}.2" >&2
exit 1
}
result "OK (second pass)"
fi
# --- optional dead pass: server stopped, the cache must survive the rollback -- # --- optional dead pass: server stopped, the cache must survive the rollback --
if test -n "$rerun_dead"; then if test -n "$rerun_dead"; then
zip="${out}/hts-cache/new.zip" zip="${out}/hts-cache/new.zip"
@@ -336,14 +306,6 @@ while test "$i" -lt "${#audit[@]}"; do
assert_equals "checking errors" "${audit[$i]}" \ assert_equals "checking errors" "${audit[$i]}" \
"$(grep -iEc "^[0-9:]*[[:space:]]Error:" "${out}/hts-log.txt")" "$(grep -iEc "^[0-9:]*[[:space:]]Error:" "${out}/hts-log.txt")"
;; ;;
--errors-content)
i=$((i + 1))
total=$(grep -icE "^[0-9:]*[[:space:]]Error:" "${out}/hts-log.txt")
# transient network failures (statuscode -2..-6) flake on busy loopback;
# the code parens are followed by " at link" or " after N retries at link"
transient=$(grep -cE '\(-[2-6]\) (at link|after )' "${out}/hts-log.txt" || true)
assert_equals "checking content errors" "${audit[$i]}" "$((total - transient))"
;;
--files) --files)
i=$((i + 1)) i=$((i + 1))
nFiles=$(grep -E "^HTTrack Website Copier/[^ ]* mirror complete in " "${out}/hts-log.txt" | nFiles=$(grep -E "^HTTrack Website Copier/[^ ]* mirror complete in " "${out}/hts-log.txt" |
@@ -426,16 +388,6 @@ while test "$i" -lt "${#audit[@]}"; do
exit 1 exit 1
else result "OK"; fi else result "OK"; fi
;; ;;
--file-min-bytes)
path="${audit[$((i + 1))]}"
i=$((i + 2))
sz=$(wc -c <"${hostroot}/${path}" 2>/dev/null | tr -d '[:space:]')
info "checking ${path} size ${sz:-0} >= ${audit[$i]} bytes"
if test -n "$sz" && test "$sz" -ge "${audit[$i]}"; then result "OK"; else
result "file too small (or missing)"
exit 1
fi
;;
esac esac
i=$((i + 1)) i=$((i + 1))
done done

View File

@@ -374,7 +374,6 @@ def big_index(port):
'<img src="/big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png">' '<img src="/big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png">'
'<a href="/big/f1/long.html?x=%s">long</a>' '<a href="/big/f1/long.html?x=%s">long</a>'
'<a href="/big/f1/gzok.html">gzok</a>' '<a href="/big/f1/gzok.html">gzok</a>'
'<a href="/big/f1/gzid.html">gzid</a>'
'<a href="//127.0.0.1:%d/big/f1/protorel.html">protorel</a>' '<a href="//127.0.0.1:%d/big/f1/protorel.html">protorel</a>'
'<a href="http://127.0.0.1:%d/big/f1/abshost.html">abshost</a>' '<a href="http://127.0.0.1:%d/big/f1/abshost.html">abshost</a>'
'<a href="/big/e/404.html">e404</a>' '<a href="/big/e/404.html">e404</a>'
@@ -400,7 +399,6 @@ BIG_SIMPLE_PAGES = {
"/big/f1/dir/": "dir index", "/big/f1/dir/": "dir index",
"/big/f1/long.html": "long", "/big/f1/long.html": "long",
"/big/f1/gzok.html": "gzok", "/big/f1/gzok.html": "gzok",
"/big/f1/gzid.html": "gzid",
"/big/f1/protorel.html": "protorel", "/big/f1/protorel.html": "protorel",
"/big/f1/abshost.html": "abshost", "/big/f1/abshost.html": "abshost",
"/big/f5/dw.html": "dw target", "/big/f5/dw.html": "dw target",
@@ -662,97 +660,6 @@ class Handler(SimpleHTTPRequestHandler):
def route_intl_page(self): def route_intl_page(self):
self.send_raw(b"<html><body>accented page</body></html>\n", "text/html") self.send_raw(b"<html><body>accented page</body></html>\n", "text/html")
# Raw non-ASCII href matrix (#180): each variant declares the page charset
# differently; the PDF exists only at its exact UTF-8 path, so a
# mis-decoded link 404s.
CHARSET_CJK = "统计大数据服务平台.pdf"
# variant -> (index Content-Type, <head> bytes, href bytes, pdf name)
CHARSET_VARIANTS = {
"header": ("text/html; charset=utf-8", b"", CHARSET_CJK.encode(), CHARSET_CJK),
"meta5": (
"text/html",
b'<meta charset="utf-8">',
CHARSET_CJK.encode(),
CHARSET_CJK,
),
"metaeq": (
"text/html",
b'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">',
CHARSET_CJK.encode(),
CHARSET_CJK,
),
"none": ("text/html", b"", CHARSET_CJK.encode(), CHARSET_CJK),
"latin1hdr": (
"text/html; charset=iso-8859-1",
b"",
CHARSET_CJK.encode(),
CHARSET_CJK,
),
# genuine latin-1 href: the charset conversion must still apply
"latin1real": (
"text/html; charset=iso-8859-1",
b"",
"café.pdf".encode("latin-1"),
"café.pdf",
),
# latin-1 declared by META only: the meta parser is load-bearing
"metalatin1": (
"text/html",
b'<meta charset="iso-8859-1">',
"déjà.pdf".encode("latin-1"),
"déjà.pdf",
),
# latin-1 bytes that form an overlong UTF-8 shape: strict validation
# must still convert them
"latin1ovl": (
"text/html; charset=iso-8859-1",
b"",
"À¡x.pdf".encode("latin-1"),
"À¡x.pdf",
),
# header wins over meta: latin-1 href only resolves if iso-8859-1 is kept
"priority": (
"text/html; charset=iso-8859-1",
b'<meta charset="utf-8">',
"nuée.pdf".encode("latin-1"),
"nuée.pdf",
),
"preenc": ("text/html", b"", quote(CHARSET_CJK).encode(), CHARSET_CJK),
"bom": ("text/html", b"", CHARSET_CJK.encode(), CHARSET_CJK),
}
def route_charset(self):
path = unquote(urlsplit(self.path).path)
parts = path.split("/")
if path == "/charset/index.html":
self.send_html(
"".join(
'\t<a href="%s/index.html">%s</a>\n' % (v, v)
for v in self.CHARSET_VARIANTS
)
)
return
if len(parts) == 4 and parts[2] in self.CHARSET_VARIANTS:
ctype, head, href, pdf = self.CHARSET_VARIANTS[parts[2]]
if parts[3] == "index.html":
body = (
b"<html><head>"
+ head
+ b'</head><body><a href="'
+ href
+ b'">doc</a></body></html>'
)
if parts[2] == "bom":
body = b"\xef\xbb\xbf" + body
self.send_raw(body, ctype)
return
if parts[3] == pdf:
self.send_raw(self.FAKE_PDF, "application/pdf")
return
self.send_response(404)
self.send_header("Content-Length", "0")
self.end_headers()
# resume / 416 loop (#206): the first GET stalls after a prefix so the crawl # resume / 416 loop (#206): the first GET stalls after a prefix so the crawl
# can be interrupted (partial + temp-ref); every later request is 416. # can be interrupted (partial + temp-ref); every later request is 416.
RESUME_PREFIX = b"PARTIAL-" + b"x" * 4096 # flushed before the stall RESUME_PREFIX = b"PARTIAL-" + b"x" * 4096 # flushed before the stall
@@ -934,33 +841,6 @@ class Handler(SimpleHTTPRequestHandler):
def route_mini304_page(self): def route_mini304_page(self):
self.big_send(b"<html><body>tiny cacheable page</body></html>\n", "text/html") self.big_send(b"<html><body>tiny cacheable page</body></html>\n", "text/html")
# --- /errmask/: issue #176 — a page that 200'd on the first crawl but 403s
# on the update fetch must keep its good copy, not be overwritten nor purged.
ERRMASK_GOOD = b"KEEP" + b"." * 1020 # 1024 B distinctive non-HTML body
ERRMASK_ERR = b"<html><body>error 403</body></html>\n"
def route_errmask_index(self):
self.send_html('\t<a href="keep.dat">keep</a>\n')
def route_errmask_keep(self):
# First crawl (no validator) gets the 1024 B body + Last-Modified; the
# update sends a conditional and gets a 403 error page.
if self.headers.get("If-Modified-Since") or self.headers.get("If-None-Match"):
self.send_response(403, "Forbidden")
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(self.ERRMASK_ERR)))
self.end_headers()
if self.command != "HEAD":
self.wfile.write(self.ERRMASK_ERR)
return
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Last-Modified", BIG_LASTMOD)
self.send_header("Content-Length", str(len(self.ERRMASK_GOOD)))
self.end_headers()
if self.command != "HEAD":
self.wfile.write(self.ERRMASK_GOOD)
# --- delayed-type degenerate paths (issues #5/#107) -------------------- # --- delayed-type degenerate paths (issues #5/#107) --------------------
def route_delayed_index(self): def route_delayed_index(self):
self.send_html( self.send_html(
@@ -1062,48 +942,6 @@ class Handler(SimpleHTTPRequestHandler):
def route_bigfile(self): def route_bigfile(self):
self.send_raw(b"x" * self.BIGFILE_BYTES, "application/octet-stream") self.send_raw(b"x" * self.BIGFILE_BYTES, "application/octet-stream")
# -M under a slow server (#77): p0 is a fast 640KB file that alone overruns
# -M; p1..p3 trickle for a minute. The cap must abort those in-flight
# transfers, not wait them out.
def route_bigtrickle_index(self):
self.send_html(
"".join('\t<a href="p%d.bin">p%d</a>\n' % (i, i) for i in range(4))
)
# -M hard-abort must not destroy an already-complete file (#77 follow-up).
# "fast.bin" alone overruns -M and completes; "slow.bin" transfers fully on
# its first fetch (initial mirror) but stalls on every later fetch (the
# --update re-fetch), so the -M abort tears it down mid-body. An engine that
# truncates the good local copy on the aborted re-fetch loses data.
slow_seen = 0
def route_bigtrunc_index(self):
self.send_html('\t<a href="fast.bin">fast</a>\n\t<a href="slow.bin">slow</a>\n')
def route_bigtrunc_slow(self):
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", str(self.BIGFILE_BYTES))
self.end_headers()
if self.command == "HEAD":
return
# Count body fetches only, so a stray HEAD can't shift which pass stalls.
Handler.slow_seen += 1
first = Handler.slow_seen == 1
try:
if first:
self.wfile.write(b"x" * self.BIGFILE_BYTES)
self.wfile.flush()
else:
self.wfile.write(b"x" * 4096)
self.wfile.flush()
for _ in range(120):
self.wfile.write(b"x")
self.wfile.flush()
time.sleep(1.0)
except OSError:
pass
ROUTES = { ROUTES = {
"/cookies/entrance.php": route_entrance, "/cookies/entrance.php": route_entrance,
"/cookies/second.php": route_second, "/cookies/second.php": route_second,
@@ -1176,14 +1014,6 @@ class Handler(SimpleHTTPRequestHandler):
"/bigfiles/p5.bin": route_bigfile, "/bigfiles/p5.bin": route_bigfile,
"/bigfiles/p6.bin": route_bigfile, "/bigfiles/p6.bin": route_bigfile,
"/bigfiles/p7.bin": route_bigfile, "/bigfiles/p7.bin": route_bigfile,
"/bigtrickle/index.html": route_bigtrickle_index,
"/bigtrickle/p0.bin": route_bigfile,
"/bigtrickle/p1.bin": route_trickle_page,
"/bigtrickle/p2.bin": route_trickle_page,
"/bigtrickle/p3.bin": route_trickle_page,
"/bigtrunc/index.html": route_bigtrunc_index,
"/bigtrunc/fast.bin": route_bigfile,
"/bigtrunc/slow.bin": route_bigtrunc_slow,
"/delayed/noloc.php": route_delayed_noloc, "/delayed/noloc.php": route_delayed_noloc,
"/delayed/selfloop.php": route_delayed_selfloop, "/delayed/selfloop.php": route_delayed_selfloop,
"/delayed/redir.php": route_delayed_redir, "/delayed/redir.php": route_delayed_redir,
@@ -1204,8 +1034,6 @@ class Handler(SimpleHTTPRequestHandler):
"/redir/target.html": route_redir_target, "/redir/target.html": route_redir_target,
"/mini304/index.html": route_mini304_index, "/mini304/index.html": route_mini304_index,
"/mini304/page.html": route_mini304_page, "/mini304/page.html": route_mini304_page,
"/errmask/index.html": route_errmask_index,
"/errmask/keep.dat": route_errmask_keep,
} }
# --- /big/ seeded pseudo-site ------------------------------------------ # --- /big/ seeded pseudo-site ------------------------------------------
@@ -1251,13 +1079,6 @@ class Handler(SimpleHTTPRequestHandler):
"text/html", "text/html",
extra=[("Content-Encoding", "gzip")], extra=[("Content-Encoding", "gzip")],
) )
elif path == "/big/f1/gzid.html":
# Plain body mislabeled as gzip: identity fallback keeps it (#47)
self.big_send(
body,
"text/html",
extra=[("Content-Encoding", "gzip")],
)
else: else:
self.big_send(body, "text/html") self.big_send(body, "text/html")
elif path == "/big/f1/list.html": elif path == "/big/f1/list.html":
@@ -1373,9 +1194,6 @@ class Handler(SimpleHTTPRequestHandler):
if path.startswith("/big/"): if path.startswith("/big/"):
self.route_big() self.route_big()
return True return True
if path.startswith("/charset/"):
self.route_charset()
return True
# Match percent-encoded paths (accented #157 route) by their decoded form. # Match percent-encoded paths (accented #157 route) by their decoded form.
handler = self.ROUTES.get(path) or self.ROUTES.get(unquote(path)) handler = self.ROUTES.get(path) or self.ROUTES.get(unquote(path))
if handler is not None: if handler is not None: