mirror of
https://github.com/xroche/httrack.git
synced 2026-07-14 12:50:52 +03:00
Compare commits
6 Commits
fix-codeql
...
fix-test19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b571ac8616 | ||
|
|
b5f28f6eb5 | ||
|
|
3d65bb2e3b | ||
|
|
68897b09ce | ||
|
|
e8d1bf9c19 | ||
|
|
ef40277c24 |
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -135,16 +135,18 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: make -j"$(sysctl -n hw.ncpu)"
|
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
|
- name: Test
|
||||||
# bigcrawl's sustained -c8 crawl drops fetches on macOS's loopback when
|
|
||||||
# it competes with other crawls, flaking its exact file count (the #527
|
|
||||||
# macOS drop). Run everything else in parallel, then bigcrawl alone (its
|
|
||||||
# serial-safe condition). Linux tolerates the full parallel run.
|
|
||||||
run: |
|
run: |
|
||||||
jobs=$(( $(sysctl -n hw.ncpu) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
jobs=$(( $(sysctl -n hw.ncpu) * 2 )); [ "$jobs" -le 16 ] || jobs=16
|
||||||
rest=$(cd tests && ls *.test | grep -v '^36_local-bigcrawl\.test$' | tr '\n' ' ')
|
make check -j"$jobs"
|
||||||
make check -j"$jobs" TESTS="$rest"
|
|
||||||
make check TESTS=36_local-bigcrawl.test
|
|
||||||
|
|
||||||
- name: Print the test log on failure
|
- name: Print the test log on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ the operational checklist: toolchain, invariants, and how to ship a change.
|
|||||||
automatically; only a test slower than the current longest raises the floor.
|
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
|
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),
|
spend much of their wall time asleep (server trickles, httrack self-pacing),
|
||||||
so an idle core covers a sleeping one. CI uses `min(2*cores, 16)`. macOS runs
|
so an idle core covers a sleeping one. CI uses `min(2*cores, 16)` on every
|
||||||
36_local-bigcrawl alone in a second pass: its sustained `-c8` crawl overloads
|
platform, macOS included: the test server raises its listen backlog
|
||||||
the macOS loopback when it competes with other crawls and flakes its exact
|
(`request_queue_size`) so macOS/BSD don't drop connections under a parallel
|
||||||
file count (Linux tolerates the full parallel run).
|
`-c16` bigcrawl the way Python's default backlog of 5 did.
|
||||||
Or run `sh build.sh` to do bootstrap + configure + make in one shot.
|
Or run `sh build.sh` to do bootstrap + configure + make in one shot.
|
||||||
|
|
||||||
## Hard invariants
|
## Hard invariants
|
||||||
|
|||||||
@@ -3908,10 +3908,11 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
|||||||
if (fp) {
|
if (fp) {
|
||||||
LLint alloc_mem = resume + 1;
|
LLint alloc_mem = resume + 1;
|
||||||
|
|
||||||
// Reject a hostile Content-Length that would
|
// Bound the in-memory buffer to a 32-bit size (real
|
||||||
// overflow the buffer size: drop the partial and
|
// in-RAM resources are far smaller); a hostile
|
||||||
// refetch.
|
// Content-Length that would overflow the add or the
|
||||||
if (back[i].r.totalsize > INT64_MAX - alloc_mem) {
|
// (size_t) cast is dropped and refetched instead.
|
||||||
|
if (back[i].r.totalsize > INT32_MAX - alloc_mem) {
|
||||||
url_savename_refname_remove(opt, back[i].url_adr,
|
url_savename_refname_remove(opt, back[i].url_adr,
|
||||||
back[i].url_fil);
|
back[i].url_fil);
|
||||||
UNLINK(back[i].url_sav);
|
UNLINK(back[i].url_sav);
|
||||||
|
|||||||
17
src/htslib.c
17
src/htslib.c
@@ -1059,10 +1059,10 @@ static int append_cookie_header(buff_struct *bstr, t_cookie *cookie,
|
|||||||
return cook;
|
return cook;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Self-test entry for append_cookie_header(): build the request Cookie line
|
/* Build the request Cookie line for domain/path into dst (always
|
||||||
into dst (always NUL-terminated). Returns the number of cookies emitted. */
|
NUL-terminated). Returns the number of cookies emitted. */
|
||||||
int http_cookie_header_selftest(t_cookie *cookie, const char *domain,
|
int http_cookie_header(t_cookie *cookie, const char *domain, const char *path,
|
||||||
const char *path, char *dst, size_t dst_size) {
|
char *dst, size_t dst_size) {
|
||||||
buff_struct bstr = {dst, dst_size, 0};
|
buff_struct bstr = {dst, dst_size, 0};
|
||||||
|
|
||||||
assertf(dst != NULL && dst_size > 0);
|
assertf(dst != NULL && dst_size > 0);
|
||||||
@@ -2004,6 +2004,15 @@ LLint http_xfread1(htsblk * r, int bufl) {
|
|||||||
|
|
||||||
if (bufl > 0) {
|
if (bufl > 0) {
|
||||||
if (!r->is_write) { // stocker en mémoire
|
if (!r->is_write) { // stocker en mémoire
|
||||||
|
// In-memory content must fit a 32-bit index (allocs below add 1, reads
|
||||||
|
// use int offsets): reject a hostile Content-Length or endless stream.
|
||||||
|
const LLint inmem_want =
|
||||||
|
(r->totalsize >= 0) ? r->totalsize : (r->size + bufl);
|
||||||
|
if (inmem_want >= INT32_MAX) {
|
||||||
|
r->statuscode = STATUSCODE_INVALID;
|
||||||
|
strcpybuff(r->msg, "In-memory content too large");
|
||||||
|
return READ_ERROR;
|
||||||
|
}
|
||||||
if (r->totalsize >= 0) { // totalsize déterminé ET ALLOUE
|
if (r->totalsize >= 0) { // totalsize déterminé ET ALLOUE
|
||||||
if (r->adr == NULL) {
|
if (r->adr == NULL) {
|
||||||
r->adr = (char *) malloct((size_t) r->totalsize + 1);
|
r->adr = (char *) malloct((size_t) r->totalsize + 1);
|
||||||
|
|||||||
@@ -184,10 +184,10 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode, const char *xsend
|
|||||||
const char *referer_adr, const char *referer_fil,
|
const char *referer_adr, const char *referer_fil,
|
||||||
htsblk * retour);
|
htsblk * retour);
|
||||||
/* Build the request "Cookie:" header line for stored cookies matching
|
/* Build the request "Cookie:" header line for stored cookies matching
|
||||||
domain/path into dst (NUL-terminated). Exposed for the -#Q self-test;
|
domain/path into dst (NUL-terminated), wrapping the logic http_sendhead()
|
||||||
wraps the same logic http_sendhead() uses. Returns cookies emitted. */
|
uses. Returns cookies emitted. */
|
||||||
int http_cookie_header_selftest(t_cookie *cookie, const char *domain,
|
int http_cookie_header(t_cookie *cookie, const char *domain, const char *path,
|
||||||
const char *path, char *dst, size_t dst_size);
|
char *dst, size_t dst_size);
|
||||||
|
|
||||||
T_SOC newhttp(httrackp * opt, const char *iadr, htsblk * retour, int port,
|
T_SOC newhttp(httrackp * opt, const char *iadr, htsblk * retour, int port,
|
||||||
int waitconnect);
|
int waitconnect);
|
||||||
|
|||||||
@@ -4311,6 +4311,8 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
|
|||||||
*forbidden_url == 0 && IS_DELAYED_EXT(afs->save) && !opt->state.stop) {
|
*forbidden_url == 0 && IS_DELAYED_EXT(afs->save) && !opt->state.stop) {
|
||||||
int loops;
|
int loops;
|
||||||
int continue_loop;
|
int continue_loop;
|
||||||
|
char BIGSTK
|
||||||
|
cookie_before[16384]; /* #15: this URL's Cookie header, pre-request */
|
||||||
|
|
||||||
hts_log_print(opt, LOG_DEBUG, "Waiting for type to be known: %s%s", afs->af.adr,
|
hts_log_print(opt, LOG_DEBUG, "Waiting for type to be known: %s%s", afs->af.adr,
|
||||||
afs->af.fil);
|
afs->af.fil);
|
||||||
@@ -4319,6 +4321,12 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
|
|||||||
for(loops = 0, continue_loop = 1;
|
for(loops = 0, continue_loop = 1;
|
||||||
IS_DELAYED_EXT(afs->save) && continue_loop && loops < 7; loops++) {
|
IS_DELAYED_EXT(afs->save) && continue_loop && loops < 7; loops++) {
|
||||||
continue_loop = 0;
|
continue_loop = 0;
|
||||||
|
/* #15: snapshot the Cookie header THIS url would send, so only its own
|
||||||
|
self-redirect Set-Cookie trips the retry, not a concurrent slot's. */
|
||||||
|
cookie_before[0] = '\0';
|
||||||
|
if (opt->accept_cookie && opt->cookie != NULL)
|
||||||
|
http_cookie_header(opt->cookie, jump_identification_const(afs->af.adr),
|
||||||
|
afs->af.fil, cookie_before, sizeof(cookie_before));
|
||||||
|
|
||||||
/* Wait for an available slot */
|
/* Wait for an available slot */
|
||||||
if (!hts_wait_available_socket(sback, opt, cache, ptr))
|
if (!hts_wait_available_socket(sback, opt, cache, ptr))
|
||||||
@@ -4516,6 +4524,17 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
|
|||||||
/* Moved! */
|
/* Moved! */
|
||||||
else if (HTTP_IS_REDIRECT(back[b].r.statuscode)) {
|
else if (HTTP_IS_REDIRECT(back[b].r.statuscode)) {
|
||||||
char BIGSTK mov_url[HTS_URLMAXSIZE * 2];
|
char BIGSTK mov_url[HTS_URLMAXSIZE * 2];
|
||||||
|
char BIGSTK cookie_after[16384];
|
||||||
|
hts_boolean cookies_changed;
|
||||||
|
|
||||||
|
/* #15: cookie-wall signal: this URL's own Cookie header changed
|
||||||
|
across its self-redirect (a Set-Cookie it just set). */
|
||||||
|
cookie_after[0] = '\0';
|
||||||
|
if (opt->accept_cookie && opt->cookie != NULL)
|
||||||
|
http_cookie_header(
|
||||||
|
opt->cookie, jump_identification_const(afs->af.adr),
|
||||||
|
afs->af.fil, cookie_after, sizeof(cookie_after));
|
||||||
|
cookies_changed = strcmp(cookie_before, cookie_after) != 0;
|
||||||
|
|
||||||
mov_url[0] = '\0';
|
mov_url[0] = '\0';
|
||||||
strcpybuff(mov_url, back[b].r.location); // copier URL
|
strcpybuff(mov_url, back[b].r.location); // copier URL
|
||||||
@@ -4585,11 +4604,24 @@ int hts_wait_delayed(htsmoduleStruct * str, lien_adrfilsave *afs,
|
|||||||
url_savename(afs, former, heap(ptr)->adr, heap(ptr)->fil,
|
url_savename(afs, former, heap(ptr)->adr, heap(ptr)->fil,
|
||||||
opt, sback, cache, hash, ptr, numero_passe,
|
opt, sback, cache, hash, ptr, numero_passe,
|
||||||
&delayed_back);
|
&delayed_back);
|
||||||
|
} else if (cookies_changed) {
|
||||||
|
// #15: cookie-wall self-redirect; evict the cached
|
||||||
|
// fast-header so the re-issue refetches with the cookie.
|
||||||
|
if (cache->cached_tests != NULL)
|
||||||
|
coucal_remove(cache->cached_tests,
|
||||||
|
concat(OPT_GET_BUFF(opt),
|
||||||
|
OPT_GET_BUFF_SIZE(opt), afs->af.adr,
|
||||||
|
afs->af.fil));
|
||||||
|
afs->save[0] = '\0';
|
||||||
|
url_savename(afs, former, heap(ptr)->adr, heap(ptr)->fil, opt,
|
||||||
|
sback, cache, hash, ptr, numero_passe,
|
||||||
|
&delayed_back);
|
||||||
|
continue_loop = 1;
|
||||||
} else {
|
} else {
|
||||||
hts_log_print(opt, LOG_WARNING,
|
hts_log_print(opt, LOG_WARNING,
|
||||||
"Unable to test %s%s (loop to same filename)",
|
"Unable to test %s%s (loop to same filename)",
|
||||||
afs->af.adr, afs->af.fil);
|
afs->af.adr, afs->af.fil);
|
||||||
} // loop to same location
|
} // loop to same location
|
||||||
} // ident_url_relatif()
|
} // ident_url_relatif()
|
||||||
} // location
|
} // location
|
||||||
} // redirect
|
} // redirect
|
||||||
|
|||||||
@@ -1310,6 +1310,59 @@ static int st_headerlong(httrackp *opt, int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* http_xfread1 must refuse an in-memory buffer whose size would exceed a 32-bit
|
||||||
|
index (hostile Content-Length or endless stream) rather than allocate it.
|
||||||
|
The guard returns before any socket read, so no real connection is needed. */
|
||||||
|
static int st_xfread_limit(httrackp *opt, int argc, char **argv) {
|
||||||
|
htsblk r;
|
||||||
|
|
||||||
|
(void) opt;
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
// Content-Length just over 2 GiB.
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.soc = INVALID_SOCKET;
|
||||||
|
r.totalsize = (LLint) INT32_MAX + 1;
|
||||||
|
printf("bylen: refused=%d adr=%s msg=%s\n",
|
||||||
|
http_xfread1(&r, 8192) == READ_ERROR, r.adr != NULL ? "alloc" : "null",
|
||||||
|
r.msg);
|
||||||
|
if (r.adr != NULL)
|
||||||
|
freet(r.adr);
|
||||||
|
|
||||||
|
// Unknown length, buffer already at the limit: the next read would exceed it.
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.soc = INVALID_SOCKET;
|
||||||
|
r.totalsize = -1;
|
||||||
|
r.size = (LLint) INT32_MAX;
|
||||||
|
printf("bygrow: refused=%d adr=%s msg=%s\n",
|
||||||
|
http_xfread1(&r, 8192) == READ_ERROR, r.adr != NULL ? "alloc" : "null",
|
||||||
|
r.msg);
|
||||||
|
if (r.adr != NULL)
|
||||||
|
freet(r.adr);
|
||||||
|
|
||||||
|
// Exactly at the 2 GiB index (size + bufl == INT32_MAX): must also be
|
||||||
|
// refused, since the reallocs below add 1 (a `> INT32_MAX` check would let
|
||||||
|
// this through and overflow the int realloc size).
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.soc = INVALID_SOCKET;
|
||||||
|
r.totalsize = -1;
|
||||||
|
r.size = (LLint) INT32_MAX - 8192;
|
||||||
|
http_xfread1(&r, 8192);
|
||||||
|
printf("boundary: msg=%s\n", r.msg);
|
||||||
|
|
||||||
|
// A legitimate small size must NOT be refused by the guard (the read then
|
||||||
|
// fails on the invalid socket, but the size-too-large msg must not be set).
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.soc = INVALID_SOCKET;
|
||||||
|
r.totalsize = 1000;
|
||||||
|
http_xfread1(&r, 8192);
|
||||||
|
printf("accept: msg=%s\n", r.msg);
|
||||||
|
if (r.adr != NULL)
|
||||||
|
freet(r.adr);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Parse a Content-Range header and print the sanitized triple. A hostile value
|
/* Parse a Content-Range header and print the sanitized triple. A hostile value
|
||||||
(negative or INT64 extreme) must clamp to 0 without signed-overflow UB. */
|
(negative or INT64 extreme) must clamp to 0 without signed-overflow UB. */
|
||||||
static int st_crange(httrackp *opt, int argc, char **argv) {
|
static int st_crange(httrackp *opt, int argc, char **argv) {
|
||||||
@@ -1702,7 +1755,7 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_cookie_header_selftest(&cookie, dom, "/", hdr, sizeof(hdr));
|
http_cookie_header(&cookie, dom, "/", hdr, sizeof(hdr));
|
||||||
if (strcmp(hdr, expected) != 0)
|
if (strcmp(hdr, expected) != 0)
|
||||||
err = 1;
|
err = 1;
|
||||||
if (strstr(hdr, "$Version") != NULL || strstr(hdr, "$Path") != NULL)
|
if (strstr(hdr, "$Version") != NULL || strstr(hdr, "$Path") != NULL)
|
||||||
@@ -2521,6 +2574,8 @@ static const struct selftest_entry {
|
|||||||
st_headerlong},
|
st_headerlong},
|
||||||
{"crange", "<raw-content-range-line> ...",
|
{"crange", "<raw-content-range-line> ...",
|
||||||
"Content-Range parse integer safety", st_crange},
|
"Content-Range parse integer safety", st_crange},
|
||||||
|
{"xfread-limit", "", "in-memory receive buffer size bound",
|
||||||
|
st_xfread_limit},
|
||||||
{"savename", "<fil> <content-type> [key=value ...]",
|
{"savename", "<fil> <content-type> [key=value ...]",
|
||||||
"local save-name for a URL", st_savename},
|
"local save-name for a URL", st_savename},
|
||||||
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
|
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
|
||||||
|
|||||||
29
tests/01_engine-xfread.test
Executable file
29
tests/01_engine-xfread.test
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# http_xfread1 must refuse an in-memory receive buffer that would exceed a
|
||||||
|
# 32-bit index (hostile Content-Length, or an endless stream) rather than
|
||||||
|
# allocate it, while still accepting a normal small size. -#test=xfread-limit
|
||||||
|
# drives both refusal paths and the accept path.
|
||||||
|
|
||||||
|
out="$(httrack -O /dev/null -#test=xfread-limit)"
|
||||||
|
for case in bylen bygrow; do
|
||||||
|
echo "$out" | grep -q "${case}: refused=1 adr=null msg=In-memory content too large" || {
|
||||||
|
echo "FAIL ${case}: $out"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
# Exactly INT32_MAX must be refused too (the reallocs add 1).
|
||||||
|
echo "$out" | grep -q 'boundary: msg=In-memory content too large' || {
|
||||||
|
echo "FAIL boundary: $out"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# The guard must NOT fire for a legitimate small size.
|
||||||
|
if echo "$out" | grep -q 'accept: msg=In-memory content too large'; then
|
||||||
|
echo "FAIL accept (guard fired on a legit size): $out"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -53,4 +53,4 @@ bash "$top_srcdir/tests/local-crawl.sh" --rerun \
|
|||||||
--log-found ', no files updated' \
|
--log-found ', no files updated' \
|
||||||
--max-mirror-bytes 700000 \
|
--max-mirror-bytes 700000 \
|
||||||
--min-mirror-bytes 500000 \
|
--min-mirror-bytes 500000 \
|
||||||
httrack 'BASEURL/big/index.html' --retries=0 -c8 -%c100 -A100000000
|
httrack 'BASEURL/big/index.html' --retries=0 -c16 -%c100 -A100000000
|
||||||
|
|||||||
34
tests/49_local-cookiewall.test
Executable file
34
tests/49_local-cookiewall.test
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Cookie-wall self-redirect (#15): httrack replays a Set-Cookie to mirror the
|
||||||
|
# real page, gives up when nothing changes, and stays bounded when it never does.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${top_srcdir:=..}"
|
||||||
|
|
||||||
|
# Unknown-ext (wall.php) and known-ext (wall.html): the wall sets the cookie once,
|
||||||
|
# then serves real content; httrack must replay it and mirror that content.
|
||||||
|
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
|
||||||
|
--found 'cookiewall/wall.html' \
|
||||||
|
--file-matches 'cookiewall/wall.html' 'REAL-CONTENT-BEHIND-COOKIE-WALL' \
|
||||||
|
httrack 'BASEURL/cookiewall/index.html'
|
||||||
|
|
||||||
|
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
|
||||||
|
--found 'cookiewall2/wall.html' \
|
||||||
|
--file-matches 'cookiewall2/wall.html' 'REAL-CONTENT-BEHIND-COOKIE-WALL' \
|
||||||
|
httrack 'BASEURL/cookiewall2/index.html'
|
||||||
|
|
||||||
|
# No Set-Cookie: the jar never changes, so httrack must give up at once (retry
|
||||||
|
# stays gated) and not mirror the wall. A dropped gate would skip this log line.
|
||||||
|
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
|
||||||
|
--not-found 'cookiewall3/wall.html' \
|
||||||
|
--log-found 'loop to same filename' \
|
||||||
|
httrack 'BASEURL/cookiewall3/index.html'
|
||||||
|
|
||||||
|
# Ever-changing cookie never satisfies the wall: httrack must stop at the retry
|
||||||
|
# cap (bounded, no early give-up) and not mirror it.
|
||||||
|
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 \
|
||||||
|
--not-found 'cookiewall4/wall.html' \
|
||||||
|
--log-not-found 'loop to same filename' \
|
||||||
|
httrack 'BASEURL/cookiewall4/index.html'
|
||||||
@@ -66,6 +66,7 @@ TESTS = \
|
|||||||
01_engine-urlhack.test \
|
01_engine-urlhack.test \
|
||||||
01_engine-unescape-bounds.test \
|
01_engine-unescape-bounds.test \
|
||||||
01_engine-useragent.test \
|
01_engine-useragent.test \
|
||||||
|
01_engine-xfread.test \
|
||||||
01_zlib-acceptencoding.test \
|
01_zlib-acceptencoding.test \
|
||||||
01_zlib-cache.test \
|
01_zlib-cache.test \
|
||||||
01_zlib-cache-corrupt.test \
|
01_zlib-cache-corrupt.test \
|
||||||
@@ -118,6 +119,7 @@ TESTS = \
|
|||||||
45_local-maxsize-recv.test \
|
45_local-maxsize-recv.test \
|
||||||
46_local-update-304-resume.test \
|
46_local-update-304-resume.test \
|
||||||
47_local-crange-overflow.test \
|
47_local-crange-overflow.test \
|
||||||
48_local-crange-memresume.test
|
48_local-crange-memresume.test \
|
||||||
|
49_local-cookiewall.test
|
||||||
|
|
||||||
CLEANFILES = check-network_sh.cache
|
CLEANFILES = check-network_sh.cache
|
||||||
|
|||||||
@@ -1143,6 +1143,56 @@ class Handler(SimpleHTTPRequestHandler):
|
|||||||
def route_delayed_empty(self):
|
def route_delayed_empty(self):
|
||||||
self.send_raw(b"", "text/html") # 200 + Content-Length: 0
|
self.send_raw(b"", "text/html") # 200 + Content-Length: 0
|
||||||
|
|
||||||
|
# --- /cookiewall/ (#15): a self-redirect that only sets a cookie is a
|
||||||
|
# consent wall; httrack must replay the cookie and fetch the real page.
|
||||||
|
WALL_MARK = b"REAL-CONTENT-BEHIND-COOKIE-WALL"
|
||||||
|
|
||||||
|
def route_cookiewall_index(self):
|
||||||
|
self.send_html('\t<a href="wall.php">wall</a>')
|
||||||
|
|
||||||
|
def route_cookiewall_wall(self):
|
||||||
|
self._cookiewall_reply("wall.php")
|
||||||
|
|
||||||
|
# Known-extension twin: .html so the type is not delayed-resolved.
|
||||||
|
def route_cookiewall2_index(self):
|
||||||
|
self.send_html('\t<a href="wall.html">wall</a>')
|
||||||
|
|
||||||
|
def route_cookiewall2_wall(self):
|
||||||
|
self._cookiewall_reply("wall.html")
|
||||||
|
|
||||||
|
def _cookiewall_reply(self, location):
|
||||||
|
if self.request_cookies().get("gate") == "1":
|
||||||
|
self.send_raw(
|
||||||
|
b"<html><body>" + self.WALL_MARK + b"</body></html>\n", "text/html"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._wall_redirect(location, "gate=1; Path=/")
|
||||||
|
|
||||||
|
# No-cookie self-redirect: the jar never changes, so httrack must give up at
|
||||||
|
# once rather than re-fetch (proves the cookie-wall retry stays gated on #15).
|
||||||
|
def route_cookiewall3_index(self):
|
||||||
|
self.send_html('\t<a href="wall.php">wall</a>')
|
||||||
|
|
||||||
|
def route_cookiewall3_wall(self):
|
||||||
|
self._wall_redirect("wall.php", None)
|
||||||
|
|
||||||
|
# Ever-changing cookie: every hit sets a fresh value, so the jar keeps
|
||||||
|
# changing; httrack must stop at the loops<7 cap, not spin forever.
|
||||||
|
def route_cookiewall4_index(self):
|
||||||
|
self.send_html('\t<a href="wall.php">wall</a>')
|
||||||
|
|
||||||
|
def route_cookiewall4_wall(self):
|
||||||
|
nonce = int(self.request_cookies().get("gate", "0")) + 1
|
||||||
|
self._wall_redirect("wall.php", f"gate={nonce}; Path=/")
|
||||||
|
|
||||||
|
def _wall_redirect(self, location, set_cookie):
|
||||||
|
self.send_response(302, "Found")
|
||||||
|
self.send_header("Location", location)
|
||||||
|
if set_cookie is not None:
|
||||||
|
self.send_header("Set-Cookie", set_cookie)
|
||||||
|
self.send_header("Content-Length", "0")
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
# -E time-limit (#481): pages that trickle far longer than any -E budget,
|
# -E time-limit (#481): pages that trickle far longer than any -E budget,
|
||||||
# so only an engine-side abort can end the crawl.
|
# so only an engine-side abort can end the crawl.
|
||||||
TRICKLE_SECONDS = 60
|
TRICKLE_SECONDS = 60
|
||||||
@@ -1359,6 +1409,14 @@ class Handler(SimpleHTTPRequestHandler):
|
|||||||
"/delayed/chain7.php": route_delayed_chain,
|
"/delayed/chain7.php": route_delayed_chain,
|
||||||
"/delayed/chain8.php": route_delayed_chain,
|
"/delayed/chain8.php": route_delayed_chain,
|
||||||
"/delayed/chain9.php": route_delayed_chain,
|
"/delayed/chain9.php": route_delayed_chain,
|
||||||
|
"/cookiewall/index.html": route_cookiewall_index,
|
||||||
|
"/cookiewall/wall.php": route_cookiewall_wall,
|
||||||
|
"/cookiewall2/index.html": route_cookiewall2_index,
|
||||||
|
"/cookiewall2/wall.html": route_cookiewall2_wall,
|
||||||
|
"/cookiewall3/index.html": route_cookiewall3_index,
|
||||||
|
"/cookiewall3/wall.php": route_cookiewall3_wall,
|
||||||
|
"/cookiewall4/index.html": route_cookiewall4_index,
|
||||||
|
"/cookiewall4/wall.php": route_cookiewall4_wall,
|
||||||
"/redir/index.html": route_redir_index,
|
"/redir/index.html": route_redir_index,
|
||||||
"/redir/go.php": route_redir_go,
|
"/redir/go.php": route_redir_go,
|
||||||
"/redir/target.html": route_redir_target,
|
"/redir/target.html": route_redir_target,
|
||||||
@@ -1587,7 +1645,12 @@ def main():
|
|||||||
def factory(*a, **kw):
|
def factory(*a, **kw):
|
||||||
return Handler(*a, directory=root, **kw)
|
return Handler(*a, directory=root, **kw)
|
||||||
|
|
||||||
httpd = ThreadingHTTPServer((args.bind, 0), factory)
|
# macOS/BSD drop SYNs when the listen backlog overflows (Linux is lenient);
|
||||||
|
# raise it from Python's default 5 so a busy -c8 crawl can't lose fetches.
|
||||||
|
class BacklogHTTPServer(ThreadingHTTPServer):
|
||||||
|
request_queue_size = 128
|
||||||
|
|
||||||
|
httpd = BacklogHTTPServer((args.bind, 0), factory)
|
||||||
|
|
||||||
if args.tls:
|
if args.tls:
|
||||||
import ssl
|
import ssl
|
||||||
|
|||||||
Reference in New Issue
Block a user