Compare commits

...

8 Commits

Author SHA1 Message Date
Xavier Roche
3563d4fbe0 Merge origin/master (#763 landed)
Signed-off-by: Xavier Roche <roche@httrack.com>

# Conflicts:
#	src/htsback.c
#	tests/96_local-refetch-keep.test
2026-07-27 09:48:55 +02:00
Xavier Roche
45630f63b4 Merge fix/refetch-debris
Signed-off-by: Xavier Roche <roche@httrack.com>

# Conflicts:
#	tests/96_local-refetch-keep.test
2026-07-27 09:26:02 +02:00
Xavier Roche
2ffe8d5582 tests: assert the retry exhaustion, not the per-platform failure message
The message a cut connection produces depends on whether any bytes arrived, so
matching it would fail on a runner that sees none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 09:25:38 +02:00
Xavier Roche
14cc7a13fc Keep the copy for every failure, not just the five retryable codes
A malformed status line, an oversized declared length or a mid-flight abort all
land on STATUSCODE_INVALID, outside the retryable set, and the purge still ate
the file. Invert the test: any failure keeps the copy except the codes that
mean the engine passed the resource over on purpose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 09:23:16 +02:00
Xavier Roche
1a7effce49 --purge-old deleted a file whose re-fetch never got a response
A transfer that dies on the wire leaves the previously mirrored copy in place,
but back_finalize() returned without noting it, so the URL fell out of new.lst
and the end-of-update purge treated the file like a page that had vanished from
the site. Note the surviving copy, the way the incomplete-transfer branch above
already does, for the connection-level failures htsparse.c retries on. A
deliberate skip (too big, MIME-excluded, cancelled) keeps its current fate.

Closes #746

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 09:19:14 +02:00
Xavier Roche
6457828b72 tests: make the re-fetch assertions catch a crawl that never re-fetched
The new test passed with no second pass at all, so a regression that stopped
re-fetching would have looked green. Assert the failure the fixture provokes,
give stay.bin a fresh pass-2 body so a fix that stopped overwriting anything
fails, and compare reset.bin by checksum rather than by length.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 09:18:27 +02:00
Xavier Roche
f118cd86d3 tests: assert the failed re-fetch keeps its bytes on every platform
Test 93 filtered reset.bin out of its bucket lists because a connection killed
before the status line surfaced differently per platform. It no longer does, so
assert the resource like any other: unchanged, with the bytes pass 1 mirrored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 09:05:56 +02:00
Xavier Roche
1373530f17 A failed re-fetch overwrote the mirrored file with the aborted read's debris
A transfer that dies before a complete response has no body, yet the save path
still consulted r.adr, which at that point holds whatever the aborted header
read left behind: raw status-line bytes, or an empty buffer that truncated the
file to zero on macOS. Require a successful transfer, as the empty-body half of
the condition already did.

Closes #748

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-27 08:59:43 +02:00
2 changed files with 26 additions and 8 deletions

View File

@@ -585,6 +585,19 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
return 0;
}
/* Did the fetch fail to produce a response, as opposed to the engine
deliberately passing the resource over? Only the latter may be purged. */
static hts_boolean back_transfer_failed(const int statuscode) {
switch (statuscode) {
case STATUSCODE_TOO_BIG:
case STATUSCODE_EXCLUDED:
case STATUSCODE_TEST_OK:
return HTS_FALSE;
default:
return statuscode <= 0 ? HTS_TRUE : HTS_FALSE;
}
}
/* Commit or restore a re-fetch backup (#77 follow-up): a re-fetch over an
existing file moved the good copy to back->tmpfile before truncating url_sav.
commit keeps the new file and drops the backup; else restore it so an aborted
@@ -1031,6 +1044,14 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
/* Aborted, error, or not ready: url_sav (if written) is broken; restore the
previous copy from the backup. */
back_finalize_backup(opt, &back[p], HTS_FALSE);
/* Note the surviving copy, or the end-of-update purge drops what this run
never managed to replace (#746). */
if (!back[p].testmode && back_transfer_failed(back[p].r.statuscode) &&
back[p].url_sav[0] != '\0' && fexist_utf8(back[p].url_sav)) {
filenote(&opt->state.strc, back[p].url_sav, NULL);
file_notify(opt, back[p].url_adr, back[p].url_fil, back[p].url_sav, 0, 0,
back[p].r.notmodified);
}
return -1;
}

View File

@@ -1,19 +1,16 @@
#!/bin/bash
#
# A re-fetch cut mid-header must leave the mirrored file alone (#748): unfixed,
# the aborted read's leftover buffer lands on it. err.bin is the control, an
# HTTP 500 on the same resource already keeping its copy; stay.bin answers
# normally with a new body, so a fix that stopped overwriting anything fails.
#
# Purging is off: an update purge deletes both files for an unrelated reason
# (#746), which would hide what this asserts.
# A re-fetch cut mid-header must leave the mirrored file alone (#748) and keep
# it out of the update purge (#746). err.bin is the control, an HTTP 500 on the
# same resource already surviving both; stay.bin answers normally with a new
# body, so a fix that stopped overwriting anything fails.
set -euo pipefail
: "${top_srcdir:=..}"
bash "$top_srcdir/tests/local-crawl.sh" \
--rerun-args '--update --purge-old=0' \
--rerun-args '--update' \
--log-found 'after 2 retries at link .*keep/page\.html' \
--log-found 'after 2 retries at link .*keep/data\.bin' \
--file-matches 'keep/page.html' 'KEEP-PAGE-V1' \