mirror of
https://github.com/xroche/httrack.git
synced 2026-07-26 18:49:39 +03:00
Compare commits
11 Commits
warnfix-li
...
warnfix-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3f1032ef | ||
|
|
0b1924194f | ||
|
|
a7fbd3f739 | ||
|
|
c4b803eb33 | ||
|
|
feace15693 | ||
|
|
bd8b2f8d15 | ||
|
|
102fafffa7 | ||
|
|
5a52f90862 | ||
|
|
eae1dd0b77 | ||
|
|
cd0d9c4d88 | ||
|
|
b15362e6d6 |
5
.github/workflows/windows-build.yml
vendored
5
.github/workflows/windows-build.yml
vendored
@@ -225,8 +225,9 @@ jobs:
|
||||
|
||||
# Every gate here exits 77, so an all-skipped suite would report green having
|
||||
# tested nothing: pin the skips, and floor the passes in case the glob empties.
|
||||
# footer-overflow skips on Windows (needs a path past MAX_PATH); crange pending #581.
|
||||
expected_skips=" 01_engine-footer-overflow.test 48_local-crange-memresume.test 71_local-crange-repaircache.test"
|
||||
# footer-overflow skips on Windows (needs a path past MAX_PATH); crange pending #581;
|
||||
# webdav-mime needs a reapable background listener, which MSYS cannot give it.
|
||||
expected_skips=" 01_engine-footer-overflow.test 48_local-crange-memresume.test 71_local-crange-repaircache.test 79_local-proxytrack-webdav-mime.test"
|
||||
[ "$pass" -ge 90 ] || { echo "::error::only $pass tests passed ($skip skipped)"; exit 1; }
|
||||
[ "$skipped" = "$expected_skips" ] || { echo "::error::unexpected skips:$skipped"; exit 1; }
|
||||
[ "$fail" -eq 0 ] || { echo "::error::failing:$failed"; exit 1; }
|
||||
|
||||
@@ -541,7 +541,7 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
|
||||
const char *ext) {
|
||||
// do not use tempnam() but a regular filename
|
||||
back->tmpfile_buffer[0] = '\0';
|
||||
if (back->url_sav != NULL && back->url_sav[0] != '\0') {
|
||||
if (back->url_sav[0] != '\0') {
|
||||
snprintf(back->tmpfile_buffer, sizeof(back->tmpfile_buffer), "%s.%s",
|
||||
back->url_sav, ext);
|
||||
back->tmpfile = back->tmpfile_buffer;
|
||||
@@ -887,8 +887,7 @@ int back_finalize(httrackp * opt, cache_back * cache, struct_back * sback,
|
||||
HTS_STAT.stat_bytes += back[p].r.size;
|
||||
HTS_STAT.stat_files++;
|
||||
hts_log_print(opt, LOG_TRACE, "added file %s%s => %s",
|
||||
back[p].url_adr, back[p].url_fil,
|
||||
back[p].url_sav != NULL ? back[p].url_sav : "");
|
||||
back[p].url_adr, back[p].url_fil, back[p].url_sav);
|
||||
}
|
||||
if ((!back[p].r.notmodified) && (opt->is_update)) {
|
||||
HTS_STAT.stat_updated_files++; // page modifiée
|
||||
@@ -2302,26 +2301,24 @@ int back_add(struct_back *sback, httrackp *opt, cache_back *cache,
|
||||
&& slot_can_be_finalized(opt, &back[i]);
|
||||
int may_serialize = slot_can_be_cached_on_disk(&back[i]);
|
||||
|
||||
hts_log_print(opt, LOG_DEBUG,
|
||||
"back[%03d]: may_clean=%d, may_finalize_disk=%d, may_serialize=%d:"
|
||||
LF "\t"
|
||||
"finalized(%d), status(%d), locked(%d), delayed(%d), test(%d), "
|
||||
LF "\t"
|
||||
"statuscode(%d), size(%d), is_write(%d), may_hypertext(%d), "
|
||||
LF "\t" "contenttype(%s), url(%s%s), save(%s)", i,
|
||||
may_clean, may_finalize, may_serialize,
|
||||
back[i].finalized, back[i].status, back[i].locked,
|
||||
IS_DELAYED_EXT(back[i].url_sav), back[i].testmode,
|
||||
back[i].r.statuscode, (int) back[i].r.size,
|
||||
back[i].r.is_write, may_be_hypertext_mime(opt,
|
||||
back[i].r.
|
||||
contenttype,
|
||||
back[i].
|
||||
url_fil),
|
||||
/* */
|
||||
back[i].r.contenttype, back[i].url_adr,
|
||||
back[i].url_fil,
|
||||
back[i].url_sav ? back[i].url_sav : "<null>");
|
||||
hts_log_print(
|
||||
opt, LOG_DEBUG,
|
||||
"back[%03d]: may_clean=%d, may_finalize_disk=%d, "
|
||||
"may_serialize=%d:" LF "\t"
|
||||
"finalized(%d), status(%d), locked(%d), delayed(%d), "
|
||||
"test(%d), " LF "\t"
|
||||
"statuscode(%d), size(%d), is_write(%d), may_hypertext(%d), " LF
|
||||
"\t"
|
||||
"contenttype(%s), url(%s%s), save(%s)",
|
||||
i, may_clean, may_finalize, may_serialize, back[i].finalized,
|
||||
back[i].status, back[i].locked, IS_DELAYED_EXT(back[i].url_sav),
|
||||
back[i].testmode, back[i].r.statuscode, (int) back[i].r.size,
|
||||
back[i].r.is_write,
|
||||
may_be_hypertext_mime(opt, back[i].r.contenttype,
|
||||
back[i].url_fil),
|
||||
/* */
|
||||
back[i].r.contenttype, back[i].url_adr, back[i].url_fil,
|
||||
back[i].url_sav);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2857,7 +2854,8 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
// new session
|
||||
back[i].r.ssl_con = SSL_new(openssl_ctx);
|
||||
if (back[i].r.ssl_con) {
|
||||
const char* hostname = jump_protocol_const(back[i].url_adr);
|
||||
/* non-const twin: the OpenSSL macro casts the qualifier away */
|
||||
char *hostname = jump_protocol(back[i].url_adr);
|
||||
// some servers expect the hostname on the clienthello (SNI TLS extension)
|
||||
SSL_set_tlsext_host_name(back[i].r.ssl_con, hostname);
|
||||
SSL_clear(back[i].r.ssl_con);
|
||||
|
||||
@@ -629,8 +629,8 @@ static htsblk cache_readex_new(httrackp * opt, cache_back * cache,
|
||||
// File exists on disk with declared cache name (this is expected!)
|
||||
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), previous_save))) { // un fichier existe déja
|
||||
// Expected size ?
|
||||
const size_t fsize =
|
||||
fsize_utf8(fconv(catbuff, sizeof(catbuff), previous_save));
|
||||
const LLint fsize = fsize_utf8(
|
||||
fconv(catbuff, sizeof(catbuff), previous_save));
|
||||
if (fsize == r.size) {
|
||||
// Target name is the previous name, and the file looks good: nothing to do!
|
||||
if (strcmp(previous_save, target_save) == 0) {
|
||||
@@ -666,7 +666,8 @@ static htsblk cache_readex_new(httrackp * opt, cache_back * cache,
|
||||
// Suppose a broken mirror, with a file being renamed: OK
|
||||
else if (fexist_utf8(fconv(catbuff, sizeof(catbuff), target_save))) {
|
||||
// Expected size ?
|
||||
const size_t fsize = fsize_utf8(fconv(catbuff, sizeof(catbuff), target_save));
|
||||
const LLint fsize =
|
||||
fsize_utf8(fconv(catbuff, sizeof(catbuff), target_save));
|
||||
|
||||
if (fsize == r.size) {
|
||||
// So far so good
|
||||
@@ -1440,7 +1441,7 @@ int cache_brstr(char *adr, char *s, size_t s_size) {
|
||||
|
||||
/* binput bounded to a NUL-terminated buffer: refuse to start a read at or
|
||||
past `end`, so a prior over-advance can't walk a cache-index parse OOB. */
|
||||
int cache_binput(char *adr, const char *end, char *s, int max) {
|
||||
int cache_binput(const char *adr, const char *end, char *s, int max) {
|
||||
if (adr >= end) {
|
||||
s[0] = '\0';
|
||||
return 0;
|
||||
|
||||
@@ -93,7 +93,7 @@ void cache_rstr(FILE *fp, char *s, size_t s_size);
|
||||
char *cache_rstr_addr(FILE * fp);
|
||||
int cache_brstr(char *adr, char *s, size_t s_size);
|
||||
/* binput over a NUL-terminated buffer, bounded: no read starts at/past end. */
|
||||
int cache_binput(char *adr, const char *end, char *s, int max);
|
||||
int cache_binput(const char *adr, const char *end, char *s, int max);
|
||||
int cache_brint(char *adr, int *i);
|
||||
void cache_rint(FILE * fp, int *i);
|
||||
void cache_rLLint(FILE * fp, LLint * i);
|
||||
|
||||
@@ -696,17 +696,19 @@ int httpmirror(char *url1, httrackp * opt) {
|
||||
// copier adresse(s) dans liste des adresses
|
||||
{
|
||||
char *a = url1;
|
||||
int primary_len = 8192;
|
||||
|
||||
if (StringNotEmpty(opt->filelist)) {
|
||||
primary_len += max(0, fsize_utf8(StringBuff(opt->filelist)) * 2);
|
||||
}
|
||||
primary_len += (int) strlen(url1) * 2;
|
||||
const LLint list_sz = StringNotEmpty(opt->filelist)
|
||||
? fsize_utf8(StringBuff(opt->filelist))
|
||||
: 0;
|
||||
/* two bytes reserved per list byte; -1 makes an undoublable size refused */
|
||||
const LLint list_room =
|
||||
list_sz > 0 ? (list_sz <= INT64_MAX / 2 ? list_sz * 2 : -1) : 0;
|
||||
const size_t primary_len =
|
||||
llint_grow_size_t(8192 + strlen(url1) * 2, list_room, 0);
|
||||
|
||||
// création de la première page, qui contient les liens de base à scanner
|
||||
// c'est plus propre et plus logique que d'entrer à la main les liens dans la pile
|
||||
// on bénéficie ainsi des vérifications et des tests du robot pour les liens "primaires"
|
||||
primary = (char *) malloct(primary_len);
|
||||
primary = primary_len != (size_t) -1 ? (char *) malloct(primary_len) : NULL;
|
||||
if (!primary) {
|
||||
printf("PANIC! : Not enough memory [%d]\n", __LINE__);
|
||||
XH_extuninit;
|
||||
@@ -887,7 +889,7 @@ int httpmirror(char *url1, httrackp * opt) {
|
||||
}
|
||||
|
||||
if (filelist_buff != NULL) {
|
||||
int filelist_ptr = 0;
|
||||
size_t filelist_ptr = 0;
|
||||
int n = 0;
|
||||
char BIGSTK line[HTS_URLMAXSIZE * 2];
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
int argv_url = -1; // ==0 : utiliser cache et doit.log
|
||||
char *argv_firsturl = NULL; // utilisé pour nommage par défaut
|
||||
char *url = NULL; // URLS séparées par un espace
|
||||
int url_sz = 65535;
|
||||
size_t url_sz = 65535;
|
||||
|
||||
// the parametres
|
||||
int httrack_logmode = 3; // ONE log file
|
||||
@@ -224,21 +224,22 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
|
||||
/* create x_argvblk buffer for transformed command line */
|
||||
{
|
||||
int current_size = 0;
|
||||
int size;
|
||||
size_t current_size = 0;
|
||||
const LLint size = fsize("config");
|
||||
size_t blk_size;
|
||||
int na;
|
||||
|
||||
for(na = 0; na < argc; na++)
|
||||
current_size += (int) (strlen(argv[na]) + 1);
|
||||
if ((size = fsize("config")) > 0)
|
||||
current_size += size;
|
||||
x_argvblk = (char *) malloct(current_size + 32768);
|
||||
current_size += strlen(argv[na]) + 1;
|
||||
/* a huge file named "config" must saturate, not wrap, the capacity */
|
||||
blk_size = llint_grow_size_t(current_size, size > 0 ? size : 0, 32768);
|
||||
x_argvblk = blk_size != (size_t) -1 ? (char *) malloct(blk_size) : NULL;
|
||||
if (x_argvblk == NULL) {
|
||||
HTS_PANIC_PRINTF("Error, not enough memory");
|
||||
htsmain_free();
|
||||
return -1;
|
||||
}
|
||||
x_argvblk_size = (size_t) (current_size + 32768);
|
||||
x_argvblk_size = blk_size;
|
||||
x_argvblk[0] = '\0';
|
||||
x_ptr = 0;
|
||||
|
||||
@@ -1456,20 +1457,29 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
FILE *fp = FOPEN(argv[na], "rb");
|
||||
|
||||
if (fp != NULL) {
|
||||
int cl = (int) strlen(url);
|
||||
size_t cl = strlen(url);
|
||||
const size_t fzs = llint_to_size_t(fz);
|
||||
const size_t capa = llint_grow_size_t(cl, fz, 8192);
|
||||
|
||||
ensureUrlCapacity(url, url_sz, cl + fz + 8192);
|
||||
if (capa == (size_t) -1) {
|
||||
fclose(fp);
|
||||
HTS_PANIC_PRINTF("File url list too large");
|
||||
htsmain_free();
|
||||
return -1;
|
||||
}
|
||||
ensureUrlCapacity(url, url_sz, capa);
|
||||
if (cl > 0) { /* don't stick! (3.43) */
|
||||
url[cl] = ' ';
|
||||
cl++;
|
||||
}
|
||||
if (fread(url + cl, 1, fz, fp) != fz) {
|
||||
if (fread(url + cl, 1, fzs, fp) != fzs) {
|
||||
fclose(fp);
|
||||
HTS_PANIC_PRINTF("File url list could not be read");
|
||||
htsmain_free();
|
||||
return -1;
|
||||
}
|
||||
fclose(fp);
|
||||
*(url + cl + fz) = '\0';
|
||||
*(url + cl + fzs) = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2296,8 +2306,8 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
|
||||
} else { // URL/filters
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
const int urlSize = (int) strlen(argv[na]);
|
||||
const int capa = (int) (strlen(url) + urlSize + 32);
|
||||
const size_t urlSize = strlen(argv[na]);
|
||||
const size_t capa = strlen(url) + urlSize + 32;
|
||||
|
||||
assertf(urlSize < HTS_URLMAXSIZE);
|
||||
if (urlSize < HTS_URLMAXSIZE) {
|
||||
|
||||
@@ -336,16 +336,20 @@ typedef int INTsys;
|
||||
#endif
|
||||
|
||||
/* Socket-handle type. An unsigned integer wide enough for a Windows SOCKET;
|
||||
a plain int file descriptor on POSIX. */
|
||||
a plain int file descriptor on POSIX. T_SOCP is its printf conversion,
|
||||
'%' included: unsigned __int64 on Win64 must not be printed with "%d". */
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN64)
|
||||
|
||||
typedef unsigned __int64 T_SOC;
|
||||
#define T_SOCP "%" PRIu64
|
||||
#else
|
||||
typedef unsigned __int32 T_SOC;
|
||||
#define T_SOCP "%" PRIu32
|
||||
#endif
|
||||
#else
|
||||
typedef int T_SOC;
|
||||
#define T_SOCP "%d"
|
||||
#endif
|
||||
|
||||
/* Buffer size for a printed network address (IPv4 or IPv6, NUL included). */
|
||||
|
||||
@@ -156,9 +156,7 @@ void help_wizard(httrackp * opt) {
|
||||
char *a;
|
||||
|
||||
//
|
||||
if (urls == NULL || mainpath == NULL || projname == NULL || stropt == NULL
|
||||
|| stropt2 == NULL || strwild == NULL || cmd == NULL || str == NULL
|
||||
|| argv == NULL) {
|
||||
if (buffers == NULL) {
|
||||
fprintf(stderr, "* memory exhausted in %s, line %d\n", __FILE__, __LINE__);
|
||||
return;
|
||||
}
|
||||
@@ -251,6 +249,7 @@ void help_wizard(httrackp * opt) {
|
||||
strcatbuff(stropt2, "--update ");
|
||||
break;
|
||||
case 0:
|
||||
freet(buffers);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
@@ -315,8 +314,10 @@ void help_wizard(httrackp * opt) {
|
||||
fflush(stdout);
|
||||
linput(stdin, str, 250);
|
||||
if (strnotempty(str)) {
|
||||
if (!((str[0] == 'y') || (str[0] == 'Y')))
|
||||
if (!((str[0] == 'y') || (str[0] == 'Y'))) {
|
||||
freet(buffers);
|
||||
return;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
@@ -340,7 +341,7 @@ void help_wizard(httrackp * opt) {
|
||||
}
|
||||
|
||||
/* Free buffers */
|
||||
free(buffers);
|
||||
freet(buffers);
|
||||
#undef urls
|
||||
#undef mainpath
|
||||
#undef projname
|
||||
|
||||
39
src/htslib.c
39
src/htslib.c
@@ -704,18 +704,16 @@ T_SOC http_xfopen(httrackp *opt, int mode, int treat, int waitconnect,
|
||||
/* Check for errors */
|
||||
if (soc == INVALID_SOCKET) {
|
||||
if (retour) {
|
||||
if (retour->msg) {
|
||||
if (!strnotempty(retour->msg)) {
|
||||
if (!strnotempty(retour->msg)) {
|
||||
#ifdef _WIN32
|
||||
int last_errno = WSAGetLastError();
|
||||
int last_errno = WSAGetLastError();
|
||||
|
||||
sprintf(retour->msg, "Connect error: %s", strerror(last_errno));
|
||||
sprintf(retour->msg, "Connect error: %s", strerror(last_errno));
|
||||
#else
|
||||
int last_errno = errno;
|
||||
int last_errno = errno;
|
||||
|
||||
sprintf(retour->msg, "Connect error: %s", strerror(last_errno));
|
||||
sprintf(retour->msg, "Connect error: %s", strerror(last_errno));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2207,7 +2205,7 @@ T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
|
||||
#if DEBUG
|
||||
printf("erreur gethostbyname\n");
|
||||
#endif
|
||||
if (retour && retour->msg) {
|
||||
if (retour != NULL) {
|
||||
#ifdef _WIN32
|
||||
snprintf(retour->msg, sizeof(retour->msg),
|
||||
"Unable to get server's address: %s", error);
|
||||
@@ -2238,7 +2236,7 @@ T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
|
||||
DEBUG_W("socket()=%d\n" _(int) soc);
|
||||
#endif
|
||||
if (soc == INVALID_SOCKET) {
|
||||
if (retour && retour->msg) {
|
||||
if (retour != NULL) {
|
||||
#ifdef _WIN32
|
||||
int last_errno = WSAGetLastError();
|
||||
|
||||
@@ -2262,17 +2260,8 @@ T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
|
||||
&bind_addr, &error) == NULL
|
||||
|| bind(soc, &SOCaddr_sockaddr(bind_addr),
|
||||
SOCaddr_size(bind_addr)) != 0) {
|
||||
if (retour && retour->msg) {
|
||||
#ifdef _WIN32
|
||||
snprintf(retour->msg, sizeof(retour->msg),
|
||||
"Unable to bind the specificied server address: %s",
|
||||
error);
|
||||
#else
|
||||
snprintf(retour->msg, sizeof(retour->msg),
|
||||
"Unable to bind the specificied server address: %s",
|
||||
error);
|
||||
#endif
|
||||
}
|
||||
snprintf(retour->msg, sizeof(retour->msg),
|
||||
"Unable to bind the specificied server address: %s", error);
|
||||
deletesoc(soc);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
@@ -2319,7 +2308,7 @@ T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
|
||||
#if HDEBUG
|
||||
printf("unable to connect!\n");
|
||||
#endif
|
||||
if (retour != NULL && retour->msg) {
|
||||
if (retour != NULL) {
|
||||
#ifdef _WIN32
|
||||
const int last_errno = WSAGetLastError();
|
||||
|
||||
@@ -2598,13 +2587,15 @@ void deletesoc(T_SOC soc) {
|
||||
if (closesocket(soc) != 0) {
|
||||
int err = WSAGetLastError();
|
||||
|
||||
fprintf(stderr, "* error closing socket %d: %s\n", soc, strerror(err));
|
||||
fprintf(stderr, "* error closing socket " T_SOCP ": %s\n", soc,
|
||||
strerror(err));
|
||||
}
|
||||
#else
|
||||
if (close(soc) != 0) {
|
||||
const int err = errno;
|
||||
|
||||
fprintf(stderr, "* error closing socket %d: %s\n", soc, strerror(err));
|
||||
fprintf(stderr, "* error closing socket " T_SOCP ": %s\n", soc,
|
||||
strerror(err));
|
||||
}
|
||||
#endif
|
||||
#if HTS_WIDE_DEBUG
|
||||
@@ -3017,7 +3008,7 @@ int finput(T_SOC fd, char *s, int max) {
|
||||
}
|
||||
|
||||
// Like linput, but in memory (optimized)
|
||||
int binput(char *buff, char *s, int max) {
|
||||
int binput(const char *buff, char *s, int max) {
|
||||
int count = 0;
|
||||
int destCount = 0;
|
||||
|
||||
|
||||
17
src/htslib.h
17
src/htslib.h
@@ -262,7 +262,7 @@ HTS_INLINE void time_rfc822_local(char *s, struct tm *A);
|
||||
|
||||
HTS_INLINE int sendc(htsblk * r, const char *s);
|
||||
int finput(T_SOC fd, char *s, int max);
|
||||
int binput(char *buff, char *s, int max);
|
||||
int binput(const char *buff, char *s, int max);
|
||||
int linput(FILE * fp, char *s, int max);
|
||||
int linputsoc(T_SOC soc, char *s, int max);
|
||||
int linputsoc_t(T_SOC soc, char *s, int max, int timeout);
|
||||
@@ -607,6 +607,21 @@ static HTS_UNUSED size_t llint_to_size_t(LLint o) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Capacity for @p used bytes plus @p extra more plus @p slack spare;
|
||||
(size_t) -1 if the total exceeds (size_t) -2 or @p extra is negative
|
||||
(llint_to_size_t() would map that to a huge valid-looking size). */
|
||||
static HTS_UNUSED size_t llint_grow_size_t(size_t used, LLint extra,
|
||||
size_t slack) {
|
||||
const size_t max = (size_t) -2; /* (size_t) -1 is the error value */
|
||||
const size_t e = extra >= 0 ? llint_to_size_t(extra) : (size_t) -1;
|
||||
|
||||
if (e == (size_t) -1 || used > max || slack > max - used ||
|
||||
e > max - used - slack) {
|
||||
return (size_t) -1;
|
||||
}
|
||||
return used + e + slack;
|
||||
}
|
||||
|
||||
/* dirent() compatibility */
|
||||
#ifdef _WIN32
|
||||
/* Holds a UTF-8 d_name: MAX_PATH (260) UTF-16 units expand to <=3 bytes each.
|
||||
|
||||
@@ -2023,6 +2023,77 @@ static int st_fsize(httrackp *opt, int argc, char **argv) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 4GB+100KB wraps to ~108KB through an int, and needs 33 unsigned bits. A
|
||||
macro, not a static const: MSVC's C mode (/TC) rejects a const object
|
||||
used inside another object's static initializer below (C2099). */
|
||||
#define HTS_ST_GROWSIZE_OVER32 (4LL * 1024 * 1024 * 1024 + 100 * 1024)
|
||||
|
||||
/* llint_grow_size_t() sizes the buffer holding a whole -%S list file: the
|
||||
result must be the exact 64-bit sum or a clean refusal, never a short one. */
|
||||
static int st_growsize(httrackp *opt, int argc, char **argv) {
|
||||
enum { REFUSE, ACCEPT, WIDTH };
|
||||
|
||||
static const struct {
|
||||
size_t used;
|
||||
LLint extra;
|
||||
size_t slack;
|
||||
int want;
|
||||
} cases[] = {
|
||||
{0, 0, 0, ACCEPT},
|
||||
{10, 100, 8192, ACCEPT},
|
||||
{(size_t) -2 - 8, 4, 4, ACCEPT}, /* exact fit, no room to spare */
|
||||
{(size_t) -2, 0, 0, ACCEPT}, /* largest representable capacity */
|
||||
{0, -1, 0, REFUSE}, /* fsize() failure */
|
||||
/* -1 already maps to SIZE_MAX; only this exercises the negative guard */
|
||||
{0, -4096, 0, REFUSE},
|
||||
{(size_t) -1, 1, 0, REFUSE},
|
||||
{(size_t) -2, 0, 1, REFUSE}, /* slack alone overruns */
|
||||
{(size_t) -1 - 8, 4, 4, REFUSE}, /* total would be the error value */
|
||||
{(size_t) -1 - 8, 4, 8, REFUSE},
|
||||
{0, HTS_ST_GROWSIZE_OVER32, 8192,
|
||||
WIDTH}, /* 32-bit size_t can't hold these */
|
||||
{10, HTS_ST_GROWSIZE_OVER32, 8192, WIDTH},
|
||||
};
|
||||
|
||||
size_t k;
|
||||
int rc = 0;
|
||||
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
for (k = 0; k < sizeof(cases) / sizeof(cases[0]); k++) {
|
||||
const size_t used = cases[k].used, slack = cases[k].slack;
|
||||
const LLint extra = cases[k].extra;
|
||||
const size_t got = llint_grow_size_t(used, extra, slack);
|
||||
const hts_boolean refused = got == (size_t) -1 ? HTS_TRUE : HTS_FALSE;
|
||||
const hts_boolean exact =
|
||||
!refused && extra >= 0 && got - used - slack == (size_t) extra;
|
||||
hts_boolean ok;
|
||||
|
||||
switch (cases[k].want) {
|
||||
case ACCEPT:
|
||||
ok = exact;
|
||||
break;
|
||||
case REFUSE:
|
||||
ok = refused;
|
||||
break;
|
||||
default:
|
||||
ok = sizeof(size_t) >= sizeof(LLint) ? exact : refused;
|
||||
break;
|
||||
}
|
||||
if (!ok) {
|
||||
fprintf(stderr,
|
||||
"growsize: grow(" LLintP ", " LLintP ", " LLintP ") = " LLintP
|
||||
" (want %s)\n",
|
||||
(LLint) used, extra, (LLint) slack, (LLint) got,
|
||||
cases[k].want == REFUSE ? "refusal" : "exact sum");
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
printf("growsize self-test %s\n", rc == 0 ? "OK" : "FAILED");
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int st_savename(httrackp *opt, int argc, char **argv) {
|
||||
lien_adrfilsave afs;
|
||||
cache_back cache;
|
||||
@@ -2417,17 +2488,20 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
|
||||
static t_cookie ck2;
|
||||
htsblk r;
|
||||
char host[600];
|
||||
char line[64]; /* treathead NUL-cuts the header in place: never a literal */
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
memset(host, 'a', sizeof(host) - 1);
|
||||
host[sizeof(host) - 1] = '\0';
|
||||
ck2.max_len = (int) sizeof(ck2.data);
|
||||
ck2.data[0] = '\0';
|
||||
treathead(&ck2, host, "/", &r, "Set-Cookie: SID=1; path=/");
|
||||
strcpybuff(line, "Set-Cookie: SID=1; path=/");
|
||||
treathead(&ck2, host, "/", &r, line);
|
||||
if (strnotempty(ck2.data)) // oversize-host cookie was not dropped
|
||||
err = 1;
|
||||
/* control: a normal host still yields a cookie through treathead */
|
||||
treathead(&ck2, dom, "/", &r, "Set-Cookie: SID=1; path=/");
|
||||
strcpybuff(line, "Set-Cookie: SID=1; path=/");
|
||||
treathead(&ck2, dom, "/", &r, line);
|
||||
if (strstr(ck2.data, "SID") == NULL) // guard wrongly dropped a valid cookie
|
||||
err = 1;
|
||||
}
|
||||
@@ -2917,7 +2991,7 @@ static int ae_write_packed(const char *path, int windowBits,
|
||||
deflateEnd(&strm);
|
||||
return 1;
|
||||
}
|
||||
strm.next_in = (Bytef *) src;
|
||||
strm.next_in = (const Bytef *) src;
|
||||
strm.avail_in = (uInt) len;
|
||||
do {
|
||||
size_t n;
|
||||
@@ -4868,6 +4942,8 @@ static const struct selftest_entry {
|
||||
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
|
||||
st_sniff},
|
||||
{"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},
|
||||
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
|
||||
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
|
||||
st_cacheindex},
|
||||
|
||||
144
src/htsserver.c
144
src/htsserver.c
@@ -147,7 +147,8 @@ HTS_UNUSED static int LANG_LIST(const char *path, char *buffer, size_t size);
|
||||
// 0- Init the URL catcher with standard port
|
||||
|
||||
// smallserver_init(&port,&return_host);
|
||||
T_SOC smallserver_init_std(int *port_prox, char *adr_prox, int defaultPort) {
|
||||
T_SOC smallserver_init_std(int *port_prox, char *adr_prox, int defaultPort,
|
||||
const char *bindAddr) {
|
||||
T_SOC soc;
|
||||
|
||||
if (defaultPort <= 0) {
|
||||
@@ -160,12 +161,12 @@ T_SOC smallserver_init_std(int *port_prox, char *adr_prox, int defaultPort) {
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
soc = smallserver_init(&try_to_listen_to[i], adr_prox);
|
||||
soc = smallserver_init(&try_to_listen_to[i], adr_prox, bindAddr);
|
||||
*port_prox = try_to_listen_to[i];
|
||||
i++;
|
||||
} while((soc == INVALID_SOCKET) && (try_to_listen_to[i] >= 0));
|
||||
} else {
|
||||
soc = smallserver_init(&defaultPort, adr_prox);
|
||||
soc = smallserver_init(&defaultPort, adr_prox, bindAddr);
|
||||
*port_prox = defaultPort;
|
||||
}
|
||||
return soc;
|
||||
@@ -243,9 +244,10 @@ static int my_gethostname(char *h_loc, size_t size) {
|
||||
}
|
||||
|
||||
// smallserver_init(&port,&return_host);
|
||||
T_SOC smallserver_init(int *port, char *adr) {
|
||||
T_SOC smallserver_init(int *port, char *adr, const char *bindAddr) {
|
||||
T_SOC soc = INVALID_SOCKET;
|
||||
char h_loc[256 + 2];
|
||||
SOCaddr server;
|
||||
|
||||
commandRunning = commandEnd = commandReturn = commandReturnSet =
|
||||
commandEndRequested = 0;
|
||||
@@ -256,25 +258,23 @@ T_SOC smallserver_init(int *port, char *adr) {
|
||||
free(commandReturnCmdl);
|
||||
commandReturnCmdl = NULL;
|
||||
|
||||
if (my_gethostname(h_loc, 256) == 0) { // host name
|
||||
SOCaddr server;
|
||||
SOCaddr_initany(server);
|
||||
if (bindAddr != NULL && *bindAddr != '\0') {
|
||||
/* advertise the bound address, else the URL we print is unreachable */
|
||||
if (strlen(bindAddr) >= sizeof(h_loc) || !gethost(bindAddr, &server)) {
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
strcpybuff(h_loc, bindAddr);
|
||||
} else if (my_gethostname(h_loc, 256) != 0) { // host name
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SOCaddr_initany(server);
|
||||
if ((soc =
|
||||
(T_SOC) socket(SOCaddr_sinfamily(server), SOCK_STREAM,
|
||||
0)) != INVALID_SOCKET) {
|
||||
SOCaddr_initport(server, *port);
|
||||
if (bind(soc, &SOCaddr_sockaddr(server), SOCaddr_size(server)) == 0) {
|
||||
if (listen(soc, 10) >= 0) {
|
||||
strcpy(adr, h_loc);
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
closesocket(soc);
|
||||
#else
|
||||
close(soc);
|
||||
#endif
|
||||
soc = INVALID_SOCKET;
|
||||
}
|
||||
if ((soc = (T_SOC) socket(SOCaddr_sinfamily(server), SOCK_STREAM, 0)) !=
|
||||
INVALID_SOCKET) {
|
||||
SOCaddr_initport(server, *port);
|
||||
if (bind(soc, &SOCaddr_sockaddr(server), SOCaddr_size(server)) == 0) {
|
||||
if (listen(soc, 10) >= 0) {
|
||||
strcpy(adr, h_loc);
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
closesocket(soc);
|
||||
@@ -283,6 +283,13 @@ T_SOC smallserver_init(int *port, char *adr) {
|
||||
#endif
|
||||
soc = INVALID_SOCKET;
|
||||
}
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
closesocket(soc);
|
||||
#else
|
||||
close(soc);
|
||||
#endif
|
||||
soc = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
return soc;
|
||||
@@ -311,6 +318,51 @@ typedef struct {
|
||||
error_redirect = "/server/error.html"; \
|
||||
} while(0)
|
||||
|
||||
/* Longest "sid" value worth unescaping: the expected one is an md5 hex digest,
|
||||
so anything near this is already invalid and is rejected unread. */
|
||||
#define SID_VALUE_MAX 64
|
||||
|
||||
/** Does the urlencoded request body present the expected session id?
|
||||
True only if at least one "sid" field is present and every occurrence
|
||||
matches, so it holds whichever one a later last-write-wins parse keeps.
|
||||
Non-destructive: it runs before the body is tokenized in place. */
|
||||
static hts_boolean body_sid_is_valid(const char *body, const char *expected) {
|
||||
const char *s = body;
|
||||
hts_boolean seen = HTS_FALSE;
|
||||
|
||||
while (s != NULL && *s != '\0') {
|
||||
const char *const amp = strchr(s, '&');
|
||||
const char *const eq = strchr(s, '=');
|
||||
|
||||
if (eq != NULL && (amp == NULL || eq < amp) && (size_t) (eq - s) == 3 &&
|
||||
strncmp(s, "sid", 3) == 0) {
|
||||
const size_t len = amp != NULL ? (size_t) (amp - eq - 1) : strlen(eq + 1);
|
||||
hts_boolean match = HTS_FALSE;
|
||||
|
||||
if (len < SID_VALUE_MAX) {
|
||||
char raw[SID_VALUE_MAX];
|
||||
String value = STRING_EMPTY;
|
||||
|
||||
memcpy(raw, eq + 1, len);
|
||||
raw[len] = '\0';
|
||||
unescapehttp(raw, &value);
|
||||
/* StringBuff is NULL until written, so an empty value lands here. */
|
||||
if (StringBuff(value) != NULL &&
|
||||
strcmp(StringBuff(value), expected) == 0) {
|
||||
match = HTS_TRUE;
|
||||
}
|
||||
StringFree(value);
|
||||
}
|
||||
if (!match) {
|
||||
return HTS_FALSE;
|
||||
}
|
||||
seen = HTS_TRUE;
|
||||
}
|
||||
s = amp != NULL ? amp + 1 : NULL;
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
int timeout = 30;
|
||||
int retour = 0;
|
||||
@@ -395,6 +447,7 @@ int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
T_SOC soc_c;
|
||||
LLint length = 0;
|
||||
const char *error_redirect = NULL;
|
||||
hts_boolean denied = HTS_FALSE;
|
||||
|
||||
line[0] = '\0';
|
||||
buffer[0] = '\0';
|
||||
@@ -506,6 +559,22 @@ int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Authenticate the body before parsing it: every field it carries is
|
||||
written straight into the global key store below, "command" included,
|
||||
and that one reaches the engine. Checking afterwards cannot work — the
|
||||
damage is already done, and the pre-seeded "sid" above would compare
|
||||
equal to itself for a request that simply omits the field. */
|
||||
if (meth && buffer[0]) {
|
||||
intptr_t expected = 0;
|
||||
|
||||
if (!coucal_readptr(NewLangList, "_sid", &expected) ||
|
||||
!body_sid_is_valid(buffer, (const char *) expected)) {
|
||||
buffer[0] = '\0';
|
||||
meth = 0;
|
||||
denied = HTS_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* check variables */
|
||||
if (meth && buffer[0]) {
|
||||
char *s = buffer;
|
||||
@@ -526,20 +595,6 @@ int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Error check */
|
||||
{
|
||||
intptr_t adr = 0;
|
||||
intptr_t adr2 = 0;
|
||||
|
||||
if (coucal_readptr(NewLangList, "sid", &adr)) {
|
||||
if (coucal_readptr(NewLangList, "_sid", &adr2)) {
|
||||
if (strcmp((char *) adr, (char *) adr2) != 0) {
|
||||
meth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check variables (internal) */
|
||||
if (meth) {
|
||||
int doLoad = 0;
|
||||
@@ -903,13 +958,11 @@ int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
}
|
||||
}
|
||||
StringMemcat(headers, redir, strlen(redir));
|
||||
{
|
||||
char tmp[256];
|
||||
|
||||
if (strlen(file) < sizeof(tmp) - 32) {
|
||||
sprintf(tmp, "Location: %s\r\n", newfile);
|
||||
StringMemcat(headers, tmp, strlen(tmp));
|
||||
}
|
||||
/* client-supplied: a CR/LF here would split the response */
|
||||
if (newfile[strcspn(newfile, "\r\n")] == '\0') {
|
||||
StringCat(headers, "Location: ");
|
||||
StringCat(headers, newfile);
|
||||
StringCat(headers, "\r\n");
|
||||
}
|
||||
coucal_write(NewLangList, "redirect", (intptr_t) NULL);
|
||||
} else if (is_html(file)) {
|
||||
@@ -1368,6 +1421,11 @@ int smallserver(T_SOC soc, char *url, char *method, char *data, char *path) {
|
||||
StringCat(output, error);
|
||||
}
|
||||
}
|
||||
} else if (denied) {
|
||||
StringCat(headers, "HTTP/1.0 403 Forbidden\r\n"
|
||||
"Server: httrack small server\r\n"
|
||||
"Content-type: text/html\r\n");
|
||||
StringCat(output, "Missing or invalid session id.\r\n");
|
||||
} else {
|
||||
#ifdef _DEBUG
|
||||
char error_hdr[] =
|
||||
|
||||
@@ -43,8 +43,11 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
// Fonctions
|
||||
void socinput(T_SOC soc, char *s, int max);
|
||||
T_SOC smallserver_init_std(int *port_prox, char *adr_prox, int defaultPort);
|
||||
T_SOC smallserver_init(int *port, char *adr);
|
||||
/* Listen on bindAddr, or every interface if NULL/empty; adr (>= 258 bytes) gets
|
||||
the address to advertise. INVALID_SOCKET on error. */
|
||||
T_SOC smallserver_init_std(int *port_prox, char *adr_prox, int defaultPort,
|
||||
const char *bindAddr);
|
||||
T_SOC smallserver_init(int *port, char *adr, const char *bindAddr);
|
||||
int smallserver(T_SOC soc, char *url, char *method, char *data, char *path);
|
||||
|
||||
#define CATCH_RESPONSE \
|
||||
|
||||
@@ -1318,19 +1318,19 @@ HTSEXT_API hts_boolean hts_findnext(find_handle find) {
|
||||
if (find) {
|
||||
#ifdef _WIN32
|
||||
if ((FindNextFileA(find->handle, &find->hdata)))
|
||||
return 1;
|
||||
return HTS_TRUE;
|
||||
#else
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
|
||||
memset(&(find->filestat), 0, sizeof(find->filestat));
|
||||
if ((find->dirp = readdir(find->hdir)))
|
||||
if (find->dirp->d_name)
|
||||
if (!STAT
|
||||
(concat(catbuff, sizeof(catbuff), find->path, find->dirp->d_name), &find->filestat))
|
||||
return 1;
|
||||
if (!STAT(
|
||||
concat(catbuff, sizeof(catbuff), find->path, find->dirp->d_name),
|
||||
&find->filestat))
|
||||
return HTS_TRUE;
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
return HTS_FALSE;
|
||||
}
|
||||
|
||||
HTSEXT_API int hts_findclose(find_handle find) {
|
||||
|
||||
21
src/htsweb.c
21
src/htsweb.c
@@ -88,7 +88,7 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
static htsmutex refreshMutex = HTSMUTEX_INIT;
|
||||
|
||||
static int help_server(char *dest_path, int defaultPort);
|
||||
static int help_server(char *dest_path, int defaultPort, const char *bindAddr);
|
||||
extern int commandRunning;
|
||||
extern int commandEnd;
|
||||
extern int commandReturn;
|
||||
@@ -153,6 +153,8 @@ int main(int argc, char *argv[]) {
|
||||
int ret = 0;
|
||||
int defaultPort = 0;
|
||||
int parentPid = 0;
|
||||
/* loopback by default: the handler trusts its input; --bind widens it */
|
||||
const char *bindAddr = "127.0.0.1";
|
||||
|
||||
printf("Initializing the server..\n");
|
||||
|
||||
@@ -179,7 +181,8 @@ int main(int argc, char *argv[]) {
|
||||
if (argc < 2 || (argc % 2) != 0) {
|
||||
fprintf(stderr, "** Warning: use the webhttrack frontend if available\n");
|
||||
fprintf(stderr,
|
||||
"usage: %s [--port <port>] [--ppid parent-pid] <path-to-html-root-dir> [key value [key value]..]\n",
|
||||
"usage: %s [--port <port>] [--bind <address>] [--ppid parent-pid] "
|
||||
"<path-to-html-root-dir> [key value [key value]..]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, "example: %s /usr/share/httrack/\n", argv[0]);
|
||||
return 1;
|
||||
@@ -267,6 +270,14 @@ int main(int argc, char *argv[]) {
|
||||
fprintf(stderr, "couldn't set the port number to %s\n", argv[i + 1]);
|
||||
return -1;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--bind") == 0 && i + 1 < argc) {
|
||||
/* empty would fall back to every interface, silently undoing the default
|
||||
*/
|
||||
if (!strnotempty(argv[i + 1])) {
|
||||
fprintf(stderr, "--bind needs an address\n");
|
||||
return -1;
|
||||
}
|
||||
bindAddr = argv[i + 1];
|
||||
} else if (strcmp(argv[i], "--ppid") == 0 && i + 1 < argc) {
|
||||
if (sscanf(argv[i + 1], "%u", &parentPid) != 1) {
|
||||
fprintf(stderr, "couldn't set the parent PID to %s\n", argv[i + 1]);
|
||||
@@ -293,7 +304,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
/* launch */
|
||||
ret = help_server(argv[1], defaultPort);
|
||||
ret = help_server(argv[1], defaultPort, bindAddr);
|
||||
|
||||
htsthread_wait_n(background_threads - 1);
|
||||
hts_uninit();
|
||||
@@ -427,11 +438,11 @@ static int webhttrack_runmain(httrackp * opt, int argc, char **argv) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int help_server(char *dest_path, int defaultPort) {
|
||||
static int help_server(char *dest_path, int defaultPort, const char *bindAddr) {
|
||||
int returncode = 0;
|
||||
char adr_prox[HTS_URLMAXSIZE * 2];
|
||||
int port_prox;
|
||||
T_SOC soc = smallserver_init_std(&port_prox, adr_prox, defaultPort);
|
||||
T_SOC soc = smallserver_init_std(&port_prox, adr_prox, defaultPort, bindAddr);
|
||||
|
||||
if (soc != INVALID_SOCKET) {
|
||||
char url[HTS_URLMAXSIZE * 2];
|
||||
|
||||
@@ -59,9 +59,10 @@
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<!-- LIBHTTRACK_EXPORTS turns HTSEXT_API into __declspec(dllexport); ZLIB_DLL
|
||||
imports zlib from its DLL. HTS_USEBROTLI/HTS_USEZSTD enable the br and
|
||||
zstd content codings. Windows 7 floor, matching WinHTTrack. -->
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_MBCS;_USRDLL;LIBHTTRACK_EXPORTS;ZLIB_DLL;HTS_USEBROTLI=1;HTS_USEZSTD=1;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
imports zlib from its DLL, ZLIB_CONST makes its next_in const as the
|
||||
autotools build does. HTS_USEBROTLI/HTS_USEZSTD enable the br and zstd
|
||||
content codings. Windows 7 floor, matching WinHTTrack. -->
|
||||
<PreprocessorDefinitions>WIN32;_WINDOWS;_MBCS;_USRDLL;LIBHTTRACK_EXPORTS;ZLIB_DLL;ZLIB_CONST;HTS_USEBROTLI=1;HTS_USEZSTD=1;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
|
||||
@@ -771,7 +771,7 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
PT_ReadIndex(indexes, StringBuff(itemUrl) + 1, FETCH_HEADERS);
|
||||
if (file != NULL && file->statuscode == HTTP_OK) {
|
||||
size = file->size;
|
||||
if (file->lastmodified) {
|
||||
if (file->lastmodified[0] != '\0') {
|
||||
timestamp = get_time_rfc822(file->lastmodified);
|
||||
}
|
||||
if (timestamp == (time_t) 0) {
|
||||
@@ -785,7 +785,7 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
}
|
||||
timestamp = timestampRep;
|
||||
}
|
||||
if (file->contenttype) {
|
||||
if (file->contenttype[0] != '\0') {
|
||||
mimeType = file->contenttype;
|
||||
}
|
||||
}
|
||||
|
||||
25
tests/01_engine-growsize.test
Normal file
25
tests/01_engine-growsize.test
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Buffer capacity for a 64-bit file size ('httrack -#test=growsize'): a -%S list
|
||||
# file past 4GB must size its buffer exactly or be refused, never wrap.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
out=$(httrack -#test=growsize)
|
||||
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
|
||||
|
||||
echo '<html><body>hi</body></html>' >"$tmp/index.html"
|
||||
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' || {
|
||||
echo "FAIL: -%S rules file was not loaded"
|
||||
printf '%s\n' "$run"
|
||||
exit 1
|
||||
}
|
||||
@@ -60,13 +60,20 @@ test -n "${url:-}" || fail "htsserver did not start: $(cat "${srvlog}")"
|
||||
|
||||
# Post a "start" whose -O dir is 'café' in the form's ISO-8859-1 charset.
|
||||
"${python}" - "${url}" "${work}" <<'PY'
|
||||
import sys, urllib.parse, urllib.request
|
||||
import re, sys, urllib.parse, urllib.request
|
||||
|
||||
url, work = sys.argv[1], sys.argv[2]
|
||||
outdir = work + "/caf\xe9" # 'café' as the single byte the browser would send
|
||||
# Port 1 refuses at once: only the decoded -O dir matters, not the fetch.
|
||||
cmd = "httrack --quiet --robots=0 http://127.0.0.1:1/x.html -O " + outdir
|
||||
fields = [("path", work), ("projname", "proj"), ("command_do", "start"),
|
||||
# The body is refused without the session id the server renders into each form.
|
||||
# Note the wizard page is under /server/; a bare /step4.html is the doc page.
|
||||
page = urllib.request.urlopen(url + "server/step4.html", timeout=20).read()
|
||||
m = re.search(rb'name="sid" value="([0-9a-f]+)"', page)
|
||||
if not m:
|
||||
raise SystemExit("no session id in server/step4.html")
|
||||
fields = [("sid", m.group(1).decode()),
|
||||
("path", work), ("projname", "proj"), ("command_do", "start"),
|
||||
("winprofile", "x"), ("command", cmd)]
|
||||
body = "&".join("%s=%s" % (k, urllib.parse.quote(v, safe="", encoding="latin-1"))
|
||||
for k, v in fields)
|
||||
|
||||
185
tests/77_webhttrack-redirect.test
Normal file
185
tests/77_webhttrack-redirect.test
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# htsserver's POST redirect: the Location header is built from a client-supplied
|
||||
# value, and the listen address is loopback unless --bind widens it.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
distdir=${top_srcdir:-$(cd "${testdir}/.." && pwd)}
|
||||
distdir=$(cd "${distdir}" && pwd)
|
||||
# run_with_timeout/kill_tree: timeout(1) is absent on macOS
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "${testdir}/testlib.sh"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v htsserver >/dev/null || fail "no htsserver in PATH"
|
||||
command -v python3 >/dev/null || {
|
||||
echo "python3 not found; skipping" >&2
|
||||
exit 77
|
||||
}
|
||||
|
||||
log=$(mktemp)
|
||||
# start() runs inside a command substitution, so its $! never reaches this
|
||||
# shell. Take the pid from the server's own announcement instead: a missed kill
|
||||
# leaves an orphan holding the CI job open long after the suite has passed.
|
||||
srvpid() { sed -n 's/^PID=//p' "${log}" 2>/dev/null | head -1; }
|
||||
cleanup() {
|
||||
stop
|
||||
rm -f "${log}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
|
||||
freeport() {
|
||||
python3 -c 'import socket
|
||||
s = socket.socket()
|
||||
s.bind(("127.0.0.1", 0))
|
||||
print(s.getsockname()[1])
|
||||
s.close()'
|
||||
}
|
||||
|
||||
# Start htsserver, echo the announced URL. Extra args are passed through.
|
||||
start() {
|
||||
local port url
|
||||
port=$(freeport)
|
||||
: >"${log}"
|
||||
(
|
||||
trap '' TERM TTOU
|
||||
exec htsserver "${distdir}/" --port "${port}" "$@" >"${log}" 2>&1
|
||||
) &
|
||||
for _ in $(seq 1 40); do
|
||||
url=$(sed -n 's/^URL=//p' "${log}" 2>/dev/null) && test -n "${url}" && break
|
||||
sleep 0.25
|
||||
done
|
||||
test -n "${url:-}" || fail "htsserver did not come up: $(cat "${log}")"
|
||||
echo "${url}"
|
||||
}
|
||||
|
||||
stop() {
|
||||
local pid
|
||||
pid=$(srvpid)
|
||||
test -z "${pid}" || kill -9 "${pid}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# The session id gates the request body, so a POST has to carry one. The server
|
||||
# renders it into every form, which is where a browser picks it up too.
|
||||
scrape_sid() {
|
||||
python3 -c 'import socket, sys
|
||||
s = socket.create_connection(("127.0.0.1", int(sys.argv[1])), 10)
|
||||
s.settimeout(20)
|
||||
s.sendall(b"GET /server/index.html HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n")
|
||||
out = b""
|
||||
while True:
|
||||
b = s.recv(65536)
|
||||
if not b:
|
||||
break
|
||||
out += b
|
||||
s.close()
|
||||
sys.stdout.write(out.decode("latin-1"))' "$1" |
|
||||
sed -n 's/.*name="sid" value="\([0-9a-f]*\)".*/\1/p' | head -1
|
||||
}
|
||||
|
||||
# POST redirect=$1 to 127.0.0.1:$2 with session id $3, print the raw headers.
|
||||
post_redirect() {
|
||||
python3 -c 'import socket, sys, urllib.parse
|
||||
body = ("sid=" + sys.argv[3] + "&redirect="
|
||||
+ urllib.parse.quote(sys.argv[1], safe=""))
|
||||
req = ("POST / HTTP/1.0\r\nHost: 127.0.0.1\r\n"
|
||||
"Content-type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-length: %d\r\n\r\n%s" % (len(body), body))
|
||||
s = socket.create_connection(("127.0.0.1", int(sys.argv[2])), 10)
|
||||
s.settimeout(20)
|
||||
s.sendall(req.encode())
|
||||
out = b""
|
||||
# stop at the end of the header block: the server need not close the connection,
|
||||
# and an unbounded recv() hung the macOS runner for over an hour.
|
||||
while b"\r\n\r\n" not in out:
|
||||
b = s.recv(65536)
|
||||
if not b:
|
||||
break
|
||||
out += b
|
||||
s.close()
|
||||
sys.stdout.write(out.split(b"\r\n\r\n")[0].decode("latin-1"))' "$1" "$2" "$3"
|
||||
}
|
||||
|
||||
portof() { echo "${1##*:}" | tr -d /; }
|
||||
|
||||
# "free"/"inuse"/"unusable": can $1 still be bound on 127.0.0.2? A wildcard
|
||||
# listener takes the port on every address, a loopback-only one does not, so
|
||||
# this discriminates where the announced URL cannot.
|
||||
probe_alias() {
|
||||
python3 -c 'import socket, sys
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.bind(("127.0.0.2", int(sys.argv[1])))
|
||||
except OSError as e:
|
||||
print("unusable" if e.errno in (99, 49, 10049) else "inuse")
|
||||
else:
|
||||
print("free")
|
||||
finally:
|
||||
s.close()' "$1"
|
||||
}
|
||||
|
||||
# Non-repeating and not a round number: a truncation, a reorder or a dropped
|
||||
# interior byte all stay visible.
|
||||
long=$(python3 -c 'print("".join(chr(33 + i % 90) for i in range(4097)))')
|
||||
url=$(start)
|
||||
port=$(portof "${url}")
|
||||
sid=$(scrape_sid "${port}")
|
||||
test "${#sid}" -eq 32 || fail "did not scrape a 32-hex sid (got '${sid}')"
|
||||
resp=$(post_redirect "${long}" "${port}" "${sid}") ||
|
||||
fail "no response to an oversized redirect value"
|
||||
stop
|
||||
loc=$(printf '%s' "${resp}" | sed -n 's/^Location: //p' | tr -d '\r')
|
||||
test "${loc}" = "${long}" ||
|
||||
fail "oversized redirect not echoed whole (got ${#loc} bytes, want ${#long})"
|
||||
|
||||
# CR/LF must suppress the header outright. Sanitising it instead would keep the
|
||||
# grep for an injected header quiet while still emitting a mangled Location.
|
||||
url=$(start)
|
||||
port=$(portof "${url}")
|
||||
sid=$(scrape_sid "${port}")
|
||||
resp=$(post_redirect '/foo
|
||||
X-Injected: pwned' "${port}" "${sid}") || fail "no response to a CRLF redirect value"
|
||||
stop
|
||||
printf '%s' "${resp}" | grep -qi 'X-Injected' &&
|
||||
fail "CRLF in the redirect value reached the response"
|
||||
test "$(printf '%s' "${resp}" | grep -ci '^Location:')" -eq 0 ||
|
||||
fail "CRLF redirect still emitted a Location header"
|
||||
test "$(printf '%s' "${resp}" | grep -c '^HTTP/1\.')" -eq 1 ||
|
||||
fail "CRLF redirect did not yield exactly one status line"
|
||||
|
||||
# Loopback unless asked otherwise. Assert the socket, not the announcement.
|
||||
url=$(start)
|
||||
port=$(portof "${url}")
|
||||
alias_state=$(probe_alias "${port}")
|
||||
stop
|
||||
if test "${alias_state}" = unusable; then
|
||||
echo "no 127.0.0.2 alias; skipping the listen-address assertions" >&2
|
||||
else
|
||||
test "${alias_state}" = free ||
|
||||
fail "default listen address is not loopback-only (127.0.0.2:${port} taken)"
|
||||
case "${url}" in
|
||||
http://127.0.0.1:*) ;;
|
||||
*) fail "default announcement is not loopback: ${url}" ;;
|
||||
esac
|
||||
|
||||
url=$(start --bind 0.0.0.0)
|
||||
port=$(portof "${url}")
|
||||
alias_state=$(probe_alias "${port}")
|
||||
stop
|
||||
test "${alias_state}" = inuse ||
|
||||
fail "--bind 0.0.0.0 did not take the wildcard (127.0.0.2:${port} ${alias_state})"
|
||||
fi
|
||||
|
||||
# An empty --bind must not quietly fall back to every interface. Bounded: if it
|
||||
# were accepted the server would listen instead of exiting.
|
||||
run_with_timeout 15 htsserver "${distdir}/" --bind "" >"${log}" 2>&1 || true
|
||||
grep -q -- "--bind needs an address" "${log}" ||
|
||||
fail "empty --bind not refused: $(cat "${log}")"
|
||||
|
||||
echo "PASS"
|
||||
142
tests/78_webhttrack-sid.test
Normal file
142
tests/78_webhttrack-sid.test
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# htsserver's session id must gate the request body: every field in it is
|
||||
# written into the global key store, and "command" from there reaches the engine.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
distdir=${top_srcdir:-$(cd "${testdir}/.." && pwd)}
|
||||
distdir=$(cd "${distdir}" && pwd)
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v htsserver >/dev/null || fail "no htsserver in PATH"
|
||||
command -v python3 >/dev/null || {
|
||||
echo "python3 not found; skipping" >&2
|
||||
exit 77
|
||||
}
|
||||
|
||||
srv=
|
||||
log=$(mktemp)
|
||||
cleanup() {
|
||||
test -z "${srv}" || kill -9 "${srv}" 2>/dev/null || true
|
||||
rm -f "${log}"
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
|
||||
freeport() {
|
||||
python3 -c 'import socket
|
||||
s = socket.socket()
|
||||
s.bind(("127.0.0.1", 0))
|
||||
print(s.getsockname()[1])
|
||||
s.close()'
|
||||
}
|
||||
|
||||
# Echo the announced URL. Runs in a command substitution, so it is a
|
||||
# subshell and cannot export the pid: the caller reads it back with srvpid.
|
||||
start() {
|
||||
local port url
|
||||
port=$(freeport)
|
||||
: >"${log}"
|
||||
(
|
||||
trap '' TERM TTOU
|
||||
exec htsserver "${distdir}/" --port "${port}" >"${log}" 2>&1
|
||||
) &
|
||||
for _ in $(seq 1 40); do
|
||||
url=$(sed -n 's/^URL=//p' "${log}" 2>/dev/null) && test -n "${url}" && break
|
||||
sleep 0.25
|
||||
done
|
||||
test -n "${url:-}" || fail "htsserver did not come up: $(cat "${log}")"
|
||||
echo "${url}"
|
||||
}
|
||||
|
||||
# The server reports its own pid; the aliveness assertions below hang off it.
|
||||
srvpid() { sed -n 's/^PID=//p' "${log}" | head -1; }
|
||||
|
||||
alive() { kill -0 "$1" 2>/dev/null; }
|
||||
|
||||
portof() { echo "${1##*:}" | tr -d /; }
|
||||
|
||||
# Raw request to 127.0.0.1:$1; $2 is the body ("" for a GET of the $3 page,
|
||||
# default index). Prints the reply.
|
||||
request() {
|
||||
python3 -c 'import socket, sys
|
||||
port, body = int(sys.argv[1]), sys.argv[2]
|
||||
page = sys.argv[3] if len(sys.argv) > 3 else "index"
|
||||
if body:
|
||||
req = ("POST / HTTP/1.0\r\nHost: 127.0.0.1\r\n"
|
||||
"Content-type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-length: %d\r\n\r\n%s" % (len(body), body))
|
||||
else:
|
||||
req = ("GET /server/%s.html HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n" % page)
|
||||
s = socket.create_connection(("127.0.0.1", port), 10)
|
||||
s.settimeout(30)
|
||||
s.sendall(req.encode())
|
||||
out = b""
|
||||
while True:
|
||||
b = s.recv(65536)
|
||||
if not b:
|
||||
break
|
||||
out += b
|
||||
s.close()
|
||||
sys.stdout.write(out.decode("latin-1"))' "$1" "$2" ${3+"$3"}
|
||||
}
|
||||
|
||||
# The server hands the sid to any client in every form; scraping it is the
|
||||
# legitimate flow, and it is what makes the accept case below meaningful.
|
||||
scrape_sid() {
|
||||
request "$1" "" | sed -n 's/.*name="sid" value="\([0-9a-f]*\)".*/\1/p' | head -1
|
||||
}
|
||||
|
||||
url=$(start)
|
||||
port=$(portof "${url}")
|
||||
srv=$(srvpid)
|
||||
test -n "${srv}" || fail "htsserver did not report its pid"
|
||||
alive "${srv}" || fail "htsserver is not running"
|
||||
|
||||
sid=$(scrape_sid "${port}")
|
||||
# Control: without a real sid every assertion below would pass vacuously.
|
||||
test "${#sid}" -eq 32 || fail "did not scrape a 32-hex sid from the page (got '${sid}')"
|
||||
|
||||
# Accept: the legitimate flow still works. "redirect" is the cheapest field
|
||||
# with a reply that is visible in the headers.
|
||||
resp=$(request "${port}" "sid=${sid}&redirect=/accepted")
|
||||
printf '%s' "${resp}" | grep -q '^Location: /accepted' ||
|
||||
fail "a body carrying the correct sid was refused"
|
||||
|
||||
# Refuse: missing and wrong. A missing one is the regression under test — the
|
||||
# expected value used to be pre-seeded into the compared key, so omitting the
|
||||
# field compared equal to itself.
|
||||
for bad in "redirect=/nosid" "sid=&redirect=/empty" \
|
||||
"sid=00000000000000000000000000000000&redirect=/wrong"; do
|
||||
resp=$(request "${port}" "${bad}")
|
||||
printf '%s' "${resp}" | grep -q '^Location:' &&
|
||||
fail "body accepted without a valid sid: ${bad}"
|
||||
# A refusal has to be a well-formed reply, not a headerless fragment: the
|
||||
# release build used to emit only Content-length, which reads as a protocol
|
||||
# error to any client and hides the reason.
|
||||
printf '%s' "${resp}" | grep -q '^HTTP/1\.0 403 ' ||
|
||||
fail "refusal was not a 403: ${bad}"
|
||||
done
|
||||
|
||||
# Suppressing the reply is not the same as refusing the write: the body is
|
||||
# applied to one global key store that later requests render from, and the
|
||||
# command dispatcher reads it from there. Probe the store itself — step3
|
||||
# interpolates ${projname} into its title — rather than the refused reply.
|
||||
title() { request "$1" "" step3; }
|
||||
|
||||
request "${port}" "projname=UNAUTHWRITE" >/dev/null 2>&1 || true
|
||||
title "${port}" | grep -q 'UNAUTHWRITE' &&
|
||||
fail "a body without a valid sid was written to the key store"
|
||||
|
||||
# Paired accept case: without it the assertion above passes even if the server
|
||||
# simply ignores every body, which would prove nothing.
|
||||
request "${port}" "sid=${sid}&projname=AUTHWRITE" >/dev/null 2>&1 || true
|
||||
title "${port}" | grep -q 'AUTHWRITE' ||
|
||||
fail "a body carrying the correct sid was not written to the key store"
|
||||
|
||||
echo "PASS"
|
||||
90
tests/79_local-proxytrack-webdav-mime.test
Executable file
90
tests/79_local-proxytrack-webdav-mime.test
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# proxytrack's WebDAV PROPFIND fallback must behave identically whether or not
|
||||
# the cache entry supplies Content-Type and Last-Modified.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "$top_srcdir/tests/testlib.sh"
|
||||
|
||||
python=$(find_python) || {
|
||||
echo "python3 missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
command -v curl >/dev/null 2>&1 || {
|
||||
echo "curl missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
# First test to run proxytrack as a live server, and MSYS cannot reap a native
|
||||
# listener: the orphan wedged the whole Windows suite for 48 minutes (#595).
|
||||
if is_windows; then
|
||||
echo "windows: cannot reap a backgrounded proxytrack, skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
dir=$(mktemp -d)
|
||||
ptpid=
|
||||
cleanup() {
|
||||
stop_server "$ptpid"
|
||||
rm -rf "$dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Neither header is present, so file->contenttype and ->lastmodified stay "".
|
||||
printf 'HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n' >"$dir/hdr"
|
||||
printf 'hello' >"$dir/body"
|
||||
alen=$(($(wc -c <"$dir/hdr") + $(wc -c <"$dir/body")))
|
||||
{
|
||||
printf 'filedesc://t.arc 0.0.0.0 20250101000000 text/plain 200 - - 0 t.arc 9\n'
|
||||
printf '2 0 test\n'
|
||||
printf '\n\n'
|
||||
printf 'http://example.com/page.html 0.0.0.0 20250101000000 text/html 200 - - 0 t.arc %d\n' "$alen"
|
||||
cat "$dir/hdr" "$dir/body"
|
||||
} >"$dir/in.arc"
|
||||
|
||||
freeport() {
|
||||
"$python" -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()'
|
||||
}
|
||||
proxyport=$(freeport)
|
||||
icpport=$(freeport)
|
||||
|
||||
proxytrack "127.0.0.1:$proxyport" "127.0.0.1:$icpport" "$dir/in.arc" >"$dir/pt.log" 2>&1 &
|
||||
ptpid=$!
|
||||
waited=0
|
||||
until grep -qE "HTTP Proxy installed on|Unable to (initialize a temporary server|create the server)" "$dir/pt.log"; do
|
||||
kill -0 "$ptpid" 2>/dev/null || {
|
||||
echo "FAIL: proxytrack exited before listening"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
test "$waited" -lt 50 || {
|
||||
echo "FAIL: proxytrack never announced its listen port"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
grep -q "HTTP Proxy installed on" "$dir/pt.log" || {
|
||||
echo "FAIL: proxytrack failed to bind"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --max-time: an unbounded read here would wedge the runner rather than fail
|
||||
curl -s --max-time 30 -X PROPFIND -H "Depth: 1" \
|
||||
"http://127.0.0.1:$proxyport/webdav/example.com/" >"$dir/resp.xml"
|
||||
|
||||
grep -q '<getcontenttype>application/octet-stream</getcontenttype>' "$dir/resp.xml" || {
|
||||
echo "FAIL: missing Content-Type did not fall back to application/octet-stream"
|
||||
cat "$dir/resp.xml"
|
||||
exit 1
|
||||
}
|
||||
grep -q '<getlastmodified>Wed, 01 Jan 2025 00:00:00 GMT</getlastmodified>' "$dir/resp.xml" || {
|
||||
echo "FAIL: missing Last-Modified did not fall back to the index timestamp"
|
||||
cat "$dir/resp.xml"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "OK: WebDAV listing falls back correctly for an entry with no Content-Type/Last-Modified"
|
||||
@@ -64,6 +64,7 @@ TESTS = \
|
||||
01_engine-reconcile.test \
|
||||
01_engine-expandhome.test \
|
||||
01_engine-fsize.test \
|
||||
01_engine-growsize.test \
|
||||
01_engine-redirect.test \
|
||||
01_engine-longpath-io.test \
|
||||
01_engine-mirror-io.test \
|
||||
@@ -171,6 +172,9 @@ TESTS = \
|
||||
74_local-warc-wacz.test \
|
||||
74_local-warc-verbatim.test \
|
||||
75_engine-longpath-posix.test \
|
||||
76_cli-resize.test
|
||||
76_cli-resize.test \
|
||||
77_webhttrack-redirect.test \
|
||||
78_webhttrack-sid.test \
|
||||
79_local-proxytrack-webdav-mime.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
Reference in New Issue
Block a user