mirror of
https://github.com/xroche/httrack.git
synced 2026-07-27 19:12:54 +03:00
Compare commits
25 Commits
ci/suite-t
...
fix/refetc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fbdc19126 | ||
|
|
c31f53132f | ||
|
|
1b3f7918b6 | ||
|
|
aeef177267 | ||
|
|
0984aa2530 | ||
|
|
bc6c53598c | ||
|
|
809d5b6ffc | ||
|
|
46bc313469 | ||
|
|
4446923d89 | ||
|
|
f39270bdd6 | ||
|
|
ea6aafb210 | ||
|
|
8bad42978a | ||
|
|
12775c2509 | ||
|
|
57098870d3 | ||
|
|
3ed88825f7 | ||
|
|
a61178bba7 | ||
|
|
6be18f430e | ||
|
|
536d7515ed | ||
|
|
38161162e2 | ||
|
|
8c7e0edc4e | ||
|
|
28c103db70 | ||
|
|
8cf32966fa | ||
|
|
374087a81c | ||
|
|
83079b788d | ||
|
|
1fa3cb4e74 |
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -627,7 +627,12 @@ jobs:
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin \
|
||||
"+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}"
|
||||
base="origin/${{ github.base_ref }}"
|
||||
# Merge base, not the branch tip: master moves during a run, and a
|
||||
# tip-relative diff blames its new C on this PR.
|
||||
if ! base="$(git merge-base "origin/${{ github.base_ref }}" HEAD)"; then
|
||||
echo "::error::no merge base with origin/${{ github.base_ref }}; cannot scope the check."
|
||||
exit 1
|
||||
fi
|
||||
set +e
|
||||
diff="$(git clang-format --binary clang-format-19 --style=file \
|
||||
--diff --extensions c,h "$base")"
|
||||
@@ -669,6 +674,8 @@ jobs:
|
||||
git fetch --no-tags origin \
|
||||
"+refs/heads/${{ github.base_ref }}:refs/remotes/origin/${{ github.base_ref }}"
|
||||
base="origin/${{ github.base_ref }}"
|
||||
# Three dots: merge-base scoped, so commits landing on master mid-run
|
||||
# are not counted as this PR's.
|
||||
changed="$(git diff --name-only "$base"...HEAD)"
|
||||
has() { printf '%s\n' "$changed" | grep -qx "$1"; }
|
||||
if has man/httrack.1 && ! has html/httrack.man.html; then
|
||||
|
||||
3
.github/workflows/windows-build.yml
vendored
3
.github/workflows/windows-build.yml
vendored
@@ -211,7 +211,8 @@ jobs:
|
||||
pass=0 fail=0 skip=0 failed="" skipped="" deadline=0
|
||||
for t in 00_runnable.test 01_engine-*.test 01_zlib-*.test \
|
||||
*_local-*.test 13_crawl_proxy_https.test 58_watchdog.test \
|
||||
60_crawl-log-salvage.test; do
|
||||
60_crawl-log-salvage.test 106_engine-repair-rename.test \
|
||||
108_engine-refetch-backup.test; do
|
||||
elapsed=$((SECONDS - started))
|
||||
if [ "$elapsed" -ge "$suite_deadline" ]; then
|
||||
echo "::error::suite deadline: ${elapsed}s elapsed, stopping before $t"
|
||||
|
||||
18
AGENTS.md
18
AGENTS.md
@@ -26,12 +26,18 @@ the operational checklist: toolchain, invariants, and how to ship a change.
|
||||
check`, or `PATH="<bld>/src:$PATH"` for a manual run.
|
||||
- Give new `.test` scripts `set -e`: the older ones predate the rule, so several
|
||||
`local-crawl.sh` calls with no `set -e` report PASS on any non-last failure.
|
||||
- Never assert with `cmd | grep -q MARKER && fail`. Under `pipefail` the
|
||||
pipeline is non-zero both when `cmd` fails and when `grep -q` matches early
|
||||
and SIGPIPEs it, so the `&&` never fires and a probe that proved nothing reads
|
||||
as "marker absent". Capture the reply, assert the status line it must carry
|
||||
(an empty, truncated or redirected one is marker-free too), then match with a
|
||||
here-string.
|
||||
- Run teardown with errexit off: `trap 'set +e; cleanup' EXIT`. Under `set -e` a
|
||||
failing cleanup command becomes the test's exit status (#773). Keep the other
|
||||
signals on their own `trap` line, or errexit stays off for the rest of the run.
|
||||
The guard also resets `$?`, so save it first if teardown reads it.
|
||||
- Never pipe into `grep -q`: it exits on the first match, so whatever the
|
||||
producer had left to write takes SIGPIPE, and under `pipefail` that becomes
|
||||
the pipeline's status. `cmd | grep -q M && fail` then never fires and a probe
|
||||
that proved nothing reads as "marker absent"; `cmd | grep -q M || fail` fails
|
||||
a test whose marker was present. bash issues one `write()` per line, so any
|
||||
match that is not on the last line is exposed. Capture the reply, assert the
|
||||
status line it must carry (an empty, truncated or redirected one is
|
||||
marker-free too), then match with a here-string: `grep -q M <<<"$reply"`.
|
||||
|
||||
## Hard invariants
|
||||
- **Generated autotools files are NOT in git.** `configure`, every
|
||||
|
||||
@@ -237,7 +237,7 @@ Build options:
|
||||
x replace external html links by error pages (--replace-external)
|
||||
%x do not include any password for external password protected websites (%x0 include) (--no-passwords)
|
||||
%q *include query string for local files (useless, for information purpose only) (%q0 don't include) (--include-query-string)
|
||||
o *generate output html file in case of error (404..) (o0 don't generate) (--generate-errors)
|
||||
o *save the server's error pages (404..) (o0 discard them) (--generate-errors)
|
||||
X *purge old files after update (X0 keep delete) (--purge-old[=N])
|
||||
|
||||
Spider options:
|
||||
@@ -413,7 +413,7 @@ site. Specifically, the defauls are:
|
||||
NN name conversion type (0 *original structure, 1+: see below)
|
||||
LN long names (L1 *long names / L0 8-3 conversion)
|
||||
K keep original links (e.g. http://www.adr/link) (K0 *relative link)
|
||||
o *generate output html file in case of error (404..) (o0 don't generate)
|
||||
o *save the server's error pages (404..) (o0 discard them)
|
||||
X *purge old files after update (X0 keep delete)
|
||||
bN accept cookies in cookies.txt (0=do not accept,* 1=accept)
|
||||
u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)
|
||||
@@ -473,11 +473,11 @@ store them with the same names used on the web site.
|
||||
URLs within this web site are adjusted to point to the files in the
|
||||
mirror.
|
||||
|
||||
<pre><b><i> o *generate output html file in case of error (404..) (o0 don't generate) </i></b></pre>
|
||||
<pre><b><i> o *save the server's error pages (404..) (o0 discard them) </i></b></pre>
|
||||
|
||||
<p align=justify> IF there are errors in downloading, create a file that
|
||||
indicates that the URL was not found. This makes browsing go a lot
|
||||
smoother.
|
||||
<p align=justify> IF a page cannot be downloaded, the error page the
|
||||
server sent is saved in its place, so a broken link still lands on the
|
||||
site's own 'not found' page. This makes browsing go a lot smoother.
|
||||
|
||||
<pre><b><i> X *purge old files after update (X0 keep delete) </i></b></pre>
|
||||
|
||||
@@ -1011,7 +1011,7 @@ Build options:
|
||||
LN long names (L1 *long names / L0 8-3 conversion)
|
||||
K keep original links (e.g. http://www.adr/link) (K0 *relative link)
|
||||
x replace external html links by error pages
|
||||
o *generate output html file in case of error (404..) (o0 don't generate)
|
||||
o *save the server's error pages (404..) (o0 discard them)
|
||||
X *purge old files after update (X0 keep delete)
|
||||
%x do not include any password for external password protected websites (%x0 include) (--no-passwords)
|
||||
%q *include query string for local files (information only) (%q0 don't include) (--include-query-string)
|
||||
@@ -1118,9 +1118,9 @@ deactivated byt his process.
|
||||
httrack http://www.shoesizes.com -O /tmp/shoesizes -x
|
||||
</i></b></pre>
|
||||
|
||||
<p align=justify> This option prevents the generation of '404' error
|
||||
files to replace files that were not found even though there were URLs
|
||||
pointing to them. It is useful for saving space as well as eliminating
|
||||
<p align=justify> This option keeps the server's '404' error pages out
|
||||
of the mirror, even though there were URLs pointing to the missing
|
||||
files. It is useful for saving space as well as eliminating
|
||||
unnecessary files in operations where a working web site is not the
|
||||
desired result.
|
||||
|
||||
|
||||
@@ -777,8 +777,8 @@ information purpose only) (%q0 don’t include)
|
||||
<td width="82%">
|
||||
|
||||
|
||||
<p>*generate output html file in case of error (404..) (o0
|
||||
don’t generate) (--generate-errors)</p></td></tr>
|
||||
<p>*save the server’s error pages (404..) (o0 discard
|
||||
them) (--generate-errors)</p></td></tr>
|
||||
<tr valign="top" align="left">
|
||||
<td width="9%"></td>
|
||||
<td width="4%">
|
||||
|
||||
@@ -122,7 +122,7 @@ h4 { margin: 0; font-weight: bold; font-size: 1.18em; }
|
||||
</small><br><br>
|
||||
<!-- -->
|
||||
<li>No error pages</li>
|
||||
<br><small>Do not generate error pages (if a 404 error occurred, for example)
|
||||
<br><small>Do not save the error pages sent by the server (if a 404 error occurred, for example)
|
||||
<br>If a page is missing on the remote site, there will not be any warning on the local site
|
||||
</small><br><br>
|
||||
<!-- -->
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
.\"
|
||||
.\" This file is generated by man/makeman.sh; do not edit by hand.
|
||||
.\" SPDX-License-Identifier: GPL-3.0-or-later
|
||||
.TH httrack 1 "26 July 2026" "httrack website copier"
|
||||
.TH httrack 1 "27 July 2026" "httrack website copier"
|
||||
.SH NAME
|
||||
httrack \- offline browser : copy websites to a local directory
|
||||
.SH SYNOPSIS
|
||||
@@ -220,7 +220,7 @@ do not include any password for external password protected websites (%x0 includ
|
||||
.IP \-%g
|
||||
strip query keys for dedup ([host/pattern=]key1,key2,...) (\-\-strip\-query <param>)
|
||||
.IP \-o
|
||||
*generate output html file in case of error (404..) (o0 don't generate) (\-\-generate\-errors)
|
||||
*save the server's error pages (404..) (o0 discard them) (\-\-generate\-errors)
|
||||
.IP \-X
|
||||
*purge old files after update (X0 keep delete) (\-\-purge\-old[=N])
|
||||
.IP \-%p
|
||||
|
||||
102
src/htsback.c
102
src/htsback.c
@@ -596,6 +596,41 @@ int back_nsoc_overall(const struct_back * sback) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Reserved subdirectory holding a mirrored file's temporaries, beside it. */
|
||||
#define HTS_TMPDIR "hts-tmp"
|
||||
|
||||
/* Build save's temporary as <dir>/hts-tmp/<name>.<ext>. Appending the extension
|
||||
to save instead put it in the mirror namespace, so a site serving <path>.bak
|
||||
had its copy taken as the backup and then unlinked (#774). HTS_FALSE (dest
|
||||
emptied) if it would not fit. Note: utf-8. */
|
||||
static hts_boolean back_tmpname(char *dest, size_t size, const char *save,
|
||||
const char *ext) {
|
||||
const char *const slash = strrchr(save, '/');
|
||||
const int dirlen = slash != NULL ? (int) (slash - save) + 1 : 0;
|
||||
|
||||
if (!slprintfbuff(dest, size, "%.*s" HTS_TMPDIR "/%s.%s", dirlen, save,
|
||||
slash != NULL ? slash + 1 : save, ext)) {
|
||||
dest[0] = '\0';
|
||||
return HTS_FALSE;
|
||||
}
|
||||
return HTS_TRUE;
|
||||
}
|
||||
|
||||
/* Note: utf-8 */
|
||||
void back_tmpdir_drop(const char *tmp) {
|
||||
char BIGSTK dir[HTS_URLMAXSIZE * 2];
|
||||
const char *slash;
|
||||
|
||||
if (tmp == NULL || (slash = strrchr(tmp, '/')) == NULL)
|
||||
return;
|
||||
if (!strclipbuff(dir, sizeof(dir), tmp))
|
||||
return;
|
||||
dir[slash - tmp] = '\0';
|
||||
slash = strrchr(dir, '/');
|
||||
if (strcmp(slash != NULL ? slash + 1 : dir, HTS_TMPDIR) == 0)
|
||||
(void) RMDIR(dir);
|
||||
}
|
||||
|
||||
/* generate temporary file on lien_back */
|
||||
/* Note: utf-8 */
|
||||
static int create_back_tmpfile(httrackp *opt, lien_back *const back,
|
||||
@@ -603,12 +638,10 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
|
||||
// do not use tempnam() but a regular filename
|
||||
back->tmpfile_buffer[0] = '\0';
|
||||
if (back->url_sav[0] != '\0') {
|
||||
/* same capacity as url_sav, so truncation drops the extension and aliases
|
||||
the temp name onto the live file that back_finalize_backup() UNLINKs */
|
||||
if (!sprintfbuff(back->tmpfile_buffer, "%s.%s", back->url_sav, ext)) {
|
||||
if (!back_tmpname(back->tmpfile_buffer, sizeof(back->tmpfile_buffer),
|
||||
back->url_sav, ext)) {
|
||||
hts_log_print(opt, LOG_WARNING, "temporary filename too long for %s",
|
||||
back->url_sav);
|
||||
back->tmpfile_buffer[0] = '\0';
|
||||
return -1;
|
||||
}
|
||||
back->tmpfile = back->tmpfile_buffer;
|
||||
@@ -646,7 +679,11 @@ void back_refetch_backup(httrackp *opt, lien_back *const back) {
|
||||
if (fexist_utf8(back->tmpfile))
|
||||
hts_log_print(opt, LOG_WARNING, "replacing leftover backup %s",
|
||||
back->tmpfile);
|
||||
saved = hts_rename_over(back->url_sav, back->tmpfile);
|
||||
saved = hts_rename_over(opt, back->url_sav, back->tmpfile);
|
||||
/* Another slot sharing the directory may have removed it between the
|
||||
structcheck above and the rename: recreate it and try once more. */
|
||||
if (!saved && structcheck(back->tmpfile) == 0)
|
||||
saved = hts_rename_over(opt, back->url_sav, back->tmpfile);
|
||||
}
|
||||
if (!saved) {
|
||||
hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
|
||||
@@ -670,16 +707,22 @@ static hts_boolean back_transfer_failed(const int statuscode) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
transfer leaves the previous copy intact. Skips the zlib .z temp. */
|
||||
static void back_finalize_backup(httrackp *opt, lien_back *const back,
|
||||
const hts_boolean commit) {
|
||||
hts_boolean back_finalize_backup(httrackp *opt, lien_back *const back,
|
||||
hts_boolean commit) {
|
||||
const hts_boolean wanted = commit;
|
||||
|
||||
if (back->tmpfile == NULL || back->r.compressed)
|
||||
return;
|
||||
return HTS_TRUE;
|
||||
/* Nothing to commit to: filecreate() can fail after the backup was taken,
|
||||
and dropping it then loses both copies (#775). */
|
||||
if (commit && !fexist_utf8(back->url_sav)) {
|
||||
hts_log_print(opt, LOG_WARNING, "%s was never created; restoring %s",
|
||||
back->url_sav, back->tmpfile);
|
||||
commit = HTS_FALSE;
|
||||
}
|
||||
if (commit) {
|
||||
(void) UNLINK(back->tmpfile); /* new copy is good; drop the backup */
|
||||
back_tmpdir_drop(back->tmpfile);
|
||||
} else {
|
||||
if (back->r.out != NULL) {
|
||||
fclose(back->r.out);
|
||||
@@ -687,12 +730,15 @@ static void back_finalize_backup(httrackp *opt, lien_back *const back,
|
||||
}
|
||||
/* On failure keep the backup: an orphaned temp beats losing the good copy.
|
||||
*/
|
||||
if (!hts_rename_over(back->tmpfile, back->url_sav))
|
||||
if (!hts_rename_over(opt, back->tmpfile, back->url_sav))
|
||||
hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
|
||||
"could not restore %s; previous copy kept as %s",
|
||||
back->url_sav, back->tmpfile);
|
||||
else
|
||||
back_tmpdir_drop(back->tmpfile);
|
||||
}
|
||||
back->tmpfile = NULL;
|
||||
return commit == wanted ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
|
||||
// objet (lien) téléchargé ou transféré depuis le cache
|
||||
@@ -794,12 +840,21 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
hts_codec_parse(back[p].r.contentencoding);
|
||||
/* Never decode over url_sav: a failed decode would destroy the
|
||||
copy an --update re-fetch is supposed to refresh (#557). */
|
||||
char BIGSTK unpacked[HTS_URLMAXSIZE * 2 + 4]; // room for ".u"
|
||||
char BIGSTK unpacked[HTS_URLMAXSIZE * 2];
|
||||
LLint size;
|
||||
|
||||
snprintf(unpacked, sizeof(unpacked), "%s.u", back[p].url_sav);
|
||||
if ((size = hts_codec_unpack(codec, back[p].tmpfile,
|
||||
unpacked)) >= 0) {
|
||||
/* fits whenever the .z temp it decodes from did */
|
||||
if (!back_tmpname(unpacked, sizeof(unpacked), back[p].url_sav,
|
||||
"u")) {
|
||||
back[p].r.statuscode = STATUSCODE_INVALID;
|
||||
strcpybuff(back[p].r.msg, "Error when decompressing (the "
|
||||
"temporary filename is too long)");
|
||||
/* as the decode-failure branch below: never let the coded
|
||||
bytes be committed as the page */
|
||||
if (!back[p].r.is_write)
|
||||
deleteaddr(&back[p].r);
|
||||
} else if ((size = hts_codec_unpack(codec, back[p].tmpfile,
|
||||
unpacked)) >= 0) {
|
||||
back[p].r.size = back[p].r.totalsize = size;
|
||||
if (back[p].r.is_write) {
|
||||
/* Sample the previous copy now: the rename below replaces
|
||||
@@ -818,7 +873,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
"Read error when decompressing");
|
||||
}
|
||||
UNLINK(unpacked);
|
||||
} else if (hts_rename_over(unpacked, back[p].url_sav)) {
|
||||
} else if (hts_rename_over(opt, unpacked, back[p].url_sav)) {
|
||||
/* The temp bypassed filecreate(), which is what chmods. */
|
||||
#ifndef _WIN32
|
||||
chmod(back[p].url_sav, HTS_ACCESS_FILE);
|
||||
@@ -871,6 +926,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
/* ensure that no remaining temporary file exists */
|
||||
if (back[p].tmpfile != NULL) {
|
||||
unlink(back[p].tmpfile);
|
||||
back_tmpdir_drop(back[p].tmpfile); /* the .u went with it */
|
||||
back[p].tmpfile = NULL;
|
||||
}
|
||||
}
|
||||
@@ -883,7 +939,14 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
}
|
||||
/* Body fully received: keep the freshly written url_sav, drop the
|
||||
backup of the previous copy. */
|
||||
back_finalize_backup(opt, &back[p], HTS_TRUE);
|
||||
if (!back_finalize_backup(opt, &back[p], HTS_TRUE)) {
|
||||
/* The previous copy is back because the new one was never created;
|
||||
caching this response's validators against it would pin the stale
|
||||
body on every later --update. */
|
||||
if (fexist_utf8(back[p].url_sav))
|
||||
filenote(&opt->state.strc, back[p].url_sav, NULL);
|
||||
return -1;
|
||||
}
|
||||
/* Write mode to disk */
|
||||
if (back[p].r.is_write && back[p].r.adr != NULL) {
|
||||
freet(back[p].r.adr);
|
||||
@@ -1719,6 +1782,7 @@ int back_clear_entry(lien_back * back) {
|
||||
// only for security
|
||||
if (back->tmpfile && back->tmpfile[0] != '\0') {
|
||||
(void) unlink(back->tmpfile);
|
||||
back_tmpdir_drop(back->tmpfile);
|
||||
back->tmpfile = NULL;
|
||||
}
|
||||
// headers
|
||||
|
||||
@@ -143,6 +143,18 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
can put it back when the re-fetch fails (#77 follow-up). Call right before
|
||||
truncating url_sav; tmpfile stays NULL when there is nothing to save. */
|
||||
void back_refetch_backup(httrackp *opt, lien_back *const back);
|
||||
/* 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, unless url_sav was never
|
||||
created; else restore it so an aborted transfer leaves the previous copy
|
||||
intact. Skips the zlib .z temp. HTS_FALSE when a requested commit had to
|
||||
restore instead: the caller then holds the OLD body and must not cache this
|
||||
response's validators against it. */
|
||||
hts_boolean back_finalize_backup(httrackp *opt, lien_back *const back,
|
||||
hts_boolean commit);
|
||||
/* Remove the reserved directory a temporary sat in, once the last slot sharing
|
||||
it is done; a non-empty one just refuses. No-op outside that directory. */
|
||||
void back_tmpdir_drop(const char *tmp);
|
||||
/* -#test=backswap: slots eligible for the on-disk ready table. */
|
||||
int back_selftest_slot_swap(void);
|
||||
void back_info(struct_back * sback, int i, int j, FILE * fp);
|
||||
|
||||
@@ -957,6 +957,35 @@ htsblk *cache_header(httrackp * opt, cache_back * cache, const char *adr,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *cache_repair(httrackp *opt, const char *name,
|
||||
unsigned long *entries, unsigned long *bytes) {
|
||||
char BIGSTK repairname[HTS_URLMAXSIZE * 2];
|
||||
unzFile zip;
|
||||
|
||||
*entries = 0;
|
||||
*bytes = 0;
|
||||
if (!slprintfbuff(repairname, sizeof(repairname), "%s%s",
|
||||
StringBuff(opt->path_log), "hts-cache/repair.zip"))
|
||||
return "the repair path is too long";
|
||||
if (unzRepair(name, repairname,
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log), "hts-cache/repair.tmp"),
|
||||
entries, bytes) != Z_OK)
|
||||
return "could not repair the cache";
|
||||
/* unzRepair writes an end-of-central-directory record whatever it found, so
|
||||
an input holding no local file header at all yields a valid empty archive
|
||||
and a short write yields a truncated one. Only an archive that holds
|
||||
something and opens may replace the cache (#824). */
|
||||
if (*entries == 0)
|
||||
return "the repaired cache holds no entry, keeping the damaged one";
|
||||
if ((zip = hts_unzOpen_utf8(repairname)) == NULL)
|
||||
return "the repaired cache does not open, keeping the damaged one";
|
||||
unzClose(zip);
|
||||
if (!hts_rename_over(opt, repairname, name))
|
||||
return "could not put the repaired cache in place";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Initialisation du cache: créer nouveau, renomer ancien, charger..
|
||||
void cache_init(cache_back * cache, httrackp * opt) {
|
||||
// ---
|
||||
@@ -985,6 +1014,7 @@ void cache_init(cache_back * cache, httrackp * opt) {
|
||||
StringBuff(opt->path_log),
|
||||
"hts-cache/new.zip")))) { // a previous cache exists.. rename it
|
||||
if (!hts_rename_over(
|
||||
opt,
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log), "hts-cache/new.zip"),
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
@@ -1025,8 +1055,9 @@ void cache_init(cache_back * cache, httrackp * opt) {
|
||||
// Corrupted ZIP file ? Try to repair!
|
||||
if (cache->zipInput == NULL && !cache->ro) {
|
||||
char *name;
|
||||
uLong repaired = 0;
|
||||
uLong repairedBytes = 0;
|
||||
const char *why;
|
||||
unsigned long repaired = 0;
|
||||
unsigned long repairedBytes = 0;
|
||||
|
||||
if (!cache->ro) {
|
||||
name =
|
||||
@@ -1039,25 +1070,16 @@ void cache_init(cache_back * cache, httrackp * opt) {
|
||||
}
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Cache: damaged cache, trying to repair");
|
||||
/* mztools has no UTF-8 hook, so repairing a corrupt cache under a
|
||||
non-ASCII path_log fails cleanly (re-crawl), never forks a twin. */
|
||||
if (unzRepair
|
||||
(name,
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
|
||||
"hts-cache/repair.zip"), fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log),
|
||||
"hts-cache/repair.tmp"),
|
||||
&repaired, &repairedBytes) == Z_OK) {
|
||||
UNLINK(name);
|
||||
RENAME(fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log), "hts-cache/repair.zip"),
|
||||
name);
|
||||
cache->zipInput = hts_unzOpen_utf8(name);
|
||||
why = cache_repair(opt, name, &repaired, &repairedBytes);
|
||||
if (why != NULL) {
|
||||
hts_log_print(opt, LOG_WARNING | LOG_ERRNO, "Cache: %s", why);
|
||||
} else if ((cache->zipInput = hts_unzOpen_utf8(name)) != NULL) {
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Cache: %d bytes successfully recovered in %d entries",
|
||||
(int) repairedBytes, (int) repaired);
|
||||
} else {
|
||||
hts_log_print(opt, LOG_WARNING, "Cache: could not repair the cache");
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Cache: the repaired cache could not be reopened");
|
||||
}
|
||||
}
|
||||
// Opened ?
|
||||
|
||||
@@ -78,6 +78,14 @@ htsblk *cache_header(httrackp * opt, cache_back * cache, const char *adr,
|
||||
const char *fil, htsblk * r);
|
||||
void cache_init(cache_back * cache, httrackp * opt);
|
||||
|
||||
/* Recover the damaged cache at name into hts-cache/repair.zip and move it over
|
||||
name, storing what was recovered in *entries and *bytes. Returns NULL on
|
||||
success, else a reason the caller reports: a recovery that is empty or does
|
||||
not open never replaces the cache, and neither does one that cannot be moved
|
||||
into place (#786, #824). Note: utf-8. */
|
||||
const char *cache_repair(httrackp *opt, const char *name,
|
||||
unsigned long *entries, unsigned long *bytes);
|
||||
|
||||
/* Which hts-cache/ generation (new.* vs old.*) is authoritative. */
|
||||
typedef enum {
|
||||
CACHE_RECONCILE_PROMOTE, /* no new cache: promote the old generation */
|
||||
|
||||
@@ -2160,8 +2160,9 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
case 'R':
|
||||
{
|
||||
char *name;
|
||||
uLong repaired = 0;
|
||||
uLong repairedBytes = 0;
|
||||
const char *why;
|
||||
unsigned long repaired = 0;
|
||||
unsigned long repairedBytes = 0;
|
||||
|
||||
if (fexist_utf8(fconcat(
|
||||
OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
@@ -2184,24 +2185,15 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "Cache: trying to repair %s\n", name);
|
||||
if (unzRepair
|
||||
(name,
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
|
||||
"hts-cache/repair.zip"),
|
||||
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
|
||||
"hts-cache/repair.tmp"), &repaired,
|
||||
&repairedBytes) == Z_OK) {
|
||||
UNLINK(name);
|
||||
RENAME(fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||
StringBuff(opt->path_log),
|
||||
"hts-cache/repair.zip"),
|
||||
name);
|
||||
fprintf(stderr,
|
||||
"Cache: %d bytes successfully recovered in %d entries\n",
|
||||
(int) repairedBytes, (int) repaired);
|
||||
} else {
|
||||
fprintf(stderr, "Cache: could not repair the cache\n");
|
||||
why = cache_repair(opt, name, &repaired, &repairedBytes);
|
||||
if (why != NULL) {
|
||||
fprintf(stderr, "Cache: %s\n", why);
|
||||
return 1;
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"Cache: %d bytes successfully recovered in %d entries\n",
|
||||
(int) repairedBytes, (int) repaired);
|
||||
}
|
||||
return 0;
|
||||
break;
|
||||
|
||||
16
src/htsftp.c
16
src/htsftp.c
@@ -483,16 +483,19 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
back->r.totalsize = size;
|
||||
}
|
||||
}
|
||||
// REST?
|
||||
if (fexist(back->url_sav) && (transfer_list == 0)) {
|
||||
/* Only over a copy back_add() judged partial: on --update every
|
||||
mirrored file exists, and resuming a complete one splices the
|
||||
old body into the new (#798). */
|
||||
if (back->range_req_size > 0 && (transfer_list == 0)) {
|
||||
strcpybuff(back->info, "rest");
|
||||
snprintf(line, sizeof(line), "REST " LLintP, (LLint) fsize(back->url_sav));
|
||||
snprintf(line, sizeof(line), "REST " LLintP,
|
||||
(LLint) back->range_req_size);
|
||||
send_line(soc_ctl, line);
|
||||
get_ftp_line(soc_ctl, line, sizeof(line), timeout);
|
||||
_CHECK_HALT_FTP;
|
||||
if ((line[0] == '3') || (line[0] == '2')) { // ok
|
||||
rest_understood = 1;
|
||||
} // sinon tant pis
|
||||
} // else never mind
|
||||
}
|
||||
} // sinon tant pis
|
||||
}
|
||||
@@ -617,9 +620,12 @@ int run_launch_ftp(FTPDownloadStruct * pStruct) {
|
||||
// Ok, connexion initiée
|
||||
//
|
||||
if (soc_dat != INVALID_SOCKET) {
|
||||
if (rest_understood) { // REST envoyée et comprise
|
||||
if (rest_understood) { // REST sent and understood
|
||||
file_notify(opt, back->url_adr, back->url_fil, back->url_sav, 0, 1,
|
||||
0);
|
||||
/* The bytes already on disk count too, or the completeness check
|
||||
below rejects every resumed transfer (#798). */
|
||||
back->r.size = back->range_req_size;
|
||||
back->r.fp = fileappend(&opt->state.strc, back->url_sav);
|
||||
} else {
|
||||
file_notify(opt, back->url_adr, back->url_fil, back->url_sav, 1, 1,
|
||||
|
||||
@@ -558,8 +558,7 @@ void help(const char *app, int more) {
|
||||
infomsg
|
||||
(" %q *include query string for local files (useless, for information purpose only) (%q0 don't include)");
|
||||
infomsg(" %g strip query keys for dedup ([host/pattern=]key1,key2,...)");
|
||||
infomsg
|
||||
(" o *generate output html file in case of error (404..) (o0 don't generate)");
|
||||
infomsg(" o *save the server's error pages (404..) (o0 discard them)");
|
||||
infomsg(" X *purge old files after update (X0 keep delete)");
|
||||
infomsg(" %p preserve html files 'as is' (identical to '-K4 -%F \"\"')");
|
||||
infomsg(" %T links conversion to UTF-8");
|
||||
|
||||
@@ -5790,7 +5790,8 @@ HTSEXT_API void hts_log_vprint(httrackp * opt, int type, const char *format, va_
|
||||
if (hts_log_print_callback != NULL) {
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
hts_log_print_callback(opt, type, format, args);
|
||||
/* the copy, so the vfprintf() below still has an unread list */
|
||||
hts_log_print_callback(opt, type, format, args_copy);
|
||||
va_end(args_copy);
|
||||
}
|
||||
if (opt != NULL && opt->log != NULL) {
|
||||
|
||||
@@ -74,6 +74,41 @@ static const char *hts_tbdev[] = {
|
||||
""
|
||||
};
|
||||
|
||||
/* Directories the engine owns inside the mirror. A URL able to name one lands
|
||||
on the cache or on another slot's temporary and destroys it (#774), so they
|
||||
are escaped like the DOS devices are. */
|
||||
static const char *hts_tbreserved[] = {"/hts-cache", "/hts-tmp", ""};
|
||||
|
||||
/* Replace /foo/<reserved>/bar by /foo/<reserved>_/bar, matching a whole path
|
||||
component only (case-insensitively: the filesystem may be too). */
|
||||
static void escapeReservedNames(char *save, size_t size,
|
||||
const char *const *names) {
|
||||
int i;
|
||||
|
||||
for (i = 0; names[i][0] != '\0'; i++) {
|
||||
const char *a = save;
|
||||
const size_t len = strlen(names[i]);
|
||||
|
||||
while ((a = strstrcase(a, names[i]))) {
|
||||
switch ((int) a[len]) {
|
||||
case '\0':
|
||||
case '/':
|
||||
case '.': {
|
||||
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
|
||||
|
||||
tempo[0] = '\0';
|
||||
strncatbuff(tempo, save, (int) (a - save) + (int) len);
|
||||
strcatbuff(tempo, "_");
|
||||
strcatbuff(tempo, a + len);
|
||||
/* clip rather than abort: the name comes from the wire */
|
||||
(void) strclipbuff(save, size, tempo);
|
||||
} break;
|
||||
}
|
||||
a += len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Strip all // */
|
||||
static void cleanDoubleSlash(char *s) {
|
||||
int i, j;
|
||||
@@ -1369,35 +1404,12 @@ int url_savename(lien_adrfilsave *const afs,
|
||||
// éliminer les // (comme ftp://)
|
||||
cleanDoubleSlash(afs->save);
|
||||
|
||||
/* Runs on every platform, and before path_html is prepended below, so the
|
||||
user's own output directory is never renamed. */
|
||||
escapeReservedNames(afs->save, sizeof(afs->save), hts_tbreserved);
|
||||
|
||||
#if HTS_OVERRIDE_DOS_FOLDERS
|
||||
/* Replace /foo/nul/bar by /foo/nul_/bar */
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while(hts_tbdev[i][0]) {
|
||||
const char *a = afs->save;
|
||||
|
||||
while((a = strstrcase(a, hts_tbdev[i]))) {
|
||||
switch ((int) a[strlen(hts_tbdev[i])]) {
|
||||
case '\0':
|
||||
case '/':
|
||||
case '.':
|
||||
{
|
||||
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
|
||||
|
||||
tempo[0] = '\0';
|
||||
strncatbuff(tempo, afs->save, (int) (a - afs->save) + strlen(hts_tbdev[i]));
|
||||
strcatbuff(tempo, "_");
|
||||
strcatbuff(tempo, a + strlen(hts_tbdev[i]));
|
||||
strcpybuff(afs->save, tempo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
a += strlen(hts_tbdev[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
escapeReservedNames(afs->save, sizeof(afs->save), hts_tbdev);
|
||||
|
||||
/* Strip ending . or ' ' forbidden on windoz */
|
||||
cleanEndingSpaceOrDot(afs->save);
|
||||
|
||||
@@ -2622,6 +2622,71 @@ static int st_savename(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char st_log_callback_seen[256];
|
||||
|
||||
static void st_log_callback(httrackp *opt, int type, const char *format,
|
||||
va_list args) {
|
||||
(void) opt;
|
||||
(void) type;
|
||||
(void) vsnprintf(st_log_callback_seen, sizeof(st_log_callback_seen), format,
|
||||
args);
|
||||
}
|
||||
|
||||
/* The callback must not consume the va_list the log file's vfprintf() needs. */
|
||||
static int st_logcallback(httrackp *opt, int argc, char **argv) {
|
||||
static const char want[] = "42 sentinel";
|
||||
static const char want_filtered[] = "7 filtered";
|
||||
char BIGSTK seen[sizeof(st_log_callback_seen)];
|
||||
char BIGSTK line[256];
|
||||
FILE *fp;
|
||||
int rc = 1;
|
||||
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
fp = tmpfile();
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "logcallback: tmpfile() failed\n");
|
||||
return 1;
|
||||
}
|
||||
opt->log = fp;
|
||||
opt->debug = LOG_NOTICE;
|
||||
st_log_callback_seen[0] = '\0';
|
||||
hts_set_log_vprint_callback(st_log_callback);
|
||||
hts_log_print(opt, LOG_NOTICE, "%d %s", 42, "sentinel");
|
||||
hts_set_log_vprint_callback(NULL);
|
||||
opt->log = NULL;
|
||||
strcpybuff(seen, st_log_callback_seen);
|
||||
|
||||
rewind(fp);
|
||||
line[0] = '\0';
|
||||
(void) fgets(line, (int) sizeof(line), fp);
|
||||
fclose(fp);
|
||||
|
||||
/* The callback runs above the level filter and without a log file at all;
|
||||
the front-ends that install one usually have no opt->log open. */
|
||||
st_log_callback_seen[0] = '\0';
|
||||
hts_set_log_vprint_callback(st_log_callback);
|
||||
hts_log_print(opt, LOG_DEBUG, "%d %s", 7, "filtered");
|
||||
hts_set_log_vprint_callback(NULL);
|
||||
|
||||
/* Same arguments both ways; the file line carries a level prefix. */
|
||||
if (strcmp(seen, want) != 0)
|
||||
fprintf(stderr, "logcallback: callback got '%s' want '%s'\n", seen, want);
|
||||
else if (strstr(line, want) == NULL)
|
||||
fprintf(stderr, "logcallback: log file got '%s' want it to carry '%s'\n",
|
||||
line, want);
|
||||
else if (strcmp(st_log_callback_seen, want_filtered) != 0)
|
||||
fprintf(stderr, "logcallback: unfiltered callback got '%s' want '%s'\n",
|
||||
st_log_callback_seen, want_filtered);
|
||||
else
|
||||
rc = 0;
|
||||
|
||||
if (rc == 0)
|
||||
printf("logcallback self-test OK\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* an empty fil started htsAddLink's codebase walk before the buffer (#730) */
|
||||
static int st_addlink(httrackp *opt, int argc, char **argv) {
|
||||
htsmoduleStruct BIGSTK str;
|
||||
@@ -6072,10 +6137,9 @@ static hts_boolean ro_is(const char *path, const char *data) {
|
||||
}
|
||||
|
||||
// -#test=renameover <dir>: hts_rename_over() must replace an existing dst and
|
||||
// never delete one it did not replace (#779). Which half is live depends on
|
||||
// never lose one it did not replace (#779, #790). Which half is live depends on
|
||||
// what rename() does to an existing target, so probe that and name the regime.
|
||||
static int st_renameover(httrackp *opt, int argc, char **argv) {
|
||||
(void) opt;
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "renameover: needs a writable base dir\n");
|
||||
return 1;
|
||||
@@ -6104,7 +6168,7 @@ static int st_renameover(httrackp *opt, int argc, char **argv) {
|
||||
ro_put(dst, "old");
|
||||
if (replaceable) {
|
||||
/* An existing dst must still be replaced: the unlink is for this. */
|
||||
if (!hts_rename_over(src, dst)) {
|
||||
if (!hts_rename_over(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: replacing an existing dst failed: %s\n",
|
||||
strerror(errno));
|
||||
err++;
|
||||
@@ -6114,7 +6178,7 @@ static int st_renameover(httrackp *opt, int argc, char **argv) {
|
||||
}
|
||||
} else {
|
||||
/* A failure the unlink cannot fix must leave dst as it was. */
|
||||
if (hts_rename_over(src, dst)) {
|
||||
if (hts_rename_over(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: an unfixable failure reported success\n");
|
||||
err++;
|
||||
}
|
||||
@@ -6124,10 +6188,36 @@ static int st_renameover(httrackp *opt, int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/* A directory in the way is not something the caller asked to replace: it
|
||||
must be refused, never parked aside and orphaned. */
|
||||
(void) UNLINK(dst);
|
||||
ro_put(src, "new");
|
||||
if (MKDIR(dst) == 0) {
|
||||
char parked[sizeof(dst) + 16];
|
||||
|
||||
snprintf(parked, sizeof(parked), "%s.hts-old0", dst);
|
||||
if (hts_rename_over(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: a directory at dst reported success\n");
|
||||
err++;
|
||||
}
|
||||
if (!ro_is(src, "new")) {
|
||||
fprintf(stderr, "renameover: a directory at dst consumed src\n");
|
||||
err++;
|
||||
}
|
||||
/* RMDIR only succeeds on a directory that is there, so it doubles as the
|
||||
probe: the parked name must not exist at all. */
|
||||
if (RMDIR(parked) == 0 || fexist_utf8(parked)) {
|
||||
fprintf(stderr, "renameover: a directory at dst was parked aside\n");
|
||||
err++;
|
||||
}
|
||||
(void) RMDIR(dst);
|
||||
}
|
||||
(void) UNLINK(src);
|
||||
|
||||
/* A missing src must leave dst alone and report failure. */
|
||||
(void) UNLINK(src);
|
||||
ro_put(dst, "keep");
|
||||
if (hts_rename_over(src, dst)) {
|
||||
if (hts_rename_over(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: a missing src reported success\n");
|
||||
err++;
|
||||
}
|
||||
@@ -6138,16 +6228,177 @@ static int st_renameover(httrackp *opt, int argc, char **argv) {
|
||||
|
||||
/* Same, with dst absent too: nothing to lose, still a failure. */
|
||||
(void) UNLINK(dst);
|
||||
if (hts_rename_over(src, dst)) {
|
||||
if (hts_rename_over(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: a missing src and dst reported success\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* The aside fallback, driven directly: a clobbering rename() never reaches
|
||||
it. Skipped in the refused regime, where no rename at all succeeds. */
|
||||
if (replaceable) {
|
||||
char aside[sizeof(dst) + 16], keep[sizeof(dst) + 16];
|
||||
|
||||
snprintf(aside, sizeof(aside), "%s.hts-old0", dst);
|
||||
snprintf(keep, sizeof(keep), "%s.hts-old1", dst);
|
||||
(void) UNLINK(aside);
|
||||
(void) UNLINK(keep);
|
||||
ro_put(src, "new");
|
||||
ro_put(dst, "old");
|
||||
if (!hts_rename_over_aside_selftest(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: the aside fallback failed: %s\n",
|
||||
strerror(errno));
|
||||
err++;
|
||||
} else if (!ro_is(dst, "new") || fexist_utf8(src) || fexist_utf8(aside)) {
|
||||
fprintf(stderr, "renameover: the aside fallback did not replace dst\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* #790: the retry fails (no src). The old content must survive, back at dst
|
||||
or, when the move back fails too, under the parked name it is logged as.
|
||||
Name the outcome so a leg cannot pass having tested the other one. */
|
||||
(void) UNLINK(src);
|
||||
ro_put(dst, "old");
|
||||
if (hts_rename_over_aside_selftest(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: a failed aside retry reported success\n");
|
||||
err++;
|
||||
}
|
||||
if (ro_is(dst, "old") && !fexist_utf8(aside)) {
|
||||
printf("renameover: restore back\n");
|
||||
} else if (ro_is(aside, "old") && !fexist_utf8(dst)) {
|
||||
printf("renameover: restore parked\n");
|
||||
(void) UNLINK(aside);
|
||||
ro_put(dst, "old");
|
||||
} else {
|
||||
fprintf(stderr, "renameover: a failed aside retry lost the old copy\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* An unrelated file already sitting on the aside name must survive. */
|
||||
ro_put(src, "new");
|
||||
ro_put(aside, "mine");
|
||||
if (!hts_rename_over_aside_selftest(opt, src, dst)) {
|
||||
fprintf(stderr, "renameover: a taken aside name failed the move: %s\n",
|
||||
strerror(errno));
|
||||
err++;
|
||||
} else if (!ro_is(dst, "new") || !ro_is(aside, "mine") ||
|
||||
fexist_utf8(keep)) {
|
||||
fprintf(stderr, "renameover: a taken aside name was not skipped\n");
|
||||
err++;
|
||||
}
|
||||
(void) UNLINK(aside);
|
||||
(void) UNLINK(keep);
|
||||
|
||||
/* A directory there reads as free to the probe, so the park must skip it
|
||||
on the refusal rather than give up. */
|
||||
ro_put(src, "new");
|
||||
ro_put(dst, "old");
|
||||
if (MKDIR(aside) == 0) {
|
||||
if (!hts_rename_over_aside_selftest(opt, src, dst)) {
|
||||
fprintf(stderr,
|
||||
"renameover: a directory on the aside name blocked the "
|
||||
"move: %s\n",
|
||||
strerror(errno));
|
||||
err++;
|
||||
} else if (!ro_is(dst, "new") || fexist_utf8(keep)) {
|
||||
fprintf(stderr, "renameover: a directory on the aside name was not "
|
||||
"skipped\n");
|
||||
err++;
|
||||
}
|
||||
(void) RMDIR(aside);
|
||||
}
|
||||
(void) UNLINK(keep);
|
||||
}
|
||||
|
||||
(void) UNLINK(src);
|
||||
(void) UNLINK(dst);
|
||||
printf("renameover: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
// -#test=refetchbackup <dir>: the #77 re-fetch backup must build its temporary
|
||||
// inside the reserved hts-tmp directory, whose segment url_savename escapes so
|
||||
// no mirrored file can ever sit there (#774), and must never leave the resource
|
||||
// without a copy (#775).
|
||||
static int st_refetchbackup(httrackp *opt, int argc, char **argv) {
|
||||
lien_back *back;
|
||||
char want[HTS_URLMAXSIZE * 2 + 32];
|
||||
int err = 0;
|
||||
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "refetchbackup: needs a writable base dir\n");
|
||||
return 1;
|
||||
}
|
||||
back = calloct(1, sizeof(lien_back));
|
||||
if (back == NULL) {
|
||||
fprintf(stderr, "refetchbackup: out of memory\n");
|
||||
return 1;
|
||||
}
|
||||
/* explicit separator: fconcat() joins without one, which would put the
|
||||
temporary in the parent of the directory under test */
|
||||
snprintf(back->url_sav, sizeof(back->url_sav), "%s/refetch.bin", argv[0]);
|
||||
snprintf(want, sizeof(want), "%s/hts-tmp/refetch.bin.bak", argv[0]);
|
||||
|
||||
/* #774: pin the name, so moving the temporary out of the reserved directory
|
||||
cannot pass without url_savename reserving wherever it went instead. */
|
||||
ro_put(back->url_sav, "old");
|
||||
back_refetch_backup(opt, back);
|
||||
if (back->tmpfile == NULL || fexist_utf8(back->url_sav)) {
|
||||
fprintf(stderr, "refetchbackup: the previous copy was not moved aside\n");
|
||||
err++;
|
||||
} else if (strcmp(back->tmpfile, want) != 0) {
|
||||
fprintf(stderr, "refetchbackup: temporary is %s, want %s\n", back->tmpfile,
|
||||
want);
|
||||
err++;
|
||||
}
|
||||
ro_put(back->url_sav, "new"); /* what filecreate() + the transfer produce */
|
||||
back_finalize_backup(opt, back, HTS_TRUE);
|
||||
if (!ro_is(back->url_sav, "new")) {
|
||||
fprintf(stderr, "refetchbackup: the committed copy is not the new one\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* #758: only a killed run can leave something there, and it must be replaced
|
||||
rather than disable the backup for good. */
|
||||
if (structcheck(want) != 0) {
|
||||
fprintf(stderr, "refetchbackup: cannot create %s\n", want);
|
||||
freet(back);
|
||||
return 1;
|
||||
}
|
||||
ro_put(want, "leftover");
|
||||
back_refetch_backup(opt, back);
|
||||
if (back->tmpfile == NULL || !ro_is(want, "new")) {
|
||||
fprintf(stderr, "refetchbackup: a leftover temporary blocked the backup\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* #775: filecreate() failed, so there is nothing to commit to. Saying so is
|
||||
load-bearing: the caller must not cache this response against the old
|
||||
body, or the next --update gets a 304 pinning it. */
|
||||
(void) UNLINK(back->url_sav);
|
||||
if (back_finalize_backup(opt, back, HTS_TRUE)) {
|
||||
fprintf(stderr, "refetchbackup: a commit that restored reported success\n");
|
||||
err++;
|
||||
}
|
||||
if (!ro_is(back->url_sav, "new")) {
|
||||
fprintf(stderr, "refetchbackup: a commit with no new copy lost both\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* An aborted transfer restores, as before. */
|
||||
back_refetch_backup(opt, back);
|
||||
ro_put(back->url_sav, "partial");
|
||||
back_finalize_backup(opt, back, HTS_FALSE);
|
||||
if (!ro_is(back->url_sav, "new")) {
|
||||
fprintf(stderr, "refetchbackup: an aborted re-fetch kept the partial\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
(void) UNLINK(back->url_sav);
|
||||
freet(back);
|
||||
printf("refetchbackup: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
// -#test=direnum <dir>: enumerate a long+non-ASCII directory via the
|
||||
// opendir/readdir wrappers; children must round-trip as UTF-8 (#133,#630).
|
||||
static int st_direnum(httrackp *opt, int argc, char **argv) {
|
||||
@@ -6824,6 +7075,8 @@ static const struct selftest_entry {
|
||||
st_growsize},
|
||||
{"addlink", "", "htsAddLink codebase walk over an empty current path",
|
||||
st_addlink},
|
||||
{"logcallback", "", "log callback must not consume the log file's va_list",
|
||||
st_logcallback},
|
||||
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
|
||||
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
|
||||
st_cacheindex},
|
||||
@@ -6896,6 +7149,9 @@ static const struct selftest_entry {
|
||||
"hts_rename_over(): replace dst, but never delete a dst it did not "
|
||||
"replace",
|
||||
st_renameover},
|
||||
{"refetchbackup", "<dir>",
|
||||
"the re-fetch backup always leaves a copy, and stays out of the mirror",
|
||||
st_refetchbackup},
|
||||
{"direnum", "<dir>",
|
||||
"enumerate a long+non-ASCII directory through opendir/readdir",
|
||||
st_direnum},
|
||||
|
||||
@@ -1109,7 +1109,7 @@ hts_boolean singlefile_rewrite_file(httrackp *opt, const char *root,
|
||||
HTS_ACCESS_FILE);
|
||||
#endif
|
||||
if (ok)
|
||||
ok = hts_rename_over(StringBuff(tmp), page_path);
|
||||
ok = hts_rename_over(opt, StringBuff(tmp), page_path);
|
||||
if (!ok) {
|
||||
hts_log_print(opt, LOG_ERROR, "single-file: could not rewrite %s",
|
||||
page_path);
|
||||
|
||||
@@ -1438,21 +1438,75 @@ HTSEXT_API hts_boolean hts_findissystem(find_handle find) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
hts_boolean hts_rename_over(const char *src, const char *dst) {
|
||||
/* Park cdst under a free sibling name; caside receives it. */
|
||||
static hts_boolean rename_park_aside(char *caside, size_t size,
|
||||
const char *cdst) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (!slprintfbuff(caside, size, "%s.hts-old%d", cdst, i))
|
||||
return HTS_FALSE;
|
||||
/* Skip a name the mirror already holds: POSIX rename() would clobber it
|
||||
(#774). A non-regular entry reads as free and the rename refuses it. */
|
||||
if (fexist_utf8(caside))
|
||||
continue;
|
||||
if (RENAME(cdst, caside) == 0)
|
||||
return HTS_TRUE;
|
||||
}
|
||||
return HTS_FALSE;
|
||||
}
|
||||
|
||||
/* cdst is in the way of the move: park it, retry, and put it back if the retry
|
||||
fails too. Unlinking it instead would leave nothing at all (#790). */
|
||||
static hts_boolean rename_over_aside(httrackp *opt, const char *csrc,
|
||||
const char *cdst) {
|
||||
char caside[CATBUFF_SIZE];
|
||||
int err;
|
||||
|
||||
/* Only a regular file may be parked: a directory in the way is not what the
|
||||
caller asked to replace, and parking it orphans it (UNLINK cannot drop). */
|
||||
if (!fexist_utf8(cdst))
|
||||
return HTS_FALSE;
|
||||
if (!rename_park_aside(caside, sizeof(caside), cdst))
|
||||
return HTS_FALSE;
|
||||
if (RENAME(csrc, cdst) == 0) {
|
||||
(void) UNLINK(caside);
|
||||
return HTS_TRUE;
|
||||
}
|
||||
err = errno;
|
||||
/* Retry once, then name the parked copy: nothing else on disk or in the log
|
||||
points at it, and an --update purge would delete it unnoticed. */
|
||||
if (RENAME(caside, cdst) != 0 && RENAME(caside, cdst) != 0)
|
||||
hts_log_print(opt, LOG_WARNING | LOG_ERRNO,
|
||||
"could not put %s back; its previous content is now %s", cdst,
|
||||
caside);
|
||||
errno = err;
|
||||
return HTS_FALSE;
|
||||
}
|
||||
|
||||
hts_boolean hts_rename_over(httrackp *opt, const char *src, const char *dst) {
|
||||
char csrc[CATBUFF_SIZE], cdst[CATBUFF_SIZE];
|
||||
|
||||
fconv(csrc, sizeof(csrc), src);
|
||||
fconv(cdst, sizeof(cdst), dst);
|
||||
if (RENAME(csrc, cdst) == 0)
|
||||
return HTS_TRUE;
|
||||
/* Only a dst in the way is something the unlink can clear, and the CRT maps
|
||||
that to EEXIST; it keeps EACCES for a src another process holds, where
|
||||
removing dst would lose a file the retry cannot replace (#790). The src
|
||||
check covers a CRT that reports neither. */
|
||||
/* Only a dst in the way is something the fallback can clear, and the CRT maps
|
||||
that to EEXIST; it keeps EACCES for a src another process holds, where the
|
||||
retry would fail the same way. The src check covers a CRT that reports
|
||||
neither. */
|
||||
const int err = errno;
|
||||
|
||||
if (err != EEXIST || !fexist_utf8(src))
|
||||
return HTS_FALSE;
|
||||
(void) UNLINK(cdst);
|
||||
return RENAME(csrc, cdst) == 0 ? HTS_TRUE : HTS_FALSE;
|
||||
return rename_over_aside(opt, csrc, cdst);
|
||||
}
|
||||
|
||||
hts_boolean hts_rename_over_aside_selftest(httrackp *opt, const char *src,
|
||||
const char *dst) {
|
||||
char csrc[CATBUFF_SIZE], cdst[CATBUFF_SIZE];
|
||||
|
||||
fconv(csrc, sizeof(csrc), src);
|
||||
fconv(cdst, sizeof(cdst), dst);
|
||||
return rename_over_aside(opt, csrc, cdst);
|
||||
}
|
||||
|
||||
@@ -137,11 +137,17 @@ HTSEXT_API hts_boolean hts_findisdir(find_handle find);
|
||||
HTSEXT_API hts_boolean hts_findisfile(find_handle find);
|
||||
HTSEXT_API hts_boolean hts_findissystem(find_handle find);
|
||||
|
||||
/* Move src onto dst, replacing an existing dst; HTS_TRUE on success. Both
|
||||
paths are fconv()'d. dst is removed only to make room for a src that exists,
|
||||
so a caller whose src was never written keeps its dst; a retry that still
|
||||
fails does not (#790). */
|
||||
hts_boolean hts_rename_over(const char *src, const char *dst);
|
||||
/* Move src onto dst, replacing an existing dst; HTS_TRUE on success. Both paths
|
||||
are fconv()'d. A dst in the way is parked under a sibling name rather than
|
||||
removed, so the old content survives a failure: back at dst, or under that
|
||||
sibling (named in the log) when the move back failed too. Not atomic: a crash
|
||||
between the two renames leaves dst absent and its content beside it. */
|
||||
hts_boolean hts_rename_over(httrackp *opt, const char *src, const char *dst);
|
||||
|
||||
/* Selftest hook: run the aside fallback directly, on a platform whose rename()
|
||||
never reaches it. Both paths are fconv()'d. */
|
||||
hts_boolean hts_rename_over_aside_selftest(httrackp *opt, const char *src,
|
||||
const char *dst);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
#include "htscore.h"
|
||||
#include "htslib.h"
|
||||
#include "htsback.h"
|
||||
#include "htstools.h"
|
||||
#include "htssafe.h"
|
||||
#include "htszlib.h"
|
||||
@@ -1114,7 +1115,7 @@ static void warc_wacz_package(warc_writer *w) {
|
||||
hts_log_print(w->opt, LOG_WARNING,
|
||||
"WACZ: packaging failed, kept existing %s untouched",
|
||||
waczpath);
|
||||
} else if (!hts_rename_over(tmppath, waczpath)) {
|
||||
} else if (!hts_rename_over(w->opt, tmppath, waczpath)) {
|
||||
(void) UNLINK(fconv(catbuff, sizeof(catbuff), tmppath));
|
||||
hts_log_print(w->opt, LOG_WARNING | LOG_ERRNO,
|
||||
"WACZ: could not finalize %s", waczpath);
|
||||
@@ -1354,6 +1355,7 @@ void warc_free_request(htsblk *r) {
|
||||
freet(r->warc_resphdr);
|
||||
if (r->warc_rawpath != NULL) {
|
||||
(void) UNLINK(r->warc_rawpath); /* owns the verbatim spool file */
|
||||
back_tmpdir_drop(r->warc_rawpath);
|
||||
freet(r->warc_rawpath);
|
||||
r->warc_rawpath = NULL;
|
||||
}
|
||||
@@ -1589,7 +1591,7 @@ static hts_boolean warc_commit(warc_writer *w) {
|
||||
for (s = 0; s < nseg; s++) {
|
||||
const char *final = warc_seg_path(w, s, finalbuf, sizeof(finalbuf));
|
||||
snprintf(tmpbuf, sizeof(tmpbuf), "%s" WARC_TMP_SUFFIX, final);
|
||||
if (!hts_rename_over(tmpbuf, final)) {
|
||||
if (!hts_rename_over(w->opt, tmpbuf, final)) {
|
||||
hts_log_print(w->opt, LOG_ERROR | LOG_ERRNO,
|
||||
"WARC: could not replace %s", final);
|
||||
return HTS_FALSE;
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
httrack -O /dev/null -#test=addlink | grep -q "addlink self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=addlink)
|
||||
grep -q "addlink self-test OK" <<<"$out"
|
||||
|
||||
@@ -5,4 +5,5 @@ set -euo pipefail
|
||||
|
||||
# A ready slot still owning a temporary must stay in memory: swapping it out
|
||||
# clears the entry, which unlinks the re-fetch backup (#771).
|
||||
httrack -O /dev/null -#test=backswap | grep -q "backswap self-test: OK"
|
||||
out=$(httrack -O /dev/null -#test=backswap)
|
||||
grep -q "backswap self-test: OK" <<<"$out"
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_changes_st.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmpdir"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmpdir"' EXIT
|
||||
trap 'rm -rf "$tmpdir"' HUP INT QUIT PIPE TERM
|
||||
|
||||
# No pipe into grep: SIGPIPE would mask a failing exit status.
|
||||
expect_ok() {
|
||||
|
||||
@@ -5,4 +5,5 @@ set -euo pipefail
|
||||
|
||||
# webhttrack posts its command line as one string: the argv split must grow past
|
||||
# 1024 arguments and keep a quote inside a value out of the option parser.
|
||||
httrack -O /dev/null -#test=cmdline-split run | grep -q "cmdline-split self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=cmdline-split run)
|
||||
grep -q "cmdline-split self-test OK" <<<"$out"
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_cmdline.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
echo '<html><body>hello</body></html>' >"$tmp/index.html"
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ set -euo pipefail
|
||||
# cookies *@*.txt) from a long, non-ASCII folder through the UTF-8/long-path
|
||||
# file wrappers (#133,#630).
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=cookieimport "$dir" | grep -q "cookieimport:.*OK"
|
||||
out=$(httrack -O /dev/null -#test=cookieimport "$dir")
|
||||
grep -q "cookieimport:.*OK" <<<"$out"
|
||||
|
||||
@@ -6,6 +6,7 @@ set -euo pipefail
|
||||
# Drives -#test=direnum: enumerate a long+non-ASCII directory through the
|
||||
# opendir/readdir wrappers, checking each child round-trips as UTF-8 (#133,#630).
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=direnum "$dir" | grep -q "direnum:.*OK"
|
||||
out=$(httrack -O /dev/null -#test=direnum "$dir")
|
||||
grep -q "direnum:.*OK" <<<"$out"
|
||||
|
||||
@@ -28,7 +28,8 @@ case "$bin" in
|
||||
esac
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_doitlog.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
site="$tmp/site"
|
||||
out="$tmp/out"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# HT_ADD_HTMLESCAPED* must reserve the escaper's worst case (6 for _full).
|
||||
httrack -O /dev/null -#test=escape-room run | grep -q "escape-room self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=escape-room run)
|
||||
grep -q "escape-room self-test OK" <<<"$out"
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_filelist.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
echo '<html><body>hi</body></html>' >"$tmp/index.html"
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ MINGW* | MSYS* | CYGWIN*) exit 77 ;;
|
||||
esac
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# A few-hundred-char file path (kept well under the URL length limit) makes the
|
||||
# {path} field long.
|
||||
@@ -50,7 +50,7 @@ httrack "file://$deep/index.html" -O "$mir" -%F "$footer" -q -s0 -%v0 \
|
||||
|
||||
# The crawled page must exist (proves the URL wasn't rejected for length, so the
|
||||
# footer path ran). Look under file/, not $mir, to skip the makeindex top index.
|
||||
find "$mir/file" -name index.html | grep -q . || {
|
||||
test -n "$(find "$mir/file" -name index.html)" || {
|
||||
echo "page not mirrored; the oversized-footer path was not exercised" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
rc=0
|
||||
out=$(httrack -#test=fsize "$dir") || rc=$?
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# get_ftp_line bounds a hostile CRLF-less FTP reply into its 1024-byte buffer.
|
||||
httrack -O /dev/null -#test=ftp-line run | grep -q "ftp-line self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=ftp-line run)
|
||||
grep -q "ftp-line self-test OK" <<<"$out"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# ftp_split_userpass bounds an over-long user:pass@ from a hostile ftp:// URL.
|
||||
httrack -O /dev/null -#test=ftp-userpass run | grep -q "ftp-userpass self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=ftp-userpass run)
|
||||
grep -q "ftp-userpass self-test OK" <<<"$out"
|
||||
|
||||
@@ -10,7 +10,8 @@ echo "$out"
|
||||
test "$out" == "growsize self-test OK"
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_growsize.XXXXXX")
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
echo '<html><body>hi</body></html>' >"$tmp/index.html"
|
||||
printf -- '-*/zzmarker*\n' >"$tmp/rules.txt"
|
||||
@@ -18,7 +19,7 @@ printf -- '-*/zzmarker*\n' >"$tmp/rules.txt"
|
||||
# the rules file lands in the URL/filter string, echoed back by the banner
|
||||
run=$(httrack -O "$tmp/out" --quiet -n "-%S" "$tmp/rules.txt" \
|
||||
"file://$tmp/index.html" 2>&1) || true
|
||||
printf '%s\n' "$run" | grep -q 'zzmarker' || {
|
||||
grep -q 'zzmarker' <<<"$run" || {
|
||||
echo "FAIL: -%S rules file was not loaded"
|
||||
printf '%s\n' "$run"
|
||||
exit 1
|
||||
|
||||
@@ -6,4 +6,4 @@ set -euo pipefail
|
||||
# httrack internal hashtable autotest on 100K keys. Assert the success line (on
|
||||
# stderr) so a misrouted registry entry can't pass on exit code alone.
|
||||
out=$(httrack -#test=hashtable 100000 2>&1)
|
||||
printf '%s\n' "$out" | grep -q "all hashtable tests were successful!" || exit 1
|
||||
grep -q "all hashtable tests were successful!" <<<"$out" || exit 1
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# inplace_escape_*() must match escape_*() on a copy: guards the shared helper.
|
||||
httrack -O /dev/null -#test=inplace-escape run | grep -q "inplace-escape self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=inplace-escape run)
|
||||
grep -q "inplace-escape self-test OK" <<<"$out"
|
||||
|
||||
8
tests/01_engine-logcallback.test
Normal file
8
tests/01_engine-logcallback.test
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# a log callback consumed the va_list the log file's vfprintf() still needed (#801).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
out=$(httrack -O /dev/null -#test=logcallback)
|
||||
grep -q "logcallback self-test OK" <<<"$out"
|
||||
@@ -6,6 +6,7 @@ set -euo pipefail
|
||||
# Drives -#test=longpath: a >MAX_PATH round trip exercising hts_pathToUCS2's
|
||||
# \\?\ prefixing on Windows (#133); a positive control on POSIX.
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=longpath "$dir" | grep -q "longpath:.*OK"
|
||||
out=$(httrack -O /dev/null -#test=longpath "$dir")
|
||||
grep -q "longpath:.*OK" <<<"$out"
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
# hts_finish_makeindex writes the footer and gates the refresh meta on a single
|
||||
# first link (guards the macro->function extraction).
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=makeindex "$dir" run |
|
||||
grep -q "makeindex self-test OK"
|
||||
|
||||
@@ -7,6 +7,7 @@ set -euo pipefail
|
||||
# (>MAX_PATH) and non-ASCII, exercising the mirror I/O wrappers the engine's
|
||||
# raw file ops now route to on Windows (#133, #630). Positive control on POSIX.
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=mirrorio "$dir" | grep -q "mirrorio:.*OK"
|
||||
out=$(httrack -O /dev/null -#test=mirrorio "$dir")
|
||||
grep -q "mirrorio:.*OK" <<<"$out"
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_parse.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
# a minimal valid 1x1 GIF, reused for every referenced asset
|
||||
gif() {
|
||||
|
||||
@@ -30,7 +30,8 @@ case "$bin" in
|
||||
esac
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_rcfile.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
|
||||
# HTS_HTTRACKRC is ".httrackrc" on POSIX but "httrackrc" on Windows: write both,
|
||||
# each platform reads the one it knows.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
out=$(httrack -#test=reconcile "$dir")
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ set -euo pipefail
|
||||
# #159: a redirect to a same-file alias (http<->https, user@host, ..) must be
|
||||
# followed through, not turned into a self-pointing "moved" stub. The decision
|
||||
# helper is exercised by the engine self-test.
|
||||
httrack -O /dev/null -#test=redirect-samefile run | grep -q "redirect-samefile self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=redirect-samefile run)
|
||||
grep -q "redirect-samefile self-test OK" <<<"$out"
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Drives -#test=renameover: hts_rename_over() must replace an existing dst, and
|
||||
# must leave dst alone when the rename failed for a reason removing dst cannot
|
||||
# fix (#779). The selftest prints the regime it detected; pin it per platform so
|
||||
# a leg cannot pass having tested the other half.
|
||||
# must leave dst alone when the rename failed for a reason moving dst aside
|
||||
# cannot fix (#779, #790). The selftest prints the regime and the restore
|
||||
# outcome it took; pin both per leg so none can pass having tested another.
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
case "$(uname -s)" in
|
||||
MINGW* | MSYS_NT*) want=fallback ;; # native rename() refuses an existing target
|
||||
@@ -16,8 +16,9 @@ MINGW* | MSYS_NT*) want=fallback ;; # native rename() refuses an existing target
|
||||
esac
|
||||
|
||||
out=$(httrack -O /dev/null -#test=renameover "$dir")
|
||||
echo "$out" | grep -q "renameover: OK"
|
||||
echo "$out" | grep -q "renameover: regime $want"
|
||||
grep -q "renameover: OK" <<<"$out"
|
||||
grep -q "renameover: regime $want" <<<"$out"
|
||||
grep -q "renameover: restore back" <<<"$out"
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
echo "renameover: LD_PRELOAD interposition is Linux-only here, skipping"
|
||||
@@ -42,14 +43,29 @@ fi
|
||||
# refuses by default. The shim allocates nothing, so the ordering is harmless.
|
||||
export ASAN_OPTIONS="${ASAN_OPTIONS:+$ASAN_OPTIONS:}verify_asan_link_order=0"
|
||||
|
||||
# The unlink fallback is dead code on POSIX, so borrow Windows' rename().
|
||||
# The aside fallback is dead code on POSIX, so borrow Windows' rename().
|
||||
out=$(LD_PRELOAD="$RENAMEFAIL_LIB" httrack -O /dev/null \
|
||||
-#test=renameover "$dir")
|
||||
echo "$out" | grep -q "renameover: OK"
|
||||
echo "$out" | grep -q "renameover: regime fallback"
|
||||
grep -q "renameover: OK" <<<"$out"
|
||||
grep -q "renameover: regime fallback" <<<"$out"
|
||||
grep -q "renameover: restore back" <<<"$out"
|
||||
|
||||
# #790: the move back out of the parked name fails once. Without the retry the
|
||||
# old copy stays parked and dst is left absent.
|
||||
out=$(RENAMEFAIL_ASIDE_FAILS=1 LD_PRELOAD="$RENAMEFAIL_LIB" httrack -O /dev/null \
|
||||
-#test=renameover "$dir")
|
||||
grep -q "renameover: OK" <<<"$out"
|
||||
grep -q "renameover: restore back" <<<"$out"
|
||||
|
||||
# It keeps failing: the old copy must survive under the parked name, never be
|
||||
# deleted, and the call must still report failure.
|
||||
out=$(RENAMEFAIL_ASIDE_FAILS=9 LD_PRELOAD="$RENAMEFAIL_LIB" httrack -O /dev/null \
|
||||
-#test=renameover "$dir")
|
||||
grep -q "renameover: OK" <<<"$out"
|
||||
grep -q "renameover: restore parked" <<<"$out"
|
||||
|
||||
# A source another process holds fails with EACCES, which dst had no part in.
|
||||
out=$(RENAMEFAIL_MODE=locked LD_PRELOAD="$RENAMEFAIL_LIB" httrack -O /dev/null \
|
||||
-#test=renameover "$dir")
|
||||
echo "$out" | grep -q "renameover: OK"
|
||||
echo "$out" | grep -q "renameover: regime refused"
|
||||
grep -q "renameover: OK" <<<"$out"
|
||||
grep -q "renameover: regime refused" <<<"$out"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# robots.txt RFC 9309 Allow/Disallow precedence (#452): longest match wins.
|
||||
httrack -O /dev/null -#test=robots run | grep -q "robots self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=robots run)
|
||||
grep -q "robots self-test OK" <<<"$out"
|
||||
|
||||
@@ -12,7 +12,7 @@ httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
|
||||
# scratch dir: body= and cached= write temp files (st-savename-body.tmp, hts-cache/)
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
trap 'set +e; rm -rf "$scratch"' EXIT
|
||||
cd "$scratch"
|
||||
|
||||
run() {
|
||||
@@ -165,3 +165,20 @@ MINGW* | MSYS* | CYGWIN*)
|
||||
name "/$(printf 'a%.0s' {1..300}).php" 'text/html' "$(printf 'a%.0s' {1..300}).html"
|
||||
;;
|
||||
esac
|
||||
|
||||
# #774: the engine owns hts-cache/ and hts-tmp/ inside the mirror, so a URL must
|
||||
# never be able to name one; a site serving them gets the /nul -> nul_ treatment.
|
||||
full '/hts-tmp/a.bin' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/hts-tmp_/a.bin'
|
||||
full '/hts-cache/new.zip' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/hts-cache_/new.zip'
|
||||
full '/d/hts-tmp/a.bin.bak' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/d/hts-tmp_/a.bin.bak'
|
||||
# A whole component only, and case-insensitively: the filesystem may be too.
|
||||
full '/hts-tmpfoo/a.bin' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/hts-tmpfoo/a.bin'
|
||||
full '/HTS-TMP/a.bin' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/HTS-TMP_/a.bin'
|
||||
# Control: the DOS device names this reuses must still be escaped.
|
||||
full '/nul/x.bin' 'application/octet-stream' \
|
||||
'/dev/null/www.example.com/nul_/x.bin'
|
||||
|
||||
@@ -7,11 +7,11 @@ set -eu
|
||||
|
||||
# Bare -#test lists known tests (printed to stderr).
|
||||
list=$(httrack -#test 2>&1)
|
||||
printf '%s\n' "$list" | grep -q "filter" || exit 1
|
||||
printf '%s\n' "$list" | grep -q "cache-writefail" || exit 1
|
||||
grep -q "filter" <<<"$list" || exit 1
|
||||
grep -q "cache-writefail" <<<"$list" || exit 1
|
||||
|
||||
# Unknown name: non-zero exit + diagnostic, and no test result line.
|
||||
rc=0
|
||||
err=$(httrack -#test=bogus 2>&1) || rc=$?
|
||||
test "$rc" -ne 0 || exit 1
|
||||
printf '%s\n' "$err" | grep -q "Unknown self-test" || exit 1
|
||||
grep -q "Unknown self-test" <<<"$err" || exit 1
|
||||
|
||||
@@ -6,4 +6,5 @@ set -euo pipefail
|
||||
# The SOCKS5 handshake framing and credential split, driven against scripted
|
||||
# server replies (frame draining, oversize rejects, RFC 1929 fields).
|
||||
|
||||
httrack -O /dev/null '-#test=socks5' | grep -q "socks5 self-test OK"
|
||||
out=$(httrack -O /dev/null '-#test=socks5')
|
||||
grep -q "socks5 self-test OK" <<<"$out"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# HTTP status -> reason phrase, including the modern 429/451 (#453).
|
||||
httrack -O /dev/null -#test=status run | grep -q "status self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=status run)
|
||||
grep -q "status self-test OK" <<<"$out"
|
||||
|
||||
@@ -5,4 +5,5 @@ set -euo pipefail
|
||||
|
||||
# --strip-query: pattern-scoped query-key stripping for dedup. All assertions
|
||||
# live in the engine self-test (hts_query_strip_keys + fil_normalized_filtered).
|
||||
httrack -O /dev/null -#test=stripquery | grep -q "strip-query self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=stripquery)
|
||||
grep -q "strip-query self-test OK" <<<"$out"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=structcheck "$dir" |
|
||||
grep -q "structcheck self-test OK"
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_threadwait_st.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmpdir"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmpdir"' EXIT
|
||||
trap 'rm -rf "$tmpdir"' HUP INT QUIT PIPE TERM
|
||||
|
||||
# No pipe into grep: SIGPIPE would mask a failing exit status.
|
||||
expect_ok() {
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
# hts_buildtopindex takes a system-charset path but verif_backblue below it
|
||||
# expects utf-8, mangling a non-ASCII project dir on Windows (#216, #217).
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=topindex "$dir" run |
|
||||
grep -q "topindex self-test OK"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Entity/URL unescapers reserve one byte for the trailing NUL (no 1-byte OOB).
|
||||
httrack -O /dev/null -#test=unescape-bounds run | grep -q "unescape-bounds self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=unescape-bounds run)
|
||||
grep -q "unescape-bounds self-test OK" <<<"$out"
|
||||
|
||||
@@ -5,4 +5,5 @@ set -euo pipefail
|
||||
|
||||
# -%u url-hack split (#271): www / // / query-order dedup toggle independently.
|
||||
# All assertions live in the engine self-test (hash compare flag resolution).
|
||||
httrack -O /dev/null -#test=urlhack run | grep -q "urlhack self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=urlhack run)
|
||||
grep -q "urlhack self-test OK" <<<"$out"
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Default User-Agent (#449): honest HTTrack token, no Windows 98 relic.
|
||||
httrack -O /dev/null -#test=useragent run | grep -q "useragent self-test OK"
|
||||
out=$(httrack -O /dev/null -#test=useragent run)
|
||||
grep -q "useragent self-test OK" <<<"$out"
|
||||
|
||||
@@ -5,4 +5,5 @@ set -euo pipefail
|
||||
|
||||
# SURT canonicalization of the CDXJ sort key (--warc-cdx). Pure string work,
|
||||
# so it runs under the MSan-instrumented 01_engine glob.
|
||||
httrack -O /dev/null -#test=warc-surt | grep -q "warc-surt: OK"
|
||||
out=$(httrack -O /dev/null -#test=warc-surt)
|
||||
grep -q "warc-surt: OK" <<<"$out"
|
||||
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
|
||||
# Accept-Encoding (#450): advertise gzip+deflate; decode gzip/zlib/raw-deflate.
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=acceptencoding "$dir" run |
|
||||
grep -q "acceptencoding self-test OK"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# the smashed-header case logs expected "Corrupted cache entry" warnings on
|
||||
# stdout; the verdict is the last line
|
||||
|
||||
@@ -27,7 +27,7 @@ test -e "$fixture/hts-cache/new.zip" || {
|
||||
}
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# Read against a private copy so the source tree is never touched (a read
|
||||
# session does not write, but copying keeps the test hermetic). Create the dir
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# the refusal errors land on stdout (no log file); pin them and the verdict
|
||||
out=$(httrack -#test=cache-legacy "$dir" 2>/dev/null)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms and GNU-only
|
||||
# tool flags despite the #!/bin/bash above.
|
||||
# Stick to POSIX tool flags: macOS ships BSD grep/sed, not the GNU ones.
|
||||
|
||||
# Cache write-failure policy (-#test=cache-writefail <dir>). #174/#219: disk
|
||||
# full or a failure streak aborts cleanly; an isolated failure or an oversized
|
||||
@@ -11,19 +9,19 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
out=$(httrack -#test=cache-writefail "$dir")
|
||||
|
||||
# Match the exact success line (error logs also go to stdout); a renamed/removed
|
||||
# test prints the registry to stderr, which exits non-zero but never prints this.
|
||||
printf '%s\n' "$out" | grep -qx "cache-writefail: OK" || {
|
||||
grep -qx "cache-writefail: OK" <<<"$out" || {
|
||||
echo "expected 'cache-writefail: OK', got: $out" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# A skipped entry must be warned about with its URL.
|
||||
printf '%s\n' "$out" | grep -q "entry not cached: example.com/" || {
|
||||
grep -q "entry not cached: example.com/" <<<"$out" || {
|
||||
echo "expected a URL-bearing skip warning" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# The working directory is a required argument; without it the test prints a
|
||||
# usage line to stderr and returns non-zero.
|
||||
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
|
||||
# brotli/zstd decode, unknown codings, and the decoded-size budget.
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=contentcodings "$dir" run |
|
||||
grep -q "contentcodings self-test OK"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms and GNU-only
|
||||
# tool flags despite the #!/bin/bash above.
|
||||
# Stick to POSIX tool flags: macOS ships BSD grep/sed, not the GNU ones.
|
||||
|
||||
# unzRepair header read must not overflow a signed shift (-#test=zip-repair-shift
|
||||
# <dir>). A damaged local file header whose CRC high word has bit 15 set made
|
||||
@@ -11,11 +9,11 @@
|
||||
set -eu
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
out=$(httrack -#test=zip-repair-shift "$dir")
|
||||
|
||||
printf '%s\n' "$out" | grep -qx "zip-repair-shift: OK (recovered 1 entry)" || {
|
||||
grep -qx "zip-repair-shift: OK (recovered 1 entry)" <<<"$out" || {
|
||||
echo "expected 'zip-repair-shift: OK (recovered 1 entry)', got: $out" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ set -euo pipefail
|
||||
httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
trap 'set +e; rm -rf "$scratch"' EXIT
|
||||
cd "$scratch"
|
||||
|
||||
name() {
|
||||
|
||||
@@ -9,7 +9,7 @@ set -euo pipefail
|
||||
httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
trap 'set +e; rm -rf "$scratch"' EXIT
|
||||
|
||||
out=$("$httrack_bin" -O /dev/null -#test=warc-cdx "$scratch/")
|
||||
echo "$out"
|
||||
|
||||
@@ -10,13 +10,14 @@ set -euo pipefail
|
||||
|
||||
httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
|
||||
if ! "$httrack_bin" -#test 2>&1 | grep -q '^ warc-wacz'; then
|
||||
registry=$("$httrack_bin" -#test 2>&1 || true)
|
||||
if ! grep -q '^ warc-wacz' <<<"$registry"; then
|
||||
echo "warc-wacz self-test unavailable (build without OpenSSL); skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
trap 'set +e; rm -rf "$scratch"' EXIT
|
||||
|
||||
out=$("$httrack_bin" -O /dev/null -#test=warc-wacz "$scratch/")
|
||||
echo "$out"
|
||||
|
||||
@@ -10,7 +10,7 @@ set -euo pipefail
|
||||
httrack_bin=$(cd "$(dirname "$(command -v httrack)")" && pwd)/httrack
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
trap 'set +e; rm -rf "$scratch"' EXIT
|
||||
|
||||
for t in warc warc-trunc warc-ftp warc-rotate warc-verbatim; do
|
||||
out=$("$httrack_bin" -O /dev/null "-#test=$t" "$scratch/")
|
||||
|
||||
@@ -29,7 +29,7 @@ command -v httrack >/dev/null 2>&1 || {
|
||||
tmp=$(mktemp) || exit 1
|
||||
committed_clean=$(mktemp) || exit 1
|
||||
generated_clean=$(mktemp) || exit 1
|
||||
trap 'rm -f "$tmp" "$committed_clean" "$generated_clean"' EXIT
|
||||
trap 'set +e; rm -f "$tmp" "$committed_clean" "$generated_clean"' EXIT
|
||||
|
||||
README="$top_srcdir/README" bash "$gen" httrack >"$tmp" 2>/dev/null || {
|
||||
echo "makeman.sh failed" >&2
|
||||
|
||||
@@ -14,7 +14,7 @@ set -euo pipefail
|
||||
|
||||
site=$(mktemp -d)
|
||||
out=$(mktemp -d)
|
||||
trap 'rm -rf "$site" "$out"' EXIT
|
||||
trap 'set +e; rm -rf "$site" "$out"' EXIT
|
||||
|
||||
cat >"$site/index.html" <<EOF
|
||||
<a href="a.html">a</a> <a href="sub/b.html">b</a>
|
||||
|
||||
@@ -30,7 +30,7 @@ test -n "$httrack_bin" || {
|
||||
httrack_bin=$(cd "$(dirname "$httrack_bin")" && pwd)/$(basename "$httrack_bin")
|
||||
|
||||
work=$(mktemp -d)
|
||||
trap 'kill -9 "${spid:-}" 2>/dev/null || true; rm -rf "$work"' EXIT
|
||||
trap 'set +e; kill -9 "${spid:-}" 2>/dev/null || true; rm -rf "$work"' EXIT
|
||||
|
||||
mkdir -p "$work/root" "$work/proj"
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ set -euo pipefail
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" \
|
||||
--plant-file bigtrunc/slow.bin.bak \
|
||||
--plant-dir bigtrunc/fast.bin.bak \
|
||||
--plant-file bigtrunc/hts-tmp/slow.bin.bak \
|
||||
--plant-dir bigtrunc/hts-tmp/fast.bin.bak \
|
||||
--rerun-args '--update -M400000' \
|
||||
--log-found 'More than 400000 bytes have been transferred.. giving up' \
|
||||
--log-found 'replacing leftover backup .*slow\.bin\.bak' \
|
||||
|
||||
@@ -25,7 +25,8 @@ cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap cleanup HUP INT QUIT PIPE TERM
|
||||
|
||||
root="${tmpdir}/root"
|
||||
out="${tmpdir}/crawl"
|
||||
@@ -151,13 +152,15 @@ ok "the kept copies survived the update purge"
|
||||
# --- the change report is the mirror's own account of the pass ----------------
|
||||
test -s "$report" || fail "pass 2 wrote no ${report}"
|
||||
test -z "$(listed gone)" || fail "the report calls something gone: $(listed gone)"
|
||||
unchanged=$(listed unchanged)
|
||||
changed=$(listed changed)
|
||||
for name in keep empty; do
|
||||
listed unchanged | grep -qx "${host}/${name}.bin:$(size_of "${tmpdir}/snap/${name}.bin")" ||
|
||||
grep -qx "${host}/${name}.bin:$(size_of "${tmpdir}/snap/${name}.bin")" <<<"$unchanged" ||
|
||||
fail "${name}.bin is not reported unchanged at its previous size"
|
||||
if listed changed | grep -q "^${host}/${name}.bin:"; then
|
||||
if grep -q "^${host}/${name}.bin:" <<<"$changed"; then
|
||||
fail "${name}.bin is reported changed as well"
|
||||
fi
|
||||
done
|
||||
listed changed | grep -qx "${host}/stay.bin:$(size_of "${out}/${host}/stay.bin")" ||
|
||||
grep -qx "${host}/stay.bin:$(size_of "${out}/${host}/stay.bin")" <<<"$changed" ||
|
||||
fail "stay.bin is not reported changed"
|
||||
ok "the change report calls the kept files unchanged and the refreshed one changed"
|
||||
|
||||
81
tests/103_teardown-status.test
Normal file
81
tests/103_teardown-status.test
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Issue #773: a test must fail only when an assertion fails. Under "set -e" a
|
||||
# failing command in an EXIT trap becomes the script's exit status, so a
|
||||
# teardown hiccup turned a test whose assertions all passed into a red CI leg.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# run <trap-body> <script-body>: exit status and stderr of a subject script.
|
||||
run() {
|
||||
{
|
||||
echo "set -euo pipefail"
|
||||
# printf, so the scanner below does not read this as a trap of our own
|
||||
printf 'trap %s EXIT\n' "$1"
|
||||
echo "$2"
|
||||
} >"$tmp/subject"
|
||||
rc=0
|
||||
err=$(bash "$tmp/subject" 2>&1 >/dev/null) || rc=$?
|
||||
}
|
||||
|
||||
# 1. the shape in the tree: teardown fails, every assertion passed -> pass
|
||||
run "'set +e; false'" "true"
|
||||
test "$rc" -eq 0 || fail "guarded teardown failure exited $rc, want 0"
|
||||
|
||||
# 2. teeth for case 1: unguarded, the same teardown is what went red
|
||||
run "'false'" "true"
|
||||
test "$rc" -eq 1 || fail "unguarded teardown failure exited $rc, want 1"
|
||||
|
||||
# 3. the guard must not swallow a real failure, nor a skip
|
||||
run "'set +e; false'" "exit 1"
|
||||
test "$rc" -eq 1 || fail "assertion failure exited $rc, want 1"
|
||||
run "'set +e; false'" "exit 77"
|
||||
test "$rc" -eq 77 || fail "skip exited $rc, want 77"
|
||||
|
||||
# 4. not failing must not mean going quiet: teardown still reports why
|
||||
run "'set +e; rm /nonexistent-teardown-probe'" "true"
|
||||
test "$rc" -eq 0 || fail "guarded teardown failure exited $rc, want 0"
|
||||
case "$err" in
|
||||
*nonexistent-teardown-probe*) ;;
|
||||
*) fail "teardown error was silenced (stderr: '$err')" ;;
|
||||
esac
|
||||
echo "OK: teardown cannot decide the verdict, and still reports"
|
||||
|
||||
# 5. no test may reintroduce the shape case 2 pins. Two spellings neutralise
|
||||
# errexit for teardown: "set +e" first, or "|| true" over the whole body.
|
||||
scanned=0
|
||||
for f in "$top_srcdir"/tests/*.test "$top_srcdir"/tests/*.sh; do
|
||||
test -r "$f" || continue
|
||||
scanned=$((scanned + 1))
|
||||
done
|
||||
# an unexpanded glob would report no offender having read nothing
|
||||
test "$scanned" -ge 50 || fail "scanned $scanned scripts in $top_srcdir/tests, want the whole suite"
|
||||
|
||||
bad=$(awk '
|
||||
FNR == 1 { errexit = 0 }
|
||||
/^[[:space:]]*set[[:space:]]+(-[a-z]*e|-o[[:space:]]+errexit)/ { errexit = 1 }
|
||||
!errexit { next }
|
||||
!/^[[:space:]]*trap[[:space:]]+.*[[:space:]]EXIT([[:space:]]|;|$)/ { next }
|
||||
{
|
||||
guarded = /^[[:space:]]*trap[[:space:]]+.set \+e; / || /\|\|[[:space:]]+true.[[:space:]]+EXIT/
|
||||
if (!guarded)
|
||||
print FILENAME ":" FNR " unguarded"
|
||||
# "set +e" on a trap shared with a signal leaves errexit off for the
|
||||
# rest of the run, so a torn-down test could still report success
|
||||
else if (/^[[:space:]]*trap[[:space:]]+.set \+e; / && /[[:space:]]EXIT[[:space:]]+[A-Z]/)
|
||||
print FILENAME ":" FNR " set +e shared with a signal trap"
|
||||
}
|
||||
' "$top_srcdir"/tests/*.test "$top_srcdir"/tests/*.sh)
|
||||
test -z "$bad" || fail "EXIT trap can decide the verdict:
|
||||
$bad"
|
||||
echo "OK: every EXIT trap under set -e runs teardown with errexit off"
|
||||
@@ -8,6 +8,6 @@ set -e
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_longurl.XXXXXX")
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
|
||||
httrack -O "$tmp/out" "-#test=warc-longurl" "$tmp"
|
||||
|
||||
@@ -23,7 +23,8 @@ fail() {
|
||||
}
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_sutmo.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'rm -rf "$tmp"' HUP INT QUIT PIPE TERM
|
||||
out="$tmp/out"
|
||||
|
||||
# --- a hanging test is killed, named, and reported 124 ----------------------
|
||||
|
||||
173
tests/106_engine-repair-rename.test
Executable file
173
tests/106_engine-repair-rename.test
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# #786: both cache-repair paths moved the recovered zip onto the cache without
|
||||
# checking the move, and announced success either way. #824: they also committed
|
||||
# a recovery holding no entry at all. Neither may replace the cache, and both
|
||||
# must be reported.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "${testdir}/testlib.sh"
|
||||
|
||||
tmpdir=$(mktemp -d) # honors the Windows-shaped TMPDIR the MSYS suite exports
|
||||
cleanup() { rm -rf "$tmpdir"; }
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap 'set +e; cleanup; exit 1' HUP INT QUIT PIPE TERM
|
||||
|
||||
# One local file header, no central directory: unzOpen fails, unzRepair
|
||||
# recovers the single entry. Same vector as -#test=zip-repair-shift.
|
||||
plant_damaged_cache() {
|
||||
rm -rf "$1"
|
||||
mkdir -p "$1/hts-cache"
|
||||
printf '\120\113\003\004\024\000\000\000\000\000\000\000\000\000\000\000\350\212\000\000\000\000\000\000\000\000\001\000\000\000\141' \
|
||||
>"$1/hts-cache/new.zip"
|
||||
}
|
||||
|
||||
dead=http://127.0.0.1:1/
|
||||
|
||||
# --- the CLI path (-#R), repair succeeding -----------------------------------
|
||||
printf '[-#R repairs in place] ..\t'
|
||||
proj="${tmpdir}/cli"
|
||||
plant_damaged_cache "$proj"
|
||||
out=$(httrack -O "$proj" -#R "$dead" 2>&1)
|
||||
grep -q 'successfully recovered' <<<"$out" || {
|
||||
echo "FAIL: no recovery reported: $out"
|
||||
exit 1
|
||||
}
|
||||
test ! -e "${proj}/hts-cache/repair.zip" || {
|
||||
echo "FAIL: the recovery was left in repair.zip"
|
||||
exit 1
|
||||
}
|
||||
test "$(wc -c <"${proj}/hts-cache/new.zip")" -gt 31 || {
|
||||
echo "FAIL: new.zip is still the damaged one"
|
||||
exit 1
|
||||
}
|
||||
echo "OK"
|
||||
|
||||
# --- #824: nothing was recoverable, so nothing may be committed --------------
|
||||
printf '[-#R refuses an empty recovery] ..\t'
|
||||
proj="${tmpdir}/empty"
|
||||
rm -rf "$proj"
|
||||
mkdir -p "$proj/hts-cache"
|
||||
printf 'not a zip at all, some bytes' >"$proj/hts-cache/new.zip"
|
||||
rc=0
|
||||
out=$(httrack -O "$proj" -#R "$dead" 2>&1) || rc=$?
|
||||
test "$rc" -ne 0 || {
|
||||
echo "FAIL: an empty recovery exited 0: $out"
|
||||
exit 1
|
||||
}
|
||||
if grep -q 'successfully recovered' <<<"$out"; then
|
||||
echo "FAIL: an empty recovery claimed success: $out"
|
||||
exit 1
|
||||
fi
|
||||
test "$(wc -c <"${proj}/hts-cache/new.zip")" -eq 28 || {
|
||||
echo "FAIL: the damaged cache was replaced by the empty recovery"
|
||||
exit 1
|
||||
}
|
||||
echo "OK"
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
echo "repair-rename: LD_PRELOAD interposition is Linux-only here, skipping"
|
||||
exit 0
|
||||
fi
|
||||
# A --disable-shared build has nothing to preload; anything else missing is a
|
||||
# build problem, not a skip, or the failure legs would pass vacuously.
|
||||
if [ ! -r "${RENAMEFAIL_LA:-}" ]; then
|
||||
echo "repair-rename: ${RENAMEFAIL_LA:-\$RENAMEFAIL_LA} was not built" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q "^dlname=''" "$RENAMEFAIL_LA"; then
|
||||
echo "repair-rename: static-only build, skipping the interposed legs"
|
||||
exit 0
|
||||
fi
|
||||
if [ ! -r "${RENAMEFAIL_LIB:-}" ]; then
|
||||
echo "repair-rename: ${RENAMEFAIL_LIB:-\$RENAMEFAIL_LIB} was not built" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# An LD_PRELOAD library loads ahead of the executable's own libasan, which ASan
|
||||
# refuses by default. The shim allocates nothing, so the ordering is harmless.
|
||||
export ASAN_OPTIONS="${ASAN_OPTIONS:+$ASAN_OPTIONS:}verify_asan_link_order=0"
|
||||
export RENAMEFAIL_MODE=repair
|
||||
export LD_PRELOAD="$RENAMEFAIL_LIB"
|
||||
|
||||
# --- the CLI path, move refused ----------------------------------------------
|
||||
printf '[-#R reports a refused move] ..\t'
|
||||
proj="${tmpdir}/cli-fail"
|
||||
plant_damaged_cache "$proj"
|
||||
rc=0
|
||||
out=$(httrack -O "$proj" -#R "$dead" 2>&1) || rc=$?
|
||||
test "$rc" -ne 0 || {
|
||||
echo "FAIL: a refused move exited 0: $out"
|
||||
exit 1
|
||||
}
|
||||
if grep -q 'successfully recovered' <<<"$out"; then
|
||||
echo "FAIL: a refused move still claimed success: $out"
|
||||
exit 1
|
||||
fi
|
||||
test -s "${proj}/hts-cache/repair.zip" || {
|
||||
echo "FAIL: the recovery was not kept for a retry"
|
||||
exit 1
|
||||
}
|
||||
test "$(wc -c <"${proj}/hts-cache/new.zip")" -eq 31 || {
|
||||
echo "FAIL: the damaged cache was destroyed anyway"
|
||||
exit 1
|
||||
}
|
||||
echo "OK"
|
||||
|
||||
# --- the engine path (cache_init), move refused ------------------------------
|
||||
printf '[cache_init reports a refused move] ..\t'
|
||||
proj="${tmpdir}/engine"
|
||||
plant_damaged_cache "$proj"
|
||||
httrack -O "$proj" "$dead" --timeout=2 --retries=0 -q >/dev/null 2>&1 || true
|
||||
log="${proj}/hts-log.txt"
|
||||
test -r "$log" || {
|
||||
echo "FAIL: no crawl log"
|
||||
exit 1
|
||||
}
|
||||
grep -q 'damaged cache' "$log" || {
|
||||
echo "FAIL: the repair path never ran"
|
||||
exit 1
|
||||
}
|
||||
grep -q 'could not put the repaired cache in place' "$log" || {
|
||||
echo "FAIL: a refused move was not reported: $(grep -i cache "$log")"
|
||||
exit 1
|
||||
}
|
||||
if grep -q 'successfully recovered' "$log"; then
|
||||
echo "FAIL: a refused move still claimed success"
|
||||
exit 1
|
||||
fi
|
||||
test -s "${proj}/hts-cache/repair.zip" || {
|
||||
echo "FAIL: the recovery was not kept for a retry"
|
||||
exit 1
|
||||
}
|
||||
echo "OK"
|
||||
|
||||
# --- the move is refused the way Windows refuses it --------------------------
|
||||
# The destination always exists there, so every repair takes hts_rename_over()'s
|
||||
# fallback. Before #790 that fallback removed the cache and then failed.
|
||||
printf '[a refused fallback keeps the cache] ..\t'
|
||||
proj="${tmpdir}/eexist"
|
||||
plant_damaged_cache "$proj"
|
||||
rc=0
|
||||
out=$(RENAMEFAIL_MODE=repair-eexist httrack -O "$proj" -#R "$dead" 2>&1) || rc=$?
|
||||
test "$rc" -ne 0 || {
|
||||
echo "FAIL: a refused fallback exited 0: $out"
|
||||
exit 1
|
||||
}
|
||||
test "$(wc -c <"${proj}/hts-cache/new.zip")" -eq 31 || {
|
||||
echo "FAIL: the fallback destroyed the cache"
|
||||
exit 1
|
||||
}
|
||||
test -s "${proj}/hts-cache/repair.zip" || {
|
||||
echo "FAIL: the recovery was not kept for a retry"
|
||||
exit 1
|
||||
}
|
||||
leftover=$(find "${proj}/hts-cache" -name '*.hts-old*' | head -1)
|
||||
test -z "$leftover" || {
|
||||
echo "FAIL: the parked copy was left behind: $leftover"
|
||||
exit 1
|
||||
}
|
||||
echo "OK"
|
||||
21
tests/107_local-bak-collision.test
Executable file
21
tests/107_local-bak-collision.test
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# #774: the re-fetch backup lives in an hts-tmp directory beside the mirrored
|
||||
# file, so the site here serves that exact path. url_savename must escape the
|
||||
# reserved segment, landing the sibling in hts-tmp_ where the backup of a.bin
|
||||
# cannot consume it. Asserting the unescaped path instead would pass with the
|
||||
# bug live, which is how the first attempt at this test missed it.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" \
|
||||
--rerun-args '--update' \
|
||||
--file-matches 'bakname/a.bin' 'BAKNAME-MAIN-V2' \
|
||||
--file-matches 'bakname/hts-tmp_/a.bin.bak' 'BAKNAME-SIBLING' \
|
||||
--file-min-bytes 'bakname/hts-tmp_/a.bin.bak' 2048 \
|
||||
--not-found 'bakname/hts-tmp/a.bin.bak' \
|
||||
--log-not-found 'could not back up' \
|
||||
--log-not-found 'could not restore' \
|
||||
httrack 'BASEURL/bakname/index.html'
|
||||
25
tests/108_engine-refetch-backup.test
Executable file
25
tests/108_engine-refetch-backup.test
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Drives -#test=refetchbackup: the #77 re-fetch backup must leave a copy of the
|
||||
# resource whatever happens, and must not build its temporary name inside the
|
||||
# mirror namespace (#774, #775).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"; exit 1' HUP INT QUIT PIPE TERM
|
||||
|
||||
rc=0
|
||||
out=$(httrack -O /dev/null -#test=refetchbackup "$dir" 2>&1) || rc=$?
|
||||
if test "$rc" -ne 0 || ! grep -q "refetchbackup: OK" <<<"$out"; then
|
||||
echo "FAIL (exit $rc): $out" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The temporaries live in hts-tmp and the last user removes it.
|
||||
leftovers=$(find "$dir" -mindepth 1 | head -5)
|
||||
test -z "$leftovers" || {
|
||||
echo "FAIL: leftover temporaries: $leftovers" >&2
|
||||
exit 1
|
||||
}
|
||||
125
tests/111_local-ftp-update-rest.test
Normal file
125
tests/111_local-ftp-update-rest.test
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# FTP resumed with REST whenever the mirrored file merely existed, so an
|
||||
# --update over a complete copy spliced the old body into the new one and the
|
||||
# result matched the remote length (#798). Pass 1 is cut short to leave a real
|
||||
# partial, pass 2 must still resume it, pass 3 must not.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "${testdir}/testlib.sh"
|
||||
|
||||
python=$(find_python) || ! echo "python3 not found; skipping" >&2 || exit 77
|
||||
command -v httrack >/dev/null || {
|
||||
echo "could not find httrack" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
server=$(nativepath "${testdir}/ftp-server.py")
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_ftp.XXXXXX")
|
||||
serverpid=
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap cleanup HUP INT QUIT PIPE TERM
|
||||
|
||||
root="${tmpdir}/root"
|
||||
out="${tmpdir}/crawl"
|
||||
mode="${tmpdir}/mode"
|
||||
cmds="${tmpdir}/cmds"
|
||||
mkdir -p "$root" "$out"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
ok() { echo "OK: $*"; }
|
||||
size_of() { wc -c <"$1" | tr -d '[:space:]'; }
|
||||
# hts-log.txt is rewritten per pass, so this only ever sees the pass just run.
|
||||
no_errors() {
|
||||
local errors
|
||||
errors=$(grep -a 'Error:' "${out}/hts-log.txt" || true)
|
||||
test -z "$errors" || fail "$1 reported errors: ${errors}"
|
||||
}
|
||||
# Commands the server saw since the last pass; each pass starts by emptying it.
|
||||
sent() { cat "$cmds"; }
|
||||
|
||||
# The two generations differ in length and in their first bytes, so a splice
|
||||
# shows up whichever end of the file it lands in.
|
||||
write_body() {
|
||||
"$python" -c 'import sys; sys.stdout.buffer.write(
|
||||
("%s-BODY " % sys.argv[1]).encode() + sys.argv[1][:1].encode() * int(sys.argv[2]))' \
|
||||
"$1" "$2" >"${root}/a.bin"
|
||||
}
|
||||
write_body OLD 8000
|
||||
echo '/a.bin truncate' >"$mode"
|
||||
|
||||
serverlog="${tmpdir}/server.out"
|
||||
"$python" "$server" --root "$(nativepath "$root")" \
|
||||
--mode-file "$(nativepath "$mode")" --log "$(nativepath "$cmds")" \
|
||||
>"$serverlog" 2>&1 &
|
||||
serverpid=$!
|
||||
port=
|
||||
for _ in $(seq 1 300); do
|
||||
line=$(grep -m1 '^PORT ' "$serverlog" 2>/dev/null) && port="${line#PORT }" && break
|
||||
kill -0 "$serverpid" 2>/dev/null || {
|
||||
echo "ftp server exited early: $(cat "$serverlog")" >&2
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
done
|
||||
test -n "$port" || {
|
||||
echo "could not discover ftp server port: $(cat "$serverlog")" >&2
|
||||
exit 1
|
||||
}
|
||||
host="127.0.0.1_${port}"
|
||||
url="ftp://127.0.0.1:${port}/a.bin"
|
||||
mirror="${out}/${host}/a.bin"
|
||||
# Bounded like every crawl pass: a wedge must fail the test, not hang the job.
|
||||
crawl() { run_with_timeout 300 httrack "$url" -O "$out" --quiet \
|
||||
--disable-security-limits --robots=0 --timeout=20 --max-time=120 --retries=1 \
|
||||
-c1 "$@"; }
|
||||
|
||||
# --- pass 1: the transfer dies mid-body and leaves a partial -----------------
|
||||
crawl >"${tmpdir}/log1" 2>&1 || true
|
||||
test -f "$mirror" || fail "pass 1 mirrored nothing to resume"
|
||||
partial=$(size_of "$mirror")
|
||||
whole=$(size_of "${root}/a.bin")
|
||||
if test "$partial" -le 0 || test "$partial" -ge "$whole"; then
|
||||
fail "pass 1 left ${partial} bytes, wanted a partial of ${whole}"
|
||||
fi
|
||||
ok "pass 1 left a ${partial}-byte partial copy"
|
||||
|
||||
# --- pass 2: the partial is resumed ------------------------------------------
|
||||
: >"$mode"
|
||||
: >"$cmds"
|
||||
crawl >"${tmpdir}/log2" 2>&1
|
||||
case "$(sent)" in
|
||||
*"REST ${partial}"*) ok "pass 2 resumed at the partial's ${partial} bytes" ;;
|
||||
*) fail "pass 2 sent no REST ${partial}; commands were: $(sent)" ;;
|
||||
esac
|
||||
cmp -s "$mirror" "${root}/a.bin" ||
|
||||
fail "the resumed copy is $(size_of "$mirror") bytes, served $(size_of "${root}/a.bin")"
|
||||
# A resume used to count only the appended tail against the remote size, so it
|
||||
# was reported "FTP file incomplete" even when it produced the whole file.
|
||||
no_errors "pass 2"
|
||||
ok "the resumed transfer produced the served body and no error"
|
||||
|
||||
# --- pass 3: --update over that complete copy --------------------------------
|
||||
write_body NEW 12000
|
||||
: >"$cmds"
|
||||
crawl --update >"${tmpdir}/log3" 2>&1
|
||||
case "$(sent)" in
|
||||
*REST*) fail "the update resumed a complete mirror: $(sent)" ;;
|
||||
*RETR*) ok "the update re-fetched without REST" ;;
|
||||
*) fail "pass 3 never reached RETR; commands were: $(sent)" ;;
|
||||
esac
|
||||
cmp -s "$mirror" "${root}/a.bin" ||
|
||||
fail "the updated copy is $(size_of "$mirror") bytes, served $(size_of "${root}/a.bin")"
|
||||
no_errors "pass 3"
|
||||
ok "the update replaced the mirror with the new body"
|
||||
@@ -31,7 +31,7 @@ cleanup() {
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; 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" \
|
||||
|
||||
@@ -40,9 +40,8 @@ serverpid=
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; cleanup' EXIT
|
||||
|
||||
# bind the live server to 127.0.0.1 only, so 127.0.0.2 refuses the connect
|
||||
"$python" "$server" --root "$root" --bind 127.0.0.1 >"$tmpdir/srv.out" 2>"$tmpdir/srv.err" &
|
||||
|
||||
@@ -13,7 +13,10 @@ bash "$top_srcdir/tests/local-crawl.sh" --errors 1 \
|
||||
--not-found 'errpage/missing.html' \
|
||||
httrack 'BASEURL/errpage/index.html' '-o0'
|
||||
|
||||
# Control -o1 (default): the 404 error page is written.
|
||||
# Control -o1 (default): the 404 body is the server's verbatim, no HTTrack marker (#787); good.html proves the marker check can fire.
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 1 \
|
||||
--found 'errpage/missing.html' \
|
||||
--file-matches 'errpage/missing.html' '404 error body' \
|
||||
--file-not-matches 'errpage/missing.html' 'HTTrack' \
|
||||
--file-matches 'errpage/good.html' 'HTTrack' \
|
||||
httrack 'BASEURL/errpage/index.html' '-o1'
|
||||
|
||||
@@ -24,7 +24,8 @@ cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap cleanup HUP INT QUIT PIPE TERM
|
||||
|
||||
# OVERLAP_COUNTER gets a byte per flaky.bin request so pass 1 knows when to interrupt.
|
||||
serverlog="${tmpdir}/server.log"
|
||||
|
||||
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
# --why: report which +/- filter rule decides for a URL, without crawling.
|
||||
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
trap 'set +e; rm -rf "$tmpdir"' EXIT
|
||||
|
||||
why() {
|
||||
local want="$1" out
|
||||
|
||||
@@ -34,7 +34,7 @@ cleanup() {
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; cleanup' EXIT
|
||||
|
||||
openssl req -x509 -newkey rsa:2048 -keyout "$tmpdir/key.pem" \
|
||||
-out "$tmpdir/cert.pem" -days 2 -nodes -subj "/CN=127.0.0.1" \
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
printf 'HTTP/1.1 404 Not Found Here At All\r\nContent-Type: text/html\r\nLast-Modified: Wed, 01 Jan 2025 00:00:00 GMT\r\nContent-Length: 5\r\n\r\n' >"$dir/hdr"
|
||||
printf 'hello' >"$dir/body"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
trap 'set +e; rm -rf "$dir"' EXIT
|
||||
|
||||
# The first length-prefixed field declares far more than firstline[256] holds;
|
||||
# cache_brstr must clamp the copy to the destination, not the declared length.
|
||||
|
||||
@@ -29,9 +29,8 @@ serverpid=
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; cleanup' EXIT
|
||||
|
||||
# decoy origin: it must stay silent. LOCAL_SERVER_VERBOSE logs any request it gets.
|
||||
LOCAL_SERVER_VERBOSE=1 "$python" "$server" --root "$root" --bind 127.0.0.1 \
|
||||
|
||||
@@ -29,7 +29,7 @@ cleanup() {
|
||||
done
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; cleanup' EXIT
|
||||
|
||||
# start_server <logdir> <mode>: launches a proxy+origin pair, sets $origin_port
|
||||
# and $proxy_port from its announced ephemeral ports.
|
||||
|
||||
@@ -12,7 +12,7 @@ testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
. "${testdir}/testlib.sh"
|
||||
|
||||
tmp=$(mktemp -d "${TMPDIR:-/tmp}/httrack_wd.XXXXXX")
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
|
||||
@@ -27,9 +27,8 @@ serverpid=
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
return 0
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'set +e; cleanup' EXIT
|
||||
|
||||
# start_stall_server <tag> <mode-args...>: sets $port from the announced one.
|
||||
start_stall_server() {
|
||||
|
||||
@@ -12,7 +12,7 @@ testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/httrack_salv.XXXXXX")
|
||||
export TMPDIR
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
trap 'set +e; rm -rf "$TMPDIR"' EXIT
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
|
||||
@@ -23,7 +23,7 @@ funcs=$(sed -n '/^function lang_index/,/^}/p' "${script}")
|
||||
# would truncate the lift and silently stop testing everything below it.
|
||||
grep -q '^# Find the browser' "${script}" || fail "locale block terminator moved in ${script}"
|
||||
block=$(sed -n '/^# Locale/,/^# Find the browser/p' "${script}" | sed '$d')
|
||||
echo "${block}" | grep -q 'LANGN=.*lang_index' || fail "could not lift the whole locale block from ${script}"
|
||||
grep -q 'LANGN=.*lang_index' <<<"${block}" || fail "could not lift the whole locale block from ${script}"
|
||||
|
||||
# Run the lifted block against whatever locale vars the caller exported.
|
||||
run_block() {
|
||||
|
||||
@@ -19,7 +19,7 @@ for f in "$eng" "$def"; do
|
||||
done
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
keys="$tmp/english.keys"
|
||||
|
||||
# Byte-wise: each file is in its own declared legacy charset, lang.def is CRLF.
|
||||
|
||||
@@ -25,10 +25,11 @@ srv=
|
||||
cleanup() {
|
||||
test -z "${srv}" || kill -9 "${srv}" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap cleanup HUP INT QUIT PIPE TERM
|
||||
|
||||
log=$(mktemp)
|
||||
trap 'cleanup; rm -f "${log}"' EXIT
|
||||
trap 'set +e; cleanup; rm -f "${log}"' EXIT
|
||||
|
||||
# Ask htsserver for its rendered default base path, under the given $HOME
|
||||
# ("-" = leave $HOME unset).
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user