mirror of
https://github.com/xroche/httrack.git
synced 2026-07-17 06:10:42 +03:00
Compare commits
2 Commits
fix-607-ss
...
fix-argord
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5083991f5a | ||
|
|
1a020911db |
@@ -2816,9 +2816,6 @@ 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
|
||||
@@ -2878,11 +2875,6 @@ 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;
|
||||
@@ -4273,8 +4265,6 @@ 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é
|
||||
|
||||
@@ -113,6 +113,7 @@ HTSEXT_API int hts_main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
static int hts_main_internal(int argc, char **argv, httrackp * opt);
|
||||
static hts_boolean cmdl_shortopt_has(const char *s, char c);
|
||||
|
||||
// Main, récupère les paramètres et appelle le robot
|
||||
HTSEXT_API int hts_main2(int argc, char **argv, httrackp * opt) {
|
||||
@@ -304,12 +305,12 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
hts_get_version_info(opt));
|
||||
return 0;
|
||||
} else {
|
||||
if (strncmp(tmp_argv[0], "--", 2)) { /* pas */
|
||||
if ((strchr(tmp_argv[0], 'q') != NULL))
|
||||
opt->quiet = 1; // ne pas poser de questions! (nohup par exemple)
|
||||
if ((strchr(tmp_argv[0], 'i') != NULL)) { // doit.log!
|
||||
argv_url = -1; /* forcer */
|
||||
opt->quiet = 1;
|
||||
if (strncmp(tmp_argv[0], "--", 2)) { /* not a long option */
|
||||
if (cmdl_shortopt_has(tmp_argv[0], 'q'))
|
||||
opt->quiet = HTS_TRUE; // never ask questions (nohup)
|
||||
if (cmdl_shortopt_has(tmp_argv[0], 'i')) { // doit.log!
|
||||
argv_url = -1;
|
||||
opt->quiet = HTS_TRUE;
|
||||
}
|
||||
} else if (strcmp(tmp_argv[0] + 2, "quiet") == 0) {
|
||||
opt->quiet = 1; // ne pas poser de questions! (nohup par exemple)
|
||||
@@ -2807,6 +2808,32 @@ int check_path(String * s, char *defaultname) {
|
||||
return return_value;
|
||||
}
|
||||
|
||||
/* Does the short-option cluster s carry c from the main option set (-i, -iC2,
|
||||
-%Mi)? Walked as the parser does below: %, &, @ and # each take the letter
|
||||
after them into another set, so the i of -%i is not the main-set -i. */
|
||||
static hts_boolean cmdl_shortopt_has(const char *s, char c) {
|
||||
const char *com;
|
||||
|
||||
if (s[0] != '-' || s[1] == '-')
|
||||
return HTS_FALSE;
|
||||
for (com = s + 1; *com != '\0'; com++) {
|
||||
switch (*com) {
|
||||
case '%':
|
||||
case '&':
|
||||
case '@':
|
||||
case '#':
|
||||
if (*(com + 1) != '\0')
|
||||
com++; /* skip the other set's letter */
|
||||
break;
|
||||
default:
|
||||
if (*com == c)
|
||||
return HTS_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return HTS_FALSE;
|
||||
}
|
||||
|
||||
// détermine si l'argument est une option
|
||||
int cmdl_opt(char *s) {
|
||||
if (s[0] == '-') { // c'est peut être une option
|
||||
|
||||
@@ -54,6 +54,13 @@ refused() {
|
||||
! echo "FAIL: $1 (exit $RC)" || exit 1
|
||||
}
|
||||
|
||||
# assert continue mode was entered: it drops the URL list, so with no cache to
|
||||
# resume the run ends on the usage screen rather than on any other error
|
||||
continued() {
|
||||
{ test "$RC" -ne 0 && grep -q 'usage:' "$1/.log"; } ||
|
||||
! echo "FAIL: $2 (exit $RC)" || exit 1
|
||||
}
|
||||
|
||||
# a value past the old 126/256 caps but within the cap is accepted, on both the
|
||||
# short and long form of each option
|
||||
long=$(nchars 900)
|
||||
@@ -102,4 +109,50 @@ for bad in nan nan:5 5:nan inf 10:5 99999; do
|
||||
refused "#185: invalid --pause '$bad' not refused cleanly"
|
||||
done
|
||||
|
||||
# An option is not -i (continue) merely because its name contains an 'i' (#615).
|
||||
# These used to wipe the URL given before them and exit on the usage screen.
|
||||
run "$tmp/ord-bti" --build-top-index
|
||||
accepted "$tmp/ord-bti" "#615: --build-top-index after the URL wiped the URL list"
|
||||
run "$tmp/ord-bti-s" "-%i"
|
||||
accepted "$tmp/ord-bti-s" "#615: -%i after the URL wiped the URL list"
|
||||
run "$tmp/ord-proto" --protocol 2
|
||||
accepted "$tmp/ord-proto" "#615: --protocol after the URL wiped the URL list"
|
||||
run "$tmp/ord-proto-s" "-@i2"
|
||||
accepted "$tmp/ord-proto-s" "#615: -@i2 after the URL wiped the URL list"
|
||||
|
||||
# %, &, @ and # take the letter after them into another option set, wherever
|
||||
# they sit in the cluster, so the i of -q%i is not the main-set -i either.
|
||||
run "$tmp/ord-qpi" "-q%i"
|
||||
accepted "$tmp/ord-qpi" "#615: -q%i after the URL wiped the URL list"
|
||||
run "$tmp/ord-qai" "-q@i"
|
||||
accepted "$tmp/ord-qai" "#615: -q@i after the URL wiped the URL list"
|
||||
|
||||
# -%q only forces quiet mode, which nothing here can see: a run whose stdout is
|
||||
# not a tty is quiet from the start (htscoremain.c:174). Just check it crawls.
|
||||
run "$tmp/ord-iqs" "-%q"
|
||||
accepted "$tmp/ord-iqs" "#615: -%q after the URL broke the crawl"
|
||||
|
||||
# The real -i still forces continue mode and drops the URL list, in every form:
|
||||
# a fix that merely stopped looking for 'i' would pass the checks above.
|
||||
run "$tmp/cont-s" -i
|
||||
continued "$tmp/cont-s" "#615: -i after the URL no longer forces continue mode"
|
||||
run "$tmp/cont-c" -iC2
|
||||
continued "$tmp/cont-c" "#615: -iC2 after the URL no longer forces continue mode"
|
||||
run "$tmp/cont-l" --continue
|
||||
continued "$tmp/cont-l" "#615: --continue after the URL no longer forces continue mode"
|
||||
run "$tmp/cont-u" --update
|
||||
continued "$tmp/cont-u" "#615: --update after the URL no longer forces continue mode"
|
||||
|
||||
# ...including where another set's letter precedes it in the cluster.
|
||||
run "$tmp/cont-pq" "-%qi"
|
||||
continued "$tmp/cont-pq" "#615: -%qi after the URL no longer forces continue mode"
|
||||
run "$tmp/cont-pm" "-%Mi"
|
||||
continued "$tmp/cont-pm" "#615: -%Mi after the URL no longer forces continue mode"
|
||||
|
||||
# Placed before the URL these always worked; they must keep working.
|
||||
run_only "$tmp/pre-bti" "-%i" "file://$tmp/index.html"
|
||||
accepted "$tmp/pre-bti" "#615: -%i before the URL broke the crawl"
|
||||
run_only "$tmp/pre-proto" "-@i2" "file://$tmp/index.html"
|
||||
accepted "$tmp/pre-proto" "#615: -@i2 before the URL broke the crawl"
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/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)"
|
||||
@@ -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 tls-stall-server.py \
|
||||
proxytestlib.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 \
|
||||
@@ -136,7 +136,6 @@ TESTS = \
|
||||
55_local-chunked.test \
|
||||
56_local-proxy-noleak.test \
|
||||
57_local-proxy-connect.test \
|
||||
58_watchdog.test \
|
||||
59_local-tls-stall.test
|
||||
58_watchdog.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -18,15 +18,6 @@ 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:
|
||||
@@ -122,7 +113,11 @@ def handle_client(conn, logdir, mode, default_port):
|
||||
|
||||
|
||||
def start_proxy(logdir, mode, default_port):
|
||||
srv, port = bind_ephemeral()
|
||||
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]
|
||||
|
||||
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 bind_ephemeral, pipe # noqa: E402
|
||||
from proxytestlib import 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,7 +180,11 @@ def handle_socks(conn, logdir, mode):
|
||||
|
||||
|
||||
def start_socks(logdir, mode):
|
||||
srv, port = bind_ephemeral()
|
||||
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]
|
||||
|
||||
def serve():
|
||||
while True:
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/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