mirror of
https://github.com/xroche/httrack.git
synced 2026-07-13 12:20:15 +03:00
Compare commits
2 Commits
ci-paralle
...
remove-dea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf483a24e4 | ||
|
|
abaea566bd |
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
@@ -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()
|
||||||
|
|||||||
63
.github/workflows/codeql.yml
vendored
63
.github/workflows/codeql.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -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.
|
||||||
*~
|
*~
|
||||||
|
|||||||
15
AGENTS.md
15
AGENTS.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
52
configure.ac
52
configure.ac
@@ -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
10
debian/changelog
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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`.
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
8
|
|
||||||
CACHE-1.1
|
|
||||||
28
|
|
||||||
Mon, 01 Jan 2024 00:00:00 GMT
|
|
||||||
www.example.com
|
|
||||||
/index.html
|
|
||||||
123
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
3
|
|
||||||
1.0
|
|
||||||
www.example.com
|
|
||||||
/page
|
|
||||||
5
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
32768
|
|
||||||
CACHE-1.1
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
8
|
|
||||||
CACHE-1.1
|
|
||||||
1
|
|
||||||
x
|
|
||||||
www.example.com
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
café naďve ¤
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
コンピュータ
|
|
||||||
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
&<>A☃é¬arealentity;�
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
**((
|
|
||||||
@@ -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
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
HTTP/1.0 301 Moved
|
|
||||||
Location: /elsewhere
|
|
||||||
Content-Disposition: attachment; filename="a.pdf"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
xn--bcher-kva.example.com
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
büchev.例åbücheple
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
bücher.例子.example
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<html><head><meta charset="utf-8"></head><body>x</body></html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<meta content="text/html; charset=gb2312" http-equiv="content-type">
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<meta charset='utf-8
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<meta charset = utf-8 />
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
%41%zz%%20%c3%a9+%2e%2e%2f
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
http://user:pass@www.example.com:8080/a/b/../c/./d.html?q=1#frag
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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/../
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ftp://ftp.example.com/pub/../file.txt
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
51
fuzz/fuzz.h
51
fuzz/fuzz.h
@@ -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
|
|
||||||
@@ -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
|
|
||||||
17
history.txt
17
history.txt
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
])
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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", ""},
|
||||||
|
|||||||
393
src/htsback.c
393
src/htsback.c
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
980
src/htscache.c
980
src/htscache.c
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
244
src/htscharset.c
244
src/htscharset.c
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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..) */
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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. */
|
||||||
{
|
{
|
||||||
|
|||||||
120
src/htsfilters.c
120
src/htsfilters.c
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
38
src/htslib.c
38
src/htslib.c
@@ -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);
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -2007,7 +2007,7 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
|
|||||||
|
|
||||||
/* Unescape/escape %20 and other */
|
/* Unescape/escape %20 and other */
|
||||||
{
|
{
|
||||||
// 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,
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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'
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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'
|
|
||||||
|
|||||||
@@ -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'
|
|
||||||
@@ -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')"
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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 \
|
||||||
|
|||||||
@@ -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\"" \
|
||||||
|
|||||||
@@ -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'
|
|
||||||
@@ -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'
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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'
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user