mirror of
https://github.com/xroche/httrack.git
synced 2026-07-17 06:10:42 +03:00
Compare commits
2 Commits
fix-lang-i
...
fix-607-ss
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a1585b35b | ||
|
|
fcb0e4c19a |
@@ -930,6 +930,7 @@ Server terminated
|
||||
Palvelin lopetettu
|
||||
A fatal error has occurred during this mirror
|
||||
Tällä peilillä tapahtui vakava virhe
|
||||
|
||||
Proxy type:
|
||||
Välityspalvelimen tyyppi:
|
||||
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
|
||||
|
||||
@@ -374,7 +374,7 @@ Create error logging and report files
|
||||
Crea file di log per segnalare errori e informazioni
|
||||
Generate DOS 8-3 filenames ONLY
|
||||
Genera solo nomi di file in formato 8.3
|
||||
Generate ISO9660 filenames ONLY for CDROM medias
|
||||
Generate ISO 9660 filenames ONLY for CDROM medias
|
||||
Genera nomi di file in formato ISO9660 per i CDROM
|
||||
Do not create HTML error pages
|
||||
Non generare pagine d'errore HTML
|
||||
|
||||
@@ -918,7 +918,7 @@ normal\nextended\ndebug
|
||||
normal\nextendido\ncorrigir
|
||||
Download web site(s)\nDownload web site(s) + questions\nGet individual files\nDownload all sites in pages (multiple mirror)\nTest links in pages (bookmark test)\n* Continue interrupted download\n* Update existing download
|
||||
Copiar site(s) da Web\nCopiar site(s) interativos da Web (perguntas)\nReceber arquivos específicos\nCopiar todas as páginas do site (alternação múltipla)\nTestar links nas páginas (testar indicador)\n* Retomar download interrompido\n* Atualizar download existente
|
||||
Relative URI / Absolute URL (default)\nAbsolute URL / Absolute URL\nAbsolute URI / Absolute URL\nOriginal URL / Original URL
|
||||
Relative URL / Absolute URL (default)\nAbsolute URL / Absolute URL\nAbsolute URI / Absolute URL\nOriginal URL / Original URL
|
||||
URI Relativa / URL Absoluta (padrão)\nURL Absoluta / URL Absoluta\nURI Absoluta / URL Absoluta\nURL Original / URL Original
|
||||
Open Source offline browser
|
||||
Abrir origem offline no navegador
|
||||
|
||||
@@ -2816,6 +2816,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
if (SSL_set_fd(back[i].r.ssl_con, (int) back[i].r.soc) == 1) {
|
||||
SSL_set_connect_state(back[i].r.ssl_con);
|
||||
back[i].status = STATUS_SSL_WAIT_HANDSHAKE; /* handshake wait */
|
||||
// the handshake gets its own timeout window, as connect does
|
||||
if (back[i].timeout > 0)
|
||||
back[i].timeout_refresh = time_local();
|
||||
} else
|
||||
back[i].r.statuscode = STATUSCODE_SSL_HANDSHAKE;
|
||||
} else
|
||||
@@ -2875,6 +2878,11 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
}
|
||||
#if HTS_USEOPENSSL
|
||||
else if (back[i].status == STATUS_SSL_WAIT_HANDSHAKE) { // wait for SSL handshake
|
||||
// a peer that never speaks TLS must be reaped by --timeout too (#607)
|
||||
if (!gestion_timeout)
|
||||
if (back[i].timeout > 0)
|
||||
gestion_timeout = 1;
|
||||
|
||||
/* SSL mode */
|
||||
if (back[i].r.ssl) {
|
||||
int conn_code;
|
||||
@@ -4265,6 +4273,8 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
strcpybuff(back[i].r.msg, "Connect Time Out");
|
||||
else if (back[i].status == STATUS_WAIT_DNS)
|
||||
strcpybuff(back[i].r.msg, "DNS Time Out");
|
||||
else if (back[i].status == STATUS_SSL_WAIT_HANDSHAKE)
|
||||
strcpybuff(back[i].r.msg, "SSL/TLS Handshake Time Out");
|
||||
else
|
||||
strcpybuff(back[i].r.msg, "Receive Time Out");
|
||||
back[i].status = STATUS_READY; // terminé
|
||||
|
||||
95
tests/59_local-tls-stall.test
Normal file
95
tests/59_local-tls-stall.test
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Issue #607: a peer that accepts the TCP connect but never speaks TLS leaves the
|
||||
# slot stuck in the handshake. The per-slot --timeout must reap it; before the
|
||||
# fix only --max-time did, so these crawls ran until the kill guard.
|
||||
|
||||
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) || {
|
||||
echo "python3 missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
|
||||
server=$(nativepath "$top_srcdir/tests/tls-stall-server.py")
|
||||
tmpdir=$(mktemp -d)
|
||||
serverpid=
|
||||
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# start_stall_server <tag> <mode-args...>: sets $port from the announced one.
|
||||
start_stall_server() {
|
||||
local tag="$1"
|
||||
shift
|
||||
"$python" "$server" "$@" >"$tmpdir/$tag.out" 2>"$tmpdir/$tag.err" &
|
||||
serverpid=$!
|
||||
port=
|
||||
for _ in $(seq 1 50); do
|
||||
line=$(head -n1 "$tmpdir/$tag.out" 2>/dev/null || true)
|
||||
if test "${line%% *}" == "PORT"; then
|
||||
port="${line#PORT }"
|
||||
return 0
|
||||
fi
|
||||
kill -0 "$serverpid" 2>/dev/null || {
|
||||
echo "server exited early: $(cat "$tmpdir/$tag.err")"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
done
|
||||
echo "could not discover server port"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# no -E/--max-time on purpose: --timeout is the only thing that can end these.
|
||||
# The kill guard stands in for the hang, so a wall time near it means no reap.
|
||||
crawl_wall() {
|
||||
local out="$tmpdir/$1"
|
||||
shift
|
||||
local start
|
||||
start=$(date +%s)
|
||||
run_with_timeout 60 httrack -O "$out" -c1 --robots=0 --retries=0 --quiet -Z \
|
||||
"$@" >>"$tmpdir/log" 2>&1 || true
|
||||
echo $(($(date +%s) - start))
|
||||
}
|
||||
|
||||
# 1. handshake stalled from the first byte: reaped at --timeout, not before.
|
||||
start_stall_server direct direct
|
||||
wall=$(crawl_wall crawl1 "https://127.0.0.1:$port/" --timeout=5)
|
||||
if test "$wall" -ge 30 || test "$wall" -lt 3; then
|
||||
echo "FAIL: stalled handshake reaped after ${wall}s, expected about 5s" >&2
|
||||
cat "$tmpdir/log" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'Handshake Time Out' "$tmpdir/crawl1/hts-log.txt" || {
|
||||
echo "FAIL: crawl ended in ${wall}s but not on a handshake timeout" >&2
|
||||
cat "$tmpdir/crawl1/hts-log.txt" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "OK: stalled TLS handshake reaped by --timeout after ${wall}s"
|
||||
|
||||
# 2. the handshake window is its own, not what the connect left over: a proxy
|
||||
# that takes 4s to answer CONNECT must still leave the full --timeout=5 for the
|
||||
# handshake (~9s total). Sharing the connect's clock would reap at ~5s.
|
||||
stop_server "$serverpid"
|
||||
start_stall_server proxy proxy 4
|
||||
wall=$(crawl_wall crawl2 "https://127.0.0.1:443/" -P "127.0.0.1:$port" --timeout=5)
|
||||
if test "$wall" -ge 20 || test "$wall" -lt 7; then
|
||||
echo "FAIL: handshake after a slow connect reaped at ${wall}s, expected about 9s" >&2
|
||||
cat "$tmpdir/log" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK: handshake keeps its own timeout window after a slow connect (${wall}s)"
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# lang/*.txt are line pairs: an English msgid, then its translation. A stray
|
||||
# blank or a drifted msgid silently unhooks every translation that follows,
|
||||
# rather than failing, so assert the pairing and the join against English.txt.
|
||||
# Pairs are physical lines here; a msgid ending in \ continues onto the next one
|
||||
# for the engine (linput_cpp) but not for us. None do today.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
langdir="${top_srcdir:-..}/lang"
|
||||
def="${top_srcdir:-..}/lang.def"
|
||||
eng="$langdir/English.txt"
|
||||
for f in "$eng" "$def"; do
|
||||
[ -f "$f" ] || {
|
||||
echo "cannot find $f"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
keys="$tmp/english.keys"
|
||||
|
||||
# Byte-wise: each file is in its own declared legacy charset, lang.def is CRLF.
|
||||
LC_ALL=C awk 'NR%2==1 { sub(/\r$/, ""); print }' "$eng" >"$keys"
|
||||
|
||||
# An empty parse would make every check below pass vacuously.
|
||||
nkeys=$(wc -l <"$keys")
|
||||
if [ "$nkeys" -lt 400 ]; then
|
||||
echo "only $nkeys msgids parsed from $eng; the parse is broken"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nfiles=0
|
||||
fail=0
|
||||
for f in "$langdir"/*.txt; do
|
||||
nfiles=$((nfiles + 1))
|
||||
LC_ALL=C awk -v keys="$keys" -v name="${f##*/}" '
|
||||
BEGIN { while ((getline k < keys) > 0) known[k] = 1 }
|
||||
{ sub(/\r$/, "") }
|
||||
NR % 2 == 1 {
|
||||
if ($0 == "")
|
||||
printf "%s:%d: blank line where an English msgid belongs\n", name, NR
|
||||
else if (!($0 in known))
|
||||
printf "%s:%d: msgid absent from English.txt: %s\n", name, NR, $0
|
||||
else
|
||||
next
|
||||
bad++
|
||||
}
|
||||
END {
|
||||
if (NR % 2) {
|
||||
printf "%s: odd line count (%d): msgid/translation pairing is broken\n", name, NR
|
||||
bad++
|
||||
}
|
||||
exit(bad > 0)
|
||||
}
|
||||
' "$f" || fail=1
|
||||
done
|
||||
|
||||
if [ "$nfiles" -lt 25 ]; then
|
||||
echo "only $nfiles language files found in $langdir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# lang.def maps each LANG_* macro to the English string the GUI compiles in,
|
||||
# which is the lookup key into every lang/*.txt: no msgid, no translations.
|
||||
LC_ALL=C awk -v keys="$keys" '
|
||||
BEGIN { while ((getline k < keys) > 0) known[k] = 1 }
|
||||
{ sub(/\r$/, "") }
|
||||
NR % 2 == 1 { macro = $0; next }
|
||||
macro ~ /^(LANG_|LISTDEF_)/ {
|
||||
n++
|
||||
if (!($0 in known)) {
|
||||
printf "lang.def:%d: %s maps to \"%s\", which is not a msgid in English.txt\n", NR, macro, $0
|
||||
bad++
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (n < 400) {
|
||||
printf "lang.def: only %d LANG_ entries parsed; the parse is broken\n", n
|
||||
bad++
|
||||
}
|
||||
exit(bad > 0)
|
||||
}
|
||||
' "$def" || fail=1
|
||||
|
||||
[ "$fail" -eq 0 ] || exit 1
|
||||
|
||||
echo "$nfiles language files consistent with $nkeys English msgids"
|
||||
@@ -3,7 +3,7 @@
|
||||
# silently drop it from the dist tarball and break "make distcheck".
|
||||
EXTRA_DIST = $(TESTS) crawl-test.sh run-all-tests.sh check-network.sh \
|
||||
proxy-https-server.py socks5-server.py proxy-connect-server.py \
|
||||
proxytestlib.py \
|
||||
proxytestlib.py tls-stall-server.py \
|
||||
local-crawl.sh local-server.py testlib.sh server.crt server.key \
|
||||
server-root/simple/basic.html server-root/simple/link.html \
|
||||
server-root/stripquery/index.html server-root/stripquery/a.html \
|
||||
@@ -137,6 +137,6 @@ TESTS = \
|
||||
56_local-proxy-noleak.test \
|
||||
57_local-proxy-connect.test \
|
||||
58_watchdog.test \
|
||||
62_lang-integrity.test
|
||||
59_local-tls-stall.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -18,6 +18,15 @@ PROXY_LOG = "proxy.log"
|
||||
ORIGIN_LOG = "origin-headers.log"
|
||||
|
||||
|
||||
def bind_ephemeral():
|
||||
"""Listening socket on a free loopback port, and that port."""
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(16)
|
||||
return srv, srv.getsockname()[1]
|
||||
|
||||
|
||||
def pipe(src, dst):
|
||||
"""Relay bytes one way until EOF, then tear both ends down."""
|
||||
try:
|
||||
@@ -113,11 +122,7 @@ def handle_client(conn, logdir, mode, default_port):
|
||||
|
||||
|
||||
def start_proxy(logdir, mode, default_port):
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(16)
|
||||
port = srv.getsockname()[1]
|
||||
srv, port = bind_ephemeral()
|
||||
|
||||
def accept_loop():
|
||||
while True:
|
||||
|
||||
@@ -19,7 +19,7 @@ import threading
|
||||
# python3 -P (PYTHONSAFEPATH) drops the script's own directory from sys.path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from proxytestlib import pipe # noqa: E402
|
||||
from proxytestlib import bind_ephemeral, pipe # noqa: E402
|
||||
|
||||
# The one name the proxy answers for; a .invalid TLD never resolves (RFC 6761),
|
||||
# so a locally-resolving client could not reach us -- success proves remote DNS.
|
||||
@@ -180,11 +180,7 @@ def handle_socks(conn, logdir, mode):
|
||||
|
||||
|
||||
def start_socks(logdir, mode):
|
||||
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
srv.bind(("127.0.0.1", 0))
|
||||
srv.listen(16)
|
||||
port = srv.getsockname()[1]
|
||||
srv, port = bind_ephemeral()
|
||||
|
||||
def serve():
|
||||
while True:
|
||||
|
||||
43
tests/tls-stall-server.py
Normal file
43
tests/tls-stall-server.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Peers that accept a connection and never speak TLS (#607).
|
||||
|
||||
Modes: "direct" stalls the handshake straight away; "proxy <secs>" answers a
|
||||
CONNECT after <secs> before stalling, so the handshake starts on a clock the
|
||||
connect has already eaten into. Prints "PORT <n>" once listening.
|
||||
Usage: tls-stall-server.py [direct | proxy <secs>]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
# python3 -P (PYTHONSAFEPATH) drops the script's own directory from sys.path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from proxytestlib import bind_ephemeral # noqa: E402
|
||||
|
||||
held = [] # keep every socket open: a close would fail the handshake outright
|
||||
|
||||
|
||||
def stall(conn, delay):
|
||||
if delay is not None:
|
||||
rfile = conn.makefile("rb")
|
||||
while rfile.readline() not in (b"\r\n", b"\n", b""):
|
||||
pass
|
||||
time.sleep(delay)
|
||||
conn.sendall(b"HTTP/1.0 200 Connection established\r\n\r\n")
|
||||
held.append(conn)
|
||||
|
||||
|
||||
def main():
|
||||
mode = sys.argv[1] if len(sys.argv) > 1 else "direct"
|
||||
delay = float(sys.argv[2]) if mode == "proxy" else None
|
||||
srv, port = bind_ephemeral()
|
||||
sys.stdout.reconfigure(newline="\n") # Windows would emit \r\n
|
||||
print("PORT %d" % port, flush=True)
|
||||
while True:
|
||||
conn, _ = srv.accept()
|
||||
threading.Thread(target=stall, args=(conn, delay), daemon=True).start()
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user