mirror of
https://github.com/xroche/httrack.git
synced 2026-07-27 19:12:54 +03:00
Compare commits
4 Commits
fix/warc-u
...
fix/refetc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ffe8d5582 | ||
|
|
6457828b72 | ||
|
|
f118cd86d3 | ||
|
|
1373530f17 |
@@ -585,6 +585,14 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Move src onto dst; RENAME does not clobber an existing target on Windows. */
|
||||
static hts_boolean replace_file(const char *src, const char *dst) {
|
||||
if (RENAME(src, dst) == 0)
|
||||
return HTS_TRUE;
|
||||
(void) UNLINK(dst);
|
||||
return RENAME(src, dst) == 0 ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
|
||||
/* Commit or restore a re-fetch backup (#77 follow-up): a re-fetch over an
|
||||
existing file moved the good copy to back->tmpfile before truncating url_sav.
|
||||
commit keeps the new file and drops the backup; else restore it so an aborted
|
||||
@@ -602,7 +610,7 @@ 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 (!replace_file(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);
|
||||
@@ -733,7 +741,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 (replace_file(unpacked, back[p].url_sav)) {
|
||||
/* The temp bypassed filecreate(), which is what chmods. */
|
||||
#ifndef _WIN32
|
||||
chmod(back[p].url_sav, HTS_ACCESS_FILE);
|
||||
|
||||
@@ -72,7 +72,7 @@ hts_codec hts_codec_parse(const char *encoding) {
|
||||
return HTS_CODEC_IDENTITY;
|
||||
if (strfield2(encoding, "gzip") || strfield2(encoding, "x-gzip") ||
|
||||
strfield2(encoding, "deflate") || strfield2(encoding, "x-deflate"))
|
||||
return HTS_CODEC_DEFLATE;
|
||||
return HTS_USEZLIB ? HTS_CODEC_DEFLATE : HTS_CODEC_UNSUPPORTED;
|
||||
if (strfield2(encoding, "br"))
|
||||
return HTS_USEBROTLI ? HTS_CODEC_BROTLI : HTS_CODEC_UNSUPPORTED;
|
||||
if (strfield2(encoding, "zstd"))
|
||||
@@ -98,11 +98,16 @@ hts_codec hts_codec_parse(const char *encoding) {
|
||||
const char *hts_acceptencoding(hts_boolean compressible, hts_boolean secure) {
|
||||
if (!compressible)
|
||||
return "identity";
|
||||
#if HTS_USEZLIB
|
||||
/* br and zstd over TLS only, as browsers do: a cleartext intermediary that
|
||||
rewrites a coding it can not read would corrupt the mirror. */
|
||||
if (secure)
|
||||
return "gzip, deflate" HTS_AE_BROTLI HTS_AE_ZSTD ", identity;q=0.9";
|
||||
return "gzip, deflate, identity;q=0.9";
|
||||
#else
|
||||
(void) secure;
|
||||
return "identity";
|
||||
#endif
|
||||
}
|
||||
|
||||
hts_boolean hts_codec_is_archive_ext(hts_codec codec, const char *ext) {
|
||||
@@ -295,7 +300,11 @@ int hts_codec_unpack(hts_codec codec, const char *filename,
|
||||
return -1;
|
||||
switch (codec) {
|
||||
case HTS_CODEC_DEFLATE:
|
||||
#if HTS_USEZLIB
|
||||
return hts_zunpack(filename, newfile);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
case HTS_CODEC_BROTLI:
|
||||
case HTS_CODEC_ZSTD:
|
||||
break;
|
||||
@@ -339,8 +348,10 @@ size_t hts_codec_head(hts_codec codec, const void *in, size_t in_len, void *out,
|
||||
if (in == NULL || in_len == 0 || out == NULL || out_len == 0)
|
||||
return 0;
|
||||
switch (codec) {
|
||||
#if HTS_USEZLIB
|
||||
case HTS_CODEC_DEFLATE:
|
||||
return hts_zhead(in, in_len, out, out_len);
|
||||
#endif
|
||||
#if HTS_USEBROTLI
|
||||
case HTS_CODEC_BROTLI:
|
||||
return codec_head_brotli(in, in_len, out, out_len);
|
||||
|
||||
@@ -2692,11 +2692,7 @@ HTSEXT_API int structcheck(const char *path) {
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
#if HTS_REMOVE_ANNOYING_INDEX
|
||||
if (S_ISREG(st.st_mode)) { /* Regular file in place ; move it and create directory */
|
||||
/* bounded here, not by the path-length guard far above */
|
||||
if (!sprintfbuff(tmpbuf, "%s.txt", file)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
sprintf(tmpbuf, "%s.txt", file);
|
||||
if (rename(file, tmpbuf) != 0) { /* Can't rename regular file */
|
||||
return -1;
|
||||
}
|
||||
@@ -2804,11 +2800,7 @@ HTSEXT_API int structcheck_utf8(const char *path) {
|
||||
if (!S_ISDIR(st.st_mode)) {
|
||||
#if HTS_REMOVE_ANNOYING_INDEX
|
||||
if (S_ISREG(st.st_mode)) { /* Regular file in place ; move it and create directory */
|
||||
/* bounded here, not by the path-length guard far above */
|
||||
if (!sprintfbuff(tmpbuf, "%s.txt", file)) {
|
||||
errno = ENAMETOOLONG;
|
||||
return -1;
|
||||
}
|
||||
sprintf(tmpbuf, "%s.txt", file);
|
||||
if (RENAME(file, tmpbuf) != 0) { /* Can't rename regular file */
|
||||
return -1;
|
||||
}
|
||||
@@ -3828,12 +3820,11 @@ int htsAddLink(htsmoduleStruct * str, char *link) {
|
||||
strcpybuff(codebase, heap(ptr)->fil);
|
||||
else
|
||||
strcpybuff(codebase, heap(heap(ptr)->precedent)->fil);
|
||||
// empty codebase has no last char; codebase-1 would underflow
|
||||
a = codebase[0] != '\0' ? codebase + strlen(codebase) - 1 : codebase;
|
||||
a = codebase + strlen(codebase) - 1;
|
||||
while((*a) && (*a != '/') && (a > codebase))
|
||||
a--;
|
||||
if (*a == '/')
|
||||
*(a + 1) = '\0'; // cut
|
||||
*(a + 1) = '\0'; // couper
|
||||
} else { // couper http:// éventuel
|
||||
if (strfield(codebase, "http://")) {
|
||||
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
|
||||
|
||||
@@ -1131,10 +1131,12 @@ int http_sendhead(httrackp * opt, t_cookie * cookie, int mode,
|
||||
|
||||
// Compression accepted ?
|
||||
if (retour->req.http11) {
|
||||
hts_boolean compressible =
|
||||
(!retour->req.range_used && !retour->req.nocompression);
|
||||
hts_boolean compressible = HTS_FALSE;
|
||||
hts_boolean secure = HTS_FALSE;
|
||||
|
||||
#if HTS_USEZLIB
|
||||
compressible = (!retour->req.range_used && !retour->req.nocompression);
|
||||
#endif
|
||||
#if HTS_USEOPENSSL
|
||||
secure = retour->ssl ? HTS_TRUE : HTS_FALSE;
|
||||
#endif
|
||||
|
||||
@@ -43,7 +43,9 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htsencoding.h"
|
||||
#include "htssniff.h"
|
||||
#include "htscodec.h"
|
||||
#if HTS_USEZLIB
|
||||
#include "htszlib.h"
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
#include "htsglobal.h"
|
||||
#include "htscore.h"
|
||||
#include "htsmodules.h"
|
||||
#include "htsback.h"
|
||||
#include "htsdefines.h"
|
||||
#include "htslib.h"
|
||||
@@ -64,7 +63,9 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htswarc.h"
|
||||
#include "htschanges.h"
|
||||
#include "htssinglefile.h"
|
||||
#if HTS_USEZLIB
|
||||
#include "htszlib.h"
|
||||
#endif
|
||||
#if HTS_USEZSTD
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
@@ -2622,68 +2623,6 @@ static int st_savename(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
cache_back cache;
|
||||
struct_back *sback;
|
||||
hash_struct hash;
|
||||
int ptr = 0;
|
||||
int i;
|
||||
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.hashtable = (void *) coucal_new(0);
|
||||
sback = back_new(opt, opt->maxsoc * 32 + 1024);
|
||||
/* same wiring as hts_mirror (htscore.c) */
|
||||
hash_init(opt, &hash, opt->urlhack);
|
||||
hash.liens = (const lien_url *const *const *) &opt->liens;
|
||||
opt->hash = &hash;
|
||||
hts_record_init(opt);
|
||||
|
||||
memset(&str, 0, sizeof(str));
|
||||
str.opt = opt;
|
||||
str.sback = sback;
|
||||
str.cache = &cache;
|
||||
str.hashptr = &hash;
|
||||
str.ptr_ = &ptr;
|
||||
str.addLink = htsAddLink;
|
||||
|
||||
/* [0] is the underflow; [1] and [2] are controls that the trim is unchanged.
|
||||
A query-only link is the one that notices the trim at all: for the others
|
||||
ident_url_relatif() re-derives the directory from the path it is given. */
|
||||
for (i = 0; i < 3; i++) {
|
||||
static const char *const fil[3] = {"", "/dir/page.html", "/dir/page.html"};
|
||||
static const char *const lnk[3] = {"sub/page.html", "sub/page.html",
|
||||
"?x=1"};
|
||||
static const char *const want[3] = {
|
||||
"untouched", "http://www.example.com/dir/sub/page.html",
|
||||
"http://www.example.com/dir/?x=1"};
|
||||
char BIGSTK loc[HTS_URLMAXSIZE * 2];
|
||||
char BIGSTK link[HTS_URLMAXSIZE];
|
||||
|
||||
strcpybuff(loc, "untouched");
|
||||
strcpybuff(link, lnk[i]);
|
||||
str.localLink = loc;
|
||||
str.localLinkSize = (int) sizeof(loc);
|
||||
if (!hts_record_link(opt, "www.example.com", fil[i], "", "", "", ""))
|
||||
return 1;
|
||||
ptr = heap_top_index();
|
||||
str.url_host = heap(ptr)->adr;
|
||||
str.url_file = heap(ptr)->fil;
|
||||
assertf(htsAddLink(&str, link) == 0); /* refused by the wizard either way */
|
||||
if (strcmp(loc, want[i]) != 0) {
|
||||
fprintf(stderr, "addlink[%d]: got '%s' want '%s'\n", i, loc, want[i]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
printf("addlink self-test OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_cache(httrackp *opt, int argc, char **argv) {
|
||||
int err;
|
||||
|
||||
@@ -3325,81 +3264,6 @@ static int st_topindex(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Build a path of exactly len chars under base; returns that length. */
|
||||
static size_t st_structcheck_longpath(char *dst, size_t dstsize,
|
||||
const char *base, size_t len) {
|
||||
size_t n = strlen(base);
|
||||
|
||||
assertf(len < dstsize && n + 2 <= len);
|
||||
memmove(dst, base, n);
|
||||
while (n < len) {
|
||||
size_t seg = len - n - 1;
|
||||
|
||||
if (seg > 200) /* stay under the usual 255-byte component limit */
|
||||
seg = len - n == 202 ? 199 : 200; /* never leave a bare separator */
|
||||
dst[n++] = '/';
|
||||
memset(dst + n, 'x', seg);
|
||||
n += seg;
|
||||
}
|
||||
dst[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
|
||||
/* The path guard, and the <name>.txt rename structcheck() performs when a
|
||||
regular file sits where a directory has to go (#745). */
|
||||
static int st_structcheck(httrackp *opt, int argc, char **argv) {
|
||||
char BIGSTK path[HTS_URLMAXSIZE * 2];
|
||||
char BIGSTK target[HTS_URLMAXSIZE * 2];
|
||||
FILE *fp;
|
||||
|
||||
(void) opt;
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "usage: -#test=structcheck <writable directory>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* over the guard: refused before a single directory is created */
|
||||
st_structcheck_longpath(path, sizeof(path), argv[0], HTS_URLMAXSIZE + 1);
|
||||
errno = 0;
|
||||
assertf(structcheck(path) == -1);
|
||||
assertf(errno == EINVAL);
|
||||
errno = 0;
|
||||
assertf(structcheck_utf8(path) == -1);
|
||||
assertf(errno == EINVAL);
|
||||
{
|
||||
char *const sep = strchr(path + strlen(argv[0]) + 1, '/');
|
||||
|
||||
assertf(sep != NULL);
|
||||
sep[1] = '\0'; /* the outermost component it would have created */
|
||||
assertf(!dir_exists(path));
|
||||
}
|
||||
|
||||
/* a regular file where a directory belongs is renamed to <name>.txt */
|
||||
snprintf(path, sizeof(path), "%s/sc", argv[0]);
|
||||
fp = fopen(path, "wb");
|
||||
assertf(fp != NULL);
|
||||
fclose(fp);
|
||||
snprintf(path, sizeof(path), "%s/sc/sub/", argv[0]);
|
||||
assertf(structcheck(path) == 0);
|
||||
assertf(dir_exists(path));
|
||||
snprintf(target, sizeof(target), "%s/sc.txt", argv[0]);
|
||||
assertf(fexist(target));
|
||||
|
||||
/* the utf-8 entry point carries the same rename */
|
||||
snprintf(path, sizeof(path), "%s/u8", argv[0]);
|
||||
fp = FOPEN(path, "wb");
|
||||
assertf(fp != NULL);
|
||||
fclose(fp);
|
||||
snprintf(path, sizeof(path), "%s/u8/sub/", argv[0]);
|
||||
assertf(structcheck_utf8(path) == 0);
|
||||
assertf(dir_exists(path));
|
||||
snprintf(target, sizeof(target), "%s/u8.txt", argv[0]);
|
||||
assertf(fexist_utf8(target));
|
||||
|
||||
printf("structcheck self-test OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Each inplace_escape_*() must equal escape_*() on a copy. */
|
||||
static int st_inplace_escape(httrackp *opt, int argc, char **argv) {
|
||||
/* >255 bytes forces the helper's malloct path, not the stack buffer */
|
||||
@@ -3513,6 +3377,7 @@ static int st_status(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HTS_USEZLIB
|
||||
/* Deflate src->path at windowBits (16+ gzip, + zlib, - raw); 0 on success. */
|
||||
static int ae_write_packed(const char *path, int windowBits,
|
||||
const unsigned char *src, size_t len) {
|
||||
@@ -3586,6 +3451,7 @@ static int ae_write_collision(const char *path, const unsigned char *src,
|
||||
freet(buf);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Write src[0..len) to path as-is; 0 on success. */
|
||||
static int ae_write_raw(const char *path, const unsigned char *src,
|
||||
@@ -3635,6 +3501,7 @@ static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
|
||||
assertf(strstr(on, "br") == NULL && strstr(on, "zstd") == NULL);
|
||||
assertf((strstr(tls, ", br") != NULL) == (HTS_USEBROTLI != 0));
|
||||
assertf((strstr(tls, "zstd") != NULL) == (HTS_USEZSTD != 0));
|
||||
#if HTS_USEZLIB
|
||||
if (argc >= 1) {
|
||||
static const int windowBits[] = {16 + MAX_WBITS, MAX_WBITS, -MAX_WBITS};
|
||||
const unsigned char small[] =
|
||||
@@ -3713,6 +3580,10 @@ static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
|
||||
}
|
||||
freet(body);
|
||||
}
|
||||
#else
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
#endif
|
||||
printf("acceptencoding self-test OK: %s\n", on);
|
||||
return 0;
|
||||
}
|
||||
@@ -4057,6 +3928,7 @@ static int st_sitemap(httrackp *opt, int argc, char **argv) {
|
||||
freet(big);
|
||||
}
|
||||
|
||||
#if HTS_USEZLIB
|
||||
/* A highly compressible document decodes without running away: the ratio
|
||||
budget cannot bind (deflate tops out near 1032:1), so this pins the
|
||||
decompression path itself rather than the 64 MiB ceiling. */
|
||||
@@ -4107,11 +3979,13 @@ static int st_sitemap(httrackp *opt, int argc, char **argv) {
|
||||
freet(z);
|
||||
freet(x);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* An unterminated <loc> at end of buffer must not read past it. */
|
||||
assertf(sm_scan("<urlset><loc>http://h.test/a", 100, &idx, &c) == 0);
|
||||
assertf(sm_scan("<urlset><lo", 100, &idx, &c) == 0);
|
||||
|
||||
#if HTS_USEZLIB
|
||||
/* A gzip-framed document is decompressed before scanning. */
|
||||
{
|
||||
const char *const xml =
|
||||
@@ -4141,6 +4015,7 @@ static int st_sitemap(httrackp *opt, int argc, char **argv) {
|
||||
assertf(hts_sitemap_scan(z, 4, 100, &idx, sm_take, &c) == -1);
|
||||
freet(z);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* robots.txt: only Sitemap: records, comments stripped, case-insensitive,
|
||||
and group-independent (no User-agent line needed). */
|
||||
@@ -6214,116 +6089,6 @@ static int st_changes(httrackp *opt, int argc, char **argv) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* #747: a thread is outstanding from the moment hts_newthread() returns, not
|
||||
from the moment it starts running, or a wait right after the spawn joins
|
||||
nothing. One thread per round is what makes the old bug visible: the wait
|
||||
had to find the counter at zero, and a batch of spawns gives the earlier
|
||||
threads time to raise it. Unfixed, one round in two caught it, so the round
|
||||
count is what turns that into a reliable failure. */
|
||||
#define THREADWAIT_N 8
|
||||
#define THREADWAIT_ROUNDS 16
|
||||
#define THREADWAIT_SLEEP_MS 50
|
||||
#define THREADWAIT_GATE_MS 10000
|
||||
|
||||
static htsmutex threadwait_lock = HTSMUTEX_INIT;
|
||||
static int threadwait_done = 0;
|
||||
static hts_boolean threadwait_gated = HTS_FALSE;
|
||||
|
||||
static int threadwait_count(void) {
|
||||
int n;
|
||||
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
n = threadwait_done;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
return n;
|
||||
}
|
||||
|
||||
static void threadwait_thread(void *arg) {
|
||||
(void) arg;
|
||||
Sleep(THREADWAIT_SLEEP_MS);
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
threadwait_done++;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
}
|
||||
|
||||
/* Stays outstanding until the gate clears, so wait_n() can be asked to leave a
|
||||
known number of live threads behind. Bounded: a wait_n() that wrongly drains
|
||||
them would otherwise never return, and hang the suite instead of failing. */
|
||||
static void threadwait_gated_thread(void *arg) {
|
||||
int waited;
|
||||
|
||||
(void) arg;
|
||||
for (waited = 0; waited < THREADWAIT_GATE_MS; waited += 10) {
|
||||
hts_boolean gated;
|
||||
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
gated = threadwait_gated;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
if (!gated)
|
||||
break;
|
||||
Sleep(10);
|
||||
}
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
threadwait_done++;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
}
|
||||
|
||||
static int st_threadwait(httrackp *opt, int argc, char **argv) {
|
||||
int err = 0;
|
||||
int i, round;
|
||||
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
/* htsthread_wait() joins a thread spawned just before it */
|
||||
for (round = 0; round < THREADWAIT_ROUNDS && !err; round++) {
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
threadwait_done = 0;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
if (hts_newthread(threadwait_thread, NULL) != 0) {
|
||||
fprintf(stderr, "threadwait: cannot spawn\n");
|
||||
return 1;
|
||||
}
|
||||
htsthread_wait();
|
||||
if (threadwait_count() != 1) {
|
||||
fprintf(stderr, "threadwait: round %d returned before the thread ran\n",
|
||||
round);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* htsthread_wait_n(n) leaves n behind rather than draining everything */
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
threadwait_done = 0;
|
||||
threadwait_gated = HTS_TRUE;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
for (i = 0; i < THREADWAIT_N; i++) {
|
||||
if (hts_newthread(threadwait_gated_thread, NULL) != 0) {
|
||||
fprintf(stderr, "threadwait: cannot spawn a gated thread\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
htsthread_wait_n(THREADWAIT_N);
|
||||
if (threadwait_count() != 0) {
|
||||
fprintf(stderr, "threadwait: wait_n(%d) joined %d gated threads\n",
|
||||
THREADWAIT_N, threadwait_count());
|
||||
err = 1;
|
||||
}
|
||||
hts_mutexlock(&threadwait_lock);
|
||||
threadwait_gated = HTS_FALSE;
|
||||
hts_mutexrelease(&threadwait_lock);
|
||||
htsthread_wait();
|
||||
if (threadwait_count() != THREADWAIT_N) {
|
||||
fprintf(stderr, "threadwait: wait left %d/%d gated threads running\n",
|
||||
THREADWAIT_N - threadwait_count(), THREADWAIT_N);
|
||||
err = 1;
|
||||
}
|
||||
|
||||
printf("threadwait self-test: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
#define CHANGES_RACE_FILES 8
|
||||
#define CHANGES_RACE_ROUNDS 400
|
||||
|
||||
@@ -6338,8 +6103,11 @@ static void changes_race_notify(httrackp *opt, int n) {
|
||||
hts_changes_notify(opt, "race.example", fil, save, HTS_TRUE, HTS_FALSE);
|
||||
}
|
||||
|
||||
/* htsthread_wait() counts a thread only once it is running, so it can return
|
||||
before any of them started; join on our own counter instead. */
|
||||
static htsmutex changes_race_lock = HTSMUTEX_INIT;
|
||||
static int changes_race_started = 0;
|
||||
static int changes_race_live = 0;
|
||||
|
||||
static int changes_race_count(int *which) {
|
||||
int n;
|
||||
@@ -6359,6 +6127,9 @@ static void changes_race_thread(void *arg) {
|
||||
hts_mutexrelease(&changes_race_lock);
|
||||
for (i = 0; i < CHANGES_RACE_ROUNDS; i++)
|
||||
changes_race_notify(opt, i % CHANGES_RACE_FILES);
|
||||
hts_mutexlock(&changes_race_lock);
|
||||
changes_race_live--;
|
||||
hts_mutexrelease(&changes_race_lock);
|
||||
}
|
||||
|
||||
/* A transfer thread the crawl never joins (FTP) reaches hts_changes_notify()
|
||||
@@ -6413,6 +6184,7 @@ static int st_changes_race(httrackp *opt, int argc, char **argv) {
|
||||
threads reaching a fresh one together race on the init itself. */
|
||||
hts_mutexlock(&changes_race_lock);
|
||||
changes_race_started = 0;
|
||||
changes_race_live = 4;
|
||||
hts_mutexrelease(&changes_race_lock);
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (hts_newthread(changes_race_thread, opt) != 0) {
|
||||
@@ -6426,7 +6198,8 @@ static int st_changes_race(httrackp *opt, int argc, char **argv) {
|
||||
for (i = 0; i < 64; i++)
|
||||
hts_changes_report(opt, &out);
|
||||
hts_changes_close_opt(opt);
|
||||
htsthread_wait();
|
||||
while (changes_race_count(&changes_race_live) > 0)
|
||||
Sleep(10);
|
||||
|
||||
/* Sealed: a straggler must be dropped, not start a report nobody writes. */
|
||||
changes_race_notify(opt, CHANGES_RACE_FILES + 1);
|
||||
@@ -6504,8 +6277,6 @@ static const struct selftest_entry {
|
||||
st_changes},
|
||||
{"changes-race", "<dir>", "--changes under a late transfer thread (#714)",
|
||||
st_changes_race},
|
||||
{"threadwait", "", "htsthread_wait() joins threads spawned just before it",
|
||||
st_threadwait},
|
||||
{"pause", "", "randomized inter-file pause target self-test", st_pause},
|
||||
{"relative", "<link> <curr-file>", "relative link between two paths",
|
||||
st_relative},
|
||||
@@ -6536,8 +6307,6 @@ static const struct selftest_entry {
|
||||
{"fsize", "<dir>", "file size past the 2GB signed-32-bit wrap", st_fsize},
|
||||
{"growsize", "", "buffer capacity for a 64-bit file size (no int wrap)",
|
||||
st_growsize},
|
||||
{"addlink", "", "htsAddLink codebase walk over an empty current path",
|
||||
st_addlink},
|
||||
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
|
||||
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
|
||||
st_cacheindex},
|
||||
@@ -6561,9 +6330,6 @@ static const struct selftest_entry {
|
||||
{"useragent", "", "default User-Agent self-test", st_useragent},
|
||||
{"makeindex", "[dir]", "hts_finish_makeindex footer/refresh self-test",
|
||||
st_makeindex},
|
||||
{"structcheck", "<dir>",
|
||||
"structcheck path guard and the <name>.txt rename it performs",
|
||||
st_structcheck},
|
||||
{"topindex", "[dir]",
|
||||
"hts_buildtopindex charset handling of a non-ASCII project dir",
|
||||
st_topindex},
|
||||
|
||||
@@ -1108,8 +1108,13 @@ hts_boolean singlefile_rewrite_file(httrackp *opt, const char *root,
|
||||
(void) chmod(fconv(catbuff, sizeof(catbuff), StringBuff(tmp)),
|
||||
HTS_ACCESS_FILE);
|
||||
#endif
|
||||
if (ok)
|
||||
ok = hts_rename_over(StringBuff(tmp), page_path);
|
||||
if (ok) {
|
||||
/* RENAME does not clobber an existing target on Windows. */
|
||||
if (RENAME(StringBuff(tmp), page_path) != 0) {
|
||||
(void) UNLINK(page_path);
|
||||
ok = RENAME(StringBuff(tmp), page_path) == 0 ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
}
|
||||
if (!ok) {
|
||||
hts_log_print(opt, LOG_ERROR, "single-file: could not rewrite %s",
|
||||
page_path);
|
||||
|
||||
@@ -1443,15 +1443,3 @@ HTSEXT_API hts_boolean hts_findissystem(find_handle find) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
hts_boolean hts_rename_over(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;
|
||||
/* RENAME does not clobber an existing target on Windows. */
|
||||
(void) UNLINK(cdst);
|
||||
return RENAME(csrc, cdst) == 0 ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
|
||||
@@ -137,10 +137,6 @@ 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. */
|
||||
hts_boolean hts_rename_over(const char *src, const char *dst);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
149
src/htswarc.c
149
src/htswarc.c
@@ -57,9 +57,6 @@ Please visit our Website: http://www.httrack.com
|
||||
/* opt->state.warc value meaning "open failed once, do not retry". */
|
||||
#define WARC_DISABLED ((void *) ~(uintptr_t) 0)
|
||||
|
||||
/* Suffix of the in-progress archive when a previous one must survive it. */
|
||||
#define WARC_TMP_SUFFIX ".tmp"
|
||||
|
||||
struct warc_writer {
|
||||
FILE *f;
|
||||
httrackp *opt; /* kept for close-time logging (warc_wacz_package) */
|
||||
@@ -89,11 +86,6 @@ struct warc_writer {
|
||||
char *base_path; /* resolved archive path minus .warc[.gz] suffix */
|
||||
const char *base_ext; /* ".warc.gz" or ".warc" (static) */
|
||||
char *arc_path; /* full single-file archive path (NULL under rotation) */
|
||||
/* A re-run must not destroy an archive it cannot replace (#759). */
|
||||
hts_boolean protect_prev; /* previous archive present: build in a temp */
|
||||
hts_boolean opened; /* open completed; a failed one swaps nothing */
|
||||
hts_boolean failed; /* a record or segment was lost: swap nothing */
|
||||
uint64_t unbacked_revisits; /* revisits whose payload no file here holds */
|
||||
char **page_lines; /* one JSON page line per 200 text/html response, owned */
|
||||
size_t page_count;
|
||||
size_t page_cap;
|
||||
@@ -972,6 +964,16 @@ static zipFile wacz_zip_open(const char *path) {
|
||||
return zipOpen2_64(path, 0 /*create*/, NULL, &ff);
|
||||
}
|
||||
|
||||
/* Move src onto dst (UTF-8/Windows-safe); RENAME won't clobber on Windows, so
|
||||
fall back to unlink+rename. Returns 0 on success. */
|
||||
static int wacz_rename_over(const char *src, const char *dst) {
|
||||
char cs[CATBUFF_SIZE], cd[CATBUFF_SIZE];
|
||||
if (RENAME(fconv(cs, sizeof(cs), src), fconv(cd, sizeof(cd), dst)) == 0)
|
||||
return 0;
|
||||
(void) UNLINK(fconv(cd, sizeof(cd), dst));
|
||||
return RENAME(fconv(cs, sizeof(cs), src), fconv(cd, sizeof(cd), dst));
|
||||
}
|
||||
|
||||
/* Package the segment(s) + .cdx + a generated pages.jsonl into <base>.wacz at
|
||||
crawl end (the archive file(s) and .cdx are already closed on disk). */
|
||||
static void warc_wacz_package(warc_writer *w) {
|
||||
@@ -1090,7 +1092,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 (wacz_rename_over(tmppath, waczpath) != 0) {
|
||||
(void) UNLINK(fconv(catbuff, sizeof(catbuff), tmppath));
|
||||
hts_log_print(w->opt, LOG_WARNING | LOG_ERRNO,
|
||||
"WACZ: could not finalize %s", waczpath);
|
||||
@@ -1137,17 +1139,13 @@ static int warc_emit(warc_writer *w, const char *type, const char *content_type,
|
||||
never split a record, and never rotate a warcinfo (it opens a segment). */
|
||||
if (w->max_size > 0 && w->seg_base != NULL && w->offset >= w->max_size &&
|
||||
strcmp(type, "warcinfo") != 0) {
|
||||
if (warc_rotate(w) != 0) {
|
||||
w->failed = HTS_TRUE;
|
||||
if (warc_rotate(w) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* F4: overflow-safe block length; http_hdr_len+sep is provably small. */
|
||||
if (payload > (size_t) -1 - http_hdr_len - sep) {
|
||||
w->failed = HTS_TRUE;
|
||||
if (payload > (size_t) -1 - http_hdr_len - sep)
|
||||
return -1;
|
||||
}
|
||||
block_len = http_hdr_len + sep + payload;
|
||||
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
@@ -1253,23 +1251,11 @@ static int warc_emit(warc_writer *w, const char *type, const char *content_type,
|
||||
rc = 0;
|
||||
done:
|
||||
wbuf_free(&hdr);
|
||||
if (rc != 0)
|
||||
w->failed = HTS_TRUE; /* a truncated run must not replace a whole one */
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ---- segment rotation (--warc-max-size) ---- */
|
||||
|
||||
/* Path to open for the segment whose final path is `final`: that path itself,
|
||||
or a sibling temp while a previous archive must survive until close. */
|
||||
static const char *warc_open_path(warc_writer *w, const char *final, char *buf,
|
||||
size_t bufsz) {
|
||||
if (!w->protect_prev)
|
||||
return final;
|
||||
snprintf(buf, bufsz, "%s" WARC_TMP_SUFFIX, final);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Emit the warcinfo that heads a segment; sets w->info_id for its records. */
|
||||
static int warc_write_warcinfo_record(warc_writer *w) {
|
||||
w->info_id[0] = '\0'; /* warcinfo itself carries no WARC-Warcinfo-ID */
|
||||
@@ -1281,23 +1267,17 @@ static int warc_write_warcinfo_record(warc_writer *w) {
|
||||
|
||||
static int warc_rotate(warc_writer *w) {
|
||||
char namebuf[HTS_URLMAXSIZE * 2];
|
||||
char openbuf[HTS_URLMAXSIZE * 2 + sizeof(WARC_TMP_SUFFIX)];
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
const unsigned next = w->seg + 1;
|
||||
FILE *f;
|
||||
snprintf(namebuf, sizeof(namebuf), "%s-%05u%s", w->seg_base, next,
|
||||
w->seg_ext);
|
||||
/* Open before advancing: w->seg must only ever name a segment that exists,
|
||||
or close-time packaging and swapping would work on a missing file. */
|
||||
f = FOPEN(fconv(catbuff, sizeof(catbuff),
|
||||
warc_open_path(w, namebuf, openbuf, sizeof(openbuf))),
|
||||
"wb");
|
||||
if (f == NULL)
|
||||
return -1;
|
||||
if (w->f != NULL)
|
||||
if (w->f != NULL) {
|
||||
fclose(w->f);
|
||||
w->f = f;
|
||||
w->seg = next;
|
||||
w->f = NULL;
|
||||
}
|
||||
w->seg++;
|
||||
snprintf(namebuf, sizeof(namebuf), "%s-%05u%s", w->seg_base, w->seg,
|
||||
w->seg_ext);
|
||||
w->f = FOPEN(fconv(catbuff, sizeof(catbuff), namebuf), "wb");
|
||||
if (w->f == NULL)
|
||||
return -1;
|
||||
w->offset = 0;
|
||||
if (w->cdx_on) {
|
||||
freet(w->cur_seg);
|
||||
@@ -1354,7 +1334,6 @@ void warc_adopt_rawspool(htsblk *r, const char *tmpfile_path) {
|
||||
warc_writer *warc_open(httrackp *opt, const char *path) {
|
||||
warc_writer *w;
|
||||
char namebuf[HTS_URLMAXSIZE * 2];
|
||||
char openbuf[HTS_URLMAXSIZE * 2 + sizeof(WARC_TMP_SUFFIX)];
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
wbuf info;
|
||||
const char *robots;
|
||||
@@ -1505,105 +1484,35 @@ warc_writer *warc_open(httrackp *opt, const char *path) {
|
||||
path = namebuf;
|
||||
}
|
||||
|
||||
if (w->max_size == 0 && (w->arc_path = strdupt(path)) == NULL) {
|
||||
warc_close(w);
|
||||
return NULL;
|
||||
}
|
||||
/* Set only once arc_path is recorded, so a half-built writer never swaps. */
|
||||
w->protect_prev = fsize_utf8(path) > 0 ? HTS_TRUE : HTS_FALSE;
|
||||
w->f = FOPEN(fconv(catbuff, sizeof(catbuff),
|
||||
warc_open_path(w, path, openbuf, sizeof(openbuf))),
|
||||
"wb");
|
||||
w->f = FOPEN(fconv(catbuff, sizeof(catbuff), path), "wb");
|
||||
if (w->f == NULL) {
|
||||
warc_close(w);
|
||||
return NULL;
|
||||
}
|
||||
if (w->cdx_on)
|
||||
w->cur_seg = path_basename_dup(path);
|
||||
if (w->wacz_on && w->max_size == 0)
|
||||
w->arc_path = strdupt(path); /* single-file: package this exact path */
|
||||
|
||||
if (warc_write_warcinfo_record(w) != 0) {
|
||||
warc_close(w);
|
||||
return NULL;
|
||||
}
|
||||
w->opened = HTS_TRUE;
|
||||
return w;
|
||||
}
|
||||
|
||||
/* Final path of segment s (the run's only archive when rotation is off). */
|
||||
static const char *warc_seg_path(warc_writer *w, unsigned s, char *buf,
|
||||
size_t bufsz) {
|
||||
if (w->max_size == 0)
|
||||
return w->arc_path;
|
||||
snprintf(buf, bufsz, "%s-%05u%s", w->seg_base, s, w->seg_ext);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Swap this run's archive into place, unless it only holds revisits naming
|
||||
bodies the previous archive still has and this one does not (#759).
|
||||
HTS_FALSE: the previous archive was kept, so leave its .cdx and .wacz too. */
|
||||
static hts_boolean warc_commit(warc_writer *w) {
|
||||
char finalbuf[HTS_URLMAXSIZE * 2];
|
||||
char tmpbuf[HTS_URLMAXSIZE * 2 + sizeof(WARC_TMP_SUFFIX)];
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
const unsigned nseg = (w->max_size > 0) ? w->seg + 1 : 1;
|
||||
hts_boolean swap;
|
||||
unsigned s;
|
||||
|
||||
if (!w->protect_prev)
|
||||
return HTS_TRUE; /* nothing was there to lose: written in place */
|
||||
|
||||
/* hts_rename_over unlinks its destination when the source is missing, so
|
||||
every segment has to be on disk before the first rename. */
|
||||
swap = w->opened && !w->failed && w->unbacked_revisits == 0;
|
||||
for (s = 0; s < nseg && swap; s++) {
|
||||
snprintf(tmpbuf, sizeof(tmpbuf), "%s" WARC_TMP_SUFFIX,
|
||||
warc_seg_path(w, s, finalbuf, sizeof(finalbuf)));
|
||||
swap = fsize_utf8(tmpbuf) > 0;
|
||||
}
|
||||
|
||||
if (swap) {
|
||||
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)) {
|
||||
hts_log_print(w->opt, LOG_ERROR | LOG_ERRNO,
|
||||
"WARC: could not replace %s", final);
|
||||
return HTS_FALSE;
|
||||
}
|
||||
}
|
||||
return HTS_TRUE;
|
||||
}
|
||||
|
||||
for (s = 0; s < nseg; s++) {
|
||||
snprintf(tmpbuf, sizeof(tmpbuf), "%s" WARC_TMP_SUFFIX,
|
||||
warc_seg_path(w, s, finalbuf, sizeof(finalbuf)));
|
||||
(void) UNLINK(fconv(catbuff, sizeof(catbuff), tmpbuf));
|
||||
}
|
||||
if (w->unbacked_revisits > 0)
|
||||
hts_log_print(
|
||||
w->opt, LOG_ERROR,
|
||||
"WARC: this pass revisited %llu URL(s) without re-downloading them, so "
|
||||
"its archive would name bodies no file holds; kept the previous %s "
|
||||
"(re-run with -C0, or --warc-file with a name of its own)",
|
||||
(unsigned long long) w->unbacked_revisits,
|
||||
warc_seg_path(w, 0, finalbuf, sizeof(finalbuf)));
|
||||
return HTS_FALSE;
|
||||
}
|
||||
|
||||
void warc_close(warc_writer *w) {
|
||||
size_t i;
|
||||
if (w == NULL)
|
||||
return;
|
||||
warc_cdx_flush(w); /* sort + write <base>.cdx before tearing down */
|
||||
if (w->f != NULL)
|
||||
fclose(w->f);
|
||||
w->f = NULL;
|
||||
if (warc_commit(w)) {
|
||||
warc_cdx_flush(w); /* sort + write <base>.cdx beside the archive */
|
||||
#if HTS_USEOPENSSL
|
||||
if (w->wacz_on) /* package once the segment(s) + .cdx are closed on disk */
|
||||
warc_wacz_package(w);
|
||||
if (w->wacz_on) /* package once the segment(s) + .cdx are closed on disk */
|
||||
warc_wacz_package(w);
|
||||
#endif
|
||||
}
|
||||
if (w->seen != NULL)
|
||||
coucal_delete(&w->seen);
|
||||
for (i = 0; i < w->cdx_count; i++)
|
||||
@@ -1684,8 +1593,6 @@ int warc_write_transaction(warc_writer *w, const char *target_uri,
|
||||
if (is_update_unchanged) {
|
||||
is_revisit = 1;
|
||||
profile = "http://netpreserve.org/warc/1.1/revisit/server-not-modified";
|
||||
/* Served from cache: the payload sits in the previous archive, not here. */
|
||||
w->unbacked_revisits++;
|
||||
} else if (have_pdig && w->seen != NULL) {
|
||||
void *prev = NULL;
|
||||
if (coucal_read_pvoid(w->seen, pdig, &prev) && prev != NULL) {
|
||||
|
||||
21
src/htsweb.c
21
src/htsweb.c
@@ -101,8 +101,8 @@ static void htsweb_sig_brpipe(int code) {
|
||||
/* ignore */
|
||||
}
|
||||
|
||||
/* Threads that never return; no wait may count on them draining. */
|
||||
static int nonjoinable_threads = 0;
|
||||
/* Number of background threads */
|
||||
static int background_threads = 0;
|
||||
|
||||
/* Server/client ping handling */
|
||||
static htsmutex pingMutex = HTSMUTEX_INIT;
|
||||
@@ -224,7 +224,9 @@ int main(int argc, char *argv[]) {
|
||||
#ifdef HTS_USESWF
|
||||
smallserver_setkey("USESWF", "1");
|
||||
#endif
|
||||
#ifdef HTS_USEZLIB
|
||||
smallserver_setkey("USEZLIB", "1");
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
smallserver_setkey("WIN32", "1");
|
||||
#endif
|
||||
@@ -297,19 +299,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
/* pinger */
|
||||
if (parentPid > 0) {
|
||||
if (hts_newthread(client_ping, (void *) (uintptr_t) parentPid) == 0) {
|
||||
#ifndef _WIN32
|
||||
nonjoinable_threads++; /* client_ping() only ever leaves through exit() */
|
||||
#endif
|
||||
}
|
||||
hts_newthread(client_ping, (void *) (uintptr_t) parentPid);
|
||||
background_threads++; /* Do not wait for this thread! */
|
||||
smallserver_setpinghandler(pingHandler, NULL);
|
||||
}
|
||||
|
||||
/* launch */
|
||||
ret = help_server(argv[1], defaultPort, bindAddr);
|
||||
|
||||
/* Drain everything a mirror may still have in flight, the pinger aside. */
|
||||
htsthread_wait_n(nonjoinable_threads);
|
||||
htsthread_wait_n(background_threads - 1);
|
||||
hts_uninit();
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -384,6 +382,7 @@ void webhttrack_main(char *cmd) {
|
||||
commandRunning = 1;
|
||||
DEBUG(fprintf(stderr, "commandRunning=1\n"));
|
||||
hts_newthread(back_launch_cmd, (void *) strdup(cmd));
|
||||
background_threads++; /* Do not wait for this thread! */
|
||||
}
|
||||
|
||||
void webhttrack_lock(void) {
|
||||
@@ -424,8 +423,8 @@ static int webhttrack_runmain(httrackp * opt, int argc, char **argv) {
|
||||
/* Rock'in! */
|
||||
ret = hts_main2(argc, argv, opt);
|
||||
|
||||
/* Wait for pending threads to finish; the pinger and this thread stay. */
|
||||
htsthread_wait_n(nonjoinable_threads + 1);
|
||||
/* Wait for pending threads to finish */
|
||||
htsthread_wait_n(background_threads);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htscodec.h"
|
||||
#include "htszlib.h"
|
||||
|
||||
#if HTS_USEZLIB
|
||||
/* zlib */
|
||||
/*
|
||||
#include <zlib.h>
|
||||
@@ -273,3 +274,4 @@ const char *hts_get_zerror(int err) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# an empty current path underflowed htsAddLink's codebase walk (#730).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
httrack -O /dev/null -#test=addlink | grep -q "addlink self-test OK"
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# structcheck(): the path-length guard, and the <name>.txt rename that once
|
||||
# built its target with an unbounded sprintf (#745).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
dir=$(mktemp -d)
|
||||
trap 'rm -rf "$dir"' EXIT
|
||||
|
||||
httrack -O /dev/null -#test=structcheck "$dir" |
|
||||
grep -q "structcheck self-test OK"
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_threadwait_st.XXXXXX") || exit 1
|
||||
trap 'rm -rf "$tmpdir"' EXIT HUP INT QUIT PIPE TERM
|
||||
|
||||
# No pipe into grep: SIGPIPE would mask a failing exit status.
|
||||
expect_ok() {
|
||||
local label="$1" out
|
||||
shift
|
||||
out=$("$@" 2>&1) || {
|
||||
echo "FAIL: ${label} exited non-zero: ${out}"
|
||||
exit 1
|
||||
}
|
||||
case "$out" in
|
||||
*"${label}: OK"*) ;;
|
||||
*)
|
||||
echo "FAIL: ${out}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# A thread is outstanding from the moment hts_newthread() returns, so a wait
|
||||
# that follows the spawn joins it, and wait_n(n) still leaves n behind (#747).
|
||||
expect_ok "threadwait self-test" httrack -O "${tmpdir}/o1" -#test=threadwait
|
||||
@@ -18,7 +18,6 @@ set -euo pipefail
|
||||
bash "$top_srcdir/tests/local-crawl.sh" \
|
||||
--rerun-args '--update -M400000' \
|
||||
--log-found 'More than 400000 bytes have been transferred.. giving up' \
|
||||
--log-not-found 'could not restore' \
|
||||
--found bigtrunc/slow.bin \
|
||||
--file-min-bytes bigtrunc/slow.bin 655360 \
|
||||
--file-min-bytes bigtrunc/fast.bin 655360 \
|
||||
|
||||
@@ -79,29 +79,6 @@ done
|
||||
# 65535 is a valid port the old "< 65535" bound refused
|
||||
web_accepted 65535
|
||||
|
||||
# --- htsserver returns from main() instead of blocking at exit -------------
|
||||
# The exit wait must exclude exactly the threads that never return (#753).
|
||||
# $tmp has no lang.def, so the server fails right after announcing and main()
|
||||
# reaches the wait on its own; rc, not EXITED, carries the verdict.
|
||||
|
||||
web_exits() {
|
||||
local rc=0
|
||||
|
||||
: >"$tmp/exit.log"
|
||||
run_with_timeout 30 htsserver "$tmp" "$@" >"$tmp/exit.log" 2>&1 || rc=$?
|
||||
grep -q "^EXITED" "$tmp/exit.log" ||
|
||||
! echo "FAIL: #753: htsserver ${*:-(no options)} never finished serving" || exit 1
|
||||
# exactly 1, not merely "not 124": a crash or an assertf abort also escapes
|
||||
# the wait, and would otherwise read as a pass
|
||||
test "$rc" -eq 1 ||
|
||||
! echo "FAIL: #753: htsserver ${*:-(no options)} exited $rc, want 1" || exit 1
|
||||
}
|
||||
|
||||
# no pinger: the excluded count went negative
|
||||
web_exits
|
||||
# a pinger, which never returns and so must stay excluded
|
||||
web_exits --ppid $$
|
||||
|
||||
# --- proxytrack <proxy-addr:port> <ICP-addr:port> --------------------------
|
||||
# A bad argument falls through to the usage screen; it had no range check at
|
||||
# all, so 65616 quietly listened on port 80. A valid one binds and blocks.
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A --warc crawl writes a standards-conformant WARC/1.1 archive, and a re-crawl
|
||||
# of an unchanged (all-304) site emits revisit records. The stdlib validator (no
|
||||
# warcio) is the real gate: it byte-compares the fresh page.html response body
|
||||
# against what the server served, asserts the encoding headers were stripped and
|
||||
# the payload digest matches, then checks the second pass turned the unchanged
|
||||
# assets into revisits (no full response).
|
||||
#
|
||||
# That second pass archives under a name of its own, which is also the recovery
|
||||
# the engine points at when it refuses to strand a previous archive (#759).
|
||||
# A --warc crawl writes a standards-conformant WARC/1.1 archive, and an
|
||||
# --update re-crawl of an unchanged (all-304) site emits revisit records.
|
||||
# The stdlib validator (no warcio) is the real gate: it byte-compares the
|
||||
# fresh page.html response body against what the server served, asserts the
|
||||
# encoding headers were stripped and the payload digest matches, then checks
|
||||
# the update pass turned the unchanged assets into revisits (no full response).
|
||||
|
||||
set -eu
|
||||
|
||||
@@ -17,10 +14,8 @@ set -eu
|
||||
# page.html body served by tests/local-server.py (route_mini304_page).
|
||||
export WARC_VALIDATE_BODY="page.html=3c68746d6c3e3c626f64793e74696e7920636163686561626c6520706167653c2f626f64793e3c2f68746d6c3e0a"
|
||||
export WARC_VALIDATE_NORESP="index.html page.html"
|
||||
export WARC_VALIDATE_UPDATE="warc-update.warc.gz"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --warc-validate \
|
||||
--rerun-args '--warc-file warc-update' \
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --rerun --warc-validate \
|
||||
--log-found 'no files updated' \
|
||||
--found 'mini304/index.html' --found 'mini304/page.html' \
|
||||
httrack 'BASEURL/mini304/index.html' --warc-file warc-out
|
||||
|
||||
@@ -5,17 +5,11 @@
|
||||
# real gate: STORE-mode entries, the fixed layout, recomputed sha256 digests and
|
||||
# the datapackage-digest chain (plus py-wacz/pywb when importable). The crawl
|
||||
# skips cleanly on a build without OpenSSL (no conformant SHA-256 -> no package).
|
||||
#
|
||||
# The cacheless (-C0) second pass repackages over the .wacz the first one left:
|
||||
# the clobber, not the happy rename, is what breaks when a platform refuses to
|
||||
# rename onto an existing file (#726). A cache-served pass keeps the previous
|
||||
# package instead, which is 97_local-warc-update-keep.
|
||||
|
||||
set -eu
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --wacz-validate \
|
||||
--rerun-args '-C0' --log-not-found 'could not finalize' \
|
||||
--found 'mini304/index.html' --found 'mini304/page.html' \
|
||||
httrack 'BASEURL/mini304/index.html' --warc-file warc-out --wacz
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A cache-served second pass fetches no bodies, so it must leave the WARC, CDXJ
|
||||
# and WACZ of the first pass untouched (#759). The kept package is re-validated
|
||||
# to prove it still replays, not just that a same-named file is still there.
|
||||
# The one Error: line is the keep notice; a second means the pass also failed.
|
||||
|
||||
set -eu
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 1 --archive-kept-on-rerun \
|
||||
--rerun --wacz-validate --log-found 'WARC: this pass revisited' \
|
||||
--found 'mini304/index.html' --found 'mini304/page.html' \
|
||||
httrack 'BASEURL/mini304/index.html' --warc-file warc-out --wacz
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# The same keep/replace rule under --warc-max-size, where the archive is several
|
||||
# segments: a cache-served pass must leave every one alone, a cacheless pass must
|
||||
# swap every one in. Half a swap is the corruption one rename loop can produce,
|
||||
# and only a rotated archive exposes it (#759). --archive-min-files pins the two
|
||||
# segments plus the .cdx, so a shrinking crawl cannot quietly stop rotating.
|
||||
|
||||
set -eu
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
# The one Error: line is the keep notice; a second means the pass also failed.
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 1 --archive-kept-on-rerun \
|
||||
--archive-min-files 3 --rerun --log-found 'WARC: this pass revisited' \
|
||||
--found 'mini304/index.html' --found 'mini304/page.html' \
|
||||
httrack 'BASEURL/mini304/index.html' --warc-file warc-out --warc-cdx \
|
||||
--warc-max-size 600
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --archive-replaced-on-rerun \
|
||||
--archive-min-files 3 --rerun-args '-C0' \
|
||||
--found 'mini304/index.html' --found 'mini304/page.html' \
|
||||
httrack 'BASEURL/mini304/index.html' --warc-file warc-out --warc-cdx \
|
||||
--warc-max-size 600
|
||||
@@ -30,7 +30,6 @@ TEST_EXTENSIONS = .test
|
||||
TEST_LOG_COMPILER = $(BASH)
|
||||
TESTS = \
|
||||
00_runnable.test \
|
||||
01_engine-addlink.test \
|
||||
01_engine-changes.test \
|
||||
01_engine-charset.test \
|
||||
01_engine-cmdline.test \
|
||||
@@ -82,9 +81,7 @@ TESTS = \
|
||||
01_engine-status.test \
|
||||
01_engine-stripquery.test \
|
||||
01_engine-strsafe.test \
|
||||
01_engine-structcheck.test \
|
||||
01_engine-syscharset.test \
|
||||
01_engine-threadwait.test \
|
||||
01_engine-topindex.test \
|
||||
01_engine-urlhack.test \
|
||||
01_engine-unescape-bounds.test \
|
||||
@@ -198,8 +195,6 @@ TESTS = \
|
||||
93_local-changes.test \
|
||||
94_local-single-file.test \
|
||||
95_local-sitemap.test \
|
||||
96_local-refetch-keep.test \
|
||||
97_local-warc-update-keep.test \
|
||||
98_local-warc-segments.test
|
||||
96_local-refetch-keep.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -34,12 +34,6 @@
|
||||
# httrack via --cookies-file, to exercise preloaded cookies.
|
||||
# --rerun-dead re-runs with the server stopped: the no-data rollback must
|
||||
# restore the previous hts-cache generation byte-identical.
|
||||
# --archive-kept-on-rerun: the second pass must leave the first pass's
|
||||
# .warc[.gz]/.cdx/.wacz byte-identical, having no bodies to replace them (#759).
|
||||
# --archive-replaced-on-rerun is its mirror: every one of them must have been
|
||||
# rewritten. Both also require no *.tmp left behind, and take an optional
|
||||
# --archive-min-files N guarding against a scenario that silently stopped
|
||||
# producing the segments it means to check.
|
||||
|
||||
set -u
|
||||
|
||||
@@ -60,14 +54,9 @@ outdir_intl=
|
||||
rerun=
|
||||
rerun_args=
|
||||
rerun_dead=
|
||||
archive_kept=
|
||||
archive_replaced=
|
||||
archive_min_files=0
|
||||
tmpdir=
|
||||
serverpid=
|
||||
crawlpid=
|
||||
wacz_poisoned=
|
||||
wacz_poison="stale-wacz-that-a-second-pass-must-replace"
|
||||
|
||||
function warning {
|
||||
echo "** $*" >&2
|
||||
@@ -127,13 +116,6 @@ while test "$pos" -lt "$nargs"; do
|
||||
--debug) verbose=1 ;;
|
||||
--rerun) rerun=1 ;; # run httrack a second time (update pass) before auditing
|
||||
--rerun-dead) rerun_dead=1 ;; # re-run with the server stopped (cache rollback)
|
||||
# the second pass must leave the first pass's archive files untouched
|
||||
--archive-kept-on-rerun) archive_kept=1 ;;
|
||||
--archive-replaced-on-rerun) archive_replaced=1 ;; # ...or rewrite all of them
|
||||
--archive-min-files)
|
||||
pos=$((pos + 1))
|
||||
archive_min_files="${args[$pos]}"
|
||||
;;
|
||||
# validate the produced .warc.gz (see the validation block near the end)
|
||||
--warc-validate) warc_validate=1 ;;
|
||||
# validate the produced .wacz package (stdlib, plus py-wacz/pywb if present)
|
||||
@@ -290,30 +272,6 @@ if test -n "$warc_validate"; then
|
||||
test -z "$w1" || cp "$w1" "${tmpdir}/warc-pass1.gz"
|
||||
fi
|
||||
|
||||
# Snapshot the archive files the second pass must keep (or must replace).
|
||||
declare -a kept_files=()
|
||||
if test -n "${archive_kept}${archive_replaced}"; then
|
||||
while read -r f; do
|
||||
test -n "$f" || continue
|
||||
cp "$f" "${tmpdir}/kept-${#kept_files[@]}" || die "could not snapshot $f"
|
||||
kept_files+=("$f")
|
||||
done < <(find "$mirrorroot" -maxdepth 2 \
|
||||
\( -name '*.warc.gz' -o -name '*.warc' -o -name '*.cdx' -o -name '*.wacz' \) \
|
||||
2>/dev/null | sort)
|
||||
test "${#kept_files[@]}" -gt 0 ||
|
||||
die "the first pass produced no archive to compare against"
|
||||
test "${#kept_files[@]}" -ge "$archive_min_files" ||
|
||||
die "only ${#kept_files[@]} archive file(s), wanted $archive_min_files: ${kept_files[*]}"
|
||||
fi
|
||||
|
||||
# Poison the first-pass .wacz when a second pass follows: repackaging moves the
|
||||
# new archive over it, so the marker must be gone afterwards (#726). Poisoning
|
||||
# beats comparing the two packages, which can come out byte-identical.
|
||||
if test -z "$archive_kept" && test -n "$wacz_validate" && test -n "${rerun}${rerun_args}"; then
|
||||
wacz_poisoned=$(find "$mirrorroot" -maxdepth 2 -name '*.wacz' 2>/dev/null | sort | tail -n1)
|
||||
test -z "$wacz_poisoned" || echo "$wacz_poison" >"$wacz_poisoned"
|
||||
fi
|
||||
|
||||
# --- optional second pass: re-mirror into the same dir (cache/update path) ----
|
||||
if test -n "$rerun"; then
|
||||
info "re-running httrack (update pass)"
|
||||
@@ -358,29 +316,6 @@ if test -n "$rerun_args"; then
|
||||
result "OK (second pass)"
|
||||
fi
|
||||
|
||||
# --- optional: did the second pass keep, or replace, the whole archive? ------
|
||||
if test "${#kept_files[@]}" -gt 0; then
|
||||
i=0
|
||||
for f in "${kept_files[@]}"; do
|
||||
if test -n "$archive_kept"; then
|
||||
info "checking the second pass kept $(basename "$f")"
|
||||
cmp -s "${tmpdir}/kept-${i}" "$f" ||
|
||||
die "$(basename "$f") was rewritten: the previous archive was destroyed"
|
||||
else
|
||||
info "checking the second pass replaced $(basename "$f")"
|
||||
cmp -s "${tmpdir}/kept-${i}" "$f" &&
|
||||
die "$(basename "$f") still holds the first pass's bytes"
|
||||
fi
|
||||
result "OK"
|
||||
i=$((i + 1))
|
||||
done
|
||||
# A leftover in-progress file is as bad: the next pass would silently eat it.
|
||||
info "checking no in-progress archive was left behind"
|
||||
leftover=$(find "$mirrorroot" -maxdepth 2 \( -name '*.warc.gz.tmp' -o -name '*.warc.tmp' \) 2>/dev/null | head -n1)
|
||||
test -z "$leftover" || die "left behind $leftover"
|
||||
result "OK"
|
||||
fi
|
||||
|
||||
# --- optional dead pass: server stopped, the cache must survive the rollback --
|
||||
if test -n "$rerun_dead"; then
|
||||
zip="${out}/hts-cache/new.zip"
|
||||
@@ -452,18 +387,14 @@ if test -n "$warc_validate"; then
|
||||
die "fresh WARC validation failed"
|
||||
result "OK"
|
||||
|
||||
# After an update pass the unchanged assets must be revisits, in an archive
|
||||
# of the pass's own (WARC_VALIDATE_UPDATE): a revisit-only pass never
|
||||
# replaces the archive holding the bodies it would strand (#759).
|
||||
if test -n "${WARC_VALIDATE_UPDATE:-}"; then
|
||||
upd=$(find "$mirrorroot" -maxdepth 2 -name "$WARC_VALIDATE_UPDATE" 2>/dev/null | head -n1)
|
||||
test -n "$upd" || die "no $WARC_VALIDATE_UPDATE produced under $mirrorroot"
|
||||
# Final file: after an update pass the unchanged assets must be revisits.
|
||||
if test -n "$rerun"; then
|
||||
declare -a revargs=(--expect-revisit)
|
||||
for sub in ${WARC_VALIDATE_NORESP:-}; do
|
||||
revargs+=(--no-response-for "$sub")
|
||||
done
|
||||
info "validating update WARC (revisits)"
|
||||
"$python" "$validator" "$(nativepath "$upd")" "${revargs[@]}" >&2 ||
|
||||
"$python" "$validator" "$(nativepath "$warc")" "${revargs[@]}" >&2 ||
|
||||
die "update WARC validation failed"
|
||||
result "OK"
|
||||
fi
|
||||
@@ -485,12 +416,6 @@ if test -n "$wacz_validate"; then
|
||||
fi
|
||||
die "no .wacz file produced under $mirrorroot"
|
||||
fi
|
||||
if test -n "$wacz_poisoned"; then
|
||||
info "checking the second pass replaced the .wacz"
|
||||
grep -q "$wacz_poison" "$wacz_poisoned" 2>/dev/null &&
|
||||
die "stale .wacz kept: $wacz_poisoned"
|
||||
result "OK"
|
||||
fi
|
||||
validator=$(nativepath "${testdir}/wacz-validate.py")
|
||||
info "validating WACZ package"
|
||||
"$python" "$validator" "$(nativepath "$wacz")" >&2 || die "WACZ validation failed"
|
||||
|
||||
Reference in New Issue
Block a user