mirror of
https://github.com/xroche/httrack.git
synced 2026-07-15 21:30:29 +03:00
* tests: run the loopback crawl suite on Windows The ~40 *_local-* tests crawl the bundled Python server: the real TLS handshake, cache, and file writer, none of which any Windows check covered. Three things stopped them running there. python3 is python.exe on Windows; MSYS hands out /d/a/... paths a native python.exe cannot resolve (and arg rewriting is off, so nothing fixes them up); and Python's text layer turns the "PORT <n>" discovery line into CRLF, so the \r landed in the parsed port. Factor the python lookup and the path conversion into tests/testlib.sh, fix the newline at the source in the three servers, and skip the --file-mode assertion on Windows, where the engine does not chmod. Behaviour on POSIX is unchanged: the full suite still passes 97/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: share the server shutdown, and make a skip fail the Windows run stop_server moves to testlib.sh: the MSYS "a signal cannot reach a native python.exe, only -9 lands" knowledge was in one of the nine places that kill a python server. Every step is "|| true" because the callers run under set -e and reaping a server we just signalled makes wait return 143. Nothing is expected to skip on Windows, so treat a skip as a failure: the pass floor alone left slack for exactly the tests that can silently gate themselves off (TLS, the content codings, socks5, connect-fallback). Add the local proxy crawl, which the glob was missing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: resolve the server path before backgrounding the server The port-discovery poll raced the server it had just started: the native path conversion sat inside the backgrounded command, so the child ran two forks (command -v cygpath, cygpath) before applying its ">server.log" redirect, and on Windows the parent's first "head" reached the file first. head then failed, and under set -e that killed the test silently. Resolve the paths in the parent, and create the log before the launch so the first poll cannot lose the race. nativepath keys off the platform, not off cygpath happening to be on PATH. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: skip the connect-fallback crawl on Windows 19_local-connect-fallback fails there because the engine never falls back to the next address: Winsock reports a failed connect in select()'s exception set rather than as writable, and the exception loop fails the slot before back_connect_next() is reached. Skip it pending the engine fix (#579). Pin the expected skips instead of counting them, so a gate that silently turns some other test off still fails the run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: trim the comments added by the Windows port Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: say why the crange resume lost its file 48_local-crange-memresume fails on Windows with a bare "blob.bin missing", and the crawl log it would have to explain that lives in the tmpdir the test wipes. Dump the engine's errors and the mirror on that branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: dump the crawl log when the crange resume loses its file Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: check the resume pass survived, don't assume it 48_local-crange-memresume ran pass 2 and printed "terminated" whatever came back, so a crashed engine read as a clean run. It fails on Windows with an empty log and an empty mirror, which is what that blind spot looks like. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: dump both passes' state for the Windows crange failure Temporary: pass-1 mirror + log and pass-2 log/mirror, to see why the resume mirrors and logs nothing on Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: skip the crange memory-resume crawl on Windows 48_local-crange-memresume needs a graceful pass-1 interrupt so the cache is clean when pass 2 resumes; MSYS can only hard-kill a native exe, and the engine's restart-whole path after an unusable 206 then fails on the repaired cache (#581). Skip on Windows pending that fix. Keep the pass-2 exit-code check the investigation added: the test printed "terminated" whatever came back, so a crashed engine read as a clean run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> --------- Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
144 lines
4.8 KiB
Bash
144 lines
4.8 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Issue #85: an https crawl must go through the configured proxy (CONNECT
|
|
# tunnel), not bypass it and hit the origin directly. Fully local: a self-signed
|
|
# TLS origin plus a logging CONNECT proxy, so no network access is needed.
|
|
|
|
set -euo pipefail
|
|
|
|
: "${top_srcdir:=..}"
|
|
|
|
# shellcheck source=tests/testlib.sh
|
|
. "$top_srcdir/tests/testlib.sh"
|
|
|
|
if test "${HTTPS_SUPPORT:-}" == "no"; then
|
|
echo "no https support compiled, skipping"
|
|
exit 77
|
|
fi
|
|
python=$(find_python) || python=
|
|
if test -z "$python" || ! command -v openssl >/dev/null 2>&1; then
|
|
echo "python3/openssl missing, skipping"
|
|
exit 77
|
|
fi
|
|
|
|
server=$(nativepath "$top_srcdir/tests/proxy-https-server.py")
|
|
tmpdir=$(mktemp -d)
|
|
pids=
|
|
|
|
cleanup() {
|
|
for pid in $pids; do
|
|
stop_server "$pid"
|
|
done
|
|
rm -rf "$tmpdir"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# self-signed cert for the local TLS origin (httrack does not verify certs)
|
|
openssl req -x509 -newkey rsa:2048 -keyout "$tmpdir/key.pem" \
|
|
-out "$tmpdir/cert.pem" -days 2 -nodes -subj "/CN=127.0.0.1" \
|
|
>/dev/null 2>&1
|
|
cat "$tmpdir/key.pem" "$tmpdir/cert.pem" >"$tmpdir/both.pem"
|
|
|
|
# start_server <logdir> <mode>: launches a proxy+origin pair, sets $origin_port
|
|
# and $proxy_port from its announced ephemeral ports.
|
|
start_server() {
|
|
local dir="$1" mode="$2" ports pem
|
|
mkdir -p "$dir"
|
|
ports="$dir/ports.txt"
|
|
pem=$(nativepath "$tmpdir/both.pem")
|
|
dir_native=$(nativepath "$dir")
|
|
: >"$ports"
|
|
"$python" "$server" "$pem" "$dir_native" "$mode" \
|
|
>"$ports" 2>"$dir/server.err" &
|
|
pids="$pids $!"
|
|
for _ in $(seq 1 100); do
|
|
grep -q "^ready" "$ports" 2>/dev/null && break
|
|
sleep 0.1
|
|
done
|
|
grep -q "^ready" "$ports" 2>/dev/null || {
|
|
echo "server ($mode) did not start" >&2
|
|
cat "$dir/server.err" >&2
|
|
exit 1
|
|
}
|
|
origin_port=$(awk '/^ORIGIN/{print $2}' "$ports")
|
|
proxy_port=$(awk '/^PROXY/{print $2}' "$ports")
|
|
}
|
|
|
|
# Run httrack, but kill it after a deadline so a hang (e.g. a missing bound on
|
|
# the proxy response) surfaces as the kill code $HANG_RC instead of stalling the
|
|
# whole job. A portable stand-in for `timeout`, which macOS lacks.
|
|
HANG_RC=137 # 128 + SIGKILL
|
|
run_crawl() {
|
|
local out="$1" proxy="$2" port="$3"
|
|
rm -rf "$out"
|
|
httrack "https://127.0.0.1:${port}/" --proxy "$proxy" \
|
|
-O "$out" -r1 -s0 --timeout=10 >"$out.log" 2>&1 &
|
|
local pid=$!
|
|
(sleep 60 && kill -9 "$pid" 2>/dev/null) &
|
|
local guard=$!
|
|
local rc=0
|
|
wait "$pid" 2>/dev/null || rc=$?
|
|
kill "$guard" 2>/dev/null || true
|
|
wait "$guard" 2>/dev/null || true
|
|
return "$rc"
|
|
}
|
|
|
|
# --- working proxy ----------------------------------------------------------
|
|
ok="$tmpdir/ok"
|
|
start_server "$ok" ok
|
|
|
|
# 1. page retrieved AND the proxy saw a CONNECT to the origin
|
|
run_crawl "$ok/out" "127.0.0.1:${proxy_port}" "$origin_port"
|
|
grep -rq "ORIGIN-PAGE-85" "$ok/out" || {
|
|
echo "FAIL: origin page not downloaded through proxy" >&2
|
|
cat "$ok/out.log" >&2
|
|
exit 1
|
|
}
|
|
grep -q "^CONNECT 127.0.0.1:${origin_port} " "$ok/proxy.log" || {
|
|
echo "FAIL: proxy never received a CONNECT (https bypassed the proxy)" >&2
|
|
cat "$ok/proxy.log" >&2
|
|
exit 1
|
|
}
|
|
echo "OK: https tunneled through proxy via CONNECT"
|
|
|
|
# 2. authenticated proxy: creds ride the CONNECT, and NEVER reach the origin
|
|
: >"$ok/proxy.log"
|
|
: >"$ok/origin-headers.log"
|
|
run_crawl "$ok/out2" "user:secret@127.0.0.1:${proxy_port}" "$origin_port"
|
|
grep -rq "ORIGIN-PAGE-85" "$ok/out2" || {
|
|
echo "FAIL: origin page not downloaded through authenticated proxy" >&2
|
|
exit 1
|
|
}
|
|
got=$(awk '/^AUTH Basic /{print $3}' "$ok/proxy.log" | head -1)
|
|
# base64("user:secret"); compared as a literal to stay portable (no base64 -d,
|
|
# which differs between GNU and BSD)
|
|
test "$got" == "dXNlcjpzZWNyZXQ=" || {
|
|
echo "FAIL: Proxy-Authorization not carried on CONNECT (got '$got')" >&2
|
|
cat "$ok/proxy.log" >&2
|
|
exit 1
|
|
}
|
|
if grep -qi "proxy-authorization" "$ok/origin-headers.log"; then
|
|
echo "FAIL: proxy credentials leaked to the origin through the tunnel" >&2
|
|
cat "$ok/origin-headers.log" >&2
|
|
exit 1
|
|
fi
|
|
echo "OK: proxy credentials carried on CONNECT, not leaked to origin"
|
|
|
|
# --- hostile proxy ----------------------------------------------------------
|
|
# A proxy that answers 200 then streams headers forever must not hang the crawl:
|
|
# the client bounds the response. run_crawl kills a hung httrack after 60s, so a
|
|
# missing bound surfaces as $HANG_RC here.
|
|
flood="$tmpdir/flood"
|
|
start_server "$flood" flood
|
|
rc=0
|
|
run_crawl "$flood/out" "127.0.0.1:${proxy_port}" "$origin_port" || rc=$?
|
|
test "$rc" -ne "$HANG_RC" || {
|
|
echo "FAIL: crawl hung on a flooding proxy (bounded read missing)" >&2
|
|
exit 1
|
|
}
|
|
grep -rq "ORIGIN-PAGE-85" "$flood/out" 2>/dev/null && {
|
|
echo "FAIL: flooding proxy unexpectedly served the page" >&2
|
|
exit 1
|
|
}
|
|
echo "OK: bounded proxy response, no hang on a flooding proxy"
|