mirror of
https://github.com/xroche/httrack.git
synced 2026-07-12 20:07:43 +03:00
Compare commits
122 Commits
feature/co
...
drop-swf-j
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48c4e57a4e | ||
|
|
84ba3044f7 | ||
|
|
f73b352b82 | ||
|
|
dced0208e1 | ||
|
|
6e34c30a7c | ||
|
|
eac13ea248 | ||
|
|
dc3e485f92 | ||
|
|
fac84f1631 | ||
|
|
c32d286938 | ||
|
|
5a7ab7a653 | ||
|
|
5756a9830c | ||
|
|
28c11d55eb | ||
|
|
b5f28f6eb5 | ||
|
|
3d65bb2e3b | ||
|
|
68897b09ce | ||
|
|
e8d1bf9c19 | ||
|
|
ef40277c24 | ||
|
|
797acecfd2 | ||
|
|
8237c3ed2b | ||
|
|
75b815e1fb | ||
|
|
8d02e60907 | ||
|
|
e70607e70c | ||
|
|
cada6a1ae3 | ||
|
|
145ea4769f | ||
|
|
c5da5a2f2e | ||
|
|
6eaefe6233 | ||
|
|
33249be5a0 | ||
|
|
167ede32c0 | ||
|
|
c8b7c63c9b | ||
|
|
04f37b2094 | ||
|
|
c07b19bc3d | ||
|
|
04bbb489cf | ||
|
|
a24b1d3c9f | ||
|
|
1abf867333 | ||
|
|
771d11326e | ||
|
|
7faceaf7d2 | ||
|
|
9105732112 | ||
|
|
9c01812141 | ||
|
|
7a02d5e411 | ||
|
|
3c7e9fa46d | ||
|
|
a707d4b845 | ||
|
|
65c1016a4e | ||
|
|
fd2252dc8c | ||
|
|
53a257448f | ||
|
|
8ba3cb6c03 | ||
|
|
b9afe755bd | ||
|
|
407916e3f6 | ||
|
|
15021cd470 | ||
|
|
5f7045c3a4 | ||
|
|
f66ec195aa | ||
|
|
7928db5729 | ||
|
|
20be48d56c | ||
|
|
484fc47eab | ||
|
|
abaf9b69a2 | ||
|
|
b0466b1d7b | ||
|
|
f785286c87 | ||
|
|
440a8603a9 | ||
|
|
4979e58dc0 | ||
|
|
894cf5a8d2 | ||
|
|
0c1aa51385 | ||
|
|
fb4267c6d7 | ||
|
|
f0b044c2f3 | ||
|
|
dfafe28002 | ||
|
|
a3f04bde72 | ||
|
|
11beef52e1 | ||
|
|
d7c4eab1f5 | ||
|
|
2eac19655b | ||
|
|
83c231d50e | ||
|
|
9d29b8329b | ||
|
|
ac4a1ca48e | ||
|
|
9f2f2e52fa | ||
|
|
92db2f2b41 | ||
|
|
ec52112446 | ||
|
|
1eaddc9c0e | ||
|
|
d97a7bdfd9 | ||
|
|
d2d02d87c2 | ||
|
|
4958bb8666 | ||
|
|
07da404cb8 | ||
|
|
694e45c698 | ||
|
|
db9ec2cc3b | ||
|
|
6a9ab2a11f | ||
|
|
13b31986d5 | ||
|
|
bd7e0989f6 | ||
|
|
bd74ec7cab | ||
|
|
1ed8ffad64 | ||
|
|
b68de172fa | ||
|
|
aabfd34380 | ||
|
|
65ff9e0f11 | ||
|
|
730a1c8c5b | ||
|
|
f9ee4702a2 | ||
|
|
cca83e5f4a | ||
|
|
97f398e508 | ||
|
|
a62f93a107 | ||
|
|
799ec88dc7 | ||
|
|
71af4a24f0 | ||
|
|
e17f4f12a0 | ||
|
|
5be8ba4bbd | ||
|
|
247a46068e | ||
|
|
669947cd23 | ||
|
|
40a66600ff | ||
|
|
768756e231 | ||
|
|
b138c87a93 | ||
|
|
3de47433b7 | ||
|
|
fb8827718e | ||
|
|
7228210061 | ||
|
|
38882c0aee | ||
|
|
bfc4a016ab | ||
|
|
756d8fb8bd | ||
|
|
5501faa7b1 | ||
|
|
6322b6fb1f | ||
|
|
58f368a91a | ||
|
|
c97b3e233e | ||
|
|
b615a4e7fd | ||
|
|
594cf0da39 | ||
|
|
3845cd1fb3 | ||
|
|
94bffb0804 | ||
|
|
a5c86e7e89 | ||
|
|
54f5717057 | ||
|
|
40fc9de360 | ||
|
|
4614eefefe | ||
|
|
b0e8262db0 | ||
|
|
addbd3136b |
220
.github/workflows/ci.yml
vendored
220
.github/workflows/ci.yml
vendored
@@ -55,7 +55,54 @@ jobs:
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Test
|
||||
run: make check
|
||||
run: |
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
run: cat tests/test-suite.log 2>/dev/null || true
|
||||
|
||||
# Reproduce the Debian buildds: they build in a minimal chroot with no
|
||||
# python3, so the local-server tests must SKIP (exit 77), not fail. GitHub
|
||||
# runners ship python3, so every other job hides this path; here we remove it
|
||||
# before `make check`. This is the guard that would have caught the 3.49.10-1
|
||||
# FTBFS (28_local-pause failed instead of skipping when python3 was absent).
|
||||
buildd-no-python3:
|
||||
name: build (no python3, Debian buildd)
|
||||
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 autoconf automake libtool autoconf-archive \
|
||||
zlib1g-dev libssl-dev
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
set -euo pipefail
|
||||
autoreconf -fi
|
||||
./configure
|
||||
|
||||
- name: Build
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Test without python3
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Hide every python3* so `command -v python3` fails like it does in the
|
||||
# buildd chroot; masking with /bin/false would still resolve.
|
||||
sudo find /usr/bin /usr/local/bin -maxdepth 1 -name 'python3*' \
|
||||
-exec mv {} {}.hidden \;
|
||||
! command -v python3
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
@@ -88,13 +135,52 @@ jobs:
|
||||
- name: Build
|
||||
run: make -j"$(sysctl -n hw.ncpu)"
|
||||
|
||||
- name: Add loopback aliases (macOS lacks 127.0.0.2/.3)
|
||||
# 19_local-connect-fallback needs the dead 127.0.0.2/.3 to refuse
|
||||
# instantly like Linux; alias them onto lo0 so they don't stall to timeout.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo ifconfig lo0 alias 127.0.0.2 up
|
||||
sudo ifconfig lo0 alias 127.0.0.3 up
|
||||
|
||||
- name: Test
|
||||
run: make check
|
||||
run: |
|
||||
jobs=$(( $(sysctl -n hw.ncpu) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
run: cat tests/test-suite.log 2>/dev/null || true
|
||||
|
||||
# Runtime smoke of the WebHTTrack launcher on macOS: it carries a Darwin-only
|
||||
# browser path (open -W) and nothing else exercises htsserver. Install into a
|
||||
# temp prefix, then check webhttrack brings up htsserver and serves the UI.
|
||||
webhttrack-macos:
|
||||
name: webhttrack smoke (macOS arm64)
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
set -euo pipefail
|
||||
brew install autoconf automake libtool autoconf-archive
|
||||
|
||||
- name: Build and install into a temp prefix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ssl="$(brew --prefix openssl@3)"
|
||||
autoreconf -fi
|
||||
./configure CPPFLAGS="-I${ssl}/include" LDFLAGS="-L${ssl}/lib" \
|
||||
--prefix="$RUNNER_TEMP/inst"
|
||||
make -j"$(sysctl -n hw.ncpu)"
|
||||
make install
|
||||
|
||||
- name: Smoke-test webhttrack
|
||||
run: bash tests/webhttrack-smoke.sh "$RUNNER_TEMP/inst"
|
||||
|
||||
# Portability/hardening: 32-bit (i386) build on the x86-64 runner via multilib
|
||||
# -- no extra hardware. Exercises the 32-bit size_t/pointer ABI, where size
|
||||
# and bounds math can truncate or wrap in ways 64-bit never reveals (the axis
|
||||
@@ -127,7 +213,9 @@ jobs:
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Test
|
||||
run: make check
|
||||
run: |
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
@@ -182,12 +270,100 @@ jobs:
|
||||
env:
|
||||
ASAN_OPTIONS: detect_leaks=0:abort_on_error=1:halt_on_error=1:strict_string_checks=1:malloc_fill_byte=202:max_malloc_fill_size=2147483647:free_fill_byte=203:max_free_fill_size=2147483647
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
run: make check
|
||||
run: |
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
run: cat tests/test-suite.log 2>/dev/null || true
|
||||
|
||||
# MemorySanitizer catches reads of uninitialized memory (#143's stack-garbage
|
||||
# size filter) that ASan/UBSan miss. It flags any byte an uninstrumented lib
|
||||
# wrote, so the job stays in our own code: offline self-tests only, no openssl
|
||||
# (--disable-https), no zlib cache tests, static (the runtime is not in .so's).
|
||||
msan:
|
||||
name: msan (MemorySanitizer, 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
|
||||
|
||||
- name: Configure (MSan, static, no https)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
autoreconf -fi
|
||||
./configure CC=clang \
|
||||
CFLAGS="-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -g -O1 -fno-omit-frame-pointer" \
|
||||
LDFLAGS="-fsanitize=memory" \
|
||||
--disable-https --disable-shared --enable-static
|
||||
|
||||
- name: Build
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Test (offline self-tests under MSan)
|
||||
env:
|
||||
MSAN_OPTIONS: abort_on_error=1:halt_on_error=1
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 01_engine-* only; zlib-dependent self-tests are named 01_zlib-* and
|
||||
# skipped here (uninstrumented libz floods MSan with false positives).
|
||||
tests="$(cd tests && ls 01_engine-*.test | tr '\n' ' ')"
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs" TESTS="$tests"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
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 --
|
||||
# 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
|
||||
@@ -218,7 +394,9 @@ jobs:
|
||||
run: make -j"$(nproc)"
|
||||
|
||||
- name: Test
|
||||
run: make check
|
||||
run: |
|
||||
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||
make check -j"$jobs"
|
||||
|
||||
- name: Print the test log on failure
|
||||
if: failure()
|
||||
@@ -232,30 +410,42 @@ jobs:
|
||||
deb:
|
||||
name: deb package (lintian)
|
||||
runs-on: ubuntu-24.04
|
||||
# Build and gate inside Debian sid, the upload target. A Debian dpkg-deb
|
||||
# produces archive-legal xz members (an Ubuntu host defaults to zstd, which
|
||||
# the archive's lintian rejects), and sid's lintian carries the same
|
||||
# data-driven checks (embedded-lib fingerprints and the like) the buildds and
|
||||
# UDD apply -- so issues surface here instead of after upload.
|
||||
container: debian:sid
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install packaging toolchain
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates git \
|
||||
build-essential autoconf automake libtool autoconf-archive \
|
||||
zlib1g-dev libssl-dev \
|
||||
debhelper devscripts lintian fakeroot
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# --unsigned: CI has no GPG key (also skips the release sig/checksums).
|
||||
# debuild builds every package, then lintian gates on errors.
|
||||
# mkdeb builds every package then runs the lintian gate (--fail-on=error,
|
||||
# warning); debuild runs the packaged test pass.
|
||||
#
|
||||
# DEB_BUILD_OPTIONS trims work CI does not need (release builds via
|
||||
# mkdeb.sh are untouched): noautodbgsym drops the -dbgsym packages whose
|
||||
# LTO payloads are slow to compress and that CI never ships; parallel uses
|
||||
# every core. We let debuild run its test pass -- the only one now that
|
||||
# mkdeb no longer runs its own -- so CI exercises the packaged tests.
|
||||
- name: Build Debian packages
|
||||
# every core.
|
||||
- name: Build and lint Debian packages
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The workspace volume is owned by the host runner uid, but the
|
||||
# container runs as root, so mkdeb's git calls (superproject and the
|
||||
# coucal submodule) trip "dubious ownership"; mark them all safe.
|
||||
git config --global --add safe.directory "*"
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym parallel=$(nproc)"
|
||||
bash tools/mkdeb.sh --unsigned --no-release-artifacts
|
||||
|
||||
|
||||
63
.github/workflows/codeql.yml
vendored
Normal file
63
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# 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"
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -34,8 +34,12 @@ Makefile
|
||||
*.so.*
|
||||
*.a
|
||||
|
||||
# make dist output.
|
||||
# make dist output; dist/ holds httrack-gh-release staging artifacts.
|
||||
/httrack-*.tar.gz
|
||||
/dist/
|
||||
|
||||
# Editor / autotools backup files.
|
||||
*~
|
||||
|
||||
# Python bytecode (tests/local-server.py).
|
||||
__pycache__/
|
||||
|
||||
20
AGENTS.md
20
AGENTS.md
@@ -6,8 +6,19 @@ the operational checklist: toolchain, invariants, and how to ship a change.
|
||||
## Build & test
|
||||
- Fresh clone first: `git submodule update --init src/coucal`
|
||||
- `./bootstrap` (regenerates `configure` via `autoreconf`; needs autoconf,
|
||||
automake, libtool), then `bash configure && make && make check`. Or run
|
||||
`sh build.sh` to do bootstrap + configure + make in one shot.
|
||||
automake, libtool), then `bash configure && make -j"$(nproc)" && make check
|
||||
-j"$(nproc)"`. Always pass `-j` to `make check`: the suite runs under
|
||||
automake's parallel harness and each crawl test binds its own ephemeral-port
|
||||
server, so `-j` never contends and a multi-minute serial run drops to
|
||||
seconds. A new `.test` added to `$(TESTS)` is scheduled onto a free worker
|
||||
automatically; only a test slower than the current longest raises the floor.
|
||||
On a few-core Linux box, `-j` at 2x the core count is faster still: the tests
|
||||
spend much of their wall time asleep (server trickles, httrack self-pacing),
|
||||
so an idle core covers a sleeping one. CI uses `min(2*cores, 16)` on every
|
||||
platform, macOS included: the test server raises its listen backlog
|
||||
(`request_queue_size`) so macOS/BSD don't drop connections under a parallel
|
||||
`-c16` bigcrawl the way Python's default backlog of 5 did.
|
||||
Or run `sh build.sh` to do bootstrap + configure + make in one shot.
|
||||
|
||||
## Hard invariants
|
||||
- **Generated autotools files are NOT in git.** `configure`, every
|
||||
@@ -33,8 +44,9 @@ the operational checklist: toolchain, invariants, and how to ship a change.
|
||||
- Be terse. Comment the why, in English; translate French comments you touch.
|
||||
- Strip AI tells from prose (em-dash overuse, rule-of-three, filler, vague
|
||||
attributions). Ref: Wikipedia "Signs of AI writing". Claude Code: `/humanizer`.
|
||||
- Behavior change → add a test. Fast path: a hidden `httrack -#N` debug
|
||||
subcommand (`htscoremain.c`) driven by a `tests/NN_*.test`, over a slow crawl.
|
||||
- Behavior change → add a test. Fast path: a hidden `httrack -#test=NAME` engine
|
||||
self-test (registry in `htsselftest.c`; `-#test` lists them) driven by a
|
||||
`tests/NN_*.test`, over a slow crawl.
|
||||
|
||||
## Review your change adversarially (strongly suggested)
|
||||
Before pushing, and when reviewing others, don't skim for bugs:
|
||||
|
||||
@@ -39,6 +39,10 @@ Welcome, and nothing to disclose. Two rules:
|
||||
|
||||
The sign-off covers AI-assisted code too.
|
||||
|
||||
## Translations
|
||||
|
||||
Interface strings live in [`lang/`](lang/). See [lang/README.md](lang/README.md) for the file format and how to add or update a language.
|
||||
|
||||
## Bugs
|
||||
|
||||
Open an issue with the version, OS, command used, and expected vs actual result.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
SUBDIRS = src man m4 libtest templates lang html tests
|
||||
SUBDIRS = src man m4 libtest templates lang html tests fuzz
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
|
||||
1
README
1
README
@@ -40,7 +40,6 @@ These are the principals limits of HTTrack for that moment. Note that we did not
|
||||
that would have solved them.
|
||||
|
||||
- Several scripts generating complex filenames may not find them (ex: img.src='image'+a+Mobj.dst+'.gif')
|
||||
- Some java classes may not find some files on them (class included)
|
||||
- Cgi-bin links may not work properly in some cases (parameters needed). To avoid them: use filters like -*cgi-bin*
|
||||
|
||||
|
||||
|
||||
59
configure.ac
59
configure.ac
@@ -1,6 +1,6 @@
|
||||
AC_PREREQ([2.71])
|
||||
|
||||
AC_INIT([httrack], [3.49.8], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_INIT([httrack], [3.49.12], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_COPYRIGHT([
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 1998-2015 Xavier Roche and other contributors
|
||||
@@ -29,9 +29,11 @@ AC_CONFIG_SRCDIR(src/httrack.c)
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
# 3:0:0: htsblk layout changed (contenttype/charset/contentencoding widened to
|
||||
# 128), an incompatible ABI break, so bump current and reset revision/age.
|
||||
VERSION_INFO="3:0:0"
|
||||
# 3:4:0: 3.49.12 tail-appends one field (why_url) to httrackp and otherwise only
|
||||
# deletes dead comments; no struct layout or exported signature broke vs 3.49.11,
|
||||
# so it stays soname .so.3; bump revision.
|
||||
# (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.)
|
||||
VERSION_INFO="3:4:0"
|
||||
AM_MAINTAINER_MODE
|
||||
AC_USE_SYSTEM_EXTENSIONS
|
||||
|
||||
@@ -90,7 +92,8 @@ 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=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([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])
|
||||
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-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-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"])
|
||||
@@ -98,9 +101,16 @@ 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,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
|
||||
# libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style
|
||||
# linker; Apple ld rejects these flags and links libSystem unconditionally.
|
||||
# libhttrack (the libtest callbacks), but only with a GNU-style linker; Apple
|
||||
# ld rejects these flags and links libSystem unconditionally.
|
||||
AX_CHECK_LINK_FLAG([-Wl,--push-state,--no-as-needed,-lc,--pop-state],
|
||||
[LIBC_FORCE_LINK="-Wl,--push-state,--no-as-needed,-lc,--pop-state"])
|
||||
AC_SUBST([LIBC_FORCE_LINK])
|
||||
@@ -214,9 +224,12 @@ AC_SUBST(OPENSSL_LIBS)
|
||||
fi
|
||||
|
||||
### Support IPv6
|
||||
V6_SUPPORT=no
|
||||
AC_CHECK_LIB(c, getaddrinfo, [V6_FLAG="-DINET6"
|
||||
V6_SUPPORT=yes
|
||||
AC_DEFINE(HTS_INET6, 1, [Check for IPv6])], AC_MSG_WARN([*** IPv6 not found IPv6 compatibility disabled]))
|
||||
AC_SUBST(V6_FLAG)
|
||||
AC_SUBST(V6_SUPPORT)
|
||||
|
||||
### Check for LFS
|
||||
AC_CHECK_LIB(c, fopen64, [LFS_FLAG="-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE"
|
||||
@@ -295,6 +308,37 @@ AC_ARG_ENABLE([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
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
@@ -306,5 +350,6 @@ lang/Makefile
|
||||
html/Makefile
|
||||
libtest/Makefile
|
||||
tests/Makefile
|
||||
fuzz/Makefile
|
||||
])
|
||||
AC_OUTPUT
|
||||
|
||||
55
debian/changelog
vendored
55
debian/changelog
vendored
@@ -1,3 +1,58 @@
|
||||
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
|
||||
|
||||
* New upstream release: crawl correctness and security fixes (network-facing
|
||||
buffer overflows, file-type detection, redirect handling) and modernized
|
||||
web defaults; full list in history.txt.
|
||||
* Add DEP-12 upstream metadata (#466).
|
||||
* Bump debhelper compat to 14 (#466).
|
||||
* Drop the redundant Priority field and update the NMU lintian override to
|
||||
the current tag names (#466).
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Sun, 05 Jul 2026 00:03:18 +0200
|
||||
|
||||
httrack (3.49.10-2) unstable; urgency=medium
|
||||
|
||||
* Fix FTBFS: tests/28_local-pause failed instead of skipping when python3 is
|
||||
absent (the local-server tests need python3, which the buildds lack). Add
|
||||
patches/skip-local-pause-test-without-python3.patch to guard the test on
|
||||
python3 up front, like its siblings, so it skips cleanly.
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Sun, 28 Jun 2026 20:18:46 +0200
|
||||
|
||||
httrack (3.49.10-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: new download-pacing and URL-handling options plus a
|
||||
batch of crawl and robustness fixes (full list in history.txt).
|
||||
* Rewrite debian/copyright in machine-readable DEP-5 format, crediting the
|
||||
bundled minizip, md5 and coucal sources (#415).
|
||||
* Lead the webhttrack browser dependency with chromium so httrack is not
|
||||
dragged into the firefox-esr autoremoval cascade (#436).
|
||||
* Override the embedded-library lint for the bundled minizip (#419).
|
||||
* Bump Standards-Version to 4.7.4 (no changes required).
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Sun, 28 Jun 2026 14:01:53 +0200
|
||||
|
||||
httrack (3.49.9-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: Content-Type and file-type detection fixes (trust a
|
||||
declared Content-Type over a binary URL extension, honor --assume under the
|
||||
delayed type check, keep a known extension against a bogus or empty
|
||||
Content-Type, and avoid an uninitialised read on an empty Content-Type), and
|
||||
restored C++ source-compatibility of the installed headers so reverse
|
||||
dependencies (httraqt) build again.
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Sun, 21 Jun 2026 17:59:38 +0200
|
||||
|
||||
httrack (3.49.8-2) unstable; urgency=medium
|
||||
|
||||
* Rename libhttrack2 to libhttrack3 to follow the SONAME, which the 3.49.8
|
||||
|
||||
7
debian/control
vendored
7
debian/control
vendored
@@ -1,9 +1,8 @@
|
||||
Source: httrack
|
||||
Section: web
|
||||
Priority: optional
|
||||
Maintainer: Xavier Roche <roche@httrack.com>
|
||||
Standards-Version: 4.7.0
|
||||
Build-Depends: debhelper-compat (= 13), autoconf, autoconf-archive, automake, libtool, zlib1g-dev, libssl-dev
|
||||
Standards-Version: 4.7.4
|
||||
Build-Depends: debhelper-compat (= 14), autoconf, autoconf-archive, automake, libtool, zlib1g-dev, libssl-dev
|
||||
Rules-Requires-Root: no
|
||||
Homepage: http://www.httrack.com
|
||||
Vcs-Git: https://github.com/xroche/httrack.git
|
||||
@@ -30,7 +29,7 @@ Description: Copy websites to your computer (Offline browser)
|
||||
Package: webhttrack
|
||||
Architecture: any
|
||||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, webhttrack-common, sensible-utils, firefox-esr | chromium | www-browser
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}, webhttrack-common, sensible-utils, chromium | firefox-esr | www-browser
|
||||
Replaces: webhttrack-common (<< 3.43.9-2)
|
||||
Breaks: webhttrack-common (<< 3.43.9-2)
|
||||
Suggests: httrack, httrack-doc
|
||||
|
||||
117
debian/copyright
vendored
117
debian/copyright
vendored
@@ -1,21 +1,108 @@
|
||||
This package was debianized by Xavier Roche <roche@httrack.com> on
|
||||
Fri, 27 Sep 2002 16:42:26 +0200
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: httrack
|
||||
Upstream-Contact: Xavier Roche <roche@httrack.com>
|
||||
Source: https://www.httrack.com/
|
||||
|
||||
The current Debian maintainer is Xavier Roche <xavier@debian.org>
|
||||
Files: *
|
||||
Copyright: 1998-2026 Xavier Roche and other contributors
|
||||
License: GPL-3+
|
||||
Comment:
|
||||
htsbasenet.h links against the system OpenSSL library (originally by Eric
|
||||
Young); no OpenSSL/SSLeay code is bundled here.
|
||||
|
||||
Upstream author: Xavier Roche <roche@httrack.com>
|
||||
Files: src/minizip/*
|
||||
Copyright: 1998-2010 Gilles Vollant
|
||||
2007-2008 Even Rouault
|
||||
2009-2010 Mathias Svensson
|
||||
1990-2000 Info-ZIP
|
||||
License: Zlib
|
||||
Comment:
|
||||
The decryption code in src/minizip/crypt.h and src/minizip/unzip.c derives
|
||||
from the Info-ZIP distribution, distributed under the same terms.
|
||||
|
||||
Copyright: 1998-2014 Xavier Roche and other contributors
|
||||
Files: src/md5.c
|
||||
Copyright: 1993 Colin Plumb
|
||||
License: public-domain-md5
|
||||
This code implements the MD5 message-digest algorithm, due to Ron Rivest.
|
||||
It was written by Colin Plumb in 1993, no copyright is claimed. This code
|
||||
is in the public domain; do with it what you wish.
|
||||
|
||||
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.
|
||||
Files: src/coucal/*
|
||||
Copyright: 2013-2014 Xavier Roche
|
||||
License: BSD-3-clause
|
||||
|
||||
On Debian systems, the complete text of the GNU General Public
|
||||
License version 3 can be found in /usr/share/common-licenses/GPL-3 file.
|
||||
Files: src/coucal/murmurhash3.h*
|
||||
Copyright: Austin Appleby
|
||||
License: public-domain-murmurhash3
|
||||
MurmurHash3 was written by Austin Appleby, and is placed in the public
|
||||
domain. The author hereby disclaims copyright to this source code.
|
||||
|
||||
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.
|
||||
Files: html/server/div/com.httrack.WebHTTrack.metainfo.xml
|
||||
Copyright: 1998-2026 Xavier Roche and other contributors
|
||||
License: FSFAP
|
||||
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.
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2002-2026 Xavier Roche <xavier@debian.org>
|
||||
License: GPL-3+
|
||||
|
||||
License: GPL-3+
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
.
|
||||
On Debian systems, the complete text of the GNU General Public License
|
||||
version 3 can be found in /usr/share/common-licenses/GPL-3.
|
||||
|
||||
License: Zlib
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the
|
||||
use of this software.
|
||||
.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
.
|
||||
1. The origin of this software must not be misrepresented; you must not claim
|
||||
that you wrote the original software. If you use this software in a product,
|
||||
an acknowledgment in the product documentation would be appreciated but is
|
||||
not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
License: BSD-3-clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
.
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
1
debian/libhttrack-dev.files
vendored
1
debian/libhttrack-dev.files
vendored
@@ -1,5 +1,4 @@
|
||||
usr/include/httrack
|
||||
usr/lib/*/libhttrack.{so}
|
||||
usr/lib/*/libhtsjava.{so}
|
||||
usr/share/httrack/libtest
|
||||
usr/lib/*/httrack/libtest
|
||||
|
||||
1
debian/libhttrack3.files
vendored
1
debian/libhttrack3.files
vendored
@@ -1,3 +1,2 @@
|
||||
usr/lib/*/libhttrack.so.3*
|
||||
usr/lib/*/libhtsjava.so.3*
|
||||
usr/share/httrack/templates
|
||||
|
||||
5
debian/libhttrack3.lintian-overrides
vendored
5
debian/libhttrack3.lintian-overrides
vendored
@@ -1,3 +1,8 @@
|
||||
# The shared libraries ship without a versioned symbols control file (ABI is
|
||||
# tracked via the SONAME plus a >= upstream-version dependency, see debian/rules).
|
||||
libhttrack3: no-symbols-control-file usr/lib/*
|
||||
|
||||
# Bundled, locally patched minizip (src/minizip): it adds a zipFlush() API the
|
||||
# system libminizip lacks (htscache.c flushes the cache .zip so an interrupted
|
||||
# crawl leaves a valid archive), plus Android/old-zlib portability fixes.
|
||||
libhttrack3: embedded-library *libminizip*
|
||||
|
||||
3
debian/proxytrack.lintian-overrides
vendored
Normal file
3
debian/proxytrack.lintian-overrides
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Statically linked against httrack's bundled, patched minizip (see src/minizip
|
||||
# and libhttrack3's override): the zipFlush() API is absent from the system one.
|
||||
proxytrack: embedded-library *libminizip*
|
||||
4
debian/source/lintian-overrides
vendored
4
debian/source/lintian-overrides
vendored
@@ -1,4 +1,6 @@
|
||||
httrack source: changelog-should-mention-nmu
|
||||
# Maintainer uploads sign the changelog as xavier@debian.org while the control
|
||||
# Maintainer is roche@httrack.com; lintian reads the address mismatch as an NMU.
|
||||
httrack source: no-nmu-in-changelog
|
||||
httrack source: source-nmu-has-incorrect-version-number
|
||||
|
||||
# The bundled HTML pages are the genuine upstream documentation taken from
|
||||
|
||||
6
debian/upstream/metadata
vendored
Normal file
6
debian/upstream/metadata
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
Repository: https://github.com/xroche/httrack.git
|
||||
Repository-Browse: https://github.com/xroche/httrack
|
||||
Bug-Database: https://github.com/xroche/httrack/issues
|
||||
Bug-Submit: https://github.com/xroche/httrack/issues/new
|
||||
Contact: Xavier Roche <roche@httrack.com>
|
||||
45
fuzz/Makefile.am
Normal file
45
fuzz/Makefile.am
Normal file
@@ -0,0 +1,45 @@
|
||||
# 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
|
||||
15
fuzz/README.md
Normal file
15
fuzz/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 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`.
|
||||
7
fuzz/corpus/cachendx/new-format.txt
Normal file
7
fuzz/corpus/cachendx/new-format.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
8
|
||||
CACHE-1.1
|
||||
28
|
||||
Mon, 01 Jan 2024 00:00:00 GMT
|
||||
www.example.com
|
||||
/index.html
|
||||
123
|
||||
5
fuzz/corpus/cachendx/old-format.txt
Normal file
5
fuzz/corpus/cachendx/old-format.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
3
|
||||
1.0
|
||||
www.example.com
|
||||
/page
|
||||
5
|
||||
2
fuzz/corpus/cachendx/regress-overadvance.bin
Normal file
2
fuzz/corpus/cachendx/regress-overadvance.bin
Normal file
@@ -0,0 +1,2 @@
|
||||
32768
|
||||
CACHE-1.1
|
||||
5
fuzz/corpus/cachendx/regress-truncated-entry.bin
Normal file
5
fuzz/corpus/cachendx/regress-truncated-entry.bin
Normal file
@@ -0,0 +1,5 @@
|
||||
8
|
||||
CACHE-1.1
|
||||
1
|
||||
x
|
||||
www.example.com
|
||||
1
fuzz/corpus/charset/latin1.txt
Normal file
1
fuzz/corpus/charset/latin1.txt
Normal file
@@ -0,0 +1 @@
|
||||
café naďve ¤
|
||||
1
fuzz/corpus/charset/sjis.txt
Normal file
1
fuzz/corpus/charset/sjis.txt
Normal file
@@ -0,0 +1 @@
|
||||
コンピュータ
|
||||
BIN
fuzz/corpus/charset/utf8.txt
Normal file
BIN
fuzz/corpus/charset/utf8.txt
Normal file
Binary file not shown.
1
fuzz/corpus/entities/entities.txt
Normal file
1
fuzz/corpus/entities/entities.txt
Normal file
@@ -0,0 +1 @@
|
||||
&<>A☃é¬arealentity;�
|
||||
BIN
fuzz/corpus/filters/filter-size.bin
Normal file
BIN
fuzz/corpus/filters/filter-size.bin
Normal file
Binary file not shown.
BIN
fuzz/corpus/filters/filter.bin
Normal file
BIN
fuzz/corpus/filters/filter.bin
Normal file
Binary file not shown.
BIN
fuzz/corpus/filters/redos-star-classes.bin
Normal file
BIN
fuzz/corpus/filters/redos-star-classes.bin
Normal file
Binary file not shown.
1
fuzz/corpus/filters/regress-empty-subject-unique.bin
Normal file
1
fuzz/corpus/filters/regress-empty-subject-unique.bin
Normal file
@@ -0,0 +1 @@
|
||||
**((
|
||||
10
fuzz/corpus/header/full-response.txt
Normal file
10
fuzz/corpus/header/full-response.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
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
|
||||
3
fuzz/corpus/header/redirect.txt
Normal file
3
fuzz/corpus/header/redirect.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
HTTP/1.0 301 Moved
|
||||
Location: /elsewhere
|
||||
Content-Disposition: attachment; filename="a.pdf"
|
||||
1
fuzz/corpus/idna/idna.txt
Normal file
1
fuzz/corpus/idna/idna.txt
Normal file
@@ -0,0 +1 @@
|
||||
xn--bcher-kva.example.com
|
||||
1
fuzz/corpus/idna/regress-multilabel-leak.txt
Normal file
1
fuzz/corpus/idna/regress-multilabel-leak.txt
Normal file
@@ -0,0 +1 @@
|
||||
büchev.例åbücheple
|
||||
1
fuzz/corpus/idna/unicode.txt
Normal file
1
fuzz/corpus/idna/unicode.txt
Normal file
@@ -0,0 +1 @@
|
||||
bücher.例子.example
|
||||
1
fuzz/corpus/meta/meta-charset.html
Normal file
1
fuzz/corpus/meta/meta-charset.html
Normal file
@@ -0,0 +1 @@
|
||||
<html><head><meta charset="utf-8"></head><body>x</body></html>
|
||||
1
fuzz/corpus/meta/meta-content-first.html
Normal file
1
fuzz/corpus/meta/meta-content-first.html
Normal file
@@ -0,0 +1 @@
|
||||
<meta content="text/html; charset=gb2312" http-equiv="content-type">
|
||||
1
fuzz/corpus/meta/meta-http-equiv.html
Normal file
1
fuzz/corpus/meta/meta-http-equiv.html
Normal file
@@ -0,0 +1 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
1
fuzz/corpus/meta/meta-truncated.html
Normal file
1
fuzz/corpus/meta/meta-truncated.html
Normal file
@@ -0,0 +1 @@
|
||||
<meta charset='utf-8
|
||||
1
fuzz/corpus/meta/meta-unquoted.html
Normal file
1
fuzz/corpus/meta/meta-unquoted.html
Normal file
@@ -0,0 +1 @@
|
||||
<meta charset = utf-8 />
|
||||
1
fuzz/corpus/unescape/percent.txt
Normal file
1
fuzz/corpus/unescape/percent.txt
Normal file
@@ -0,0 +1 @@
|
||||
%41%zz%%20%c3%a9+%2e%2e%2f
|
||||
1
fuzz/corpus/url/http-url.txt
Normal file
1
fuzz/corpus/url/http-url.txt
Normal file
@@ -0,0 +1 @@
|
||||
http://user:pass@www.example.com:8080/a/b/../c/./d.html?q=1#frag
|
||||
1
fuzz/corpus/url/regress-file-empty-path.txt
Normal file
1
fuzz/corpus/url/regress-file-empty-path.txt
Normal file
@@ -0,0 +1 @@
|
||||
file://
|
||||
1
fuzz/corpus/url/regress-long-path-abort.txt
Normal file
1
fuzz/corpus/url/regress-long-path-abort.txt
Normal file
@@ -0,0 +1 @@
|
||||
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
fuzz/corpus/url/relative-path.txt
Normal file
1
fuzz/corpus/url/relative-path.txt
Normal file
@@ -0,0 +1 @@
|
||||
ftp://ftp.example.com/pub/../file.txt
|
||||
65
fuzz/fuzz-cachendx.c
Normal file
65
fuzz/fuzz-cachendx.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
84
fuzz/fuzz-charset.c
Normal file
84
fuzz/fuzz-charset.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
51
fuzz/fuzz-entities.c
Normal file
51
fuzz/fuzz-entities.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
65
fuzz/fuzz-filters.c
Normal file
65
fuzz/fuzz-filters.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
74
fuzz/fuzz-header.c
Normal file
74
fuzz/fuzz-header.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
47
fuzz/fuzz-idna.c
Normal file
47
fuzz/fuzz-idna.c
Normal file
@@ -0,0 +1,47 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
40
fuzz/fuzz-meta.c
Normal file
40
fuzz/fuzz-meta.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
53
fuzz/fuzz-unescape.c
Normal file
53
fuzz/fuzz-unescape.c
Normal file
@@ -0,0 +1,53 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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;
|
||||
}
|
||||
49
fuzz/fuzz-url.c
Normal file
49
fuzz/fuzz-url.c
Normal file
@@ -0,0 +1,49 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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
Normal file
51
fuzz/fuzz.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
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
|
||||
41
fuzz/run-fuzzers.sh
Executable file
41
fuzz/run-fuzzers.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/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
|
||||
58
history.txt
58
history.txt
@@ -4,6 +4,64 @@ HTTrack Website Copier release history:
|
||||
|
||||
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
|
||||
+ 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: follow <source> and <track> media elements as embedded links (#451)
|
||||
+ New: added modern web MIME types to the type/extension table (#448)
|
||||
+ Fixed: enforce the -E time limit during a slow transfer instead of only between files (#481)
|
||||
+ Fixed: sniff the leading bytes of a download so a misdeclared Content-Type no longer renames a correct URL extension
|
||||
+ Fixed: fast transfers could be saved under their temporary .delayed placeholder name (#5, #107)
|
||||
+ Fixed: follow a redirect that maps to the same saved file instead of writing a self-pointing stub (#159)
|
||||
+ Fixed: several network-facing buffer overflows in the FTP, Java and HTML parsers
|
||||
+ Fixed: the htsjava plugin could not be loaded (hidden entry points, stale library name)
|
||||
+ Fixed: HTML-escape truncation and a cache-buffer leak in the parser
|
||||
+ Changed: modernized the default User-Agent to an honest HTTrack identifier (#449)
|
||||
+ Changed: decode the full WHATWG set of HTML named character references (#443)
|
||||
+ Changed: refreshed stale HTTP status, proxy-port and TLS-floor constants (#453)
|
||||
+ Changed: multiple internal hardening, build, test and CI improvements
|
||||
|
||||
3.49-10
|
||||
+ New: --cookies-file to preload a Netscape cookies.txt before crawling (#215)
|
||||
+ New: --pause to space out file downloads by a random delay (#185)
|
||||
+ New: --strip-query to drop selected query keys from the dedup naming (#112)
|
||||
+ Changed: split the -%u URL hacks into independent --keep-www-prefix, --keep-double-slashes and --keep-query-order toggles (#271)
|
||||
+ Fixed: follow a redirect Location after dropping its #fragment, instead of requesting the fragment and polluting the saved name (#204)
|
||||
+ Fixed: escaped brackets inside a *[...] filter character class (#148)
|
||||
+ Fixed: honor the server's Content-Range when resuming a partial download, instead of appending overlapping bytes (#198)
|
||||
+ Fixed: abort the download as soon as the response type is excluded by -mime:, instead of fetching then discarding the body (#58)
|
||||
+ Fixed: keep size-based filter rules neutral until the file size is known (#143)
|
||||
+ Fixed: stop the mirror with a clean fatal error on a cache write failure, instead of crashing (#174, #219)
|
||||
+ Fixed: stop the 412/416 partial re-get loop on --continue and --update (#206)
|
||||
+ Fixed: keep an unrecognized URL tail instead of mangling it to .html (#115)
|
||||
+ Fixed: honor --tolerant (-%B) on a broken Content-Length, and fix an out-of-bounds read it exposed (#32, #41)
|
||||
+ Fixed: fall back to the next resolved address when a connection fails or stalls, instead of hanging on a dead IPv6 address
|
||||
+ Fixed: report why a -%L URL list could not be loaded (#49)
|
||||
+ Changed: multiple internal hardening, build and CI improvements
|
||||
|
||||
.49-9
|
||||
+ Fixed: file-type detection from the Content-Type header: trust a declared type over a binary URL extension, honor --assume under the delayed type check, and keep a known extension against a bogus or empty Content-Type (#267, #29, #56)
|
||||
+ Fixed: an uninitialized-buffer read when the Content-Type is empty (#411)
|
||||
+ Fixed: restored C++ source-compatibility of the installed headers so reverse dependencies (httraqt) build again (#413)
|
||||
+ Changed: multiple internal build, packaging and test-harness improvements
|
||||
|
||||
3.49-8
|
||||
+ New: tunnel HTTPS downloads through the configured HTTP proxy via CONNECT (#85)
|
||||
+ New: parse every candidate URL in <img> and <source> srcset lists (#326)
|
||||
|
||||
@@ -609,10 +609,9 @@ most mirrors do work. We still are working to improve the mirror quality of HTTr
|
||||
<br>
|
||||
|
||||
</a><a NAME="Q2b">Q: <strong>Some Java applets do not work properly!</strong><br>
|
||||
A: <em>Java applets may not work in some cases, for example if HTTrack failed to detect all included classes
|
||||
or files called within the class file. Sometimes, Java applets need to be online, because remote files are
|
||||
directly caught. Finally, the site structure can be incompatible with the class (always try to keep the original site structure
|
||||
when you want to get Java classes)<br>
|
||||
A: <em>Java applets may not work in some cases, because HTTrack does not parse compiled class files to find
|
||||
the resources they load. Java applets often need to be online anyway, since remote files are fetched directly
|
||||
at runtime.<br>
|
||||
If there is no way to make some classes work properly, you can exclude them with the filters.
|
||||
They will be available, but only online.
|
||||
</em>
|
||||
|
||||
@@ -243,7 +243,7 @@ Build options:
|
||||
Spider options:
|
||||
bN accept cookies in cookies.txt (0=do not accept,* 1=accept) (--cookies[=N])
|
||||
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (--check-type[=N])
|
||||
j *parse Java Classes (j0 don't parse) (--parse-java[=N])
|
||||
j *parse scripts (j0 don't parse) (--parse-java[=N])
|
||||
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always) (--robots[=N])
|
||||
%h force HTTP/1.0 requests (reduce update features, only for old servers or proxies) (--http-10)
|
||||
%B tolerant requests (accept bogus responses on some servers, but not standard!) (--tolerant)
|
||||
@@ -417,7 +417,7 @@ site. Specifically, the defauls are:
|
||||
X *purge old files after update (X0 keep delete)
|
||||
bN accept cookies in cookies.txt (0=do not accept,* 1=accept)
|
||||
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)
|
||||
j *parse Java Classes (j0 don't parse)
|
||||
j *parse scripts (j0 don't parse)
|
||||
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always)
|
||||
C create/use a cache for updates and retries (C0 no cache,C1 cache is prioritary,* C2 test update before)
|
||||
f *log file mode
|
||||
@@ -497,9 +497,9 @@ about your browser retaining them.
|
||||
<p align=justify> This causes different document types to be analyzed
|
||||
differently.
|
||||
|
||||
<pre><b><i> j *parse Java Classes (j0 don't parse) </i></b></pre>
|
||||
<pre><b><i> j *parse scripts (j0 don't parse) </i></b></pre>
|
||||
|
||||
<p align=justify> This causes Java class files to be parsed looking for
|
||||
<p align=justify> This causes scripts to be parsed looking for
|
||||
URLs.
|
||||
|
||||
<pre><b><i> sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always) </i></b></pre>
|
||||
@@ -1178,7 +1178,7 @@ accepted and sent back in order to allow access.
|
||||
Spider options:
|
||||
bN accept cookies in cookies.txt (0=do not accept,* 1=accept)
|
||||
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)
|
||||
j *parse Java Classes (j0 don't parse)
|
||||
j *parse scripts (j0 don't parse)
|
||||
sN follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always)
|
||||
%h force HTTP/1.0 requests (reduce update features, only for old servers or proxies)
|
||||
%B tolerant requests (accept bogus responses on some servers, but not standard!)
|
||||
@@ -2451,12 +2451,10 @@ work. We still are working to improve the mirror quality of HTTrack.
|
||||
|
||||
<p align=justify><b>Q: Some Java applets do not work properly! </b>
|
||||
|
||||
<p align=justify> A: Java applets may not work in some cases, for
|
||||
example if HTTrack failed to detect all included classes or files called
|
||||
within the class file. Sometimes, Java applets need to be online,
|
||||
because remote files are directly caught. Finally, the site structure
|
||||
can be incompatible with the class (always try to keep the original site
|
||||
structure when you want to get Java classes)
|
||||
<p align=justify> A: Java applets may not work in some cases, because
|
||||
HTTrack does not parse compiled class files to find the resources they
|
||||
load. Java applets often need to be online anyway, since remote files
|
||||
are fetched directly at runtime.
|
||||
|
||||
<p align=justify> If there is no way to make some classes work properly,
|
||||
you can exclude them with the filters. They will be available, but only
|
||||
|
||||
@@ -247,7 +247,7 @@ See also: The <a href="faq.html#VF1">FAQ</a><br>
|
||||
<td>the \ character</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td nowrap><tt>*[\[\]]</tt></td>
|
||||
<td nowrap><tt>*[\[,\]]</tt></td>
|
||||
<td>the [ or ] character</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -764,9 +764,9 @@ check, * u1 check but /, u2 check always)
|
||||
<td width="78%">
|
||||
|
||||
|
||||
<p>*parse Java Classes (j0 don t parse, bitmask: |1 parse
|
||||
default, |2 don t parse .class |4 don t parse .js |8 don t
|
||||
be aggressive) (--parse-java[=N])</p></td></tr>
|
||||
<p>*parse scripts (j0 don t parse, bitmask: |1 parse
|
||||
default, |4 don t parse .js |8 don t be aggressive)
|
||||
(--parse-java[=N])</p></td></tr>
|
||||
<tr valign="top" align="left">
|
||||
<td width="11%"></td>
|
||||
<td width="6%">
|
||||
@@ -889,8 +889,8 @@ only) (--protocol[=N])</p></td></tr>
|
||||
<td width="78%">
|
||||
|
||||
|
||||
<p>disable a specific external mime module (-%w htsswf -%w
|
||||
htsjava) (--disable-module <param>)</p></td></tr>
|
||||
<p>disable a specific external mime module (-%w
|
||||
httrack-plugin) (--disable-module <param>)</p></td></tr>
|
||||
</table>
|
||||
|
||||
<p style="margin-left:11%; margin-top: 1em"><b>Browser
|
||||
@@ -2175,10 +2175,6 @@ Several scripts generating complex filenames may not find
|
||||
them (ex:
|
||||
img.src=’image’+a+Mobj.dst+’.gif’)</small></p>
|
||||
|
||||
<p style="margin-left:11%; margin-top: 1em"><small>- Some
|
||||
java classes may not find some files on them (class
|
||||
included)</small></p>
|
||||
|
||||
<p style="margin-left:11%; margin-top: 1em"><small>-
|
||||
Cgi-bin links may not work properly in some cases
|
||||
(parameters needed). To avoid them: use filters like
|
||||
|
||||
@@ -288,8 +288,8 @@ This option define whether the engine has to abandon a host if a timeout/"too sl
|
||||
&P extended parsing, attempt to parse all links (even in unknown tags or Javascript)
|
||||
This option activates the extended parsing, that attempt to find links in unknown Html code/javascript
|
||||
|
||||
j *parse Java Classes (j0 don't parse)
|
||||
This option define whether the engine has to parse java files or not to catch included files
|
||||
j *parse scripts (j0 don't parse)
|
||||
This option defines whether the engine has to parse scripts or not to catch included files
|
||||
|
||||
I *make an index (I0 don't make)
|
||||
This option define whether the engine has to generate an index.html on the top directory
|
||||
|
||||
413
html/plug.html
413
html/plug.html
@@ -103,220 +103,219 @@ h4 { margin: 0; font-weight: bold; font-size: 1.18em; }
|
||||
|
||||
<br>
|
||||
|
||||
You can write external functions to be plugged in the httrack library very easily.
|
||||
We'll see there some examples.
|
||||
You can write external functions to be plugged in the httrack library very easily.
|
||||
We'll see there some examples.
|
||||
|
||||
<br><br>
|
||||
|
||||
The <tt>httrack</tt> commandline tool allows (since the 3.30 release) to plug external functions to various callbacks defined in httrack.
|
||||
The 3.41 release introduces a cleaned up verion of callbacks, with two major changes:
|
||||
<ul>
|
||||
<li>Cleaned up function prototypes, with two arguments always passed (the caller carg structure, and the httrackp* object), convenient to pass an user-defined pointer (see <tt>CALLBACKARG_USERDEF(carg)</tt>)</li>
|
||||
<li>The httrackp* option structure can be directly accessed to plug callbacks (no need to give the callback name and function name in the commandline!)</li>
|
||||
<li>The callback plug is made through the CHAIN_FUNCTION() helper, allowing to chain multiple callbacks of the same type (the callbacks MUST preserve the chain by calling ancestors)</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
References:
|
||||
<ul>
|
||||
<li>the <tt>httrack-library.h</tt> prototype file
|
||||
<br />
|
||||
|
||||
The <tt>httrack</tt> commandline tool allows (since the 3.30 release) to plug external functions to various callbacks defined in httrack.
|
||||
The 3.41 release introduces a cleaned up verion of callbacks, with two major changes:
|
||||
<ul>
|
||||
<li>Cleaned up function prototypes, with two arguments always passed (the caller carg structure, and the httrackp* object), convenient to pass an user-defined pointer (see <tt>CALLBACKARG_USERDEF(carg)</tt>)</li>
|
||||
<li>The httrackp* option structure can be directly accessed to plug callbacks (no need to give the callback name and function name in the commandline!)</li>
|
||||
<li>The callback plug is made through the CHAIN_FUNCTION() helper, allowing to chain multiple callbacks of the same type (the callbacks MUST preserve the chain by calling ancestors)</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
References:
|
||||
<ul>
|
||||
<li>the <tt>httrack-library.h</tt> prototype file
|
||||
<br />
|
||||
Note: the <i>Initialization</i>, <i>Main functions</i>, <i>Options handling</i> and <i>Wrapper functions</i> sections are generally the only ones to be considered.
|
||||
</li>
|
||||
<li>the <tt>htsdefines.h</tt> prototype file, which describes callback function prototypes</li>
|
||||
<li>the <tt>htsopt.h</tt> prototype file, which describes the full httrackp* structure</li>
|
||||
<li>the <tt>callbacks-example*.c</tt> files given in the httrack archive</li>
|
||||
<li>the <tt>htsjava.c</tt> source file (the java class plugin ; overrides 'detect' and 'parse')</li>
|
||||
<li>the example given at the end of this document</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
Below the list of functions to be defined in the module (plugin).<br />
|
||||
<br />
|
||||
|
||||
<table width="100%">
|
||||
<tr><td><b><i>module function name</i></b></td><td><b>function description</b></td><td><b>function signature</b></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>hts_plug</i></td><td background="img/fade.gif">
|
||||
The module entry point. The opt structure can be used to plug callbacks, using the CHAIN_FUNCTION() macro helper. The argv optional argument is the one passed in the commandline as --wrapper parameter.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)<br />
|
||||
|
||||
<br />
|
||||
Wrappers can be plugged inside hts_plug() using:<br />
|
||||
<tt>
|
||||
CHAIN_FUNCTION(opt, <callback name>, <our callback function name>, <our callback function optional custom pointer argument>);
|
||||
</tt>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
Example:
|
||||
<br />
|
||||
<tt>
|
||||
CHAIN_FUNCTION(opt, check_html, process, userdef);
|
||||
</tt>
|
||||
<br />
|
||||
|
||||
</td><td background="img/fade.gif"><tt>extern int hts_plug(httrackp *opt, const char* argv);</tt></td></tr>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<tr><td background="img/fade.gif"><i>hts_unplug</i></td><td background="img/fade.gif">
|
||||
The module exit point. To free allocated resources without using global variables, use the uninit callback (see below)</td><td background="img/fade.gif"><tt>extern int hts_unplug(httrackp *opt);</tt></td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<br />
|
||||
Note that all callbacks (except init and uninit) take as first two argument:
|
||||
<ul>
|
||||
<li>the t_hts_callbackarg structure<br />
|
||||
this structure holds the callback chain (parent callbacks defined before the current callback) pointers, and the user-defined pointer ; see <tt>CALLBACKARG_USERDEF(carg)</tt>)
|
||||
</li>
|
||||
<li>the httrackp structure<br />
|
||||
this structure, holding all current httrack options and mirror state, can be read or mofidied
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
Below the list of callbacks, and associated external wrappers.
|
||||
|
||||
<table width="100%">
|
||||
<tr><td><b><i>callback name</i></b></td><td><b>callback description</b></td><td><b>callback function signature</b></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>init</i></td><td background="img/fade.gif">Note: the use the "start" callback is advised. Called during initialization.<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>uninit</i></td><td background="img/fade.gif">Note: the use os the "end" callback is advised.<br />Called during un-initialization<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>start</i></td><td background="img/fade.gif">Called when the mirror starts. The <tt>opt</tt> structure passed lists all options defined for this mirror. You may modify the <tt>opt</tt> structure to fit your needs.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>end</i></td><td background="img/fade.gif">Called when the mirror ends<br>return value: 1 upon success, 0 upon error (the mirror will then be considered aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>chopt</i></td><td background="img/fade.gif">Called when options are to be changed. The <tt>opt</tt> structure passed lists all options, updated to take account of recent changes<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>preprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is to be parsed (original, not yet modified document). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>postprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is parsed and transformed (links rewritten). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_html</i></td><td background="img/fade.gif">Called when a document (which may not be an html document) is to be parsed. The <tt>html</tt> address points to the document data, of lenth <tt>len</tt>. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the parsing can be processed, 0 if the file must be skipped without being parsed</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* html, int len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question. The <tt>question</tt> string contains the question for the (human) user<br>return value: the string answer ("" for default reply)</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query2</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query3</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>loop</i></td><td background="img/fade.gif">Called periodically (informational, to display statistics)<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back, int back_max, int back_index, int lien_tot, int lien_ntot, int stat_time, hts_stat_struct* stats);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_link</i></td><td background="img/fade.gif">Called when a link has to be tested. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, int status);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_mime</i></td><td background="img/fade.gif">Called when a link download has begun, and needs to be tested against its MIME type. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested, and the <tt>mime</tt> string contains the link type being processed. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, const char* mime, int status);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>pause</i></td><td background="img/fade.gif">Called when the engine must pause. When the <tt>lockfile</tt> passed is deleted, the function can return<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* lockfile);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>filesave</i></td><td background="img/fade.gif">Called when a file is to be saved on disk<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>filesave2</i></td><td background="img/fade.gif">Called when a file is to be saved or checked on disk<br>The hostname, filename and local filename are given. Two additional flags tells if the local file is new (is_new), if the local file is to be modified (is_modified), and if the file was not updated remotely (not_updated).<br>(!is_new && !is_modified): the file is up-to-date, and will not be modified<br>(is_new && is_modified): a new file will be written (or an updated file is being written)<br>(!is_new && is_modified): a file is being updated (append)<br>(is_new && !is_modified): an empty file will be written ("do not recatch locally erased files")<br>not_updated: the file was not re-downloaded because it was up-to-date (no data transferred again)<br><br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* hostname, const char* filename, const char* localfile, int is_new, int is_modified, int not_updated);</tt></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>linkdetected</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>linkdetected2</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link, const const char* tag_start);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>xfrstatus</i></td><td background="img/fade.gif">Called when a file has been processed (downloaded, updated, or error)<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>savename</i></td><td background="img/fade.gif">Called when a local filename has to be processed. The <tt>adr_complete</tt> and <tt>fil_complete</tt> are the address and URI of the file being saved ; the <tt>referer_adr</tt> and <tt>referer_fil</tt> are the address and URI of the referer link. The <tt>save</tt> string contains the local filename being used. You may modifiy the <tt>save</tt> string to fit your needs, up to 1024 bytes (note: filename collisions, if any, will be handled by the engine by renaming the file into file-2.ext, file-3.ext ..).<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr_complete, const char* fil_complete, const char* referer_adr, const char* referer_fil, char* save);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>sendhead</i></td><td background="img/fade.gif">Called when HTTP headers are to be sent to the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>outgoing</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* outgoing);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>receivehead</i></td><td background="img/fade.gif">Called when HTTP headers are recevived from the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>incoming</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* incoming);</tt></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>detect</i></td><td background="img/fade.gif">Called when an unknown document is to be parsed. The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the type is known and can be parsed, 0 if the document type is unknown</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>parse</i></td><td background="img/fade.gif">The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the document was successfully parsed, 0 if an error occurred</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
|
||||
|
||||
|
||||
</table>
|
||||
<li>the <tt>htsdefines.h</tt> prototype file, which describes callback function prototypes</li>
|
||||
<li>the <tt>htsopt.h</tt> prototype file, which describes the full httrackp* structure</li>
|
||||
<li>the <tt>callbacks-example*.c</tt> files given in the httrack archive</li>
|
||||
<li>the example given at the end of this document</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
Below the list of functions to be defined in the module (plugin).<br />
|
||||
<br />
|
||||
|
||||
<table width="100%">
|
||||
<tr><td><b><i>module function name</i></b></td><td><b>function description</b></td><td><b>function signature</b></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>hts_plug</i></td><td background="img/fade.gif">
|
||||
The module entry point. The opt structure can be used to plug callbacks, using the CHAIN_FUNCTION() macro helper. The argv optional argument is the one passed in the commandline as --wrapper parameter.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)<br />
|
||||
|
||||
<br />
|
||||
Wrappers can be plugged inside hts_plug() using:<br />
|
||||
<tt>
|
||||
CHAIN_FUNCTION(opt, <callback name>, <our callback function name>, <our callback function optional custom pointer argument>);
|
||||
</tt>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
Example:
|
||||
<br />
|
||||
<tt>
|
||||
CHAIN_FUNCTION(opt, check_html, process, userdef);
|
||||
</tt>
|
||||
<br />
|
||||
|
||||
</td><td background="img/fade.gif"><tt>extern int hts_plug(httrackp *opt, const char* argv);</tt></td></tr>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<tr><td background="img/fade.gif"><i>hts_unplug</i></td><td background="img/fade.gif">
|
||||
The module exit point. To free allocated resources without using global variables, use the uninit callback (see below)</td><td background="img/fade.gif"><tt>extern int hts_unplug(httrackp *opt);</tt></td></tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
<br />
|
||||
Note that all callbacks (except init and uninit) take as first two argument:
|
||||
<ul>
|
||||
<li>the t_hts_callbackarg structure<br />
|
||||
this structure holds the callback chain (parent callbacks defined before the current callback) pointers, and the user-defined pointer ; see <tt>CALLBACKARG_USERDEF(carg)</tt>)
|
||||
</li>
|
||||
<li>the httrackp structure<br />
|
||||
this structure, holding all current httrack options and mirror state, can be read or mofidied
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
Below the list of callbacks, and associated external wrappers.
|
||||
|
||||
<table width="100%">
|
||||
<tr><td><b><i>callback name</i></b></td><td><b>callback description</b></td><td><b>callback function signature</b></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>init</i></td><td background="img/fade.gif">Note: the use the "start" callback is advised. Called during initialization.<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>uninit</i></td><td background="img/fade.gif">Note: the use os the "end" callback is advised.<br />Called during un-initialization<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>start</i></td><td background="img/fade.gif">Called when the mirror starts. The <tt>opt</tt> structure passed lists all options defined for this mirror. You may modify the <tt>opt</tt> structure to fit your needs.<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>end</i></td><td background="img/fade.gif">Called when the mirror ends<br>return value: 1 upon success, 0 upon error (the mirror will then be considered aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>chopt</i></td><td background="img/fade.gif">Called when options are to be changed. The <tt>opt</tt> structure passed lists all options, updated to take account of recent changes<br>return value: 1 upon success, 0 upon error (the mirror will then be aborted)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>preprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is to be parsed (original, not yet modified document). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>postprocess</i></td><td background="img/fade.gif">Called when a document (which is an html document) is parsed and transformed (links rewritten). The <tt>html</tt> address points to the document data address (char**), and the <tt>length</tt> address points to the lenth of this document. Both pointer values (address and size) can be modified to change the document. It is up to the callback function to reallocate the given pointer (using the hts_realloc()/hts_free() library functions), which will be free()'ed by the engine. Hence, return of static buffers is strictly forbidden, and the use of hts_strdup() in such cases is advised. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the new pointers can be applied (default value)</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char** html, int* len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_html</i></td><td background="img/fade.gif">Called when a document (which may not be an html document) is to be parsed. The <tt>html</tt> address points to the document data, of lenth <tt>len</tt>. The <tt>url_address</tt> and <tt>url_file</tt> are the address and URI of the file being processed<br>return value: 1 if the parsing can be processed, 0 if the file must be skipped without being parsed</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* html, int len, const char* url_address, const char* url_file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question. The <tt>question</tt> string contains the question for the (human) user<br>return value: the string answer ("" for default reply)</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query2</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>query3</i></td><td background="img/fade.gif">Called when the wizard needs to ask a question</td><td background="img/fade.gif"><tt>const char* mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* question);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>loop</i></td><td background="img/fade.gif">Called periodically (informational, to display statistics)<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back, int back_max, int back_index, int lien_tot, int lien_ntot, int stat_time, hts_stat_struct* stats);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_link</i></td><td background="img/fade.gif">Called when a link has to be tested. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, int status);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>check_mime</i></td><td background="img/fade.gif">Called when a link download has begun, and needs to be tested against its MIME type. The <tt>adr</tt> and <tt>fil</tt> are the address and URI of the link being tested, and the <tt>mime</tt> string contains the link type being processed. The passed <tt>status</tt> value has the following meaning: 0 if the link is to be accepted by default, 1 if the link is to be refused by default, and -1 if no decision has yet been taken by the engine<br>return value: same meaning as the passed <tt>status</tt> value ; you may generally return -1 to let the engine take the decision by itself</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr, const char* fil, const char* mime, int status);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>pause</i></td><td background="img/fade.gif">Called when the engine must pause. When the <tt>lockfile</tt> passed is deleted, the function can return<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* lockfile);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>filesave</i></td><td background="img/fade.gif">Called when a file is to be saved on disk<br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* file);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>filesave2</i></td><td background="img/fade.gif">Called when a file is to be saved or checked on disk<br>The hostname, filename and local filename are given. Two additional flags tells if the local file is new (is_new), if the local file is to be modified (is_modified), and if the file was not updated remotely (not_updated).<br>(!is_new && !is_modified): the file is up-to-date, and will not be modified<br>(is_new && is_modified): a new file will be written (or an updated file is being written)<br>(!is_new && is_modified): a file is being updated (append)<br>(is_new && !is_modified): an empty file will be written ("do not recatch locally erased files")<br>not_updated: the file was not re-downloaded because it was up-to-date (no data transferred again)<br><br>return value: none</td><td background="img/fade.gif"><tt>void mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* hostname, const char* filename, const char* localfile, int is_new, int is_modified, int not_updated);</tt></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>linkdetected</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>linkdetected2</i></td><td background="img/fade.gif">Called when a link has been detected<br>return value: 1 if the link can be analyzed, 0 if the link must not even be considered</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* link, const const char* tag_start);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>xfrstatus</i></td><td background="img/fade.gif">Called when a file has been processed (downloaded, updated, or error)<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, lien_back* back);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>savename</i></td><td background="img/fade.gif">Called when a local filename has to be processed. The <tt>adr_complete</tt> and <tt>fil_complete</tt> are the address and URI of the file being saved ; the <tt>referer_adr</tt> and <tt>referer_fil</tt> are the address and URI of the referer link. The <tt>save</tt> string contains the local filename being used. You may modifiy the <tt>save</tt> string to fit your needs, up to 1024 bytes (note: filename collisions, if any, will be handled by the engine by renaming the file into file-2.ext, file-3.ext ..).<br>return value: must return 1</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, const char* adr_complete, const char* fil_complete, const char* referer_adr, const char* referer_fil, char* save);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>sendhead</i></td><td background="img/fade.gif">Called when HTTP headers are to be sent to the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>outgoing</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* outgoing);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>receivehead</i></td><td background="img/fade.gif">Called when HTTP headers are recevived from the remote server. The <tt>buff</tt> buffer contains text headers, <tt>adr</tt> and <tt>fil</tt> the URL, and <tt>referer_adr</tt> and <tt>referer_fil</tt> the referer URL. The <tt>incoming</tt> structure contains all information related to the current slot.<br>return value: 1 if the mirror can continue, 0 if the mirror must be aborted</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, char* buff, const char* adr, const char* fil, const char* referer_adr, const char* referer_fil, htsblk* incoming);</tt></td></tr>
|
||||
|
||||
<tr><td background="img/fade.gif"><i>detect</i></td><td background="img/fade.gif">Called when an unknown document is to be parsed. The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the type is known and can be parsed, 0 if the document type is unknown</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
|
||||
<tr><td background="img/fade.gif"><i>parse</i></td><td background="img/fade.gif">The <tt>str</tt> structure contains all information related to the document.<br>return value: 1 if the document was successfully parsed, 0 if an error occurred</td><td background="img/fade.gif"><tt>int mycallback(t_hts_callbackarg *carg, httrackp* opt, htsmoduleStruct* str);</tt></td></tr>
|
||||
|
||||
|
||||
</table>
|
||||
|
||||
<br><br>
|
||||
Note: the optional libhttrack-plugin module (libhttrack-plugin.dll or libhttrack-plugin.so), if found in the library environment, is loaded automatically, and its <tt>hts_plug()</tt> function being called.<br />
|
||||
|
||||
Note: the optional libhttrack-plugin module (libhttrack-plugin.dll or libhttrack-plugin.so), if found in the library environment, is loaded automatically, and its <tt>hts_plug()</tt> function being called.<br />
|
||||
|
||||
|
||||
<br />
|
||||
An example is generally more efficient than anything else, so let's write our first module, aimed to stupidely print all parsed html files:
|
||||
<table width="100%" border="2">
|
||||
<tr><td>
|
||||
<pre>
|
||||
/* system includes */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* standard httrack module includes */
|
||||
#include "httrack-library.h"
|
||||
#include "htsopt.h"
|
||||
#include "htsdefines.h"
|
||||
|
||||
/* local function called as "check_html" callback */
|
||||
static int process_file(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt,
|
||||
/*other parameters are callback-specific*/
|
||||
char* html, int len, const char* url_address, const char* url_file) {
|
||||
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
|
||||
|
||||
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
|
||||
if (CALLBACKARG_PREV_FUN(carg, check_html) != NULL) {
|
||||
if (!CALLBACKARG_PREV_FUN(carg, check_html)(CALLBACKARG_PREV_CARG(carg), opt,
|
||||
html, len, url_address, url_file)) {
|
||||
return 0; /* abort */
|
||||
}
|
||||
}
|
||||
|
||||
printf("file %s%s content: %s\n", url_address, url_file, html);
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/* local function called as "end" callback */
|
||||
static int end_of_mirror(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt) {
|
||||
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
|
||||
|
||||
/* processing */
|
||||
fprintf(stderr, "That's all, folks!\n");
|
||||
|
||||
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
|
||||
if (CALLBACKARG_PREV_FUN(carg, end) != NULL) {
|
||||
/* status is ok on our side, return other callabck's status */
|
||||
return CALLBACKARG_PREV_FUN(carg, end)(CALLBACKARG_PREV_CARG(carg), opt);
|
||||
}
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/*
|
||||
module entry point
|
||||
the function name and prototype MUST match this prototype
|
||||
*/
|
||||
EXTERNAL_FUNCTION int hts_plug(httrackp *opt, const char* argv) {
|
||||
/* optional argument passed in the commandline we won't be using here */
|
||||
const char *arg = strchr(argv, ',');
|
||||
if (arg != NULL)
|
||||
arg++;
|
||||
|
||||
/* plug callback functions */
|
||||
CHAIN_FUNCTION(opt, check_html, process_file, /*optional user-defined arg*/NULL);
|
||||
CHAIN_FUNCTION(opt, end, end_of_mirror, /*optional user-defined arg*/NULL);
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/*
|
||||
module exit point
|
||||
the function name and prototype MUST match this prototype
|
||||
*/
|
||||
EXTERNAL_FUNCTION int hts_unplug(httrackp *opt) {
|
||||
fprintf(stder, "Module unplugged");
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
</pre>
|
||||
</td></tr></table>
|
||||
|
||||
<br />
|
||||
Compile this file ; for example:
|
||||
<br />
|
||||
<tt>
|
||||
gcc -O -g3 -shared -o mylibrary.so myexample.c
|
||||
</tt>
|
||||
<br />
|
||||
and plug the module using the commandline ; for example:
|
||||
<br />
|
||||
<tt>
|
||||
httrack --wrapper mylibrary http://www.example.com
|
||||
</tt>
|
||||
<br />
|
||||
or, if some parameters are desired:
|
||||
<br />
|
||||
<tt>
|
||||
httrack --wrapper mylibrary,myparameter-string http://www.example.com
|
||||
</tt>
|
||||
<br />
|
||||
(the "myparameter-string" string will be available in the 'arg' parameter passed to the hts_plug entry point)
|
||||
<br />
|
||||
|
||||
|
||||
<br />
|
||||
An example is generally more efficient than anything else, so let's write our first module, aimed to stupidely print all parsed html files:
|
||||
<table width="100%" border="2">
|
||||
<tr><td>
|
||||
<pre>
|
||||
/* system includes */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* standard httrack module includes */
|
||||
#include "httrack-library.h"
|
||||
#include "htsopt.h"
|
||||
#include "htsdefines.h"
|
||||
|
||||
/* local function called as "check_html" callback */
|
||||
static int process_file(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt,
|
||||
/*other parameters are callback-specific*/
|
||||
char* html, int len, const char* url_address, const char* url_file) {
|
||||
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
|
||||
|
||||
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
|
||||
if (CALLBACKARG_PREV_FUN(carg, check_html) != NULL) {
|
||||
if (!CALLBACKARG_PREV_FUN(carg, check_html)(CALLBACKARG_PREV_CARG(carg), opt,
|
||||
html, len, url_address, url_file)) {
|
||||
return 0; /* abort */
|
||||
}
|
||||
}
|
||||
|
||||
printf("file %s%s content: %s\n", url_address, url_file, html);
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/* local function called as "end" callback */
|
||||
static int end_of_mirror(t_hts_callbackarg /*the carg structure, holding various information*/*carg, /*the option settings*/httrackp *opt) {
|
||||
void *ourDummyArg = (void*) CALLBACKARG_USERDEF(carg); /*optional user-defined arg*/
|
||||
|
||||
/* processing */
|
||||
fprintf(stderr, "That's all, folks!\n");
|
||||
|
||||
/* call parent functions if multiple callbacks are chained. you can skip this part, if you don't want previous callbacks to be called. */
|
||||
if (CALLBACKARG_PREV_FUN(carg, end) != NULL) {
|
||||
/* status is ok on our side, return other callabck's status */
|
||||
return CALLBACKARG_PREV_FUN(carg, end)(CALLBACKARG_PREV_CARG(carg), opt);
|
||||
}
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/*
|
||||
module entry point
|
||||
the function name and prototype MUST match this prototype
|
||||
*/
|
||||
EXTERNAL_FUNCTION int hts_plug(httrackp *opt, const char* argv) {
|
||||
/* optional argument passed in the commandline we won't be using here */
|
||||
const char *arg = strchr(argv, ',');
|
||||
if (arg != NULL)
|
||||
arg++;
|
||||
|
||||
/* plug callback functions */
|
||||
CHAIN_FUNCTION(opt, check_html, process_file, /*optional user-defined arg*/NULL);
|
||||
CHAIN_FUNCTION(opt, end, end_of_mirror, /*optional user-defined arg*/NULL);
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
|
||||
/*
|
||||
module exit point
|
||||
the function name and prototype MUST match this prototype
|
||||
*/
|
||||
EXTERNAL_FUNCTION int hts_unplug(httrackp *opt) {
|
||||
fprintf(stder, "Module unplugged");
|
||||
|
||||
return 1; /* success */
|
||||
}
|
||||
</pre>
|
||||
</td></tr></table>
|
||||
|
||||
<br />
|
||||
Compile this file ; for example:
|
||||
<br />
|
||||
<tt>
|
||||
gcc -O -g3 -shared -o mylibrary.so myexample.c
|
||||
</tt>
|
||||
<br />
|
||||
and plug the module using the commandline ; for example:
|
||||
<br />
|
||||
<tt>
|
||||
httrack --wrapper mylibrary http://www.example.com
|
||||
</tt>
|
||||
<br />
|
||||
or, if some parameters are desired:
|
||||
<br />
|
||||
<tt>
|
||||
httrack --wrapper mylibrary,myparameter-string http://www.example.com
|
||||
</tt>
|
||||
<br />
|
||||
(the "myparameter-string" string will be available in the 'arg' parameter passed to the hts_plug entry point)
|
||||
<br />
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
<!-- ==================== Start epilogue ==================== -->
|
||||
|
||||
@@ -118,8 +118,8 @@ h4 { margin: 0; font-weight: bold; font-size: 1.18em; }
|
||||
<br>Avoid "never", because the local mirror could be bogus
|
||||
</small><br><br>
|
||||
<!-- -->
|
||||
<li>Parse java files</li>
|
||||
<br><small>Must the engine parse .java files (java classes) to seek included filenames?
|
||||
<li>Parse scripts</li>
|
||||
<br><small>Must the engine parse scripts to seek included filenames?
|
||||
<br>It is checked by default
|
||||
</small><br><br>
|
||||
<!-- -->
|
||||
|
||||
@@ -295,7 +295,7 @@ Max Depth
|
||||
Maximum external depth:
|
||||
Maximum external depth:
|
||||
Filters (refuse/accept links) :
|
||||
Filters (refuse/accept links) :
|
||||
Filters (refuse/accept links):
|
||||
Paths
|
||||
Paths
|
||||
Save prefs
|
||||
|
||||
37
lang/README.md
Normal file
37
lang/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Translating HTTrack
|
||||
|
||||
Interface strings live here, one `.txt` file per language. `English.txt` is the reference: every other file maps each English string to its translation.
|
||||
|
||||
## File format
|
||||
|
||||
Plain text, entries in consecutive pairs of lines:
|
||||
|
||||
```
|
||||
<English string>
|
||||
<translation>
|
||||
```
|
||||
|
||||
The first line of a pair is the lookup key and must stay identical to the one in `English.txt`; translate only the second line. Missing entries fall back to the English text at runtime, so a partial translation works.
|
||||
|
||||
Preserve any `\r\n`, `\t` and `printf` placeholders (`%s`, `%d`, ...) in the translation.
|
||||
|
||||
A few `LANGUAGE_*` entries at the top describe the file itself:
|
||||
|
||||
| Key | Meaning |
|
||||
| --- | --- |
|
||||
| `LANGUAGE_NAME` | Name shown in the language picker, in its own language (`Deutsch`, not `German`) |
|
||||
| `LANGUAGE_ISO` | ISO 639 code, with region if needed (`de`, `pt_BR`) |
|
||||
| `LANGUAGE_CHARSET` | Encoding the file is saved in (`ISO-8859-1`, `windows-1251`, `UTF-8`, ...) |
|
||||
| `LANGUAGE_AUTHOR` | Your name and contact |
|
||||
| `LANGUAGE_WINDOWSID` | Windows locale name used by WinHTTrack (`German (Standard)`) |
|
||||
|
||||
Save the file in exactly its declared `LANGUAGE_CHARSET`; an editor that rewrites it as UTF-8 will corrupt the non-ASCII bytes.
|
||||
|
||||
## Adding or updating a language
|
||||
|
||||
1. Copy `English.txt` to `<Language>.txt`, or edit the existing file.
|
||||
2. Translate each second line; leave the English keys untouched.
|
||||
3. Fill in the `LANGUAGE_*` header for a new file.
|
||||
4. Open a pull request, or attach the file to a GitHub issue.
|
||||
|
||||
When new strings land in `English.txt` they show up untranslated (as English) until a translator fills them in.
|
||||
119
m4/ax_add_fortify_source.m4
Normal file
119
m4/ax_add_fortify_source.m4
Normal file
@@ -0,0 +1,119 @@
|
||||
# ===========================================================================
|
||||
# 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.
|
||||
.\" SPDX-License-Identifier: GPL-3.0-or-later
|
||||
.TH httrack 1 "13 June 2026" "httrack website copier"
|
||||
.TH httrack 1 "12 July 2026" "httrack website copier"
|
||||
.SH NAME
|
||||
httrack \- offline browser : copy websites to a local directory
|
||||
.SH SYNOPSIS
|
||||
@@ -24,6 +24,7 @@ httrack \- offline browser : copy websites to a local directory
|
||||
[ \fB\-EN, \-\-max\-time[=N]\fR ]
|
||||
[ \fB\-AN, \-\-max\-rate[=N]\fR ]
|
||||
[ \fB\-%cN, \-\-connection\-per\-second[=N]\fR ]
|
||||
[ \fB\-%G, \-\-pause\fR ]
|
||||
[ \fB\-GN, \-\-max\-pause[=N]\fR ]
|
||||
[ \fB\-cN, \-\-sockets[=N]\fR ]
|
||||
[ \fB\-TN, \-\-timeout[=N]\fR ]
|
||||
@@ -43,11 +44,14 @@ httrack \- offline browser : copy websites to a local directory
|
||||
[ \fB\-x, \-\-replace\-external\fR ]
|
||||
[ \fB\-%x, \-\-disable\-passwords\fR ]
|
||||
[ \fB\-%q, \-\-include\-query\-string\fR ]
|
||||
[ \fB\-%g, \-\-strip\-query\fR ]
|
||||
[ \fB\-o, \-\-generate\-errors\fR ]
|
||||
[ \fB\-X, \-\-purge\-old[=N]\fR ]
|
||||
[ \fB\-%p, \-\-preserve\fR ]
|
||||
[ \fB\-%T, \-\-utf8\-conversion\fR ]
|
||||
[ \fB\-bN, \-\-cookies[=N]\fR ]
|
||||
[ \fB\-%K, \-\-cookies\-file\fR ]
|
||||
[ \fB\-%Y, \-\-why\fR ]
|
||||
[ \fB\-u, \-\-check\-type[=N]\fR ]
|
||||
[ \fB\-j, \-\-parse\-java[=N]\fR ]
|
||||
[ \fB\-sN, \-\-robots[=N]\fR ]
|
||||
@@ -153,6 +157,8 @@ maximum mirror time in seconds (60=1 minute, 3600=1 hour) (\-\-max\-time[=N])
|
||||
maximum transfer rate in bytes/seconds (1000=1KB/s max) (\-\-max\-rate[=N])
|
||||
.IP \-%cN
|
||||
maximum number of connections/seconds (*%c10) (\-\-connection\-per\-second[=N])
|
||||
.IP \-%G
|
||||
random pause of MIN[:MAX] seconds between files (e.g. %G5:10) (\-\-pause <param>)
|
||||
.IP \-GN
|
||||
pause transfer if N bytes reached, and wait until lock file is deleted (\-\-max\-pause[=N])
|
||||
.SS Flow control:
|
||||
@@ -198,6 +204,8 @@ replace external html links by error pages (\-\-replace\-external)
|
||||
do not include any password for external password protected websites (%x0 include) (\-\-disable\-passwords)
|
||||
.IP \-%q
|
||||
*include query string for local files (useless, for information purpose only) (%q0 don't include) (\-\-include\-query\-string)
|
||||
.IP \-%g
|
||||
strip query keys for dedup ([host/pattern=]key1,key2,...) (\-\-strip\-query <param>)
|
||||
.IP \-o
|
||||
*generate output html file in case of error (404..) (o0 don't generate) (\-\-generate\-errors)
|
||||
.IP \-X
|
||||
@@ -209,10 +217,14 @@ links conversion to UTF\-8 (\-\-utf8\-conversion)
|
||||
.SS Spider options:
|
||||
.IP \-bN
|
||||
accept cookies in cookies.txt (0=do not accept,* 1=accept) (\-\-cookies[=N])
|
||||
.IP \-%K
|
||||
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
|
||||
check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N])
|
||||
.IP \-j
|
||||
*parse Java Classes (j0 don't parse, bitmask: |1 parse default, |2 don't parse .class |4 don't parse .js |8 don't be aggressive) (\-\-parse\-java[=N])
|
||||
*parse scripts (j0 don't parse, bitmask: |1 parse default, |4 don't parse .js |8 don't be aggressive) (\-\-parse\-java[=N])
|
||||
.IP \-sN
|
||||
follow robots.txt and meta robots tags (0=never,1=sometimes,* 2=always, 3=always (even strict rules)) (\-\-robots[=N])
|
||||
.IP \-%h
|
||||
@@ -225,6 +237,8 @@ tolerant requests (accept bogus responses on some servers, but not standard!) (\
|
||||
update hacks: various hacks to limit re\-transfers when updating (identical size, bogus response..) (\-\-updatehack)
|
||||
.IP \-%u
|
||||
url hacks: various hacks to limit duplicate URLs (strip //, www.foo.com==foo.com..) (\-\-urlhack)
|
||||
.br
|
||||
opt out of one url\-hack part: \-\-keep\-www\-prefix (www.foo.com<>foo.com), \-\-keep\-double\-slashes (//), \-\-keep\-query\-order (?b&a)
|
||||
.IP \-%A
|
||||
assume that a type (cgi,asp..) is always linked with a mime type (\-%A php3,cgi=text/html;dat,bin=application/x\-zip) (\-\-assume <param>)
|
||||
.br
|
||||
@@ -234,7 +248,7 @@ can also be used to force a specific file type: \-\-assume foo.cgi=text/html
|
||||
.IP \-@iN
|
||||
internet protocol (0=both ipv6+ipv4, 4=ipv4 only, 6=ipv6 only) (\-\-protocol[=N])
|
||||
.IP \-%w
|
||||
disable a specific external mime module (\-%w htsswf \-%w htsjava) (\-\-disable\-module <param>)
|
||||
disable a specific external mime module (\-%w httrack\-plugin) (\-\-disable\-module <param>)
|
||||
.SS Browser ID:
|
||||
.IP \-F
|
||||
user\-agent field sent in HTTP headers (\-F "user\-agent name") (\-\-user\-agent <param>)
|
||||
@@ -313,12 +327,8 @@ debug HTTP headers in logfile (\-\-debug\-headers)
|
||||
.SS Guru options: (do NOT use if possible)
|
||||
.IP \-#X
|
||||
*use optimized engine (limited memory boundary checks) (\-\-fast\-engine)
|
||||
.IP \-#0
|
||||
filter test (\-#0 '*.gif' 'www.bar.com/foo.gif') (\-\-debug\-testfilters <param>)
|
||||
.IP \-#1
|
||||
simplify test (\-#1 ./foo/bar/../foobar)
|
||||
.IP \-#2
|
||||
type test (\-#2 /foo/bar.php)
|
||||
.IP \-#test
|
||||
list engine self\-tests (run one with \-#test=NAME [args])
|
||||
.IP \-#C
|
||||
cache list (\-#C '*.com/spider*.gif' (\-\-debug\-cache <param>)
|
||||
.IP \-#R
|
||||
@@ -504,8 +514,6 @@ that would have solved them.
|
||||
.SM
|
||||
\- Several scripts generating complex filenames may not find them (ex: img.src='image'+a+Mobj.dst+'.gif')
|
||||
.SM
|
||||
\- Some java classes may not find some files on them (class included)
|
||||
.SM
|
||||
\- Cgi\-bin links may not work properly in some cases (parameters needed). To avoid them: use filters like \-*cgi\-bin*
|
||||
.SH BUGS
|
||||
Please reports bugs to
|
||||
|
||||
@@ -44,7 +44,7 @@ httrack_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
|
||||
proxytrack_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
|
||||
htsserver_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_PIE)
|
||||
|
||||
lib_LTLIBRARIES = libhttrack.la libhtsjava.la
|
||||
lib_LTLIBRARIES = libhttrack.la
|
||||
|
||||
htsserver_SOURCES = htsserver.c htsserver.h htsweb.c htsweb.h
|
||||
proxytrack_SOURCES = proxy/main.c \
|
||||
@@ -56,21 +56,21 @@ whttrackrundir = $(bindir)
|
||||
whttrackrun_SCRIPTS = webhttrack
|
||||
|
||||
libhttrack_la_SOURCES = htscore.c htsparse.c htsback.c htscache.c \
|
||||
htscache_selftest.c \
|
||||
htscache_selftest.c htsdns_selftest.c htsselftest.c \
|
||||
htscatchurl.c htsfilters.c htsftp.c htshash.c coucal/coucal.c \
|
||||
htshelp.c htslib.c htscoremain.c \
|
||||
htsname.c htsrobots.c htstools.c htswizard.c \
|
||||
htsalias.c htsthread.c htsindex.c htsbauth.c \
|
||||
htsmd5.c htszlib.c htswrap.c htsconcat.c \
|
||||
htsmodules.c htscharset.c punycode.c htsencoding.c \
|
||||
htsmodules.c htscharset.c punycode.c htsencoding.c htssniff.c \
|
||||
md5.c \
|
||||
minizip/ioapi.c minizip/mztools.c minizip/unzip.c minizip/zip.c \
|
||||
hts-indextmpl.h htsalias.h htsback.h htsbase.h htssafe.h \
|
||||
htsbasenet.h htsbauth.h htscache.h htscache_selftest.h htscatchurl.h \
|
||||
htsbasenet.h htsbauth.h htscache.h htscache_selftest.h htsdns_selftest.h htsselftest.h htscatchurl.h \
|
||||
htsconfig.h htscore.h htsparse.h htscoremain.h htsdefines.h \
|
||||
htsfilters.h htsftp.h htsglobal.h htshash.h coucal/coucal.h \
|
||||
htshelp.h htsindex.h htslib.h htsmd5.h \
|
||||
htsmodules.h htsname.h htsnet.h \
|
||||
htsmodules.h htsname.h htsnet.h htssniff.h \
|
||||
htsopt.h htsrobots.h htsthread.h \
|
||||
htstools.h htswizard.h htswrap.h htszlib.h \
|
||||
htsstrings.h htsarrays.h httrack-library.h \
|
||||
@@ -82,14 +82,6 @@ libhttrack_la_LIBADD = $(THREADS_LIBS) $(ZLIB_LIBS) $(OPENSSL_LIBS) $(DL_LIBS) $
|
||||
libhttrack_la_CFLAGS = $(AM_CFLAGS) -DLIBHTTRACK_EXPORTS -DZLIB_CONST
|
||||
libhttrack_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(VERSION_INFO)
|
||||
|
||||
libhtsjava_la_SOURCES = htsjava.c htsjava.h
|
||||
libhtsjava_la_LIBADD = $(THREADS_LIBS) $(DL_LIBS) libhttrack.la
|
||||
# This thin JNI wrapper reaches libc only through libhttrack, so the direct
|
||||
# libc edge is dropped from DT_NEEDED (library-not-linked-against-libc). Force
|
||||
# libc back as a dependency; configure gates the flag since only a GNU-style
|
||||
# linker accepts it (LIBC_FORCE_LINK is empty on e.g. macOS).
|
||||
libhtsjava_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(VERSION_INFO) $(LIBC_FORCE_LINK)
|
||||
|
||||
EXTRA_DIST = httrack.h webhttrack \
|
||||
coucal/murmurhash3.h.diff \
|
||||
coucal/murmurhash3.h.orig \
|
||||
@@ -119,7 +111,6 @@ EXTRA_DIST = httrack.h webhttrack \
|
||||
coucal/README.md \
|
||||
coucal/sample.c \
|
||||
coucal/tests.c \
|
||||
htsjava.vcproj \
|
||||
httrack.dsp httrack.dsw httrack.vcproj \
|
||||
libhttrack.dsp libhttrack.dsw libhttrack.vcproj \
|
||||
webhttrack.dsp webhttrack.dsw webhttrack.vcproj
|
||||
|
||||
@@ -60,6 +60,9 @@ Please visit our Website: http://www.httrack.com
|
||||
param1 : this option must be alone, and needs one distinct parameter (-P <path>)
|
||||
param0 : this option must be alone, but the parameter should be put together (+*.gif)
|
||||
*/
|
||||
/* clang-format off: hand-aligned table; clang-format reflows the whole
|
||||
initializer (2->4 space) on any edit, churning every untouched row. */
|
||||
/* clang-format off */
|
||||
const char *hts_optalias[][4] = {
|
||||
/* {"","","",""}, */
|
||||
{"path", "-O", "param1", "output path"},
|
||||
@@ -107,6 +110,14 @@ const char *hts_optalias[][4] = {
|
||||
{"disable-passwords", "-%x", "single", ""}, {"disable-password", "-%x",
|
||||
"single", ""},
|
||||
{"include-query-string", "-%q", "single", ""},
|
||||
{"strip-query", "-%g", "param1",
|
||||
"strip [host/pattern=]key1,key2,... from URLs"},
|
||||
{"cookies-file", "-%K", "param1",
|
||||
"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",
|
||||
"random pause of MIN[:MAX] seconds between files"},
|
||||
{"generate-errors", "-o", "single", ""},
|
||||
{"do-not-generate-errors", "-o0", "single", ""},
|
||||
{"purge-old", "-X", "param", ""},
|
||||
@@ -123,6 +134,9 @@ const char *hts_optalias[][4] = {
|
||||
{"tolerant", "-%B", "single", ""},
|
||||
{"updatehack", "-%s", "single", ""}, {"sizehack", "-%s", "single", ""},
|
||||
{"urlhack", "-%u", "single", ""},
|
||||
{"keep-www-prefix", "-%j", "single", ""},
|
||||
{"keep-double-slashes", "-%o", "single", ""},
|
||||
{"keep-query-order", "-%y", "single", ""},
|
||||
{"user-agent", "-F", "param1", "user-agent identity"},
|
||||
{"referer", "-%R", "param1", "default referer URL"},
|
||||
{"from", "-%E", "param1", "from email address"},
|
||||
@@ -241,6 +255,7 @@ const char *hts_optalias[][4] = {
|
||||
|
||||
{"", "", "", ""}
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
/*
|
||||
Check for alias in command-line
|
||||
@@ -270,7 +285,6 @@ int optalias_check(int argc, const char *const *argv, int n_arg,
|
||||
char *position;
|
||||
int need_param = 1;
|
||||
|
||||
//int return_param=0;
|
||||
int pos;
|
||||
|
||||
command[0] = param[0] = addcommand[0] = '\0';
|
||||
@@ -348,7 +362,6 @@ int optalias_check(int argc, const char *const *argv, int n_arg,
|
||||
strlcatbuff(return_argv[0], "0", return_argv_size);
|
||||
else if (strcmp(param, "on") == 0) {
|
||||
// on is the default
|
||||
// strcatbuff(return_argv[0],"1");
|
||||
} else
|
||||
strlcatbuff(return_argv[0], param, return_argv_size);
|
||||
}
|
||||
|
||||
923
src/htsback.c
923
src/htsback.c
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,12 @@ void back_set_locked(struct_back * sback, const int p);
|
||||
void back_set_unlocked(struct_back * sback, const int p);
|
||||
int back_delete(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
const int p);
|
||||
/* Discard back's on-disk .delayed placeholder and its refname. */
|
||||
void back_delayed_discard(httrackp *opt, lien_back *back);
|
||||
/* Move back's .delayed placeholder (and open stream) to newname;
|
||||
HTS_FALSE = file lost, slot flagged in error. */
|
||||
hts_boolean back_delayed_rename(httrackp *opt, lien_back *back,
|
||||
const char *newname);
|
||||
void back_index_unlock(struct_back * sback, const int p);
|
||||
int back_clear_entry(lien_back * back);
|
||||
int back_flush_output(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
@@ -132,10 +138,11 @@ LLint back_transferred(LLint add, struct_back * sback);
|
||||
|
||||
// hostback
|
||||
#if HTS_XGETHOST
|
||||
void back_solve(httrackp * opt, lien_back * sback);
|
||||
int host_wait(httrackp * opt, lien_back * sback);
|
||||
#endif
|
||||
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
|
||||
or -E deadline overruns its grace period (callers must stop waiting). */
|
||||
int back_checkmirror(httrackp * opt);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -55,8 +55,6 @@ Please visit our Website: http://www.httrack.com
|
||||
// inline function in Wspiapi.h.
|
||||
#include <ws2tcpip.h>
|
||||
#include <Wspiapi.h>
|
||||
// #include <winsock2.h>
|
||||
// #include <tpipv6.h>
|
||||
|
||||
#endif
|
||||
|
||||
@@ -129,6 +127,8 @@ typedef enum HTTPStatusCode {
|
||||
HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
HTTP_EXPECTATION_FAILED = 417,
|
||||
HTTP_TOO_MANY_REQUESTS = 429,
|
||||
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
||||
HTTP_INTERNAL_SERVER_ERROR = 500,
|
||||
HTTP_NOT_IMPLEMENTED = 501,
|
||||
HTTP_BAD_GATEWAY = 502,
|
||||
@@ -146,7 +146,8 @@ typedef enum BackStatusCode {
|
||||
STATUSCODE_NON_FATAL = -5,
|
||||
STATUSCODE_SSL_HANDSHAKE = -6,
|
||||
STATUSCODE_TOO_BIG = -7,
|
||||
STATUSCODE_TEST_OK = -10
|
||||
STATUSCODE_TEST_OK = -10,
|
||||
STATUSCODE_EXCLUDED = -11 /* aborted: MIME excluded by a -mime: filter */
|
||||
} BackStatusCode;
|
||||
|
||||
/** HTTrack status ('status' member of of 'lien_back') **/
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
# Change this to download files
|
||||
if false; then
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/ISO8859/8859-*.TXT" | lftp
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP*.TXT" | lftp
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP*.TXT" | lftp
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/EBCDIC/CP*.TXT" | lftp
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MISC/CP*.TXT" | lftp
|
||||
echo "mget ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/ISO8859/8859-*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/EBCDIC/CP*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/CP*.TXT" | lftp
|
||||
echo "mget https://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8*.TXT" | lftp
|
||||
rm -f CP932.TXT CP936.TXT CP949.TXT CP950.TXT
|
||||
fi
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ int cookie_add(t_cookie * cookie, const char *cook_name, const char *cook_value,
|
||||
#if DEBUG_COOK
|
||||
printf("add_new cookie: name=\"%s\" value=\"%s\" domain=\"%s\" path=\"%s\"\n",
|
||||
cook_name, cook_value, domain, path);
|
||||
//printf(">>>cook: %s<<<\n",cookie->data);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -208,8 +207,6 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
char buffer[8192];
|
||||
|
||||
// cookie->data[0]='\0';
|
||||
|
||||
// Fusionner d'abord les éventuels cookies IE
|
||||
#ifdef _WIN32
|
||||
{
|
||||
@@ -310,14 +307,25 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// écrire cookies.txt
|
||||
// !=0 : erreur
|
||||
/* Write cookies.txt; returns 0 on success. The jar holds live session
|
||||
cookies, so keep it owner-only on Unix (Windows inherits folder ACLs). */
|
||||
int cookie_save(t_cookie * cookie, const char *name) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
|
||||
if (strnotempty(cookie->data)) {
|
||||
char BIGSTK line[8192];
|
||||
#ifdef _WIN32
|
||||
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) {
|
||||
char *a = cookie->data;
|
||||
|
||||
1176
src/htscache.c
1176
src/htscache.c
File diff suppressed because it is too large
Load Diff
@@ -66,8 +66,11 @@ htsblk cache_read(httrackp * opt, cache_back * cache, const char *adr,
|
||||
const char *fil, const char *save, char *location);
|
||||
htsblk cache_read_ro(httrackp * opt, cache_back * cache, const char *adr,
|
||||
const char *fil, const char *save, char *location);
|
||||
htsblk cache_read_including_broken(httrackp * opt, cache_back * cache,
|
||||
const char *adr, const char *fil);
|
||||
/* Like cache_read, but also yields entries whose transfer broke; return_save
|
||||
(optional, HTS_URLMAXSIZE*2) receives the entry's recorded save name. */
|
||||
htsblk cache_read_including_broken(httrackp *opt, cache_back *cache,
|
||||
const char *adr, const char *fil,
|
||||
char *return_save);
|
||||
htsblk cache_readex(httrackp * opt, cache_back * cache, const char *adr,
|
||||
const char *fil, const char *save, char *location,
|
||||
char *return_save, int readonly);
|
||||
@@ -75,14 +78,22 @@ htsblk *cache_header(httrackp * opt, cache_back * cache, const char *adr,
|
||||
const char *fil, htsblk * r);
|
||||
void cache_init(cache_back * cache, httrackp * opt);
|
||||
|
||||
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);
|
||||
/* Which hts-cache/ generation (new.* vs old.*) is authoritative. */
|
||||
typedef enum {
|
||||
CACHE_RECONCILE_PROMOTE, /* no new cache: promote the old generation */
|
||||
CACHE_RECONCILE_INTERRUPTED, /* aborted run: keep the larger generation */
|
||||
CACHE_RECONCILE_ROLLBACK /* nothing transferred: restore the old one */
|
||||
} hts_cache_reconcile_mode;
|
||||
|
||||
/* Reconcile the on-disk cache generations according to mode; a no-op when
|
||||
the involved files are absent. */
|
||||
void hts_cache_reconcile(httrackp *opt, hts_cache_reconcile_mode mode);
|
||||
|
||||
void cache_rstr(FILE *fp, char *s, size_t s_size);
|
||||
char *cache_rstr_addr(FILE * fp);
|
||||
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);
|
||||
void cache_rint(FILE * fp, int *i);
|
||||
void cache_rLLint(FILE * fp, LLint * i);
|
||||
|
||||
@@ -47,6 +47,8 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htslib.h"
|
||||
#include "htszlib.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -76,14 +78,6 @@ static void selftest_open_for_read(cache_back *cache, httrackp *opt) {
|
||||
}
|
||||
|
||||
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) {
|
||||
zipClose(cache->zipOutput,
|
||||
"Created by HTTrack Website Copier (cache self-test)");
|
||||
@@ -316,6 +310,328 @@ static int disk_fallback_selftest(httrackp *opt) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t budget; /**< bytes allowed through before writes start failing */
|
||||
int fail_errno; /**< errno set on the failing write (ENOSPC, EIO, ...) */
|
||||
int writes; /**< zwrite call count, to detect re-entry into the stream */
|
||||
int fail_once; /**< recover (unlimited budget) after the first failure */
|
||||
} writefail_inject;
|
||||
|
||||
/* zwrite that copies until the budget runs out, then fails with inj->fail_errno
|
||||
(the #174/#219 condition). Counts calls so the test can prove a flagged cache
|
||||
never re-enters the stream. */
|
||||
static uLong selftest_failing_zwrite(voidpf opaque, voidpf stream,
|
||||
const void *buf, uLong size) {
|
||||
writefail_inject *inj = (writefail_inject *) opaque;
|
||||
|
||||
inj->writes++;
|
||||
if (inj->budget >= (size_t) size) {
|
||||
inj->budget -= (size_t) size;
|
||||
return (uLong) fwrite(buf, 1, (size_t) size, (FILE *) stream);
|
||||
}
|
||||
if (inj->fail_once)
|
||||
inj->budget = (size_t) -1; /* the backend recovers after this failure */
|
||||
errno = inj->fail_errno;
|
||||
return 0; /* short write -> the minizip op returns an error */
|
||||
}
|
||||
|
||||
/* Open a ZIP whose writes fail past inj->budget, so cache_add() hits an error.
|
||||
*/
|
||||
static zipFile selftest_open_failing_zip(const char *path,
|
||||
writefail_inject *inj) {
|
||||
zlib_filefunc_def ff;
|
||||
|
||||
fill_fopen_filefunc(&ff); /* real fopen/read/seek/close; ignores opaque */
|
||||
ff.zwrite_file = selftest_failing_zwrite;
|
||||
ff.opaque = inj;
|
||||
return zipOpen2(path, APPEND_STATUS_CREATE, NULL, &ff);
|
||||
}
|
||||
|
||||
/* Store one octet-stream body into `cache` (all-in-cache, body in the ZIP). */
|
||||
static void writefail_store(httrackp *opt, cache_back *cache, const char *fil,
|
||||
const char *body, size_t body_len) {
|
||||
htsblk r;
|
||||
char locbuf[4];
|
||||
char *bodycopy = malloct(body_len);
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
r.statuscode = 200;
|
||||
r.size = (LLint) body_len;
|
||||
strcpybuff(r.msg, "OK");
|
||||
strcpybuff(r.contenttype, "application/octet-stream");
|
||||
locbuf[0] = '\0';
|
||||
r.location = locbuf;
|
||||
r.is_write = 0;
|
||||
memcpy(bodycopy, body, body_len);
|
||||
r.adr = bodycopy;
|
||||
cache_add(opt, cache, &r, "example.com", fil, "example.com/blob.bin", 1,
|
||||
NULL);
|
||||
freet(bodycopy);
|
||||
}
|
||||
|
||||
/* Store an entry claiming a >2GB body; the degrade path never reads data. */
|
||||
static void writefail_store_oversized(httrackp *opt, cache_back *cache,
|
||||
const char *fil, int is_write) {
|
||||
htsblk r;
|
||||
char locbuf[4];
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
r.statuscode = 200;
|
||||
r.size = (LLint) INT_MAX + 1;
|
||||
strcpybuff(r.msg, "OK");
|
||||
strcpybuff(r.contenttype, "application/octet-stream");
|
||||
locbuf[0] = '\0';
|
||||
r.location = locbuf;
|
||||
r.is_write = (short int) is_write;
|
||||
cache_add(opt, cache, &r, "example.com", fil, "example.com/big.bin", 1, NULL);
|
||||
}
|
||||
|
||||
/* Read back `entryname`: extra field (cached headers) and body. Returns the
|
||||
body length, or -1 if the entry is absent or unreadable. */
|
||||
static int writefail_read_entry(const char *path, const char *entryname,
|
||||
char *extra, size_t extralen, char *body,
|
||||
size_t bodylen) {
|
||||
unzFile z = unzOpen(path);
|
||||
int n = -1;
|
||||
|
||||
if (z == NULL)
|
||||
return -1;
|
||||
if (unzLocateFile(z, entryname, 1) == UNZ_OK &&
|
||||
unzOpenCurrentFile(z) == UNZ_OK) {
|
||||
const int elen = unzGetLocalExtrafield(z, extra, (unsigned) (extralen - 1));
|
||||
|
||||
if (elen >= 0) {
|
||||
extra[elen] = '\0';
|
||||
n = unzReadCurrentFile(z, body, (unsigned) bodylen);
|
||||
}
|
||||
unzCloseCurrentFile(z);
|
||||
}
|
||||
unzClose(z);
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Cache write-failure policy (#174/#219): fatal errno or a failure streak
|
||||
stops the mirror (exit_xh=-1, no crash); isolated/oversized drops the entry.
|
||||
*/
|
||||
int cache_write_failure_selftest(httrackp *opt, const char *dir) {
|
||||
int fail = 0;
|
||||
char path[HTS_URLMAXSIZE];
|
||||
/* incompressible + big, so deflate flushes (and fails) mid-write, before
|
||||
* close */
|
||||
static const size_t body_len = 256 * 1024;
|
||||
char *body = malloct(body_len);
|
||||
int phase;
|
||||
|
||||
gen_body(body, body_len, 1 /* incompressible */);
|
||||
fconcat(path, sizeof(path), dir, "/wfail.zip");
|
||||
|
||||
/* phase 0: fatal errno (ENOSPC) aborts at once; phase 1: persistent EIO
|
||||
drops entries until the streak caps out, then aborts. */
|
||||
for (phase = 0; phase < 2; phase++) {
|
||||
cache_back cache;
|
||||
writefail_inject inj;
|
||||
int writes_after_fail;
|
||||
|
||||
inj.budget = (phase == 0) ? 4096 : 0;
|
||||
inj.fail_errno = (phase == 0) ? ENOSPC : EIO;
|
||||
inj.writes = 0;
|
||||
inj.fail_once = 0;
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.type = 1;
|
||||
cache.log = stderr;
|
||||
cache.errlog = stderr;
|
||||
cache.hashtable = coucal_new(0);
|
||||
cache.zipOutput = selftest_open_failing_zip(path, &inj);
|
||||
if (cache.zipOutput == NULL) {
|
||||
fprintf(stderr, "cache-writefail: could not open injected ZIP\n");
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
opt->state.exit_xh = 0; /* clear; the failing write must set it to -1 */
|
||||
if (phase == 0) {
|
||||
writefail_store(opt, &cache, "/blob.bin", body, body_len);
|
||||
} else {
|
||||
/* the abort must land exactly on the 8th consecutive failure */
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 7; i++) {
|
||||
char fil[32];
|
||||
|
||||
snprintf(fil, sizeof(fil), "/b%d.bin", i);
|
||||
writefail_store(opt, &cache, fil, body, 16);
|
||||
}
|
||||
if (cache.zipWriteFailed) {
|
||||
fprintf(stderr, "cache-writefail: phase 1: aborted before the "
|
||||
"8th consecutive failure\n");
|
||||
fail++;
|
||||
}
|
||||
writefail_store(opt, &cache, "/b7.bin", body, 16);
|
||||
}
|
||||
if (!cache.zipWriteFailed) {
|
||||
fprintf(stderr, "cache-writefail: phase %d: write error not caught\n",
|
||||
phase);
|
||||
fail++;
|
||||
}
|
||||
if (opt->state.exit_xh != -1) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: phase %d: mirror not aborted (exit_xh=%d)\n",
|
||||
phase, opt->state.exit_xh);
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* a flagged cache must no-op a sibling write: no further backend write */
|
||||
writes_after_fail = inj.writes;
|
||||
writefail_store(opt, &cache, "/blob2.bin", body, 16);
|
||||
if (inj.writes != writes_after_fail) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: phase %d: sibling write re-entered the broken "
|
||||
"stream (%d extra backend writes)\n",
|
||||
phase, inj.writes - writes_after_fail);
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (cache.zipOutput != NULL) {
|
||||
zipClose(cache.zipOutput,
|
||||
NULL); /* best-effort; may fail on the backend */
|
||||
cache.zipOutput = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* failures with successes in between reset the streak: never aborts */
|
||||
{
|
||||
cache_back cache;
|
||||
writefail_inject inj;
|
||||
int i;
|
||||
|
||||
inj.budget = (size_t) -1;
|
||||
inj.fail_errno = EIO;
|
||||
inj.writes = 0;
|
||||
inj.fail_once = 0;
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.type = 1;
|
||||
cache.log = stderr;
|
||||
cache.errlog = stderr;
|
||||
cache.hashtable = coucal_new(0);
|
||||
cache.zipOutput = selftest_open_failing_zip(path, &inj);
|
||||
opt->state.exit_xh = 0;
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
char fil[32];
|
||||
|
||||
inj.budget = 0; /* this store fails */
|
||||
snprintf(fil, sizeof(fil), "/s%d.bin", i);
|
||||
writefail_store(opt, &cache, fil, body, 16);
|
||||
inj.budget = (size_t) -1; /* this one succeeds and resets the streak */
|
||||
snprintf(fil, sizeof(fil), "/ok%d.bin", i);
|
||||
writefail_store(opt, &cache, fil, body, 16);
|
||||
}
|
||||
if (cache.zipWriteFailed || opt->state.exit_xh != 0) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: scattered: non-consecutive failures aborted "
|
||||
"the mirror (flagged=%d, exit_xh=%d)\n",
|
||||
(int) cache.zipWriteFailed, opt->state.exit_xh);
|
||||
fail++;
|
||||
}
|
||||
zipClose(cache.zipOutput, NULL);
|
||||
cache.zipOutput = NULL;
|
||||
}
|
||||
|
||||
/* isolated failure: only that entry drops; a later sibling round-trips */
|
||||
{
|
||||
cache_back cache;
|
||||
writefail_inject inj;
|
||||
char extra[8192];
|
||||
char rbody[64];
|
||||
int n;
|
||||
|
||||
inj.budget = 4096;
|
||||
inj.fail_errno = EIO;
|
||||
inj.writes = 0;
|
||||
inj.fail_once = 1;
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.type = 1;
|
||||
cache.log = stderr;
|
||||
cache.errlog = stderr;
|
||||
cache.hashtable = coucal_new(0);
|
||||
cache.zipOutput = selftest_open_failing_zip(path, &inj);
|
||||
opt->state.exit_xh = 0;
|
||||
|
||||
writefail_store(opt, &cache, "/blob.bin", body, body_len);
|
||||
if (cache.zipWriteFailed || opt->state.exit_xh != 0) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: skip: isolated failure aborted the mirror "
|
||||
"(flagged=%d, exit_xh=%d)\n",
|
||||
(int) cache.zipWriteFailed, opt->state.exit_xh);
|
||||
fail++;
|
||||
}
|
||||
writefail_store(opt, &cache, "/blob2.bin", body, 16);
|
||||
zipClose(cache.zipOutput, NULL);
|
||||
cache.zipOutput = NULL;
|
||||
n = writefail_read_entry(path, "http://example.com/blob2.bin", extra,
|
||||
sizeof(extra), rbody, sizeof(rbody));
|
||||
if (n != 16 || memcmp(rbody, body, 16) != 0) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: skip: sibling entry lost after a skipped "
|
||||
"entry (%d)\n",
|
||||
n);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* >2GB bodies: in-memory drops the entry, on-disk degrades to headers-only */
|
||||
{
|
||||
cache_back cache;
|
||||
writefail_inject inj;
|
||||
char extra[8192];
|
||||
char rbody[64];
|
||||
int n;
|
||||
|
||||
inj.budget = (size_t) -1; /* no injected failure */
|
||||
inj.fail_errno = 0;
|
||||
inj.writes = 0;
|
||||
inj.fail_once = 0;
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.type = 1;
|
||||
cache.log = stderr;
|
||||
cache.errlog = stderr;
|
||||
cache.hashtable = coucal_new(0);
|
||||
cache.zipOutput = selftest_open_failing_zip(path, &inj);
|
||||
opt->state.exit_xh = 0;
|
||||
|
||||
writefail_store_oversized(opt, &cache, "/bigmem.bin", 0 /* in-memory */);
|
||||
writefail_store_oversized(opt, &cache, "/bigdisk.bin", 1 /* on-disk */);
|
||||
zipClose(cache.zipOutput, NULL);
|
||||
cache.zipOutput = NULL;
|
||||
|
||||
if (cache.zipWriteFailed || opt->state.exit_xh != 0) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: oversize: mirror aborted (flagged=%d, "
|
||||
"exit_xh=%d)\n",
|
||||
(int) cache.zipWriteFailed, opt->state.exit_xh);
|
||||
fail++;
|
||||
}
|
||||
if (writefail_read_entry(path, "http://example.com/bigmem.bin", extra,
|
||||
sizeof(extra), rbody, sizeof(rbody)) >= 0) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: oversize: in-memory entry was stored\n");
|
||||
fail++;
|
||||
}
|
||||
n = writefail_read_entry(path, "http://example.com/bigdisk.bin", extra,
|
||||
sizeof(extra), rbody, sizeof(rbody));
|
||||
if (n != 0 || strstr(extra, "X-In-Cache: 0") == NULL) {
|
||||
fprintf(stderr,
|
||||
"cache-writefail: oversize: on-disk entry not stored "
|
||||
"headers-only (%d)\n",
|
||||
n);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
freet(body);
|
||||
return fail;
|
||||
}
|
||||
|
||||
int cache_selftests(httrackp *opt, const char *dir) {
|
||||
int failures = 0;
|
||||
cache_back cache;
|
||||
@@ -585,3 +901,583 @@ int cache_golden_selftest(httrackp *opt, const char *dir, int regen) {
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
/* --- hts_cache_reconcile() policies -------------------------------------- */
|
||||
|
||||
/* All reconcile inputs/outputs, wiped between cases. */
|
||||
static const char *const reconcile_files[] = {
|
||||
"hts-cache/new.zip", "hts-cache/old.zip", "hts-cache/new.dat",
|
||||
"hts-cache/old.dat", "hts-cache/new.ndx", "hts-cache/old.ndx",
|
||||
"hts-cache/new.lst", "hts-cache/old.lst", "hts-cache/new.txt",
|
||||
"hts-cache/old.txt", "hts-in_progress.lock"};
|
||||
|
||||
static char *reconcile_st_path(httrackp *opt, const char *name) {
|
||||
return fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log), name);
|
||||
}
|
||||
|
||||
static void reconcile_wipe(httrackp *opt) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(reconcile_files) / sizeof(reconcile_files[0]); i++)
|
||||
remove(reconcile_st_path(opt, reconcile_files[i]));
|
||||
}
|
||||
|
||||
/* Create a filler file of exactly `size` bytes. */
|
||||
static void reconcile_put(httrackp *opt, const char *name, size_t size) {
|
||||
FILE *const fp = fopen(reconcile_st_path(opt, name), "wb");
|
||||
static const char filler[1024] = {'x'};
|
||||
|
||||
assertf(fp != NULL);
|
||||
while (size > 0) {
|
||||
const size_t n = size > sizeof(filler) ? sizeof(filler) : size;
|
||||
|
||||
assertf(fwrite(filler, 1, n, fp) == n);
|
||||
size -= n;
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Expect `name` to weigh `size` bytes, or be absent when size == -1. */
|
||||
static int reconcile_expect(httrackp *opt, const char *name, off_t size,
|
||||
const char *what) {
|
||||
const off_t got = fsize(reconcile_st_path(opt, name));
|
||||
|
||||
if (got != size) {
|
||||
fprintf(stderr, "cache-reconcile: %s: %s is %d bytes, expected %d\n", what,
|
||||
name, (int) got, (int) size);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
||||
int failures = 0;
|
||||
|
||||
/* around the interrupted-run thresholds (new < 32768, old > 65536) */
|
||||
static const off_t TINY = 1024, MID = 40000, SOLID = 131072;
|
||||
|
||||
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
|
||||
|
||||
/* PROMOTE: a zip old generation replaces a missing new one */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
|
||||
failures += reconcile_expect(opt, "hts-cache/new.zip", SOLID, "promote-zip");
|
||||
failures += reconcile_expect(opt, "hts-cache/old.zip", -1, "promote-zip");
|
||||
|
||||
/* PROMOTE: an existing new.zip is left alone */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", TINY, "promote-zip-noop");
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/old.zip", SOLID, "promote-zip-noop");
|
||||
|
||||
/* PROMOTE: the removed pre-3.31 legacy pair is left alone */
|
||||
reconcile_wipe(opt);
|
||||
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", -1, "promote-dat");
|
||||
failures += reconcile_expect(opt, "hts-cache/new.ndx", -1, "promote-dat");
|
||||
failures += reconcile_expect(opt, "hts-cache/old.dat", SOLID, "promote-dat");
|
||||
failures += reconcile_expect(opt, "hts-cache/old.ndx", TINY, "promote-dat");
|
||||
|
||||
/* INTERRUPTED: no lock file, no action */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", TINY, "interrupted-nolock");
|
||||
|
||||
/* INTERRUPTED: an absent new.zip must NOT promote old.zip (fsize(-1) would
|
||||
spuriously pass "< TINY"); leave the solid old generation for ROLLBACK */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", -1, "interrupted-nonew");
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/old.zip", SOLID, "interrupted-nonew");
|
||||
|
||||
/* INTERRUPTED: stalled tiny new.zip loses to a solid old.zip (was dead for
|
||||
zip caches: the arm was gated on a legacy new.dat) */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", SOLID, "interrupted-zip");
|
||||
failures += reconcile_expect(opt, "hts-cache/old.zip", -1, "interrupted-zip");
|
||||
|
||||
/* INTERRUPTED: old below the confidence threshold, keep new */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.zip", MID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", TINY, "interrupted-smallold");
|
||||
|
||||
/* INTERRUPTED: new big enough to trust, keep it */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||
reconcile_put(opt, "hts-cache/new.zip", MID);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.zip", MID, "interrupted-bignew");
|
||||
|
||||
/* INTERRUPTED: the removed pre-3.31 legacy pair is left alone */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||
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.ndx", MID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.dat", TINY, "interrupted-dat");
|
||||
failures +=
|
||||
reconcile_expect(opt, "hts-cache/new.ndx", TINY, "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
|
||||
its only good generation here) */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.zip", SOLID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
|
||||
failures += reconcile_expect(opt, "hts-cache/new.zip", SOLID, "rollback-zip");
|
||||
failures += reconcile_expect(opt, "hts-cache/old.zip", -1, "rollback-zip");
|
||||
|
||||
/* ROLLBACK: sidecars are restored regardless of format */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/new.lst", TINY);
|
||||
reconcile_put(opt, "hts-cache/old.lst", MID);
|
||||
reconcile_put(opt, "hts-cache/old.txt", MID);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
|
||||
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-lst");
|
||||
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-txt");
|
||||
|
||||
/* ROLLBACK: sidecars restored, the removed pre-3.31 pair left alone */
|
||||
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", MID);
|
||||
reconcile_put(opt, "hts-cache/old.lst", MID);
|
||||
reconcile_put(opt, "hts-cache/old.txt", MID);
|
||||
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.ndx", -1, "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.txt", MID, "rollback-dat");
|
||||
|
||||
/* ROLLBACK: nothing to restore, the new generation stays */
|
||||
reconcile_wipe(opt);
|
||||
reconcile_put(opt, "hts-cache/new.zip", TINY);
|
||||
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
|
||||
failures += reconcile_expect(opt, "hts-cache/new.zip", TINY, "rollback-noop");
|
||||
|
||||
reconcile_wipe(opt);
|
||||
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 --------------------------------------- */
|
||||
|
||||
/* canary read back intact after each corruption; victim gets the byte surgery
|
||||
*/
|
||||
#define CORRUPT_ADR "corrupt.example.com"
|
||||
static char corrupt_body_a[33 + 1];
|
||||
static char corrupt_body_b[44 + 1];
|
||||
|
||||
/* Write a fresh two-entry cache: /canary.html then /victim.html. */
|
||||
static void corrupt_build(httrackp *opt) {
|
||||
cache_back cache;
|
||||
|
||||
memset(corrupt_body_a, 'a', sizeof(corrupt_body_a) - 1);
|
||||
memset(corrupt_body_b, 'b', sizeof(corrupt_body_b) - 1);
|
||||
remove(reconcile_st_path(opt, "hts-cache/new.zip"));
|
||||
remove(reconcile_st_path(opt, "hts-cache/old.zip"));
|
||||
selftest_open_for_write(&cache, opt);
|
||||
store_entry(opt, &cache, CORRUPT_ADR, "/canary.html", "canary.html", 200,
|
||||
"OK", "text/html", "utf-8", "", "", "", "", corrupt_body_a,
|
||||
strlen(corrupt_body_a));
|
||||
store_entry(opt, &cache, CORRUPT_ADR, "/victim.html", "victim.html", 200,
|
||||
"OK", "text/html", "utf-8", "", "", "", "", corrupt_body_b,
|
||||
strlen(corrupt_body_b));
|
||||
selftest_close(&cache);
|
||||
}
|
||||
|
||||
/* Like corrupt_build, but the victim carries a 20-char Etag whose header line
|
||||
is later overwritten with a forged oversized X-Size (same byte length). */
|
||||
static void corrupt_build_etag(httrackp *opt) {
|
||||
cache_back cache;
|
||||
|
||||
memset(corrupt_body_a, 'a', sizeof(corrupt_body_a) - 1);
|
||||
memset(corrupt_body_b, 'b', sizeof(corrupt_body_b) - 1);
|
||||
remove(reconcile_st_path(opt, "hts-cache/new.zip"));
|
||||
remove(reconcile_st_path(opt, "hts-cache/old.zip"));
|
||||
selftest_open_for_write(&cache, opt);
|
||||
store_entry(opt, &cache, CORRUPT_ADR, "/canary.html", "canary.html", 200,
|
||||
"OK", "text/html", "utf-8", "", "", "", "", corrupt_body_a,
|
||||
strlen(corrupt_body_a));
|
||||
store_entry(opt, &cache, CORRUPT_ADR, "/victim.html", "victim.html", 200,
|
||||
"OK", "text/html", "utf-8", "", "AAAAAAAAAAAAAAAAAAAA", "", "",
|
||||
corrupt_body_b, strlen(corrupt_body_b));
|
||||
selftest_close(&cache);
|
||||
}
|
||||
|
||||
/* Like corrupt_build_etag, but the victim is headers-only (X-In-Cache: 0,
|
||||
body on disk): the shape every non-html file is stored with. */
|
||||
static void corrupt_build_disk(httrackp *opt) {
|
||||
cache_back cache;
|
||||
htsblk w;
|
||||
char locw[4];
|
||||
char BIGSTK save[HTS_URLMAXSIZE * 2];
|
||||
char BIGSTK catbuff[HTS_URLMAXSIZE * 2];
|
||||
char *path;
|
||||
FILE *fp;
|
||||
|
||||
memset(corrupt_body_a, 'a', sizeof(corrupt_body_a) - 1);
|
||||
remove(reconcile_st_path(opt, "hts-cache/new.zip"));
|
||||
remove(reconcile_st_path(opt, "hts-cache/old.zip"));
|
||||
fconcat(save, sizeof(save), StringBuff(opt->path_html_utf8),
|
||||
CORRUPT_ADR "/victim.bin");
|
||||
selftest_open_for_write(&cache, opt);
|
||||
store_entry(opt, &cache, CORRUPT_ADR, "/canary.html", "canary.html", 200,
|
||||
"OK", "text/html", "utf-8", "", "", "", "", corrupt_body_a,
|
||||
strlen(corrupt_body_a));
|
||||
hts_init_htsblk(&w);
|
||||
w.statuscode = 200;
|
||||
w.size = (LLint) sizeof(corrupt_body_b) - 1;
|
||||
strcpybuff(w.msg, "OK");
|
||||
strcpybuff(w.contenttype, "application/octet-stream");
|
||||
strcpybuff(w.etag, "AAAAAAAAAAAAAAAAAAAA");
|
||||
locw[0] = '\0';
|
||||
w.location = locw;
|
||||
w.is_write = 0;
|
||||
cache_add(opt, &cache, &w, CORRUPT_ADR, "/victim.bin", save,
|
||||
0 /* all_in_cache */, NULL);
|
||||
selftest_close(&cache);
|
||||
/* the reader only checks this file exists; it never reads it here */
|
||||
path = fconv(catbuff, sizeof(catbuff), save);
|
||||
(void) structcheck(path);
|
||||
fp = FOPEN(path, "wb");
|
||||
assertf(fp != NULL);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Patch the nth of total occurrences of pat (same-length rep) in new.zip. */
|
||||
static void corrupt_patch(httrackp *opt, const char *pat, size_t patlen,
|
||||
const char *rep, size_t nth, size_t total) {
|
||||
LLint fsz = 0;
|
||||
char *data = readfile2(reconcile_st_path(opt, "hts-cache/new.zip"), &fsz);
|
||||
const size_t n = (size_t) fsz;
|
||||
size_t k, hits = 0, at = 0;
|
||||
FILE *fp;
|
||||
|
||||
assertf(data != NULL);
|
||||
for (k = 0; k + patlen <= n; k++) {
|
||||
if (memcmp(data + k, pat, patlen) == 0) {
|
||||
hits++;
|
||||
if (hits == nth)
|
||||
at = k;
|
||||
}
|
||||
}
|
||||
assertf(hits == total);
|
||||
memcpy(data + at, rep, patlen);
|
||||
fp = fopen(reconcile_st_path(opt, "hts-cache/new.zip"), "wb");
|
||||
assertf(fp != NULL);
|
||||
assertf(fwrite(data, 1, n, fp) == n);
|
||||
fclose(fp);
|
||||
freet(data);
|
||||
}
|
||||
|
||||
/* Garbage the first bytes of the victim's deflated data (2nd local header). */
|
||||
static void corrupt_victim_body(httrackp *opt) {
|
||||
LLint fsz = 0;
|
||||
char *data = readfile2(reconcile_st_path(opt, "hts-cache/new.zip"), &fsz);
|
||||
const size_t n = (size_t) fsz;
|
||||
size_t k, hits = 0, off = 0;
|
||||
FILE *fp;
|
||||
|
||||
assertf(data != NULL);
|
||||
for (k = 0; k + 4 <= n; k++) {
|
||||
if (memcmp(data + k, "PK\x03\x04", 4) == 0 && ++hits == 2) {
|
||||
const size_t namelen =
|
||||
(unsigned char) data[k + 26] | ((unsigned char) data[k + 27] << 8);
|
||||
const size_t extralen =
|
||||
(unsigned char) data[k + 28] | ((unsigned char) data[k + 29] << 8);
|
||||
|
||||
off = k + 30 + namelen + extralen;
|
||||
}
|
||||
}
|
||||
assertf(hits == 2);
|
||||
assertf(off != 0 && off + 4 <= n);
|
||||
memset(data + off, 0xFF, 4);
|
||||
fp = fopen(reconcile_st_path(opt, "hts-cache/new.zip"), "wb");
|
||||
assertf(fp != NULL);
|
||||
assertf(fwrite(data, 1, n, fp) == n);
|
||||
fclose(fp);
|
||||
freet(data);
|
||||
}
|
||||
|
||||
/* Read the corrupt /victim.html and, in the SAME read session, the intact
|
||||
/canary.html: the victim must be rejected (wantmsg pins which path) and the
|
||||
canary must still decode byte-exact, proving one bad entry never taints a
|
||||
sibling read. */
|
||||
static int corrupt_expect_victim_fil(httrackp *opt, const char *fil,
|
||||
const char *wantmsg, const char *what) {
|
||||
cache_back cache;
|
||||
htsblk v, c;
|
||||
char BIGSTK lv[HTS_URLMAXSIZE * 2];
|
||||
char BIGSTK lc[HTS_URLMAXSIZE * 2];
|
||||
int fail = 0;
|
||||
|
||||
selftest_open_for_read(&cache, opt);
|
||||
lv[0] = lc[0] = '\0';
|
||||
v = cache_readex(opt, &cache, CORRUPT_ADR, fil, "", lv, NULL, 1);
|
||||
if (v.statuscode != STATUSCODE_INVALID) {
|
||||
fprintf(stderr, "%s: %s: victim: statuscode is %d, expected %d\n",
|
||||
selftest_tag, what, v.statuscode, STATUSCODE_INVALID);
|
||||
fail++;
|
||||
}
|
||||
if (wantmsg != NULL && strcmp(v.msg, wantmsg) != 0) {
|
||||
fprintf(stderr, "%s: %s: victim: msg is '%s', expected '%s'\n",
|
||||
selftest_tag, what, v.msg, wantmsg);
|
||||
fail++;
|
||||
}
|
||||
c = cache_readex(opt, &cache, CORRUPT_ADR, "/canary.html", "", lc, NULL, 1);
|
||||
if (c.statuscode != 200 || c.adr == NULL ||
|
||||
c.size != (LLint) strlen(corrupt_body_a) ||
|
||||
memcmp(c.adr, corrupt_body_a, strlen(corrupt_body_a)) != 0) {
|
||||
fprintf(stderr, "%s: %s: canary tainted (status %d)\n", selftest_tag, what,
|
||||
c.statuscode);
|
||||
fail++;
|
||||
}
|
||||
if (v.adr != NULL)
|
||||
freet(v.adr);
|
||||
if (c.adr != NULL)
|
||||
freet(c.adr);
|
||||
selftest_close(&cache);
|
||||
return fail;
|
||||
}
|
||||
|
||||
static int corrupt_expect_victim(httrackp *opt, const char *wantmsg,
|
||||
const char *what) {
|
||||
return corrupt_expect_victim_fil(opt, "/victim.html", wantmsg, what);
|
||||
}
|
||||
|
||||
/* Headers-only probe of the disk victim: must parse OK with the size kept. */
|
||||
static int corrupt_expect_disk_header(httrackp *opt, LLint wantsize,
|
||||
const char *what) {
|
||||
cache_back cache;
|
||||
htsblk v;
|
||||
char BIGSTK lv[HTS_URLMAXSIZE * 2];
|
||||
int fail = 0;
|
||||
|
||||
selftest_open_for_read(&cache, opt);
|
||||
lv[0] = '\0';
|
||||
v = cache_readex(opt, &cache, CORRUPT_ADR, "/victim.bin", NULL, lv, NULL, 1);
|
||||
if (v.statuscode != 200 || v.size != wantsize) {
|
||||
fprintf(stderr,
|
||||
"%s: %s: statuscode %d size " LLintP ", expected 200/" LLintP "\n",
|
||||
selftest_tag, what, v.statuscode, (LLint) v.size, wantsize);
|
||||
fail++;
|
||||
}
|
||||
if (v.adr != NULL)
|
||||
freet(v.adr);
|
||||
selftest_close(&cache);
|
||||
return fail;
|
||||
}
|
||||
|
||||
/* One zip corruption case: build, patch, then check victim+canary in-session.
|
||||
*/
|
||||
static int corrupt_case_zip(httrackp *opt, const char *pat, const char *rep,
|
||||
size_t nth, size_t total, const char *wantmsg,
|
||||
const char *what) {
|
||||
corrupt_build(opt);
|
||||
corrupt_patch(opt, pat, strlen(pat), rep, nth, total);
|
||||
return corrupt_expect_victim(opt, wantmsg, what);
|
||||
}
|
||||
|
||||
int cache_corruption_selftest(httrackp *opt, const char *dir) {
|
||||
int failures = 0;
|
||||
|
||||
selftest_tag = "cache-corrupt";
|
||||
golden_setup(opt, dir);
|
||||
|
||||
failures +=
|
||||
corrupt_case_zip(opt, "X-Size: 44", "X-Size: 99", 1, 1,
|
||||
"Cache Read Error : Read Data", "oversized X-Size");
|
||||
failures +=
|
||||
corrupt_case_zip(opt, "X-Size: 44", "X-Size: -4", 1, 1,
|
||||
"Cache Read Error : Bad Size", "negative X-Size");
|
||||
/* both entries carry the line; the victim's is the second */
|
||||
failures += corrupt_case_zip(opt, "X-In-Cache: 1", "X-In-Cache: 0", 2, 2,
|
||||
"Previous cache file not found (empty filename)",
|
||||
"blanked X-In-Cache");
|
||||
/* smashed local file header: the entry is dropped at index load */
|
||||
failures +=
|
||||
corrupt_case_zip(opt, "PK\x03\x04", "XK\x03\x04", 2, 2,
|
||||
"File Cache Entry Not Found", "smashed local header");
|
||||
|
||||
corrupt_build(opt);
|
||||
corrupt_victim_body(opt);
|
||||
failures += corrupt_expect_victim(opt, "Cache Read Error : Read Data",
|
||||
"garbled deflate stream");
|
||||
|
||||
/* An X-Size above INT_MAX is positive as int64 (slips a bare sign check) but
|
||||
truncates negative in the (int) cast the malloc uses: a wraparound alloc.
|
||||
cache_add asserts size fits an int, so such a value only reaches the reader
|
||||
from a corrupt/foreign cache; inject it by overwriting the victim's long
|
||||
Etag line with a same-length forged X-Size line (the parser keeps the last
|
||||
X-Size it sees), keeping the zip byte-length and offsets intact. */
|
||||
corrupt_build_etag(opt);
|
||||
corrupt_patch(opt, "Etag: AAAAAAAAAAAAAAAAAAAA", 26,
|
||||
"X-Size: 2147483648AAAAAAAA", 1, 1);
|
||||
failures += corrupt_expect_victim(opt, "Cache Read Error : Bad Size",
|
||||
"X-Size above INT_MAX");
|
||||
|
||||
/* A headers-only entry (X-In-Cache: 0) may carry an X-Size >= INT_MAX: that
|
||||
is how every >2GB non-html file is stored. It must survive a header probe
|
||||
(or every update re-fetches the file); an in-memory read still rejects. */
|
||||
corrupt_build_disk(opt);
|
||||
corrupt_patch(opt, "Etag: AAAAAAAAAAAAAAAAAAAA", 26,
|
||||
"X-Size: 2147483648AAAAAAAA", 1, 1);
|
||||
failures += corrupt_expect_disk_header(opt, (LLint) 2147483648LL,
|
||||
"headers-only X-Size above INT_MAX");
|
||||
failures += corrupt_expect_victim_fil(opt, "/victim.bin",
|
||||
"Cache Read Error : Bad Size",
|
||||
"in-memory X-Size above INT_MAX");
|
||||
|
||||
/* exactly INT_MAX pins the >= boundary: (int) r.size + 1 would overflow */
|
||||
corrupt_build_disk(opt);
|
||||
corrupt_patch(opt, "Etag: AAAAAAAAAAAAAAAAAAAA", 26,
|
||||
"X-Size: 2147483647AAAAAAAA", 1, 1);
|
||||
failures += corrupt_expect_victim_fil(opt, "/victim.bin",
|
||||
"Cache Read Error : Bad Size",
|
||||
"in-memory X-Size at INT_MAX");
|
||||
|
||||
/* the negative check must stay global, headers-only included */
|
||||
corrupt_build_disk(opt);
|
||||
corrupt_patch(opt, "Etag: AAAAAAAAAAAAAAAAAAAA", 26,
|
||||
"X-Size: -2147483648AAAAAAA", 1, 1);
|
||||
failures += corrupt_expect_victim_fil(opt, "/victim.bin",
|
||||
"Cache Read Error : Bad Size",
|
||||
"headers-only negative X-Size");
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,23 @@ int cache_selftests(httrackp *opt, const char *dir);
|
||||
committed file, never by the test). Returns the failed-check count. */
|
||||
int cache_golden_selftest(httrackp *opt, const char *dir, int regen);
|
||||
|
||||
/* Cache write-failure policy (#174/#219): abort on fatal errno or a streak,
|
||||
drop just the entry otherwise. Returns the failed-check count. */
|
||||
int cache_write_failure_selftest(httrackp *opt, const char *dir);
|
||||
|
||||
/* Exercise the hts_cache_reconcile() generation policies on file fixtures
|
||||
under <dir>. Returns the failed-check count. */
|
||||
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)
|
||||
under <dir> and assert every case degrades to STATUSCODE_INVALID without
|
||||
tainting a sibling entry. */
|
||||
int cache_corruption_selftest(httrackp *opt, const char *dir);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -64,7 +64,7 @@ Please visit our Website: http://www.httrack.com
|
||||
// catch_url_init(&port,&return_host);
|
||||
HTSEXT_API T_SOC catch_url_init_std(int *port_prox, char *adr_prox) {
|
||||
T_SOC soc;
|
||||
int try_to_listen_to[] = { 8080, 3128, 80, 81, 82, 8081, 3129, 31337, 0, -1 };
|
||||
int try_to_listen_to[] = {8080, 3128, 80, 81, 82, 8081, 3129, 0, -1};
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
@@ -175,7 +175,9 @@ HTSEXT_API hts_boolean catch_url(T_SOC soc, char *url, char *method,
|
||||
//
|
||||
socinput(soc, line, 1000);
|
||||
if (strnotempty(line)) {
|
||||
if (sscanf(line, "%s %s %s", method, url, protocol) == 3) {
|
||||
/* widths bound the caller buffers: method[32], url[HTS_URLMAXSIZE*2],
|
||||
protocol[256] */
|
||||
if (sscanf(line, "%31s %2047s %255s", method, url, protocol) == 3) {
|
||||
lien_adrfil af;
|
||||
|
||||
// méthode en majuscule
|
||||
@@ -195,7 +197,6 @@ HTSEXT_API hts_boolean catch_url(T_SOC soc, char *url, char *method,
|
||||
htsblk blkretour;
|
||||
|
||||
hts_init_htsblk(&blkretour);
|
||||
//memset(&blkretour, 0, sizeof(htsblk)); // effacer
|
||||
blkretour.location = loc; // si non nul, contiendra l'adresse véritable en cas de moved xx
|
||||
// Lire en têtes restants
|
||||
sprintf(data, "%s %s %s\r\n", method, af.fil, protocol);
|
||||
@@ -205,8 +206,8 @@ HTSEXT_API hts_boolean catch_url(T_SOC soc, char *url, char *method,
|
||||
strlcatbuff(data, line, CATCH_URL_DATA_SIZE);
|
||||
strlcatbuff(data, "\r\n", CATCH_URL_DATA_SIZE);
|
||||
}
|
||||
// CR/LF final de l'en tête inutile car déja placé via la ligne vide juste au dessus
|
||||
//strcatbuff(data,"\r\n");
|
||||
// CR/LF final de l'en tête inutile car déja placé via la ligne vide
|
||||
// juste au dessus
|
||||
if (blkretour.totalsize > 0) {
|
||||
int len = (int) min(blkretour.totalsize, 32000);
|
||||
int pos = (int) strlen(data);
|
||||
|
||||
309
src/htscharset.c
309
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. */
|
||||
static char *hts_stringMemCopy(const char *s, size_t size) {
|
||||
char *dest = malloc(size + 1);
|
||||
char *dest = malloct(size + 1);
|
||||
|
||||
if (dest != NULL) {
|
||||
memcpy(dest, s, size);
|
||||
@@ -549,42 +549,6 @@ char *hts_convertStringFromUTF8(const char *s, size_t size, const char *charset)
|
||||
|
||||
#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
|
||||
#define strcasecmp(a,b) stricmp(a,b)
|
||||
#define strncasecmp(a,b,n) strnicmp(a,b,n)
|
||||
@@ -644,58 +608,106 @@ int hts_isCharsetUTF8(const char *charset) {
|
||||
|| 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) {
|
||||
int i;
|
||||
size_t i;
|
||||
|
||||
/* <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8" > */
|
||||
for(i = 0; i < size; i++) {
|
||||
if (html[i] == '<' && strncasecmp(&html[i + 1], "meta", 4) == 0
|
||||
&& is_space(html[i + 5])) {
|
||||
/* Skip spaces */
|
||||
for(i += 5; is_space(html[i]); i++) ;
|
||||
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;
|
||||
/* HTML5 <meta charset=X> or legacy
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=X">,
|
||||
attributes in any order; first resolvable tag wins. */
|
||||
for (i = 0; i + 6 < size; i++) {
|
||||
size_t j;
|
||||
const char *equiv = NULL, *content = NULL;
|
||||
size_t equiv_len = 0, content_len = 0;
|
||||
|
||||
for(i += 7; is_space_or_equal(html[i]) || html[i] == '\'';
|
||||
i++) ;
|
||||
/* Charset */
|
||||
for(len = 0;
|
||||
i + len < size && html[i + len] != '"'
|
||||
&& html[i + len] != '\'' && html[i + len] != ' '; len++) ;
|
||||
/* No error ? */
|
||||
if (len != 0 && i < size) {
|
||||
char *const s = malloc(len + 1);
|
||||
int j;
|
||||
if (html[i] != '<' || strncasecmp(&html[i + 1], "meta", 4) != 0 ||
|
||||
!is_space(html[i + 5])) {
|
||||
continue;
|
||||
}
|
||||
/* Attribute scan, strictly bounded by size. */
|
||||
for (j = i + 6; j < size && html[j] != '>';) {
|
||||
size_t name, name_len, val = 0, val_len = 0;
|
||||
|
||||
for(j = 0; j < len; j++) {
|
||||
s[j] = html[i + j];
|
||||
}
|
||||
s[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_space(html[j]) || html[j] == '/') {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
for (name = j; j < size && html[j] != '=' && html[j] != '>' &&
|
||||
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;
|
||||
}
|
||||
@@ -841,71 +853,6 @@ static unsigned int nlz8(unsigned char x) {
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* Sample. */
|
||||
#if 0
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int i;
|
||||
int hex = 0;
|
||||
|
||||
#define READ_INT(DEST) \
|
||||
( ( !hex && sscanf(argv[i], "%d", &(DEST)) == 1) \
|
||||
|| (hex && sscanf(argv[i], "%x", &(DEST)) == 1 ) )
|
||||
|
||||
for(i = 1 ; i < argc ; i++) {
|
||||
unsigned int uc, from, to;
|
||||
|
||||
if (strcmp(argv[i], "--hex") == 0) {
|
||||
hex = 1;
|
||||
}
|
||||
else if (strcmp(argv[i], "--decimal") == 0) {
|
||||
hex = 0;
|
||||
}
|
||||
else if (strcmp(argv[i], "--decode") == 0) {
|
||||
#define RD fgetc_unlocked(stdin)
|
||||
#define WR(C) do { \
|
||||
if (C != -1) { \
|
||||
printf("%04x\n", C); \
|
||||
} else if (!feof(stdin)) { \
|
||||
fprintf(stderr, "read error\n"); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} \
|
||||
} while(0)
|
||||
while(!feof(stdin)) {
|
||||
READ_UNICODE(RD, WR);
|
||||
}
|
||||
#undef RD
|
||||
#undef WR
|
||||
}
|
||||
else if (strcmp(argv[i], "-range") == 0
|
||||
&& i + 2 < argc
|
||||
&& (++i, 1)
|
||||
&& READ_INT(from)
|
||||
&& (++i, 1)
|
||||
&& READ_INT(to)
|
||||
) {
|
||||
unsigned int i;
|
||||
for(i = from ; i < to ; i++) {
|
||||
#define EM(C) fputc_unlocked(C, stdout)
|
||||
EMIT_UNICODE(i, EM);
|
||||
#undef EM
|
||||
}
|
||||
}
|
||||
else if (READ_INT(uc)) {
|
||||
#define EM(C) fputc_unlocked(C, stdout)
|
||||
EMIT_UNICODE(uc, EM);
|
||||
#undef EM
|
||||
}
|
||||
else {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* IDNA helpers. */
|
||||
#undef ADD_BYTE
|
||||
#undef INCREASE_CAPA
|
||||
@@ -975,19 +922,19 @@ char *hts_convertStringUTF8ToIDNA(const char *s, size_t size) {
|
||||
/* Reader: can read bytes up to j */
|
||||
#define RD ( utfSeq < j ? segData[utfSeq++] : -1 )
|
||||
|
||||
/* Writer: upon error, return FFFD (replacement character) */
|
||||
#define WR(C) do { \
|
||||
if ((C) != -1) { \
|
||||
/* copy character */ \
|
||||
assertf(segOutputSize < segSize); \
|
||||
segInt[segOutputSize++] = (C); \
|
||||
} \
|
||||
/* In case of error, abort. */ \
|
||||
else { \
|
||||
FREE_BUFFER(); \
|
||||
return NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
/* Writer: a malformed sequence abandons the encode (NULL) */
|
||||
#define WR(C) \
|
||||
do { \
|
||||
if ((C) != -1) { \
|
||||
/* copy character */ \
|
||||
assertf(segOutputSize < segSize); \
|
||||
segInt[segOutputSize++] = (C); \
|
||||
} else { \
|
||||
freet(segInt); \
|
||||
FREE_BUFFER(); \
|
||||
return NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Read/Write Unicode character. */
|
||||
READ_UNICODE(RD, WR);
|
||||
@@ -1205,17 +1152,45 @@ int hts_isStringUTF8(const char *s, size_t size) {
|
||||
const unsigned char *const data = (const unsigned char*) s;
|
||||
size_t i;
|
||||
|
||||
/* Strict RFC 3629: reject overlongs, surrogates, > U+10FFFF, 5/6-byte. */
|
||||
for(i = 0 ; i < size ; ) {
|
||||
/* Reader: can read bytes up to j */
|
||||
#define RD ( i < size ? data[i++] : -1 )
|
||||
const unsigned char c = data[i];
|
||||
size_t len, k;
|
||||
hts_UCS4 uc, min;
|
||||
|
||||
/* Writer: upon error, return FFFD (replacement character) */
|
||||
#define WR(C) if ((C) == -1) { return 0; }
|
||||
if (c < 0x80) {
|
||||
i++;
|
||||
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];
|
||||
|
||||
/* Read Unicode character. */
|
||||
READ_UNICODE(RD, WR);
|
||||
#undef RD
|
||||
#undef WR
|
||||
if ((cc & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
uc = (uc << 6) | (cc & 0x3f);
|
||||
}
|
||||
if (uc < min || uc > 0x10FFFF || (uc >= 0xD800 && uc <= 0xDFFF)) {
|
||||
return 0;
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@@ -76,7 +76,8 @@ extern char *hts_convertStringIDNAToUTF8(const char *s, size_t size);
|
||||
extern int hts_isStringIDNA(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Extract the charset from the HTML buffer "html"
|
||||
* Extract the <meta> charset from the HTML buffer "html" (HTML5 charset= or
|
||||
* legacy http-equiv form). Returns a malloc'ed string, or NULL if none.
|
||||
**/
|
||||
extern char *hts_getCharsetFromMeta(const char *html, size_t size);
|
||||
|
||||
@@ -86,7 +87,8 @@ extern char *hts_getCharsetFromMeta(const char *html, size_t size);
|
||||
extern int hts_isStringAscii(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Is the given string an UTF-8 string ?
|
||||
* Is the given string valid UTF-8 ? Strict RFC 3629: overlong forms,
|
||||
* surrogates, codepoints above U+10FFFF and 5/6-byte sequences are rejected.
|
||||
**/
|
||||
extern int hts_isStringUTF8(const char *s, size_t size);
|
||||
|
||||
|
||||
@@ -54,10 +54,6 @@ Please visit our Website: http://www.httrack.com
|
||||
// extension par défaut pour fichiers n'en ayant pas
|
||||
#define DEFAULT_EXT ".html"
|
||||
#define DEFAULT_EXT_SHORT ".htm"
|
||||
// #define DEFAULT_BIN_EXT ".bin"
|
||||
// #define DEFAULT_BIN_EXT_SHORT ".bin"
|
||||
// #define DEFAULT_EXT ".txt"
|
||||
// #define DEFAULT_EXT_SHORT ".txt"
|
||||
|
||||
// éviter les /nul, /con..
|
||||
#define HTS_OVERRIDE_DOS_FOLDERS 1
|
||||
@@ -80,13 +76,6 @@ Please visit our Website: http://www.httrack.com
|
||||
// always direct-to-disk (0/1)
|
||||
#define HTS_DIRECTDISK_ALWAYS 1
|
||||
|
||||
// gérer une table de hachage?
|
||||
// REMOVED
|
||||
// #define HTS_HASH 1
|
||||
|
||||
// fast cache (build hash table)
|
||||
#define HTS_FAST_CACHE 1
|
||||
|
||||
// le > peut être considéré comme un tag de fermeture de commentaire (<!-- > est
|
||||
// valide)
|
||||
#define GT_ENDS_COMMENT 1
|
||||
@@ -97,27 +86,10 @@ Please visit our Website: http://www.httrack.com
|
||||
// always transform a '//' into a sigle '/'
|
||||
#define HTS_STRIP_DOUBLE_SLASH 0
|
||||
|
||||
// case-sensitive pour les dossiers et fichiers (0/1)
|
||||
// [normalement 1, mais pose des problèmes (url malformée par exemple) et n'est
|
||||
// pas très utile..
|
||||
// ..et pas bcp respecté]
|
||||
// REMOVED
|
||||
// #define HTS_CASSE 0
|
||||
|
||||
// Un fichier ayant une taille différente du content-length doit il être annulé?
|
||||
// SEE opt.tolerant and opt.http10
|
||||
// #define HTS_CL_IS_FATAL 0
|
||||
|
||||
// une erreur supprime le fichier sur disque
|
||||
// (non fixé pour cause de retry)
|
||||
#define HTS_REMOVE_BAD_FILES 0
|
||||
|
||||
// en cas de Range: xx- donnant un Content-length: xx
|
||||
// alors skipper le fichier, considéré comme transmis
|
||||
// #define HTS_SKIP_FULL_RANGE 1
|
||||
|
||||
// nombre max de filtres que l'utilisateur peut fixer
|
||||
// #define HTS_FILTERSMAX 10000
|
||||
#define HTS_FILTERSINC 1000
|
||||
|
||||
// connect non bloquant? (poll sur write)
|
||||
|
||||
761
src/htscore.c
761
src/htscore.c
File diff suppressed because it is too large
Load Diff
@@ -152,6 +152,15 @@ struct lien_adrfilsave {
|
||||
char save[HTS_URLMAXSIZE * 2]; /**< local save path (with directory) */
|
||||
};
|
||||
|
||||
/** Per-slot connect-fallback bookkeeping (parallel to struct_back.lnk).
|
||||
Tracks which resolved address the slot is currently connecting to so a
|
||||
stuck connect can be retried against the next one. */
|
||||
typedef struct hts_connect_fallback {
|
||||
int addr_index; /**< candidate being connected (0-based) */
|
||||
int addr_count; /**< resolved addresses; -1 = not yet probed */
|
||||
TStamp connect_start; /**< when the current candidate's connect began */
|
||||
} hts_connect_fallback;
|
||||
|
||||
/** The download-slot ring: the set of concurrent transfers in flight.
|
||||
Allocated/owned by the engine; consumers (status callbacks, the loop)
|
||||
read it but do not resize or free it. */
|
||||
@@ -168,6 +177,7 @@ struct struct_back {
|
||||
int count; /**< number of usable slots (back_max) */
|
||||
coucal ready; /**< index of slots whose transfer completed */
|
||||
LLint ready_size_bytes; /**< total bytes buffered in completed slots */
|
||||
hts_connect_fallback *connect_fallback; /**< per-slot, count+1 entries */
|
||||
};
|
||||
|
||||
typedef struct cache_back_zip_entry cache_back_zip_entry;
|
||||
@@ -183,8 +193,7 @@ struct cache_back {
|
||||
/* */
|
||||
int type;
|
||||
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 *txt; /**< human-readable file list (info) */
|
||||
char lastmodified[256];
|
||||
@@ -204,6 +213,9 @@ struct cache_back {
|
||||
cache_back_zip_entry *zipEntries;
|
||||
int zipEntriesOffs;
|
||||
int zipEntriesCapa;
|
||||
hts_boolean
|
||||
zipWriteFailed; /**< a cache write failed; stop touching the stream */
|
||||
int zipWriteFailures; /**< consecutive entry write failures; reset on store */
|
||||
};
|
||||
|
||||
#ifndef HTS_DEF_FWSTRUCT_hash_struct
|
||||
@@ -222,8 +234,12 @@ struct hash_struct {
|
||||
coucal adrfil;
|
||||
/* former address+path -> link index (renamed/moved entries) */
|
||||
coucal former_adrfil;
|
||||
/* scratch buffers reused across lookups (not reentrant) */
|
||||
int normalized;
|
||||
/* effective urlhack sub-flags: www.==host / // collapse / query-arg sort */
|
||||
hts_boolean norm_host;
|
||||
hts_boolean norm_slash;
|
||||
hts_boolean norm_query;
|
||||
/* query-strip keys (not owned); set from opt->strip_query at hash_init */
|
||||
const char *strip_query;
|
||||
char normfil[HTS_URLMAXSIZE * 2];
|
||||
char normfil2[HTS_URLMAXSIZE * 2];
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
@@ -271,12 +287,12 @@ struct filecreate_params {
|
||||
|
||||
/* True if a new cache is being written (plain or zip backend). */
|
||||
HTS_STATIC int cache_writable(cache_back * cache) {
|
||||
return (cache != NULL && (cache->dat != NULL || cache->zipOutput != NULL));
|
||||
return (cache != NULL && cache->zipOutput != NULL);
|
||||
}
|
||||
|
||||
/* True if an old cache is available to read (plain or zip backend). */
|
||||
HTS_STATIC int cache_readable(cache_back * cache) {
|
||||
return (cache != NULL && (cache->olddat != NULL || cache->zipInput != NULL));
|
||||
return (cache != NULL && cache->zipInput != NULL);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -346,12 +362,42 @@ void usercommand(httrackp * opt, int exe, const char *cmd, const char *file,
|
||||
|
||||
void usercommand_exe(const char *cmd, const char *file);
|
||||
|
||||
// Finish the makeindex index.html (footer + refresh meta), run usercommand.
|
||||
// Updates *makeindex_done/*makeindex_fp in place; adr/fil are the mode strings.
|
||||
void hts_finish_makeindex(httrackp *opt, int *makeindex_done,
|
||||
FILE **makeindex_fp, int makeindex_links,
|
||||
const char *makeindex_firstlink,
|
||||
const char *template_footer, const char *adr,
|
||||
const char *fil);
|
||||
|
||||
// Flush ht_buff[0..ht_len] to save on disk (skip if MD5 unchanged); *fp
|
||||
// closed+NULLed on write. Precondition: ht_len>0.
|
||||
void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
|
||||
FILE **fp, const char *ht_buff, size_t ht_len,
|
||||
const char *adr, const char *fil, const char *save);
|
||||
|
||||
int filters_init(char ***ptrfilters, int maxfilter, int filterinc);
|
||||
|
||||
int fspc(httrackp * opt, FILE * fp, const char *type);
|
||||
|
||||
char *next_token(char *p, int flag);
|
||||
|
||||
/* Like fil_normalized(), but first drops query keys in STRIP (comma-separated,
|
||||
"*" = all); STRIP NULL/empty behaves exactly like fil_normalized(). */
|
||||
char *fil_normalized_filtered(const char *source, char *dest,
|
||||
const char *strip);
|
||||
|
||||
/* As fil_normalized_filtered(), but DO_SLASH/DO_QUERY gate the // collapse and
|
||||
the query-argument sort independently (the urlhack sub-flags). */
|
||||
char *fil_normalized_filtered_ex(const char *source, char *dest,
|
||||
const char *strip, int do_slash, int do_query);
|
||||
|
||||
/* For URL ADR/FIL, return (in DEST) the comma keylist to strip from the
|
||||
'\n'-separated "[pattern=]keys" RULES (patterns matched on host/path via
|
||||
strjoker, last wins); NULL if none match. Feeds fil_normalized_filtered(). */
|
||||
const char *hts_query_strip_keys(const char *rules, const char *adr,
|
||||
const char *fil, char *dest, size_t destsize);
|
||||
|
||||
/* Read a whole file into a freshly malloc'd, NUL-terminated buffer; the caller
|
||||
owns it and must release it with freet(). Return NULL on missing/unreadable
|
||||
file (readfile_or substitutes defaultdata instead). The byte content is NOT
|
||||
@@ -365,13 +411,16 @@ char *readfile_utf8(const char *fil);
|
||||
|
||||
char *readfile_or(const char *fil, const char *defaultdata);
|
||||
|
||||
#if 0
|
||||
void check_rate(TStamp stat_timestart, int maxrate);
|
||||
#endif
|
||||
|
||||
/* Backing (download-slot) scheduler. Operate on the back[] ring (struct_back).
|
||||
Not thread-safe; call from the single crawl loop. */
|
||||
|
||||
/* True if a connecting slot should give up on the current address and try the
|
||||
next one: a fallback address remains (addr_index+1 < addr_count) and the
|
||||
candidate has been connecting for at least its deadline, min(timeout, an
|
||||
internal cap). elapsed/timeout in seconds. Exposed for the -#D self-test. */
|
||||
int back_connect_fallback_due(int addr_index, int addr_count, int elapsed,
|
||||
int timeout);
|
||||
|
||||
/* How many new sockets may be opened now, honoring maxsoc and the maxconn rate
|
||||
limit (>=0). _strict ignores reserved-slot headroom; the plain form leaves
|
||||
room for naming tests and stops at 0 when the stack is nearly full. */
|
||||
@@ -379,6 +428,19 @@ int back_pluggable_sockets(struct_back * sback, httrackp * opt);
|
||||
|
||||
int back_pluggable_sockets_strict(struct_back * sback, httrackp * opt);
|
||||
|
||||
/* One engine-loop tick: refresh the transfer stats and run the loop callback
|
||||
for slot b (-1 = none). HTS_FALSE = the callback requested an abort. */
|
||||
hts_boolean hts_loop_tick(struct_back *sback, httrackp *opt, int b, int ptr);
|
||||
|
||||
/* Wait until a test socket can be plugged, pumping transfers, stats and the
|
||||
loop callback; gives up past the -E deadline. HTS_FALSE = callback abort. */
|
||||
hts_boolean hts_wait_available_socket(struct_back *sback, httrackp *opt,
|
||||
cache_back *cache, int ptr);
|
||||
|
||||
/* Randomized inter-file pause target in [min_ms,max_ms] (#185), derived from a
|
||||
timestamp seed so it is stable within one gap and rerolls per launch. */
|
||||
int hts_pause_target_ms(TStamp seed, int min_ms, int max_ms);
|
||||
|
||||
/* Schedule more links from the heap into free slots. Returns the number queued,
|
||||
or <=0 if none could be added (no free slot / paused / stopped). */
|
||||
int back_fill(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
@@ -400,11 +462,6 @@ int ask_continue(httrackp * opt);
|
||||
/* Number of decimal digits in n. */
|
||||
int nombre_digit(int n);
|
||||
|
||||
// Java
|
||||
#if 0
|
||||
int hts_add_file(char *file, int file_position);
|
||||
#endif
|
||||
|
||||
// Polling
|
||||
#if HTS_POLL
|
||||
int check_flot(T_SOC s);
|
||||
@@ -427,4 +484,8 @@ void voidf(void);
|
||||
/* HTML marker comment marking where the top index is spliced. */
|
||||
#define HTS_TOPINDEX "TOP_INDEX_HTTRACK"
|
||||
|
||||
/* Worst-case byte expansion HT_ADD_HTMLESCAPED* must reserve per escaper. */
|
||||
#define HTS_HTMLESCAPE_MAXEXP 5 /* escape_for_html_print: '&'->"&" */
|
||||
#define HTS_HTMLESCAPE_FULL_MAXEXP 6 /* _full: high byte->"&#xHH;" */
|
||||
|
||||
#endif
|
||||
|
||||
1395
src/htscoremain.c
1395
src/htscoremain.c
File diff suppressed because it is too large
Load Diff
@@ -69,11 +69,15 @@ typedef struct t_hts_callbackarg t_hts_callbackarg;
|
||||
typedef struct t_hts_callbackarg t_hts_callbackarg;
|
||||
#endif
|
||||
|
||||
/* Marks a symbol an external wrapper module exports back to the engine
|
||||
(dllexport on Windows, nothing elsewhere). */
|
||||
/* Marks a symbol an external wrapper module exports back to the engine.
|
||||
Must override -fvisibility=hidden on ELF, or dlopen()ed plugins
|
||||
hide their own hts_plug()/hts_unplug() entry points. */
|
||||
#ifndef EXTERNAL_FUNCTION
|
||||
#ifdef _WIN32
|
||||
#define EXTERNAL_FUNCTION __declspec(dllexport)
|
||||
#elif ((defined(__GNUC__) && (__GNUC__ >= 4)) || \
|
||||
(defined(HAVE_VISIBILITY) && HAVE_VISIBILITY))
|
||||
#define EXTERNAL_FUNCTION __attribute__((visibility("default")))
|
||||
#else
|
||||
#define EXTERNAL_FUNCTION
|
||||
#endif
|
||||
|
||||
379
src/htsdns_selftest.c
Normal file
379
src/htsdns_selftest.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 2026 Xavier Roche and other contributors
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Ethical use: we kindly ask that you NOT use this software to harvest email
|
||||
addresses or to collect any other private information about people. Doing so
|
||||
would dishonor our work and waste the many hours we have spent on it.
|
||||
|
||||
Please visit our Website: http://www.httrack.com
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* File: htsdns_selftest.c subroutines: */
|
||||
/* in-process self-test for the DNS resolver and cache */
|
||||
/* Author: Xavier Roche */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/* Routes the resolver through a scripted getaddrinfo (hts_resolver_backend)
|
||||
instead of the network, so resolution and the DNS cache are testable for a
|
||||
fixed set of scenarios (IPv4/IPv6/dual-stack, errors, family filter,
|
||||
cache reuse) with no live DNS. */
|
||||
|
||||
#define HTS_INTERNAL_BYTECODE
|
||||
|
||||
#include "htsdns_selftest.h"
|
||||
|
||||
#include "htscore.h"
|
||||
#include "htslib.h"
|
||||
#include "htsnet.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if HTS_INET6 != 0
|
||||
|
||||
/* IPV6_resolver: 0 = v4+v6, 1 = v4 only, 2 = v6 only (htscoremain -@i). */
|
||||
extern int IPV6_resolver;
|
||||
|
||||
/* One scripted host: either a getaddrinfo error, or an ordered address list. */
|
||||
typedef struct mock_addr {
|
||||
int family; /* AF_INET / AF_INET6 */
|
||||
unsigned char addr[16]; /* 4 (v4) or 16 (v6) meaningful bytes */
|
||||
} mock_addr;
|
||||
|
||||
typedef struct mock_host {
|
||||
const char *name;
|
||||
int gai_err; /* non-zero: getaddrinfo returns this */
|
||||
int naddr;
|
||||
mock_addr addr[6];
|
||||
int calls; /* times the backend resolved this host */
|
||||
} mock_host;
|
||||
|
||||
static mock_host mock_hosts[] = {
|
||||
{"v4only.test", 0, 1, {{AF_INET, {1, 2, 3, 4}}}, 0},
|
||||
{"v6only.test", 0, 1, {{AF_INET6, {0x20, 0x01, 0x0d, 0xb8, [15] = 1}}}, 0},
|
||||
/* dual stack, IPv6 first (RFC 6724 order) then IPv4 */
|
||||
{"dual.test",
|
||||
0,
|
||||
2,
|
||||
{{AF_INET6, {0x20, 0x01, 0x0d, 0xb8, [15] = 2}}, {AF_INET, {5, 6, 7, 8}}},
|
||||
0},
|
||||
/* dual stack, IPv4 first: distinguishes "keep the first address" from
|
||||
"prefer a family", so the selection contract is actually pinned. */
|
||||
{"dual4.test",
|
||||
0,
|
||||
2,
|
||||
{{AF_INET, {9, 10, 11, 12}},
|
||||
{AF_INET6, {0x20, 0x01, 0x0d, 0xb8, [15] = 3}}},
|
||||
0},
|
||||
/* more addresses than HTS_MAXADDRNUM: the list must clamp to the cap. */
|
||||
{"many.test",
|
||||
0,
|
||||
6,
|
||||
{{AF_INET, {10, 0, 0, 1}},
|
||||
{AF_INET, {10, 0, 0, 2}},
|
||||
{AF_INET, {10, 0, 0, 3}},
|
||||
{AF_INET, {10, 0, 0, 4}},
|
||||
{AF_INET, {10, 0, 0, 5}},
|
||||
{AF_INET, {10, 0, 0, 6}}},
|
||||
0},
|
||||
{"nodns.test", EAI_NONAME, 0, {{0}}, 0},
|
||||
};
|
||||
|
||||
static mock_host *mock_find(const char *name) {
|
||||
for (size_t i = 0; i < sizeof(mock_hosts) / sizeof(mock_hosts[0]); i++) {
|
||||
if (strcmp(mock_hosts[i].name, name) == 0)
|
||||
return &mock_hosts[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mock_reset_calls(void) {
|
||||
for (size_t i = 0; i < sizeof(mock_hosts) / sizeof(mock_hosts[0]); i++)
|
||||
mock_hosts[i].calls = 0;
|
||||
}
|
||||
|
||||
/* Build one addrinfo node owning its sockaddr (freed by mock_freeaddrinfo). */
|
||||
static struct addrinfo *mock_mkai(const mock_addr *a) {
|
||||
struct addrinfo *ai = calloct(1, sizeof(*ai));
|
||||
|
||||
ai->ai_family = a->family;
|
||||
if (a->family == AF_INET) {
|
||||
struct sockaddr_in *sin = calloct(1, sizeof(*sin));
|
||||
|
||||
sin->sin_family = AF_INET;
|
||||
memcpy(&sin->sin_addr, a->addr, 4);
|
||||
ai->ai_addr = (struct sockaddr *) sin;
|
||||
ai->ai_addrlen = sizeof(*sin);
|
||||
} else {
|
||||
struct sockaddr_in6 *sin6 = calloct(1, sizeof(*sin6));
|
||||
|
||||
sin6->sin6_family = AF_INET6;
|
||||
memcpy(&sin6->sin6_addr, a->addr, 16);
|
||||
ai->ai_addr = (struct sockaddr *) sin6;
|
||||
ai->ai_addrlen = sizeof(*sin6);
|
||||
}
|
||||
return ai;
|
||||
}
|
||||
|
||||
static int mock_getaddrinfo(const char *node, const char *service,
|
||||
const struct addrinfo *hints,
|
||||
struct addrinfo **res) {
|
||||
mock_host *const h = mock_find(node);
|
||||
const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
|
||||
struct addrinfo *head = NULL, *tail = NULL;
|
||||
|
||||
(void) service;
|
||||
*res = NULL;
|
||||
if (h == NULL)
|
||||
return EAI_NONAME;
|
||||
h->calls++; /* a real backend hit; a cached host skips this */
|
||||
if (h->gai_err != 0)
|
||||
return h->gai_err;
|
||||
for (int i = 0; i < h->naddr; i++) {
|
||||
if (want != PF_UNSPEC && want != h->addr[i].family)
|
||||
continue; /* honor the requested family (v4/v6 only) */
|
||||
struct addrinfo *const ai = mock_mkai(&h->addr[i]);
|
||||
|
||||
if (head == NULL)
|
||||
head = ai;
|
||||
else
|
||||
tail->ai_next = ai;
|
||||
tail = ai;
|
||||
}
|
||||
if (head == NULL)
|
||||
return EAI_NONAME; /* filtered to empty, as the libc resolver does */
|
||||
*res = head;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mock_freeaddrinfo(struct addrinfo *res) {
|
||||
while (res != NULL) {
|
||||
struct addrinfo *const next = res->ai_next;
|
||||
|
||||
freet(res->ai_addr);
|
||||
freet(res);
|
||||
res = next;
|
||||
}
|
||||
}
|
||||
|
||||
static const hts_resolver_backend mock_backend = {mock_getaddrinfo,
|
||||
mock_freeaddrinfo};
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
failures++; \
|
||||
fprintf(stderr, "dns-selftest: FAIL at %s:%d: %s\n", __FILE__, __LINE__, \
|
||||
#cond); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Resolve via the uncached entry point; return the address family, or
|
||||
AF_UNSPEC if the host did not resolve. */
|
||||
static int resolve_family_nocache(const char *host) {
|
||||
SOCaddr addr;
|
||||
const char *err = NULL;
|
||||
|
||||
if (hts_dns_resolve_nocache2(host, &addr, &err) == NULL)
|
||||
return AF_UNSPEC;
|
||||
return SOCaddr_sinfamily(addr);
|
||||
}
|
||||
|
||||
int dns_selftests(httrackp *opt) {
|
||||
failures = 0;
|
||||
hts_dns_set_resolver_backend(&mock_backend);
|
||||
|
||||
/* IPv4-only / IPv6-only hosts map to the right family. */
|
||||
IPV6_resolver = 0;
|
||||
CHECK(resolve_family_nocache("v4only.test") == AF_INET);
|
||||
CHECK(resolve_family_nocache("v6only.test") == AF_INET6);
|
||||
|
||||
/* Dual-stack: the single-address API returns the *first* resolved address.
|
||||
Both orderings pin selection by position, not a family preference. The
|
||||
multi-address API (resolve_all, below) exposes the whole list. */
|
||||
CHECK(resolve_family_nocache("dual.test") == AF_INET6); /* v6 listed first */
|
||||
CHECK(resolve_family_nocache("dual4.test") == AF_INET); /* v4 listed first */
|
||||
|
||||
/* Unknown host does not resolve. */
|
||||
CHECK(resolve_family_nocache("nodns.test") == AF_UNSPEC);
|
||||
|
||||
/* Family filter (-@i4 / -@i6) selects v4 / v6 out of the dual-stack host. */
|
||||
IPV6_resolver = 1;
|
||||
CHECK(resolve_family_nocache("dual.test") == AF_INET);
|
||||
IPV6_resolver = 2;
|
||||
CHECK(resolve_family_nocache("dual.test") == AF_INET6);
|
||||
IPV6_resolver = 0;
|
||||
|
||||
/* Cached driver resolves a host once and reuses the *same* address. */
|
||||
mock_reset_calls();
|
||||
{
|
||||
SOCaddr a1, a2;
|
||||
char ip1[64], ip2[64];
|
||||
const char *err = NULL;
|
||||
|
||||
CHECK(hts_dns_resolve2(opt, "v4only.test", &a1, &err) != NULL);
|
||||
CHECK(hts_dns_resolve2(opt, "v4only.test", &a2, &err) != NULL);
|
||||
CHECK(mock_find("v4only.test")->calls == 1);
|
||||
/* the cache returns the right address, not merely a hit for the key */
|
||||
SOCaddr_inetntoa(ip1, sizeof(ip1), a1);
|
||||
SOCaddr_inetntoa(ip2, sizeof(ip2), a2);
|
||||
CHECK(strcmp(ip1, "1.2.3.4") == 0);
|
||||
CHECK(strcmp(ip1, ip2) == 0);
|
||||
}
|
||||
|
||||
/* A negative result is cached too: a second lookup does not re-resolve. */
|
||||
{
|
||||
SOCaddr a1, a2;
|
||||
const char *err = NULL;
|
||||
|
||||
CHECK(hts_dns_resolve2(opt, "nodns.test", &a1, &err) == NULL);
|
||||
CHECK(hts_dns_resolve2(opt, "nodns.test", &a2, &err) == NULL);
|
||||
CHECK(mock_find("nodns.test")->calls == 1); /* resolved once, then cached */
|
||||
}
|
||||
|
||||
/* Multi-address resolution: count and order are the connect-fallback
|
||||
contract. A dead first address is retried against the next, so both must be
|
||||
exact. */
|
||||
mock_reset_calls();
|
||||
{
|
||||
SOCaddr addrs[HTS_MAXADDRNUM];
|
||||
char ip[64];
|
||||
const char *err = NULL;
|
||||
|
||||
/* dual-stack, in resolver order: [0]=v6, [1]=v4 */
|
||||
CHECK(hts_dns_resolve_all(opt, "dual.test", addrs, HTS_MAXADDRNUM, &err) ==
|
||||
2);
|
||||
CHECK(SOCaddr_sinfamily(addrs[0]) == AF_INET6);
|
||||
CHECK(SOCaddr_sinfamily(addrs[1]) == AF_INET);
|
||||
SOCaddr_inetntoa(ip, sizeof(ip), addrs[1]);
|
||||
CHECK(strcmp(ip, "5.6.7.8") == 0);
|
||||
CHECK(mock_find("dual.test")->calls ==
|
||||
1); /* one backend hit for the list */
|
||||
|
||||
/* single-address host: count 1 */
|
||||
CHECK(hts_dns_resolve_all(opt, "v4only.test", addrs, HTS_MAXADDRNUM,
|
||||
&err) == 1);
|
||||
SOCaddr_inetntoa(ip, sizeof(ip), addrs[0]);
|
||||
CHECK(strcmp(ip, "1.2.3.4") == 0);
|
||||
|
||||
/* does-not-resolve: count 0 (negative), no addresses */
|
||||
CHECK(hts_dns_resolve_all(opt, "nodns.test", addrs, HTS_MAXADDRNUM, &err) ==
|
||||
0);
|
||||
|
||||
/* more than the cap: the kept list is clamped to HTS_MAXADDRNUM, keeping
|
||||
the FIRST addresses in resolver order (not some other window) */
|
||||
CHECK(hts_dns_resolve_all(opt, "many.test", addrs, HTS_MAXADDRNUM, &err) ==
|
||||
HTS_MAXADDRNUM);
|
||||
SOCaddr_inetntoa(ip, sizeof(ip), addrs[0]);
|
||||
CHECK(strcmp(ip, "10.0.0.1") == 0);
|
||||
SOCaddr_inetntoa(ip, sizeof(ip), addrs[HTS_MAXADDRNUM - 1]);
|
||||
CHECK(strcmp(ip, "10.0.0.4") == 0);
|
||||
|
||||
/* family filter still applies through the list path */
|
||||
IPV6_resolver = 1;
|
||||
CHECK(hts_dns_resolve_all(opt, "dual4.test", addrs, HTS_MAXADDRNUM, &err) ==
|
||||
1);
|
||||
CHECK(SOCaddr_sinfamily(addrs[0]) == AF_INET);
|
||||
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
|
||||
first: this is what back_connect_next relies on to reach the fallback. */
|
||||
{
|
||||
htsblk r;
|
||||
int count = -1;
|
||||
T_SOC s;
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
s = newhttp_addr(opt, "dual.test", &r, 80, 0, 0, &count);
|
||||
CHECK(count == 2);
|
||||
CHECK(SOCaddr_sinfamily(r.address) == AF_INET6); /* index 0 = v6 */
|
||||
if (s != INVALID_SOCKET)
|
||||
deletesoc(s);
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
count = -1;
|
||||
s = newhttp_addr(opt, "dual.test", &r, 80, 0, 1, &count);
|
||||
CHECK(count == 2);
|
||||
CHECK(SOCaddr_sinfamily(r.address) == AF_INET); /* index 1 = v4 */
|
||||
if (s != INVALID_SOCKET)
|
||||
deletesoc(s);
|
||||
|
||||
/* out-of-range index: no address selected (address stays unset) */
|
||||
hts_init_htsblk(&r);
|
||||
s = newhttp_addr(opt, "dual.test", &r, 80, 0, 2, NULL);
|
||||
CHECK(s == INVALID_SOCKET);
|
||||
if (s != INVALID_SOCKET)
|
||||
deletesoc(s);
|
||||
}
|
||||
|
||||
/* Connect-fallback decision (consumer of the multi-address list): when a
|
||||
stuck connect should abandon the current address for the next one. */
|
||||
{
|
||||
/* no fallback for the last/only candidate, whatever the elapsed time */
|
||||
CHECK(back_connect_fallback_due(0, 1, 9999, 120) == 0);
|
||||
CHECK(back_connect_fallback_due(1, 2, 9999, 120) == 0);
|
||||
CHECK(back_connect_fallback_due(3, 4, 9999, 120) == 0);
|
||||
/* fallback available: wait the per-candidate deadline (cap 10s here) */
|
||||
CHECK(back_connect_fallback_due(0, 2, 9, 120) == 0);
|
||||
CHECK(back_connect_fallback_due(0, 2, 10, 120) == 1);
|
||||
CHECK(back_connect_fallback_due(2, 4, 10, 120) == 1);
|
||||
/* a shorter slot timeout shortens the deadline (min(timeout, cap)) */
|
||||
CHECK(back_connect_fallback_due(0, 2, 4, 5) == 0);
|
||||
CHECK(back_connect_fallback_due(0, 2, 5, 5) == 1);
|
||||
/* no timeout management: never force a fallback */
|
||||
CHECK(back_connect_fallback_due(0, 2, 9999, 0) == 0);
|
||||
}
|
||||
|
||||
hts_dns_set_resolver_backend(NULL);
|
||||
return failures;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int dns_selftests(httrackp *opt) {
|
||||
(void) opt;
|
||||
return 0; /* resolver seam only exists in the IPv6 build */
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 1998 Xavier Roche and other contributors
|
||||
Copyright (C) 2026 Xavier Roche and other contributors
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -26,40 +26,25 @@ Please visit our Website: http://www.httrack.com
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* File: Java classes parser .h */
|
||||
/* Author: Yann Philippot */
|
||||
/* File: htsdns_selftest.h */
|
||||
/* Author: Xavier Roche */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
#ifndef HTSJAVA_DEFH
|
||||
#define HTSJAVA_DEFH
|
||||
#ifndef HTSDNS_SELFTEST_DEFH
|
||||
#define HTSDNS_SELFTEST_DEFH
|
||||
|
||||
#ifndef HTS_DEF_FWSTRUCT_JAVA_HEADER
|
||||
#define HTS_DEF_FWSTRUCT_JAVA_HEADER
|
||||
typedef struct JAVA_HEADER JAVA_HEADER;
|
||||
#endif
|
||||
struct JAVA_HEADER {
|
||||
unsigned long int magic;
|
||||
unsigned short int minor;
|
||||
unsigned short int major;
|
||||
unsigned short int count;
|
||||
};
|
||||
|
||||
#ifndef HTS_DEF_FWSTRUCT_RESP_STRUCT
|
||||
#define HTS_DEF_FWSTRUCT_RESP_STRUCT
|
||||
typedef struct RESP_STRUCT RESP_STRUCT;
|
||||
#endif
|
||||
struct RESP_STRUCT {
|
||||
int file_position;
|
||||
//
|
||||
unsigned int index1;
|
||||
unsigned int type;
|
||||
char name[1024];
|
||||
};
|
||||
|
||||
/* Library internal definictions */
|
||||
#ifdef HTS_INTERNAL_BYTECODE
|
||||
|
||||
EXTERNAL_FUNCTION int hts_plug_java(httrackp * opt, const char *argv);
|
||||
#ifndef HTS_DEF_FWSTRUCT_httrackp
|
||||
#define HTS_DEF_FWSTRUCT_httrackp
|
||||
typedef struct httrackp httrackp;
|
||||
#endif
|
||||
|
||||
/* Drive the DNS resolver and cache through a scripted (mock) getaddrinfo,
|
||||
asserting address family, single-address selection, negative caching, the
|
||||
IPv4/IPv6 family filter, and that a cached host is resolved only once.
|
||||
Returns the number of failed checks (0 == success). */
|
||||
int dns_selftests(httrackp *opt);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,12 +30,14 @@ Please visit our Website: http://www.httrack.com
|
||||
/* Author: Xavier Roche */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "htscharset.h"
|
||||
#include "htsencoding.h"
|
||||
#include "htssafe.h"
|
||||
|
||||
/* static int decode_entity(const unsigned int hash, const size_t len);
|
||||
*/
|
||||
/* static int decode_entity(const uint64_t hash, const size_t len);
|
||||
*/
|
||||
#include "htsentities.h"
|
||||
|
||||
/* hexadecimal conversion */
|
||||
@@ -50,30 +52,31 @@ static int get_hex_value(char c) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Numerical Recipes,
|
||||
see <http://en.wikipedia.org/wiki/Linear_congruential_generator> */
|
||||
#define HASH_PRIME ( 1664525 )
|
||||
#define HASH_CONST ( 1013904223 )
|
||||
#define HASH_ADD(HASH, C) do { \
|
||||
(HASH) *= HASH_PRIME; \
|
||||
(HASH) += HASH_CONST; \
|
||||
(HASH) += (C); \
|
||||
} while(0)
|
||||
/* 64-bit FNV-1a; must match htsentities.sh, which keys the entity table on it.
|
||||
*/
|
||||
#define HASH_INIT 0xcbf29ce484222325ULL
|
||||
#define HASH_PRIME 0x100000001b3ULL
|
||||
#define HASH_ADD(HASH, C) \
|
||||
do { \
|
||||
(HASH) ^= (unsigned char) (C); \
|
||||
(HASH) *= HASH_PRIME; \
|
||||
} while (0)
|
||||
|
||||
int hts_unescapeEntitiesWithCharset(const char *src, char *dest, const size_t max, const char *charset) {
|
||||
size_t i, j, ampStart, ampStartDest;
|
||||
int uc;
|
||||
int hex;
|
||||
unsigned int hash;
|
||||
uint64_t hash;
|
||||
|
||||
assertf(max != 0);
|
||||
for(i = 0, j = 0, ampStart = (size_t) -1, ampStartDest = 0,
|
||||
uc = -1, hex = 0, hash = 0 ; src[i] != '\0' ; i++) {
|
||||
for (i = 0, j = 0, ampStart = (size_t) -1, ampStartDest = 0, uc = -1, hex = 0,
|
||||
hash = HASH_INIT;
|
||||
src[i] != '\0'; i++) {
|
||||
/* start of entity */
|
||||
if (src[i] == '&') {
|
||||
ampStart = i;
|
||||
ampStartDest = j;
|
||||
hash = 0;
|
||||
hash = HASH_INIT;
|
||||
uc = -1;
|
||||
}
|
||||
/* inside a potential entity */
|
||||
@@ -174,14 +177,11 @@ int hts_unescapeEntitiesWithCharset(const char *src, char *dest, const size_t ma
|
||||
}
|
||||
/* alphanumerical entity */
|
||||
else {
|
||||
/* alphanum and not too far ('ϑ' is the longest) */
|
||||
if (i <= ampStart + 10 &&
|
||||
(
|
||||
(src[i] >= '0' && src[i] <= '9')
|
||||
|| (src[i] >= 'A' && src[i] <= 'Z')
|
||||
|| (src[i] >= 'a' && src[i] <= 'z')
|
||||
)
|
||||
) {
|
||||
/* alphanum, capped at the longest name
|
||||
* '∳' (31) */
|
||||
if (i <= ampStart + 31 && ((src[i] >= '0' && src[i] <= '9') ||
|
||||
(src[i] >= 'A' && src[i] <= 'Z') ||
|
||||
(src[i] >= 'a' && src[i] <= 'z'))) {
|
||||
/* compute hash */
|
||||
HASH_ADD(hash, (unsigned char) src[i]);
|
||||
} else {
|
||||
@@ -190,9 +190,9 @@ int hts_unescapeEntitiesWithCharset(const char *src, char *dest, const size_t ma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy */
|
||||
if (j + 1 > max) {
|
||||
|
||||
/* reserve one byte for the trailing NUL written after the loop */
|
||||
if (j + 1 >= max) {
|
||||
/* overflow */
|
||||
return -1;
|
||||
}
|
||||
@@ -300,6 +300,11 @@ int hts_unescapeUrlSpecial(const char *src, char *dest, const size_t max,
|
||||
|
||||
/* Was the character read successfully ? */
|
||||
if (nRead == utfBufferSize) {
|
||||
/* the 'continue' below skips the NUL-reserve guard: re-check */
|
||||
if (utfBufferJ + utfBufferSize >= max) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Rollback write position to sequence start write position */
|
||||
j = utfBufferJ;
|
||||
|
||||
@@ -314,8 +319,8 @@ int hts_unescapeUrlSpecial(const char *src, char *dest, const size_t max,
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for overflow */
|
||||
if (j + 1 > max) {
|
||||
/* reserve one byte for the trailing NUL written after the loop */
|
||||
if (j + 1 >= max) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
13586
src/htsentities.h
13586
src/htsentities.h
File diff suppressed because it is too large
Load Diff
@@ -1,75 +1,92 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Regenerate htsentities.h from the WHATWG named character references.
|
||||
|
||||
src=html40.txt
|
||||
url=http://www.w3.org/TR/1998/REC-html40-19980424/html40.txt
|
||||
set -euo pipefail
|
||||
|
||||
src=entities.json
|
||||
url=https://html.spec.whatwg.org/entities.json
|
||||
dest=htsentities.h
|
||||
|
||||
(
|
||||
cat <<EOF
|
||||
/*
|
||||
-- ${dest} --
|
||||
FILE GENERATED BY $0, DO NOT MODIFY
|
||||
# 64-bit FNV-1a of $1, printed as a C constant. Must match the hash in
|
||||
# htsencoding.c. The offset basis is stored as its wrapped (signed) bit pattern;
|
||||
# bash arithmetic is 64-bit two's complement, so the result is bit-exact.
|
||||
fnv1a() {
|
||||
local s=$1 i c h=$((0xcbf29ce484222325))
|
||||
for ((i = 0; i < ${#s}; i++)); do
|
||||
printf -v c '%d' "'${s:i:1}"
|
||||
h=$(((h ^ (c & 0xff)) * 0x100000001b3))
|
||||
done
|
||||
printf '0x%016xULL' "$h"
|
||||
}
|
||||
|
||||
We compute the LCG hash
|
||||
(see <http://en.wikipedia.org/wiki/Linear_congruential_generator>)
|
||||
for each entity. We should in theory check using strncmp() that we
|
||||
actually have the correct entity, but this is actually statistically
|
||||
not needed.
|
||||
if [ ! -f "$src" ]; then
|
||||
curl -fsS "$url" -o "$src"
|
||||
fi
|
||||
|
||||
We may want to do better, but we expect the hash function to be uniform, and
|
||||
let the compiler be smart enough to optimize the switch (for example by
|
||||
checking in log2() intervals)
|
||||
|
||||
This code has been generated using the evil $0 script.
|
||||
*/
|
||||
# Keep ';'-terminated single-codepoint names; the ~93 multi-codepoint refs can't
|
||||
# fit decode_entity's single-codepoint return and are skipped (left verbatim).
|
||||
pairs=$(jq -r '
|
||||
to_entries
|
||||
| map(select((.key | endswith(";")) and (.value.codepoints | length == 1)))
|
||||
| sort_by(.key)
|
||||
| .[] | "\(.key | ltrimstr("&") | rtrimstr(";"))\t\(.value.codepoints[0])"' "$src")
|
||||
|
||||
static int decode_entity(const unsigned int hash, const size_t len) {
|
||||
# Skipped multi-codepoint names, kept to prove none aliases an emitted hash.
|
||||
skipped=$(jq -r '
|
||||
to_entries
|
||||
| map(select((.key | endswith(";")) and (.value.codepoints | length > 1)))
|
||||
| .[] | .key | ltrimstr("&") | rtrimstr(";")' "$src")
|
||||
|
||||
cases=""
|
||||
emit_hashes=""
|
||||
while IFS=$'\t' read -r name cp; do
|
||||
hash=$(fnv1a "$name")
|
||||
cases+=" /* $name */"$'\n'
|
||||
cases+=" case $hash:"$'\n'
|
||||
cases+=" if (len == ${#name}) {"$'\n'
|
||||
cases+=" return $cp;"$'\n'
|
||||
cases+=" }"$'\n'
|
||||
cases+=" break;"$'\n'
|
||||
emit_hashes+="$hash"$'\n'
|
||||
done <<<"$pairs"
|
||||
|
||||
skip_hashes=""
|
||||
while IFS= read -r name; do
|
||||
[ -n "$name" ] && skip_hashes+="$(fnv1a "$name")"$'\n'
|
||||
done <<<"$skipped"
|
||||
|
||||
# The switch keys on the hash alone, so the dispatch is correct only while every
|
||||
# emitted name hashes uniquely; prove it here, no runtime name compare needed.
|
||||
dups=$(printf '%s' "$emit_hashes" | sort | uniq -d || true)
|
||||
if [ -n "$dups" ]; then
|
||||
echo "FATAL: two entity names share a hash (duplicate switch case); change the hash:" >&2
|
||||
echo "$dups" >&2
|
||||
exit 1
|
||||
fi
|
||||
# A skipped name colliding with an emitted hash would mis-decode instead of
|
||||
# staying verbatim; forbid that too.
|
||||
aliased=$(comm -12 <(printf '%s' "$emit_hashes" | sort -u) <(printf '%s' "$skip_hashes" | sort -u) || true)
|
||||
if [ -n "$aliased" ]; then
|
||||
echo "FATAL: a skipped multi-codepoint name aliases an emitted hash:" >&2
|
||||
echo "$aliased" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat >"$dest" <<EOF
|
||||
/* GENERATED by $0 from the WHATWG named character references
|
||||
(${url}). DO NOT EDIT.
|
||||
Dispatch keys on a 64-bit FNV-1a hash of the entity name; the generator
|
||||
aborts on any hash collision, so no runtime name compare is needed. */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static int decode_entity(const uint64_t hash, const size_t len) {
|
||||
switch(hash) {
|
||||
EOF
|
||||
(
|
||||
if test -f ${src}; then
|
||||
cat ${src}
|
||||
else
|
||||
GET "${url}"
|
||||
fi
|
||||
) |
|
||||
grep -E '^<!ENTITY [a-zA-Z0-9_]' |
|
||||
sed \
|
||||
-e 's/<!ENTITY //' -e "s/[[:space:]][[:space:]]*/ /g" \
|
||||
-e 's/-->$//' \
|
||||
-e 's/\([^ ]*\) CDATA "&#\([^\"]*\);" -- \(.*\)/\1 \2 \3/' |
|
||||
(
|
||||
read -r A
|
||||
while test -n "$A"; do
|
||||
ent="${A%% *}"
|
||||
code=$(echo "$A" | cut -f2 -d' ')
|
||||
# compute hash
|
||||
hash=0
|
||||
i=0
|
||||
a=1664525
|
||||
c=1013904223
|
||||
m="$((1 << 32))"
|
||||
while test "$i" -lt ${#ent}; do
|
||||
d="$(echo -n "${ent:${i}:1}" | hexdump -v -e '/1 "%d"')"
|
||||
hash="$((((hash * a) % (m) + d + c) % (m)))"
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo -e " /* $A */"
|
||||
echo -e " case ${hash}u:"
|
||||
echo -e " if (len == ${#ent} /* && strncmp(ent, \"${ent}\") == 0 */) {"
|
||||
echo -e " return ${code};"
|
||||
echo -e " }"
|
||||
echo -e " break;"
|
||||
|
||||
# next
|
||||
read -r A
|
||||
done
|
||||
)
|
||||
cat <<EOF
|
||||
}
|
||||
${cases} }
|
||||
/* unknown */
|
||||
return -1;
|
||||
}
|
||||
EOF
|
||||
) >${dest}
|
||||
|
||||
echo "wrote $dest ($(grep -c '^ case ' "$dest") entities)" >&2
|
||||
|
||||
175
src/htsfilters.c
175
src/htsfilters.c
@@ -48,6 +48,7 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htsbase.h"
|
||||
#include "htslib.h"
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
/* END specific definitions */
|
||||
|
||||
// à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé
|
||||
@@ -76,7 +77,8 @@ int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * siz
|
||||
}
|
||||
if (size)
|
||||
sz = *size;
|
||||
if (strjoker(nom, filters[i] + filteroffs, &sz, size_flag)) { // reconnu
|
||||
/* size unknown (scan time): no size pointer => size tests stay neutral */
|
||||
if (strjoker(nom, filters[i] + filteroffs, size ? &sz : NULL, size_flag)) {
|
||||
if (size)
|
||||
if (sz != *size)
|
||||
sizelimit = sz;
|
||||
@@ -93,17 +95,127 @@ int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * siz
|
||||
return verdict;
|
||||
}
|
||||
|
||||
// supercomparateur joker (tm)
|
||||
// compare a et b (b=avec joker dedans), case insensitive [voir CI]
|
||||
// renvoi l'adresse de la première lettre de la chaine
|
||||
// (càd *[..]toto.. renvoi adresse de toto dans la chaine)
|
||||
// accepte les délires du genre www.*.*/ * / * truc*.*
|
||||
// cet algo est 'un peu' récursif mais ne consomme pas trop de tm
|
||||
// * = toute lettre
|
||||
// --?-- : spécifique à HTTrack et aux ?
|
||||
HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * size,
|
||||
int *size_flag) {
|
||||
//int err=0;
|
||||
int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
|
||||
const char *nom2, LLint *size, int *size_flag,
|
||||
int *depth) {
|
||||
int depth1 = 0, depth2 = 0;
|
||||
int flag1 = 0, flag2 = 0;
|
||||
LLint sz1 = 0, sz2 = 0;
|
||||
int jok1, jok2, use1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* Real filters/URLs fit HTS_URLMAXSIZE*2; past it a hostile pattern recurses
|
||||
O(len) deep or runs O(n^2*stars). Cap length (depth) and steps (work). */
|
||||
#define STRJOKER_MAXLEN (HTS_URLMAXSIZE * 2)
|
||||
#define STRJOKER_MAXSTEPS 2000000u
|
||||
|
||||
/* Failure memo for the recursive matcher: one bit per (chaine, joker) offset
|
||||
pair keeps star-heavy patterns polynomial instead of exponential (#501). */
|
||||
typedef struct strjoker_memo {
|
||||
const char *chaine0, *joker0; /* offsets are relative to these bases */
|
||||
size_t stride; /* strlen(joker0) + 1 */
|
||||
unsigned char *failed; /* failed-pair bitmap; NULL: no memo */
|
||||
size_t *nsteps; /* shared work counter; NULL: unbounded (oracle) */
|
||||
} 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->nsteps != NULL && ++*memo->nsteps > STRJOKER_MAXSTEPS)
|
||||
return NULL; /* work budget spent: fail the match safely */
|
||||
if (memo->failed) {
|
||||
bit = (size_t) (chaine - memo->chaine0) * memo->stride +
|
||||
(size_t) (joker - memo->joker0);
|
||||
if (memo->failed[bit >> 3] & (unsigned char) (1u << (bit & 7)))
|
||||
return NULL;
|
||||
}
|
||||
adr = strjoker_impl(memo, chaine, joker, size, size_flag);
|
||||
if (adr == NULL && memo->failed)
|
||||
memo->failed[bit >> 3] |= (unsigned char) (1u << (bit & 7));
|
||||
return adr;
|
||||
}
|
||||
|
||||
/* Match chaine against joker with a shared work budget *nsteps (a strjokerfind
|
||||
sweep passes one counter, bounding the whole scan). */
|
||||
static const char *strjoker_bounded(const char *chaine, const char *joker,
|
||||
LLint *size, int *size_flag,
|
||||
size_t *nsteps) {
|
||||
strjoker_memo memo = {chaine, joker, 0, NULL, nsteps};
|
||||
unsigned char stackbits[2048];
|
||||
hts_boolean onheap = HTS_FALSE;
|
||||
const char *adr;
|
||||
|
||||
if (chaine != NULL && joker != NULL) {
|
||||
const size_t l1 = strlen(chaine), l2 = strlen(joker);
|
||||
|
||||
if (l1 > STRJOKER_MAXLEN || l2 > STRJOKER_MAXLEN)
|
||||
return NULL; /* beyond any real filter/URL: bound depth+work */
|
||||
memo.stride = l2 + 1;
|
||||
if (l1 + 1 <= (SIZE_MAX - 7) / memo.stride) { // overflow-safe bitmap size
|
||||
const size_t bytes = ((l1 + 1) * memo.stride + 7) / 8;
|
||||
|
||||
if (bytes <= sizeof(stackbits)) {
|
||||
memset(stackbits, 0, bytes);
|
||||
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;
|
||||
}
|
||||
|
||||
// wildcard comparator: match chaine against joker (pattern), case-insensitive
|
||||
// returns the address of the first matched letter past any leading joker
|
||||
// (ie. *[..]toto.. returns the address of toto within chaine), NULL on mismatch
|
||||
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
||||
LLint *size, int *size_flag) {
|
||||
size_t nsteps = 0;
|
||||
|
||||
return strjoker_bounded(chaine, joker, size, size_flag, &nsteps);
|
||||
}
|
||||
|
||||
/* 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(chaine) == 0) // fin aussi pour la chaine: ok
|
||||
return chaine;
|
||||
@@ -192,7 +304,12 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
int len = (int) strlen(joker);
|
||||
|
||||
while((joker[i] != RIGHT) && (joker[i]) && (i < len)) {
|
||||
if ((joker[i] == '<') || (joker[i] == '>')) { // *[<10]
|
||||
// '\' escapes the next char as a literal member, e.g. *[\[\]]
|
||||
if (joker[i] == '\\' && joker[i + 1] != '\0') {
|
||||
i++;
|
||||
pass[(int) (unsigned char) joker[i]] = 1;
|
||||
i++;
|
||||
} else if ((joker[i] == '<') || (joker[i] == '>')) { // *[<10]
|
||||
int lsize = 0;
|
||||
int lverdict;
|
||||
|
||||
@@ -220,7 +337,9 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
while(isdigit((unsigned char) joker[i]))
|
||||
i++;
|
||||
}
|
||||
} else if (joker[i + 1] == '-') { // 2 car, ex: *[A-Z]
|
||||
} else if (joker[i + 1] == '-' && joker[i + 2] != '\0') {
|
||||
// range *[A-Z]; the '\0' guard rejects a truncated *[a- (else
|
||||
// i+=3 overshoots the NUL)
|
||||
if ((int) (unsigned char) joker[i + 2] >
|
||||
(int) (unsigned char) joker[i]) {
|
||||
int j;
|
||||
@@ -228,14 +347,9 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
for(j = (int) (unsigned char) joker[i];
|
||||
j <= (int) (unsigned char) joker[i + 2]; j++)
|
||||
pass[j] = 1;
|
||||
|
||||
}
|
||||
// else err=1;
|
||||
i += 3;
|
||||
} else { // 1 car, ex: *[ ]
|
||||
if (joker[i + 2] == '\\' && joker[i + 3] != 0) { // escaped char, such as *[\[] or *[\]]
|
||||
i++;
|
||||
}
|
||||
} else { // 1 car, ex: *[ ]
|
||||
pass[(int) (unsigned char) joker[i]] = 1;
|
||||
i++;
|
||||
}
|
||||
@@ -257,7 +371,6 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
for(i = 0; i < 256; i++)
|
||||
pass[i] = 1; // tout autoriser
|
||||
jmp = 1;
|
||||
////if (joker[2]==LEFT) jmp=3; // permet de recher *<crochet ouvrant>
|
||||
}
|
||||
|
||||
{
|
||||
@@ -276,7 +389,7 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
|
||||
// tester sans le joker (pas ()+ mais ()*)
|
||||
if (!unique) {
|
||||
if ((adr = strjoker(chaine, joker + jmp, size, size_flag))) {
|
||||
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag))) {
|
||||
return adr;
|
||||
}
|
||||
}
|
||||
@@ -285,10 +398,11 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
if (!unique)
|
||||
max = (int) strlen(chaine);
|
||||
else /* *(a) only match a (not aaaaa) */
|
||||
max = 1;
|
||||
max = strnotempty(chaine) ? 1 : 0; /* empty chaine: no char to eat */
|
||||
while(i < (int) max) {
|
||||
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
|
||||
if ((adr = strjoker(chaine + i + 1, joker + jmp, size, size_flag))) {
|
||||
if ((adr = strjoker_rec(memo, chaine + i + 1, joker + jmp, size,
|
||||
size_flag))) {
|
||||
return adr;
|
||||
}
|
||||
i++;
|
||||
@@ -298,7 +412,8 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
|
||||
// tester chaîne vide
|
||||
if (i != max + 2) // avant c'est ok
|
||||
if ((adr = strjoker(chaine + max, joker + jmp, size, size_flag)))
|
||||
if ((adr = strjoker_rec(memo, chaine + max, joker + jmp, size,
|
||||
size_flag)))
|
||||
return adr;
|
||||
|
||||
return NULL; // perdu
|
||||
@@ -320,7 +435,7 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
// comparaison ok?
|
||||
if (ok) {
|
||||
// continuer la comparaison.
|
||||
if (strjoker(chaine + jmp, joker + jmp, size, size_flag))
|
||||
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag))
|
||||
return chaine; // retourner 1e lettre
|
||||
}
|
||||
|
||||
@@ -336,12 +451,18 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * s
|
||||
// d'un strcpy sur une variable ayant un nom en lettres et copiant une chaine de chiffres
|
||||
// ATTENTION!! Eviter les jokers en début, où gare au temps machine!
|
||||
const char *strjokerfind(const char *chaine, const char *joker) {
|
||||
size_t nsteps = 0; /* one budget for the whole scan */
|
||||
const char *adr;
|
||||
|
||||
if (chaine != NULL && strlen(chaine) > STRJOKER_MAXLEN)
|
||||
return NULL;
|
||||
while(*chaine) {
|
||||
if ((adr = strjoker(chaine, joker, NULL, NULL))) { // ok trouvé
|
||||
if ((adr = strjoker_bounded(chaine, joker, NULL, NULL,
|
||||
&nsteps))) { // ok trouvé
|
||||
return adr;
|
||||
}
|
||||
if (nsteps > STRJOKER_MAXSTEPS) // scan budget spent: no match
|
||||
return NULL;
|
||||
chaine++;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
@@ -41,8 +41,17 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * size,
|
||||
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,
|
||||
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);
|
||||
#endif
|
||||
|
||||
|
||||
185
src/htsftp.c
185
src/htsftp.c
@@ -54,15 +54,9 @@ Please visit our Website: http://www.httrack.com
|
||||
#endif
|
||||
|
||||
// ftp mode passif
|
||||
// #if HTS_INET6==0
|
||||
#define FTP_PASV 1
|
||||
// #else
|
||||
// no passive mode for v6
|
||||
// #define FTP_PASV 0
|
||||
// #endif
|
||||
|
||||
#define FTP_DEBUG 0
|
||||
//#define FORK_DEBUG 0
|
||||
|
||||
#if USE_BEGINTHREAD
|
||||
|
||||
@@ -128,6 +122,33 @@ void launch_ftp(FTPDownloadStruct * params) {
|
||||
return 0; \
|
||||
}
|
||||
|
||||
/* Bounded split of a hostile-URL "user[:pass]@" prefix (see htsftp.h). */
|
||||
void ftp_split_userpass(const char *src, const char *end, char *user,
|
||||
size_t user_size, char *pass, size_t pass_size) {
|
||||
size_t n = 0;
|
||||
|
||||
assertf(user_size > 0 && pass_size > 0); /* the size-1 math underflows on 0 */
|
||||
|
||||
while (src[n] != '\0' && src[n] != ':') {
|
||||
if (n < user_size - 1)
|
||||
user[n] = src[n];
|
||||
n++;
|
||||
}
|
||||
user[n < user_size ? n : user_size - 1] = '\0';
|
||||
pass[0] = '\0';
|
||||
if (src[n] == ':') { // password follows the colon
|
||||
const size_t base = n + 1;
|
||||
size_t k = 0;
|
||||
|
||||
while (&src[base + k + 1] < end && src[base + k] != '\0') {
|
||||
if (k < pass_size - 1)
|
||||
pass[k] = src[base + k];
|
||||
k++;
|
||||
}
|
||||
pass[k < pass_size ? k : pass_size - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// la véritable fonction une fois lancées les routines thread/fork
|
||||
int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
lien_back *back = pStruct->pBack;
|
||||
@@ -173,43 +194,14 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
while(*real_adr == '/')
|
||||
real_adr++; // sauter /
|
||||
if ((adr = jump_identification(real_adr)) != real_adr) { // user
|
||||
int i = -1;
|
||||
|
||||
pass[0] = '\0';
|
||||
do {
|
||||
i++;
|
||||
user[i] = real_adr[i];
|
||||
} while((real_adr[i] != ':') && (real_adr[i]));
|
||||
user[i] = '\0';
|
||||
if (real_adr[i] == ':') { // pass
|
||||
int j = -1;
|
||||
|
||||
i++; // oui on saute aussi le :
|
||||
do {
|
||||
j++;
|
||||
pass[j] = real_adr[i + j];
|
||||
} while(((&real_adr[i + j + 1]) < adr) && (real_adr[i + j]));
|
||||
pass[j] = '\0';
|
||||
}
|
||||
ftp_split_userpass(real_adr, adr, user, sizeof(user), pass, sizeof(pass));
|
||||
}
|
||||
// Calculer RETR <nom>
|
||||
{
|
||||
char *a;
|
||||
|
||||
#if 0
|
||||
a = back->url_fil + strlen(back->url_fil) - 1;
|
||||
while((a > back->url_fil) && (*a != '/'))
|
||||
a--;
|
||||
if (*a != '/') {
|
||||
a = NULL;
|
||||
}
|
||||
#else
|
||||
a = back->url_fil;
|
||||
#endif
|
||||
if (a != NULL && *a != '\0') {
|
||||
#if 0
|
||||
a++; // sauter /
|
||||
#endif
|
||||
ftp_filename = a;
|
||||
if (strnotempty(a)) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
@@ -233,7 +225,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unexpected PORT error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
}
|
||||
@@ -267,7 +258,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
if (hts_dns_resolve2(opt, _adr, &server, &error) == NULL) {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg),
|
||||
"Unable to get server's address: %s", error);
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_NON_FATAL;
|
||||
_HALT_FTP return 0;
|
||||
}
|
||||
@@ -280,20 +270,17 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
soc_ctl = (T_SOC) socket(SOCaddr_sinfamily(server), SOCK_STREAM, 0);
|
||||
if (soc_ctl == INVALID_SOCKET) {
|
||||
strcpybuff(back->r.msg, "Unable to create a socket");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
_HALT_FTP return 0;
|
||||
}
|
||||
|
||||
SOCaddr_initport(server, port);
|
||||
// server.sin_port = htons((unsigned short int) port);
|
||||
|
||||
// connexion (bloquante, on est en thread)
|
||||
strcpybuff(back->info, "connect");
|
||||
|
||||
if (connect(soc_ctl, &SOCaddr_sockaddr(server), SOCaddr_size(server)) != 0) {
|
||||
strcpybuff(back->r.msg, "Unable to connect to the server");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
_HALT_FTP return 0;
|
||||
#ifdef _WIN32
|
||||
@@ -335,64 +322,22 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
// ok
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "TYPE I error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
#if 0
|
||||
// --CWD--
|
||||
char *a;
|
||||
|
||||
a = back->url_fil + strlen(back->url_fil) - 1;
|
||||
while((a > back->url_fil) && (*a != '/'))
|
||||
a--;
|
||||
if (*a == '/') { // ok repéré
|
||||
char BIGSTK target[1024];
|
||||
|
||||
target[0] = '\0';
|
||||
strncatbuff(target, back->url_fil, (int) (a - back->url_fil));
|
||||
if (strnotempty(target) == 0)
|
||||
strcatbuff(target, "/");
|
||||
strcpybuff(back->info, "cwd");
|
||||
snprintf(line, sizeof(line), "CWD %s", target);
|
||||
send_line(soc_ctl, line);
|
||||
get_ftp_line(soc_ctl, line, sizeof(line), timeout);
|
||||
_CHECK_HALT_FTP;
|
||||
if (line[0] == '2') {
|
||||
send_line(soc_ctl, "TYPE I");
|
||||
get_ftp_line(soc_ctl, line, sizeof(line), timeout);
|
||||
_CHECK_HALT_FTP;
|
||||
if (line[0] == '2') {
|
||||
// ok..
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "TYPE I error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "CWD error: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unexpected ftp error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
#endif
|
||||
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Bad password: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Bad password: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Bad user name: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Bad user name: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Connection refused: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Connection refused: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
|
||||
@@ -459,8 +404,8 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
// -- fin analyse de l'adresse IP et du port --
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV incorrect: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV incorrect: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
@@ -491,13 +436,13 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "EPSV incorrect: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "EPSV incorrect: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV/EPSV error: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV/EPSV error: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
}
|
||||
@@ -603,9 +548,8 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
deletesoc(soc_dat);
|
||||
soc_dat = INVALID_SOCKET;
|
||||
//
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "RETR command error: %s",
|
||||
linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg),
|
||||
"RETR command error: %s", linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
@@ -616,23 +560,20 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
soc_dat = INVALID_SOCKET;
|
||||
//
|
||||
strcpybuff(back->r.msg, "Unable to connect");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unable to create a socket");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg),
|
||||
"Unable to resolve IP %s: %s", adr_ip, error);
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV incorrect: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PASV incorrect: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
} // sinon on est prêts
|
||||
#else
|
||||
@@ -653,17 +594,16 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
//T_SOC soc_dat;
|
||||
if ((soc_dat = accept(soc_servdat, NULL, NULL)) == INVALID_SOCKET) {
|
||||
strcpybuff(back->r.msg, "Unable to accept connection");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "RETR command error: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg),
|
||||
"RETR command error: %s", linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PORT command error: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "PORT command error: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
@@ -673,7 +613,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
#endif
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unable to listen to a port");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
#endif
|
||||
@@ -697,21 +636,18 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
int len = 1;
|
||||
int read_len = 1024;
|
||||
|
||||
//HTS_TOTAL_RECV_CHECK(read_len); // Diminuer au besoin si trop de données reçues
|
||||
|
||||
while((len > 0) && (!stop_ftp(back))) {
|
||||
// attendre les données
|
||||
len = 1; // pas d'erreur pour le moment
|
||||
switch (wait_socket_receive(soc_dat, timeout)) {
|
||||
case -1:
|
||||
strcpybuff(back->r.msg, "FTP read error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
len = 0; // fin
|
||||
break;
|
||||
case 0:
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Time out (%d)", timeout);
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "Time out (%d)",
|
||||
timeout);
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
len = 0; // fin
|
||||
break;
|
||||
@@ -733,17 +669,14 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
*/
|
||||
strcpybuff(back->r.msg, "Write error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
len = 0; // error
|
||||
}
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unexpected write error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else { // Erreur ou terminé
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
} else { // Erreur ou terminé
|
||||
back->r.statuscode = 0;
|
||||
if (back->r.totalsize > 0
|
||||
&& back->r.size != back->r.totalsize) {
|
||||
@@ -752,7 +685,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
}
|
||||
read_len = 1024;
|
||||
//HTS_TOTAL_RECV_CHECK(read_len); // Diminuer au besoin si trop de données reçues
|
||||
}
|
||||
}
|
||||
if (back->r.fp) {
|
||||
@@ -761,7 +693,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
}
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "Unable to write file");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
@@ -777,16 +708,14 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
get_ftp_line(soc_ctl, line, sizeof(line), timeout);
|
||||
if (line[0] == '2') { // OK
|
||||
strcpybuff(back->r.msg, "OK");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = HTTP_OK;
|
||||
} else {
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "RETR incorrect: %s", linejmp(line));
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
snprintf(back->r.msg, sizeof(back->r.msg), "RETR incorrect: %s",
|
||||
linejmp(line));
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
} else {
|
||||
strcpybuff(back->r.msg, "FTP read error");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
}
|
||||
}
|
||||
@@ -812,7 +741,6 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
back->r.statuscode = HTTP_OK;
|
||||
strcpybuff(back->r.msg, "OK");
|
||||
}
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -979,13 +907,11 @@ int get_ftp_line(T_SOC soc, char *ptrline, size_t line_size, int timeout) {
|
||||
break;
|
||||
}
|
||||
|
||||
//HTS_TOTAL_RECV_CHECK(dummy); // Diminuer au besoin si trop de données reçues
|
||||
switch (recv(soc, &b, 1, 0)) {
|
||||
//case 0: break; // pas encore --> erreur (on attend)!
|
||||
case 1:
|
||||
HTS_STAT.HTS_TOTAL_RECV += 1; // compter flux entrant
|
||||
if ((b != 10) && (b != 13))
|
||||
data[i++] = b;
|
||||
if ((b != 10) && (b != 13) && (i < (int) sizeof(data) - 1))
|
||||
data[i++] = b; // truncate hostile over-long reply lines
|
||||
break;
|
||||
default:
|
||||
if (ptrline)
|
||||
@@ -1108,7 +1034,6 @@ int wait_socket_receive(T_SOC soc, int timeout) {
|
||||
int stop_ftp(lien_back * back) {
|
||||
if (back->stop_ftp) {
|
||||
strcpybuff(back->r.msg, "Cancelled by User");
|
||||
// back->status=STATUS_FTP_READY; // fini
|
||||
back->r.statuscode = STATUSCODE_INVALID;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,11 @@ int back_launch_ftp(FTPDownloadStruct * params);
|
||||
int run_launch_ftp(FTPDownloadStruct * params);
|
||||
int send_line(T_SOC soc, const char *data);
|
||||
int get_ftp_line(T_SOC soc, char *line, size_t line_size, int timeout);
|
||||
/* Split a "user[:pass]@" prefix (end = jump_identification result) into
|
||||
bounded, NUL-terminated user/pass buffers, truncating to fit.
|
||||
Both sizes must be nonzero. */
|
||||
void ftp_split_userpass(const char *src, const char *end, char *user,
|
||||
size_t user_size, char *pass, size_t pass_size);
|
||||
T_SOC get_datasocket(char *to_send, size_t to_send_size);
|
||||
int stop_ftp(lien_back * back);
|
||||
char *linejmp(char *line);
|
||||
|
||||
120
src/htsglobal.h
120
src/htsglobal.h
@@ -43,8 +43,8 @@ Please visit our Website: http://www.httrack.com
|
||||
configure.ac, decoupled from these). VERSION is the display form, VERSIONID
|
||||
the dotted numeric form, AFF_VERSION the short form shown in footers,
|
||||
LIB_VERSION the data/cache format generation. */
|
||||
#define HTTRACK_VERSION "3.49-8"
|
||||
#define HTTRACK_VERSIONID "3.49.8"
|
||||
#define HTTRACK_VERSION "3.49-12"
|
||||
#define HTTRACK_VERSIONID "3.49.12"
|
||||
#define HTTRACK_AFF_VERSION "3.x"
|
||||
#define HTTRACK_LIB_VERSION "2.0"
|
||||
|
||||
@@ -56,6 +56,10 @@ Please visit our Website: http://www.httrack.com
|
||||
// Platform detection (sizes, feature macros)
|
||||
#include "htsconfig.h"
|
||||
|
||||
// Fixed-width integer types + PRI* format macros for the LLint/TStamp typedefs
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
// WIN32 types
|
||||
#ifdef _WIN32
|
||||
#ifndef SIZEOF_LONG
|
||||
@@ -111,29 +115,6 @@ Please visit our Website: http://www.httrack.com
|
||||
#define HTS_DO_NOT_USE_UID
|
||||
#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
|
||||
#define HTS_DLOPEN 1
|
||||
#else
|
||||
@@ -229,6 +210,10 @@ Please visit our Website: http://www.httrack.com
|
||||
#define HTS_DEFAULT_FOOTER \
|
||||
"<!-- Mirrored from %s%s by HTTrack Website Copier/" HTTRACK_AFF_VERSION \
|
||||
" " HTTRACK_AFF_AUTHORS ", %s -->"
|
||||
/* Honest crawler User-Agent; no fake OS/browser to go stale. */
|
||||
#define HTS_DEFAULT_USER_AGENT \
|
||||
"Mozilla/5.0 (compatible; HTTrack/" HTTRACK_AFF_VERSION \
|
||||
"; +https://www.httrack.com/)"
|
||||
#define HTTRACK_WEB "http://www.httrack.com"
|
||||
#define HTS_UPDATE_WEBSITE \
|
||||
"http://www.httrack.com/" \
|
||||
@@ -247,13 +232,23 @@ Please visit our Website: http://www.httrack.com
|
||||
#define HTS_NOPARAM "(none)"
|
||||
#define HTS_NOPARAM2 "\"(none)\""
|
||||
|
||||
/* Boolean flag for option fields and API yes/no returns. An enum (not C bool)
|
||||
so it stays int-sized: option fields keep the httrackp layout/ABI, and a
|
||||
return type stays compatible with the int it replaces. */
|
||||
/* Boolean flag for option fields and API yes/no returns. Int-backed, not an
|
||||
enum: an enum makes C++ reject `field = 1` / `f(0)` on the exported fields
|
||||
and params. Int-sized, so the httrackp layout and the ABI are unchanged. */
|
||||
#ifndef HTS_DEF_DEFSTRUCT_hts_boolean
|
||||
#define HTS_DEF_DEFSTRUCT_hts_boolean
|
||||
|
||||
typedef enum hts_boolean { HTS_FALSE = 0, HTS_TRUE = 1 } hts_boolean;
|
||||
typedef int hts_boolean;
|
||||
#define HTS_FALSE 0
|
||||
#define HTS_TRUE 1
|
||||
#endif
|
||||
|
||||
#ifndef HTS_DEF_DEFSTRUCT_hts_tristate
|
||||
#define HTS_DEF_DEFSTRUCT_hts_tristate
|
||||
/* Tri-state hts_boolean: HTS_DEFAULT (-1) = "unspecified" (copy_htsopt leaves
|
||||
the target untouched); HTS_FALSE/HTS_TRUE = off/on. */
|
||||
typedef int hts_tristate;
|
||||
#define HTS_DEFAULT (-1)
|
||||
#endif
|
||||
|
||||
/* Larger/smaller of two values. Macros: arguments are evaluated twice. */
|
||||
@@ -312,59 +307,11 @@ typedef enum hts_boolean { HTS_FALSE = 0, HTS_TRUE = 1 } hts_boolean;
|
||||
#define HTS_DEPRECATED(msg)
|
||||
#endif
|
||||
|
||||
#ifndef HTS_LONGLONG
|
||||
#ifdef HTS_NO_64_BIT
|
||||
#define HTS_LONGLONG 0
|
||||
#else
|
||||
#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
|
||||
/* LLint/TStamp: signed exact-width 64-bit; -1 is a sentinel engine-wide. */
|
||||
typedef int64_t LLint;
|
||||
typedef int64_t TStamp;
|
||||
/* Full printf conversion, '%' included (PRId64 has none): "X: " LLintP. */
|
||||
#define LLintP "%" PRId64
|
||||
|
||||
/* Integer type for file offsets/sizes passed to the C library. Widens to
|
||||
LLint (with HTS_FSEEKO for fseeko/ftello) under large-file support, plain
|
||||
@@ -398,15 +345,20 @@ typedef int T_SOC;
|
||||
/* Buffer size for a printed network address (IPv4 or IPv6, NUL included). */
|
||||
#define HTS_MAXADDRLEN 64
|
||||
|
||||
/* Max resolved addresses kept per host for connect fallback (dead IPv6 etc.).
|
||||
*/
|
||||
#define HTS_MAXADDRNUM 4
|
||||
|
||||
#ifdef _WIN32
|
||||
#else
|
||||
#define __cdecl
|
||||
#endif
|
||||
|
||||
/* Permission bits for created folders and files (mkdir and chmod).
|
||||
PROTECT_FOLDER is owner-only. With HTS_ACCESS set (the default) the ACCESS_
|
||||
modes also grant group/other read; otherwise they stay owner-only. */
|
||||
PROTECT_FOLDER/FILE are owner-only. With HTS_ACCESS set (the default) the
|
||||
ACCESS_ modes also grant group/other read; otherwise they stay owner-only. */
|
||||
#define HTS_PROTECT_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR)
|
||||
#define HTS_PROTECT_FILE (S_IRUSR | S_IWUSR)
|
||||
|
||||
#if HTS_ACCESS
|
||||
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
||||
|
||||
@@ -106,10 +106,10 @@ static coucal_hashkeys key_adrfil_hashes_generic(void *arg,
|
||||
const lien_url*const lien = (const lien_url*) value;
|
||||
const char *const adr = !former ? lien->adr : lien->former_adr;
|
||||
const char *const fil = !former ? lien->fil : lien->former_fil;
|
||||
const char *const adr_norm = adr != NULL ?
|
||||
( hash->normalized ? jump_normalized_const(adr)
|
||||
: jump_identification_const(adr) )
|
||||
: NULL;
|
||||
const char *const adr_norm =
|
||||
adr != NULL ? (hash->norm_host ? jump_normalized_const(adr)
|
||||
: jump_identification_const(adr))
|
||||
: NULL;
|
||||
|
||||
// copy address
|
||||
assertf(adr_norm != NULL);
|
||||
@@ -117,10 +117,18 @@ static coucal_hashkeys key_adrfil_hashes_generic(void *arg,
|
||||
|
||||
// copy link
|
||||
assertf(fil != NULL);
|
||||
if (hash->normalized) {
|
||||
fil_normalized(fil, &hash->normfil[strlen(hash->normfil)]);
|
||||
} else {
|
||||
strcpy(&hash->normfil[strlen(hash->normfil)], fil);
|
||||
{
|
||||
/* resolve the per-URL strip keys; strip applies even when urlhack is off */
|
||||
char BIGSTK keybuf[HTS_URLMAXSIZE];
|
||||
const char *const keys = hts_query_strip_keys(hash->strip_query, adr, fil,
|
||||
keybuf, sizeof(keybuf));
|
||||
|
||||
if (hash->norm_slash || hash->norm_query || keys != NULL) {
|
||||
fil_normalized_filtered_ex(fil, &hash->normfil[strlen(hash->normfil)],
|
||||
keys, hash->norm_slash, hash->norm_query);
|
||||
} else {
|
||||
strcpy(&hash->normfil[strlen(hash->normfil)], fil);
|
||||
}
|
||||
}
|
||||
|
||||
// hash
|
||||
@@ -132,8 +140,7 @@ static int key_adrfil_equals_generic(void *arg,
|
||||
coucal_key_const a_,
|
||||
coucal_key_const b_,
|
||||
const int former) {
|
||||
hash_struct *const hash = (hash_struct*) arg;
|
||||
const int normalized = hash->normalized;
|
||||
hash_struct *const hash = (hash_struct *) arg;
|
||||
const lien_url*const a = (const lien_url*) a_;
|
||||
const lien_url*const b = (const lien_url*) b_;
|
||||
const char *const a_adr = !former ? a->adr : a->former_adr;
|
||||
@@ -150,10 +157,10 @@ static int key_adrfil_equals_generic(void *arg,
|
||||
assertf(b_fil != NULL);
|
||||
|
||||
// skip scheme and authentication to the domain (possibly without www.)
|
||||
ja = normalized
|
||||
? jump_normalized_const(a_adr) : jump_identification_const(a_adr);
|
||||
jb = normalized
|
||||
? jump_normalized_const(b_adr) : jump_identification_const(b_adr);
|
||||
ja = hash->norm_host ? jump_normalized_const(a_adr)
|
||||
: jump_identification_const(a_adr);
|
||||
jb = hash->norm_host ? jump_normalized_const(b_adr)
|
||||
: jump_identification_const(b_adr);
|
||||
assertf(ja != NULL);
|
||||
assertf(jb != NULL);
|
||||
if (strcasecmp(ja, jb) != 0) {
|
||||
@@ -161,12 +168,23 @@ static int key_adrfil_equals_generic(void *arg,
|
||||
}
|
||||
|
||||
// now compare pathes
|
||||
if (normalized) {
|
||||
fil_normalized(a_fil, hash->normfil);
|
||||
fil_normalized(b_fil, hash->normfil2);
|
||||
return strcmp(hash->normfil, hash->normfil2) == 0;
|
||||
} else {
|
||||
return strcmp(a_fil, b_fil) == 0;
|
||||
{
|
||||
char BIGSTK ka[HTS_URLMAXSIZE], kb[HTS_URLMAXSIZE];
|
||||
const char *const keysa =
|
||||
hts_query_strip_keys(hash->strip_query, a_adr, a_fil, ka, sizeof(ka));
|
||||
const char *const keysb =
|
||||
hts_query_strip_keys(hash->strip_query, b_adr, b_fil, kb, sizeof(kb));
|
||||
|
||||
if (hash->norm_slash || hash->norm_query || keysa != NULL ||
|
||||
keysb != NULL) {
|
||||
fil_normalized_filtered_ex(a_fil, hash->normfil, keysa, hash->norm_slash,
|
||||
hash->norm_query);
|
||||
fil_normalized_filtered_ex(b_fil, hash->normfil2, keysb, hash->norm_slash,
|
||||
hash->norm_query);
|
||||
return strcmp(hash->normfil, hash->normfil2) == 0;
|
||||
} else {
|
||||
return strcmp(a_fil, b_fil) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,11 +240,17 @@ static int key_former_adrfil_equals(void *arg,
|
||||
return key_adrfil_equals_generic(arg, a, b, 1);
|
||||
}
|
||||
|
||||
void hash_init(httrackp *opt, hash_struct * hash, int normalized) {
|
||||
void hash_init(httrackp *opt, hash_struct *hash, hts_boolean normalized) {
|
||||
hash->sav = coucal_new(0);
|
||||
hash->adrfil = coucal_new(0);
|
||||
hash->former_adrfil = coucal_new(0);
|
||||
hash->normalized = normalized;
|
||||
/* urlhack is the umbrella; per-feature negatives opt out of each part */
|
||||
hash->norm_host = normalized && !opt->no_www_dedup;
|
||||
hash->norm_slash = normalized && !opt->no_slash_dedup;
|
||||
hash->norm_query = normalized && !opt->no_query_dedup;
|
||||
/* snapshot the query-strip list (not owned; valid for the hash lifetime) */
|
||||
hash->strip_query =
|
||||
StringNotEmpty(opt->strip_query) ? StringBuff(opt->strip_query) : NULL;
|
||||
|
||||
hts_set_hash_handler(hash->sav, opt);
|
||||
hts_set_hash_handler(hash->adrfil, opt);
|
||||
@@ -282,6 +306,26 @@ void hash_free(hash_struct *hash) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Test helper: do the two URLs dedupe to the same key under opt's urlhack
|
||||
flags? Exercises the live hash compare (norm_host/slash/query resolution). */
|
||||
hts_boolean hash_url_equals(httrackp *opt, const char *adra, const char *fila,
|
||||
const char *adrb, const char *filb) {
|
||||
hash_struct hash;
|
||||
lien_url la, lb;
|
||||
hts_boolean eq;
|
||||
|
||||
memset(&la, 0, sizeof(la));
|
||||
memset(&lb, 0, sizeof(lb));
|
||||
la.adr = key_duphandler(NULL, adra);
|
||||
la.fil = key_duphandler(NULL, fila);
|
||||
lb.adr = key_duphandler(NULL, adrb);
|
||||
lb.fil = key_duphandler(NULL, filb);
|
||||
hash_init(opt, &hash, opt->urlhack);
|
||||
eq = key_adrfil_equals(&hash, &la, &lb);
|
||||
hash_free(&hash);
|
||||
return eq;
|
||||
}
|
||||
|
||||
// retour: position ou -1 si non trouvé
|
||||
int hash_read(const hash_struct * hash, const char *nom1, const char *nom2,
|
||||
hash_struct_type type) {
|
||||
|
||||
@@ -51,8 +51,12 @@ typedef enum hash_struct_type {
|
||||
} hash_struct_type;
|
||||
|
||||
// tables de hachage
|
||||
void hash_init(httrackp *opt, hash_struct *hash, int normalized);
|
||||
void hash_init(httrackp *opt, hash_struct *hash, hts_boolean normalized);
|
||||
void hash_free(hash_struct *hash);
|
||||
/* Test helper: HTS_TRUE if the two URLs dedupe together under opt's urlhack
|
||||
flags. */
|
||||
hts_boolean hash_url_equals(httrackp *opt, const char *adra, const char *fila,
|
||||
const char *adrb, const char *filb);
|
||||
int hash_read(const hash_struct * hash, const char *nom1, const char *nom2,
|
||||
hash_struct_type type);
|
||||
void hash_write(hash_struct * hash, size_t lpos);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user