mirror of
https://github.com/xroche/httrack.git
synced 2026-07-11 19:36:52 +03:00
Compare commits
1 Commits
3.49.12
...
remove-old
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4020cda340 |
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -43,12 +43,6 @@ jobs:
|
||||
languages: c-cpp
|
||||
build-mode: manual
|
||||
queries: security-extended
|
||||
# fopen's umask-controlled 0666 is intended for mirror/cache/log
|
||||
# output; the one credential file (cookies.txt) is kept 0600 on Unix.
|
||||
config: |
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: cpp/world-writable-file-creation
|
||||
|
||||
# Manual build: CodeQL traces the compiler, so build exactly what ships.
|
||||
- name: Build
|
||||
|
||||
10
configure.ac
10
configure.ac
@@ -1,6 +1,6 @@
|
||||
AC_PREREQ([2.71])
|
||||
|
||||
AC_INIT([httrack], [3.49.12], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_INIT([httrack], [3.49.11], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
|
||||
AC_COPYRIGHT([
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 1998-2015 Xavier Roche and other contributors
|
||||
@@ -29,11 +29,11 @@ AC_CONFIG_SRCDIR(src/httrack.c)
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
# 3:4:0: 3.49.12 tail-appends one field (why_url) to httrackp and otherwise only
|
||||
# deletes dead comments; no struct layout or exported signature broke vs 3.49.11,
|
||||
# so it stays soname .so.3; bump revision.
|
||||
# 3:3:0: 3.49.11 only adds enum values, macros and inline helpers to the
|
||||
# installed headers (no struct layout or exported signature changed vs
|
||||
# 3.49.10), so it stays soname .so.3; bump revision.
|
||||
# (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.)
|
||||
VERSION_INFO="3:4:0"
|
||||
VERSION_INFO="3:3:0"
|
||||
AM_MAINTAINER_MODE
|
||||
AC_USE_SYSTEM_EXTENSIONS
|
||||
|
||||
|
||||
10
debian/changelog
vendored
10
debian/changelog
vendored
@@ -1,13 +1,3 @@
|
||||
httrack (3.49.12-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: security and crawl-correctness fixes (remote stack
|
||||
overflow in Content-Type/-Encoding parsing, fuzzer-found parser over-reads,
|
||||
world-readable cookies.txt, filter-pattern denial of service) plus a new
|
||||
--why filter diagnostic; full list in history.txt.
|
||||
* Build with _FORTIFY_SOURCE and -fstack-protector-strong.
|
||||
|
||||
-- Xavier Roche <xavier@debian.org> Fri, 10 Jul 2026 21:11:09 +0200
|
||||
|
||||
httrack (3.49.11-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release: crawl correctness and security fixes (network-facing
|
||||
|
||||
@@ -36,7 +36,6 @@ EXTRA_DIST = README.md run-fuzzers.sh \
|
||||
corpus/unescape/percent.txt \
|
||||
corpus/filters/filter.bin corpus/filters/filter-size.bin \
|
||||
corpus/filters/regress-empty-subject-unique.bin \
|
||||
corpus/filters/redos-star-classes.bin \
|
||||
corpus/url/http-url.txt corpus/url/relative-path.txt \
|
||||
corpus/url/regress-file-empty-path.txt corpus/url/regress-long-path-abort.txt \
|
||||
corpus/header/full-response.txt corpus/header/redirect.txt \
|
||||
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<meta content="text/html; charset=gb2312" http-equiv="content-type">
|
||||
@@ -1 +0,0 @@
|
||||
<meta charset='utf-8
|
||||
@@ -1 +0,0 @@
|
||||
<meta charset = utf-8 />
|
||||
@@ -50,13 +50,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
while (p != NULL && *p != '\0') {
|
||||
char *nl = strchr(p, '\n');
|
||||
size_t n = (nl != NULL) ? (size_t) (nl - p) : strlen(p);
|
||||
size_t i, len = 0;
|
||||
|
||||
/* binput drops every '\r' on the wire; mirror it */
|
||||
for (i = 0; i < n; i++)
|
||||
if (p[i] != '\r')
|
||||
line[len++] = p[i];
|
||||
line[len] = '\0';
|
||||
memcpy(line, p, n);
|
||||
line[n] = '\0';
|
||||
if (first) {
|
||||
treatfirstline(&r, line);
|
||||
first = 0;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# Drive every built harness against its seed corpus.
|
||||
# run-fuzzers.sh <build-fuzz-dir> check deterministic replay (CI smoke)
|
||||
# run-fuzzers.sh <build-fuzz-dir> [seconds] timed mutation run (discovery)
|
||||
# Replay is crash/leak-only and never mutates; the per-unit -timeout guards
|
||||
# both modes against pathological slowdowns (e.g. pre-#501 strjoker).
|
||||
# Replay is crash/leak-only and never mutates, so it can't hit strjoker's
|
||||
# catastrophic backtracking; the timed mode can, hence the per-unit -timeout.
|
||||
set -euo pipefail
|
||||
|
||||
srcdir=$(cd "$(dirname "$0")" && pwd)
|
||||
|
||||
17
history.txt
17
history.txt
@@ -4,23 +4,6 @@ HTTrack Website Copier release history:
|
||||
|
||||
This file lists all changes and fixes that have been made for HTTrack
|
||||
|
||||
3.49-12
|
||||
+ New: --why explains which filter rule accepts or rejects a given URL, then exits (#505)
|
||||
+ Fixed: links carrying raw UTF-8 bytes were fetched double-encoded and 404'd (#516)
|
||||
+ Fixed: an uncompressed body mislabeled as gzip no longer loses the page (#515)
|
||||
+ Fixed: remote stack overflow and uninitialized read in Content-Type/-Encoding parsing (#506)
|
||||
+ Fixed: several over-reads and a leak in the filter, URL and IDNA parsers (#499)
|
||||
+ Fixed: bound the cache-index (.ndx) parser to its buffer (#507)
|
||||
+ Fixed: catastrophic backtracking on '*'-heavy filter patterns (#513)
|
||||
+ Fixed: cookies.txt was created world-readable (#511)
|
||||
+ Fixed: a single corrupt cache entry no longer aborts the whole mirror (#494)
|
||||
+ Fixed: cache-reconcile policy was broken for zip caches (#491, #493, #495)
|
||||
+ Fixed: cancelling a crawl mid type-check no longer orphans .delayed placeholders (#496)
|
||||
+ Fixed: detect URLs after the first inline script and in mid-tag attributes (#497)
|
||||
+ Changed: build with _FORTIFY_SOURCE and -fstack-protector-strong (#504)
|
||||
+ Changed: removed the pre-3.31 (.dat/.ndx) cache import (#512)
|
||||
+ Changed: multiple internal hardening and build improvements (libFuzzer harnesses, CodeQL, dead-code removal)
|
||||
|
||||
3.49-11
|
||||
+ New: parse robots.txt Allow rules and path wildcards per RFC 9309 (#452)
|
||||
+ New: advertise deflate in Accept-Encoding and decode deflate responses (#450)
|
||||
|
||||
169
src/htsback.c
169
src/htsback.c
@@ -1646,104 +1646,138 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
|
||||
}
|
||||
}
|
||||
// tester cache
|
||||
if ((strcmp(adr, "file://")) /* pas fichier */
|
||||
&& ((!test) ||
|
||||
(cache->type == 1)) /* cache prioritaire, laisser passer en test! */
|
||||
&&
|
||||
((strnotempty(save)) || (strcmp(fil, "/robots.txt") ==
|
||||
0))) { // si en test on ne doit pas utiliser le
|
||||
// cache sinon telescopage avec le 302..
|
||||
if ((strcmp(adr, "file://")) /* pas fichier */
|
||||
&&((!test) || (cache->type == 1)) /* cache prioritaire, laisser passer en test! */
|
||||
&&((strnotempty(save)) || (strcmp(fil, "/robots.txt") == 0))) { // si en test on ne doit pas utiliser le cache sinon telescopage avec le 302..
|
||||
#if HTS_FAST_CACHE
|
||||
intptr_t hash_pos;
|
||||
int hash_pos_return = 0;
|
||||
|
||||
#else
|
||||
char *a = NULL;
|
||||
#endif
|
||||
#if HTS_FAST_CACHE
|
||||
if (cache->hashtable) {
|
||||
#else
|
||||
if (cache->use) {
|
||||
#endif
|
||||
char BIGSTK buff[HTS_URLMAXSIZE * 4];
|
||||
|
||||
#if HTS_FAST_CACHE
|
||||
strcpybuff(buff, adr);
|
||||
strcatbuff(buff, fil);
|
||||
hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos);
|
||||
#else
|
||||
buff[0] = '\0';
|
||||
strcatbuff(buff, "\n");
|
||||
strcatbuff(buff, adr);
|
||||
strcatbuff(buff, "\n");
|
||||
strcatbuff(buff, fil);
|
||||
strcatbuff(buff, "\n");
|
||||
a = strstr(cache->use, buff);
|
||||
#endif
|
||||
|
||||
// Ok, noté en cache->. mais bien présent dans le cache ou sur disque?
|
||||
#if HTS_FAST_CACHE
|
||||
// negative values when data is not in cache
|
||||
if (hash_pos_return < 0) {
|
||||
if (!test) { // not test mode
|
||||
/* note: no check with IS_DELAYED_EXT() enabled - postcheck by
|
||||
* client please! */
|
||||
if (save[0] != '\0' && !IS_DELAYED_EXT(save) &&
|
||||
fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <=
|
||||
0) { // final file missing or empty
|
||||
int found = 0;
|
||||
#else
|
||||
if (a) {
|
||||
#endif
|
||||
if (!test) { // non mode test
|
||||
#if HTS_FAST_CACHE==0
|
||||
int pos = -1;
|
||||
|
||||
/* It is possible that the file has been moved due to changes in
|
||||
* build structure */
|
||||
{
|
||||
char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
|
||||
htsblk r;
|
||||
a += strlen(buff);
|
||||
sscanf(a, "%d", &pos); // lire position
|
||||
#endif
|
||||
|
||||
previous_save[0] = '\0';
|
||||
r = cache_readex(opt, cache, adr, fil, /*head */ NULL,
|
||||
#if HTS_FAST_CACHE==0
|
||||
if (pos < 0) { // pas de mise en cache data, vérifier existence
|
||||
#endif
|
||||
/* note: no check with IS_DELAYED_EXT() enabled - postcheck by client please! */
|
||||
if (save[0] != '\0' && !IS_DELAYED_EXT(save) && fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <= 0) { // fichier final n'existe pas ou est vide!
|
||||
int found = 0;
|
||||
|
||||
/* It is possible that the file has been moved due to changes in build structure */
|
||||
{
|
||||
char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
|
||||
htsblk r;
|
||||
|
||||
previous_save[0] = '\0';
|
||||
r =
|
||||
cache_readex(opt, cache, adr, fil, /*head */ NULL,
|
||||
/*bound to back[p] (temporary) */
|
||||
back[p].location_buffer, previous_save, /*ro */
|
||||
1);
|
||||
/* Is supposed to be on disk only */
|
||||
if (r.is_write && previous_save[0] != '\0') {
|
||||
/* Exists, but with another (old) filename: rename (almost)
|
||||
* silently */
|
||||
if (strcmp(previous_save, save) != 0 &&
|
||||
fexist_utf8(
|
||||
fconv(catbuff, sizeof(catbuff), previous_save))) {
|
||||
rename(fconv(catbuff, sizeof(catbuff), previous_save),
|
||||
fconv(catbuff2, sizeof(catbuff2), save));
|
||||
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
|
||||
found = 1;
|
||||
hts_log_print(opt, LOG_DEBUG,
|
||||
"File '%s' has been renamed since last "
|
||||
"mirror to '%s' ; applying changes",
|
||||
previous_save, save);
|
||||
} else {
|
||||
hts_log_print(opt, LOG_ERROR,
|
||||
"Could not rename '%s' to '%s' ; will have "
|
||||
"to retransfer it",
|
||||
previous_save, save);
|
||||
/* Is supposed to be on disk only */
|
||||
if (r.is_write && previous_save[0] != '\0') {
|
||||
/* Exists, but with another (old) filename: rename (almost) silently */
|
||||
if (strcmp(previous_save, save) != 0
|
||||
&& fexist_utf8(fconv(catbuff, sizeof(catbuff), previous_save))) {
|
||||
rename(fconv(catbuff, sizeof(catbuff), previous_save),
|
||||
fconv(catbuff2, sizeof(catbuff2), save));
|
||||
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
|
||||
found = 1;
|
||||
hts_log_print(opt, LOG_DEBUG,
|
||||
"File '%s' has been renamed since last mirror to '%s' ; applying changes",
|
||||
previous_save, save);
|
||||
} else {
|
||||
hts_log_print(opt, LOG_ERROR,
|
||||
"Could not rename '%s' to '%s' ; will have to retransfer it",
|
||||
previous_save, save);
|
||||
}
|
||||
}
|
||||
}
|
||||
back[p].location_buffer[0] = '\0';
|
||||
}
|
||||
back[p].location_buffer[0] = '\0';
|
||||
}
|
||||
|
||||
/* Not found ? */
|
||||
if (!found) {
|
||||
// invalidate: gone from disk, force a refetch
|
||||
hash_pos_return = 0;
|
||||
if (opt->norecatch) {
|
||||
if (!fexist_utf8(fconv(
|
||||
catbuff, sizeof(catbuff),
|
||||
save))) { // declared but missing: user erased it
|
||||
FILE *fp =
|
||||
FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb");
|
||||
/* Not found ? */
|
||||
if (!found) {
|
||||
#if HTS_FAST_CACHE
|
||||
hash_pos_return = 0;
|
||||
#else
|
||||
a = NULL;
|
||||
#endif
|
||||
// dévalider car non présent sur disque dans structure originale!!!
|
||||
// sinon, le fichier est ok à priori, mais on renverra un if-modified-since pour
|
||||
// en être sûr
|
||||
if (opt->norecatch) { // tester norecatch
|
||||
if (!fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) { // fichier existe pas mais déclaré: on l'a effacé
|
||||
FILE *fp = FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb");
|
||||
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Previous file '%s' not found (erased by user ?), ignoring: %s%s",
|
||||
save, back[p].url_adr, back[p].url_fil);
|
||||
}
|
||||
} else {
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Previous file '%s' not found (erased by "
|
||||
"user ?), ignoring: %s%s",
|
||||
"Previous file '%s' not found (erased by user ?), recatching: %s%s",
|
||||
save, back[p].url_adr, back[p].url_fil);
|
||||
}
|
||||
} else {
|
||||
hts_log_print(opt, LOG_WARNING,
|
||||
"Previous file '%s' not found (erased by user "
|
||||
"?), recatching: %s%s",
|
||||
save, back[p].url_adr, back[p].url_fil);
|
||||
}
|
||||
}
|
||||
} // fsize() <= 0
|
||||
} // fsize() <= 0
|
||||
#if HTS_FAST_CACHE==0
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
//
|
||||
} else {
|
||||
#if HTS_FAST_CACHE
|
||||
hash_pos_return = 0;
|
||||
#else
|
||||
a = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (hash_pos_return) { // in cache, with data
|
||||
// Existe pas en cache, ou bien pas de cache présent
|
||||
#if HTS_FAST_CACHE
|
||||
if (hash_pos_return) { // OK existe en cache (et données aussi)!
|
||||
#else
|
||||
if (a != NULL) { // OK existe en cache (et données aussi)!
|
||||
#endif
|
||||
const int cache_is_prioritary = cache->type == 1
|
||||
|| opt->state.stop != 0;
|
||||
if (cache_is_prioritary) { // cache prioritaire (pas de test if-modified..)
|
||||
@@ -1864,8 +1898,7 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/* Not in cache ; maybe in temporary cache ? Warning: non-movable
|
||||
"url_sav" */
|
||||
/* Not in cache ; maybe in temporary cache ? Warning: non-movable "url_sav" */
|
||||
else if (back_unserialize_ref(opt, adr, fil, &itemback) == 0) {
|
||||
const off_t file_size = fsize_utf8(itemback->url_sav);
|
||||
|
||||
|
||||
@@ -307,25 +307,14 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Write cookies.txt; returns 0 on success. The jar holds live session
|
||||
cookies, so keep it owner-only on Unix (Windows inherits folder ACLs). */
|
||||
// écrire cookies.txt
|
||||
// !=0 : erreur
|
||||
int cookie_save(t_cookie * cookie, const char *name) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
|
||||
if (strnotempty(cookie->data)) {
|
||||
char BIGSTK line[8192];
|
||||
#ifdef _WIN32
|
||||
FILE *fp = fopen(fconv(catbuff, sizeof(catbuff), name), "wb");
|
||||
#else
|
||||
const int fd = open(fconv(catbuff, sizeof(catbuff), name),
|
||||
O_WRONLY | O_CREAT | O_TRUNC, HTS_PROTECT_FILE);
|
||||
FILE *fp = fd != -1 ? fdopen(fd, "wb") : NULL;
|
||||
|
||||
if (fd != -1 && fp == NULL)
|
||||
close(fd); /* fdopen failed: don't leak the descriptor */
|
||||
if (fp != NULL) /* O_CREAT's mode skips pre-existing jars: tighten those */
|
||||
(void) fchmod(fd, HTS_PROTECT_FILE);
|
||||
#endif
|
||||
|
||||
if (fp) {
|
||||
char *a = cookie->data;
|
||||
|
||||
@@ -491,7 +491,11 @@ static htsblk cache_readex_new(httrackp * opt, cache_back * cache,
|
||||
hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos);
|
||||
/* avoid errors on data entries */
|
||||
if (adr[0] == '/' && adr[1] == '/' && adr[2] == '[') {
|
||||
#if HTS_FAST_CACHE
|
||||
hash_pos_return = 0;
|
||||
#else
|
||||
a = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (hash_pos_return != 0) {
|
||||
|
||||
218
src/htscharset.c
218
src/htscharset.c
@@ -84,7 +84,7 @@ static int hts_equalsAlphanum(const char *a, const char *b) {
|
||||
|
||||
/* Copy the memory region [s .. s + size - 1 ] as a \0-terminated string. */
|
||||
static char *hts_stringMemCopy(const char *s, size_t size) {
|
||||
char *dest = malloct(size + 1);
|
||||
char *dest = malloc(size + 1);
|
||||
|
||||
if (dest != NULL) {
|
||||
memcpy(dest, s, size);
|
||||
@@ -549,6 +549,42 @@ char *hts_convertStringFromUTF8(const char *s, size_t size, const char *charset)
|
||||
|
||||
#endif
|
||||
|
||||
HTS_STATIC char *hts_getCharsetFromContentType(const char *mime) {
|
||||
/* text/html; charset=utf-8 */
|
||||
const char *const charset = "charset";
|
||||
char *pos = strstr(mime, charset);
|
||||
|
||||
if (pos != NULL) {
|
||||
/* Skip spaces */
|
||||
int eq = 0;
|
||||
|
||||
for(pos += strlen(charset);
|
||||
*pos == ' ' || *pos == '=' || *pos == '"' || *pos == '\''; pos++) {
|
||||
if (*pos == '=') {
|
||||
eq = 1;
|
||||
}
|
||||
}
|
||||
if (eq == 1) {
|
||||
int len;
|
||||
|
||||
for(len = 0;
|
||||
pos[len] == ' ' || pos[len] == ';' || pos[len] == '"' || *pos == '\'';
|
||||
pos++) ;
|
||||
if (len != 0) {
|
||||
char *const s = malloc(len + 1);
|
||||
int i;
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
s[i] = pos[i];
|
||||
}
|
||||
s[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define strcasecmp(a,b) stricmp(a,b)
|
||||
#define strncasecmp(a,b,n) strnicmp(a,b,n)
|
||||
@@ -608,106 +644,58 @@ int hts_isCharsetUTF8(const char *charset) {
|
||||
|| strcasecmp(charset, "utf8") == 0 );
|
||||
}
|
||||
|
||||
/* Extract X from a "text/html; charset=X" content= attribute value. */
|
||||
static char *charset_from_content(const char *s, size_t len) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i + 7 < len; i++) {
|
||||
if ((i == 0 || is_space(s[i - 1]) || s[i - 1] == ';') &&
|
||||
strncasecmp(&s[i], "charset", 7) == 0 && is_space_or_equal(s[i + 7])) {
|
||||
size_t j, val;
|
||||
|
||||
for (j = i + 7; j < len && is_space_or_equal_or_quote(s[j]); j++)
|
||||
;
|
||||
for (val = j; j < len && s[j] != '"' && s[j] != '\'' && s[j] != ';' &&
|
||||
!is_space(s[j]);
|
||||
j++)
|
||||
;
|
||||
if (j != val) {
|
||||
return hts_stringMemCopy(&s[val], j - val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *hts_getCharsetFromMeta(const char *html, size_t size) {
|
||||
size_t i;
|
||||
int i;
|
||||
|
||||
/* HTML5 <meta charset=X> or legacy
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=X">,
|
||||
attributes in any order; first resolvable tag wins. */
|
||||
for (i = 0; i + 6 < size; i++) {
|
||||
size_t j;
|
||||
const char *equiv = NULL, *content = NULL;
|
||||
size_t equiv_len = 0, content_len = 0;
|
||||
/* <META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8" > */
|
||||
for(i = 0; i < size; i++) {
|
||||
if (html[i] == '<' && strncasecmp(&html[i + 1], "meta", 4) == 0
|
||||
&& is_space(html[i + 5])) {
|
||||
/* Skip spaces */
|
||||
for(i += 5; is_space(html[i]); i++) ;
|
||||
if (strncasecmp(&html[i], "HTTP-EQUIV", 10) == 0
|
||||
&& is_space_or_equal(html[i + 10])) {
|
||||
for(i += 10; is_space_or_equal_or_quote(html[i]); i++) ;
|
||||
if (strncasecmp(&html[i], "CONTENT-TYPE", 12) == 0) {
|
||||
for(i += 12; is_space_or_equal_or_quote(html[i]); i++) ;
|
||||
if (strncasecmp(&html[i], "CONTENT", 7) == 0
|
||||
&& is_space_or_equal(html[i + 7])) {
|
||||
for(i += 7; is_space_or_equal_or_quote(html[i]); i++) ;
|
||||
/* Skip content-type */
|
||||
for(;
|
||||
i < size && html[i] != ';' && html[i] != '"' && html[i] != '\'';
|
||||
i++) ;
|
||||
/* Expect charset attribute here */
|
||||
if (html[i] == ';') {
|
||||
for(i++; is_space(html[i]); i++) ;
|
||||
/* Look for charset */
|
||||
if (strncasecmp(&html[i], "charset", 7) == 0
|
||||
&& is_space_or_equal(html[i + 7])) {
|
||||
int len;
|
||||
|
||||
if (html[i] != '<' || strncasecmp(&html[i + 1], "meta", 4) != 0 ||
|
||||
!is_space(html[i + 5])) {
|
||||
continue;
|
||||
}
|
||||
/* Attribute scan, strictly bounded by size. */
|
||||
for (j = i + 6; j < size && html[j] != '>';) {
|
||||
size_t name, name_len, val = 0, val_len = 0;
|
||||
for(i += 7; is_space_or_equal(html[i]) || html[i] == '\'';
|
||||
i++) ;
|
||||
/* Charset */
|
||||
for(len = 0;
|
||||
i + len < size && html[i + len] != '"'
|
||||
&& html[i + len] != '\'' && html[i + len] != ' '; len++) ;
|
||||
/* No error ? */
|
||||
if (len != 0 && i < size) {
|
||||
char *const s = malloc(len + 1);
|
||||
int j;
|
||||
|
||||
if (is_space(html[j]) || html[j] == '/') {
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
for (name = j; j < size && html[j] != '=' && html[j] != '>' &&
|
||||
html[j] != '/' && !is_space(html[j]);
|
||||
j++)
|
||||
;
|
||||
name_len = j - name;
|
||||
for (; j < size && is_space(html[j]); j++)
|
||||
;
|
||||
if (j < size && html[j] == '=') {
|
||||
for (j++; j < size && is_space(html[j]); j++)
|
||||
;
|
||||
if (j < size && (html[j] == '"' || html[j] == '\'')) {
|
||||
const char quote = html[j++];
|
||||
|
||||
for (val = j; j < size && html[j] != quote; j++)
|
||||
;
|
||||
if (j >= size) /* unterminated quote: drop the tag */
|
||||
break;
|
||||
val_len = j++ - val;
|
||||
} else {
|
||||
/* unquoted: ends at whitespace or '>' only (HTML5 prescan); '/'
|
||||
belongs to the value except as the tail of a self-close */
|
||||
for (val = j; j < size && !is_space(html[j]) && html[j] != '>'; j++)
|
||||
;
|
||||
val_len = j - val;
|
||||
if (val_len != 0 && j < size && html[j] == '>' &&
|
||||
html[val + val_len - 1] == '/') {
|
||||
val_len--;
|
||||
for(j = 0; j < len; j++) {
|
||||
s[j] = html[i + j];
|
||||
}
|
||||
s[len] = '\0';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* first occurrence of each attribute wins, as in browsers */
|
||||
if (val_len != 0) {
|
||||
if (name_len == 7 && strncasecmp(&html[name], "charset", 7) == 0) {
|
||||
return hts_stringMemCopy(&html[val], val_len);
|
||||
} else if (equiv == NULL && name_len == 10 &&
|
||||
strncasecmp(&html[name], "http-equiv", 10) == 0) {
|
||||
equiv = &html[val];
|
||||
equiv_len = val_len;
|
||||
} else if (content == NULL && name_len == 7 &&
|
||||
strncasecmp(&html[name], "content", 7) == 0) {
|
||||
content = &html[val];
|
||||
content_len = val_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (equiv_len == 12 && strncasecmp(equiv, "content-type", 12) == 0 &&
|
||||
content != NULL) {
|
||||
char *const s = charset_from_content(content, content_len);
|
||||
|
||||
if (s != NULL) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -1152,45 +1140,17 @@ int hts_isStringUTF8(const char *s, size_t size) {
|
||||
const unsigned char *const data = (const unsigned char*) s;
|
||||
size_t i;
|
||||
|
||||
/* Strict RFC 3629: reject overlongs, surrogates, > U+10FFFF, 5/6-byte. */
|
||||
for(i = 0 ; i < size ; ) {
|
||||
const unsigned char c = data[i];
|
||||
size_t len, k;
|
||||
hts_UCS4 uc, min;
|
||||
/* Reader: can read bytes up to j */
|
||||
#define RD ( i < size ? data[i++] : -1 )
|
||||
|
||||
if (c < 0x80) {
|
||||
i++;
|
||||
continue;
|
||||
} else if (c >= 0xc2 && c <= 0xdf) {
|
||||
len = 2;
|
||||
min = 0x80;
|
||||
uc = c & 0x1f;
|
||||
} else if (c >= 0xe0 && c <= 0xef) {
|
||||
len = 3;
|
||||
min = 0x800;
|
||||
uc = c & 0x0f;
|
||||
} else if (c >= 0xf0 && c <= 0xf4) {
|
||||
len = 4;
|
||||
min = 0x10000;
|
||||
uc = c & 0x07;
|
||||
} else { /* continuation byte, overlong C0/C1, or F5..FF lead */
|
||||
return 0;
|
||||
}
|
||||
if (size - i < len) {
|
||||
return 0;
|
||||
}
|
||||
for (k = 1; k < len; k++) {
|
||||
const unsigned char cc = data[i + k];
|
||||
/* Writer: a malformed sequence means the string is not UTF-8 (return 0) */
|
||||
#define WR(C) if ((C) == -1) { return 0; }
|
||||
|
||||
if ((cc & 0xc0) != 0x80) {
|
||||
return 0;
|
||||
}
|
||||
uc = (uc << 6) | (cc & 0x3f);
|
||||
}
|
||||
if (uc < min || uc > 0x10FFFF || (uc >= 0xD800 && uc <= 0xDFFF)) {
|
||||
return 0;
|
||||
}
|
||||
i += len;
|
||||
/* Read Unicode character. */
|
||||
READ_UNICODE(RD, WR);
|
||||
#undef RD
|
||||
#undef WR
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
@@ -76,8 +76,7 @@ extern char *hts_convertStringIDNAToUTF8(const char *s, size_t size);
|
||||
extern int hts_isStringIDNA(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Extract the <meta> charset from the HTML buffer "html" (HTML5 charset= or
|
||||
* legacy http-equiv form). Returns a malloc'ed string, or NULL if none.
|
||||
* Extract the charset from the HTML buffer "html"
|
||||
**/
|
||||
extern char *hts_getCharsetFromMeta(const char *html, size_t size);
|
||||
|
||||
@@ -87,8 +86,7 @@ extern char *hts_getCharsetFromMeta(const char *html, size_t size);
|
||||
extern int hts_isStringAscii(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Is the given string valid UTF-8 ? Strict RFC 3629: overlong forms,
|
||||
* surrogates, codepoints above U+10FFFF and 5/6-byte sequences are rejected.
|
||||
* Is the given string an UTF-8 string ?
|
||||
**/
|
||||
extern int hts_isStringUTF8(const char *s, size_t size);
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ Please visit our Website: http://www.httrack.com
|
||||
// always direct-to-disk (0/1)
|
||||
#define HTS_DIRECTDISK_ALWAYS 1
|
||||
|
||||
// fast cache (build hash table)
|
||||
#define HTS_FAST_CACHE 1
|
||||
|
||||
// le > peut être considéré comme un tag de fermeture de commentaire (<!-- > est
|
||||
// valide)
|
||||
#define GT_ENDS_COMMENT 1
|
||||
|
||||
@@ -134,6 +134,10 @@ RUN_CALLBACK0(opt, end); \
|
||||
back_delete_all(opt, &cache, sback); \
|
||||
back_free(&sback); \
|
||||
checkrobots_free(&robots); \
|
||||
if (cache.use) { \
|
||||
freet(cache.use); \
|
||||
cache.use = NULL; \
|
||||
} \
|
||||
if (cache.zipOutput) { \
|
||||
zipClose(cache.zipOutput, \
|
||||
"Created by HTTrack Website Copier/" HTTRACK_VERSION); \
|
||||
|
||||
@@ -194,6 +194,7 @@ struct cache_back {
|
||||
int type;
|
||||
int ro; /**< read-only: no new cache is written */
|
||||
|
||||
char *use; /**< in-memory list of cached adr+fil keys */
|
||||
FILE *lst; /**< file list, used for purge */
|
||||
FILE *txt; /**< human-readable file list (info) */
|
||||
char lastmodified[256];
|
||||
|
||||
@@ -48,7 +48,6 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htsbase.h"
|
||||
#include "htslib.h"
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
/* END specific definitions */
|
||||
|
||||
// à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé
|
||||
@@ -121,81 +120,16 @@ int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
|
||||
return use1 ? jok1 : jok2;
|
||||
}
|
||||
|
||||
/* Failure memo for the recursive matcher: one bit per (chaine, joker) offset
|
||||
pair keeps star-heavy patterns polynomial instead of exponential (#501). */
|
||||
typedef struct strjoker_memo {
|
||||
const char *chaine0, *joker0; /* offsets are relative to these bases */
|
||||
size_t stride; /* strlen(joker0) + 1 */
|
||||
unsigned char *failed; /* failed-pair bitmap; NULL: no memo */
|
||||
} strjoker_memo;
|
||||
|
||||
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
const char *joker, LLint *size,
|
||||
int *size_flag);
|
||||
|
||||
/* A pair that failed once fails forever (*size is only written on the success
|
||||
path), so record NULL results and cut the re-exploration. */
|
||||
static const char *strjoker_rec(const strjoker_memo *memo, const char *chaine,
|
||||
const char *joker, LLint *size,
|
||||
int *size_flag) {
|
||||
size_t bit = 0;
|
||||
const char *adr;
|
||||
|
||||
if (memo->failed) {
|
||||
bit = (size_t) (chaine - memo->chaine0) * memo->stride +
|
||||
(size_t) (joker - memo->joker0);
|
||||
if (memo->failed[bit >> 3] & (unsigned char) (1u << (bit & 7)))
|
||||
return NULL;
|
||||
}
|
||||
adr = strjoker_impl(memo, chaine, joker, size, size_flag);
|
||||
if (adr == NULL && memo->failed)
|
||||
memo->failed[bit >> 3] |= (unsigned char) (1u << (bit & 7));
|
||||
return adr;
|
||||
}
|
||||
|
||||
// wildcard comparator: match chaine against joker (pattern), case-insensitive
|
||||
// returns the address of the first matched letter past any leading joker
|
||||
// (ie. *[..]toto.. returns the address of toto within chaine), NULL on mismatch
|
||||
// supercomparateur joker (tm)
|
||||
// compare a et b (b=avec joker dedans), case insensitive [voir CI]
|
||||
// renvoi l'adresse de la première lettre de la chaine
|
||||
// (càd *[..]toto.. renvoi adresse de toto dans la chaine)
|
||||
// accepte les délires du genre www.*.*/ * / * truc*.*
|
||||
// cet algo est 'un peu' récursif mais ne consomme pas trop de tm
|
||||
// * = toute lettre
|
||||
// --?-- : spécifique à HTTrack et aux ?
|
||||
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
||||
LLint *size, int *size_flag) {
|
||||
strjoker_memo memo = {chaine, joker, 0, NULL};
|
||||
unsigned char stackbits[2048];
|
||||
hts_boolean onheap = HTS_FALSE;
|
||||
const char *adr;
|
||||
|
||||
if (chaine != NULL && joker != NULL) {
|
||||
const size_t n1 = strlen(chaine) + 1;
|
||||
|
||||
memo.stride = strlen(joker) + 1;
|
||||
if (n1 <= (SIZE_MAX - 7) / memo.stride) { // overflow-safe bitmap size
|
||||
const size_t bytes = (n1 * memo.stride + 7) / 8;
|
||||
|
||||
if (bytes <= sizeof(stackbits)) {
|
||||
memset(stackbits, 0, bytes);
|
||||
memo.failed = stackbits;
|
||||
} else {
|
||||
memo.failed = (unsigned char *) calloct(bytes, 1);
|
||||
onheap = memo.failed != NULL ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
adr = strjoker_rec(&memo, chaine, joker, size, size_flag);
|
||||
if (onheap)
|
||||
freet(memo.failed);
|
||||
return adr;
|
||||
}
|
||||
|
||||
/* Test-only oracle: the same matcher without the failure memo. */
|
||||
const char *strjoker_nomemo(const char *chaine, const char *joker, LLint *size,
|
||||
int *size_flag) {
|
||||
const strjoker_memo memo = {chaine, joker, 0, NULL};
|
||||
|
||||
return strjoker_rec(&memo, chaine, joker, size, size_flag);
|
||||
}
|
||||
|
||||
static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
const char *joker, LLint *size,
|
||||
int *size_flag) {
|
||||
if (strnotempty(joker) == 0) { // fin de chaine joker
|
||||
if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok
|
||||
return chaine;
|
||||
@@ -369,7 +303,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
|
||||
// tester sans le joker (pas ()+ mais ()*)
|
||||
if (!unique) {
|
||||
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag))) {
|
||||
if ((adr = strjoker(chaine, joker + jmp, size, size_flag))) {
|
||||
return adr;
|
||||
}
|
||||
}
|
||||
@@ -381,8 +315,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
max = strnotempty(chaine) ? 1 : 0; /* empty chaine: no char to eat */
|
||||
while(i < (int) max) {
|
||||
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
|
||||
if ((adr = strjoker_rec(memo, chaine + i + 1, joker + jmp, size,
|
||||
size_flag))) {
|
||||
if ((adr = strjoker(chaine + i + 1, joker + jmp, size, size_flag))) {
|
||||
return adr;
|
||||
}
|
||||
i++;
|
||||
@@ -392,8 +325,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
|
||||
// tester chaîne vide
|
||||
if (i != max + 2) // avant c'est ok
|
||||
if ((adr = strjoker_rec(memo, chaine + max, joker + jmp, size,
|
||||
size_flag)))
|
||||
if ((adr = strjoker(chaine + max, joker + jmp, size, size_flag)))
|
||||
return adr;
|
||||
|
||||
return NULL; // perdu
|
||||
@@ -415,7 +347,7 @@ static const char *strjoker_impl(const strjoker_memo *memo, const char *chaine,
|
||||
// comparaison ok?
|
||||
if (ok) {
|
||||
// continuer la comparaison.
|
||||
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag))
|
||||
if (strjoker(chaine + jmp, joker + jmp, size, size_flag))
|
||||
return chaine; // retourner 1e lettre
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,6 @@ int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
|
||||
const char *nom2, LLint *size, int *size_flag, int *depth);
|
||||
HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * size,
|
||||
int *size_flag);
|
||||
/* strjoker() without the failure memo (exponential worst case); test-only
|
||||
oracle for the memoized matcher. */
|
||||
const char *strjoker_nomemo(const char *chaine, const char *joker, LLint *size,
|
||||
int *size_flag);
|
||||
const char *strjokerfind(const char *chaine, const char *joker);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ Please visit our Website: http://www.httrack.com
|
||||
configure.ac, decoupled from these). VERSION is the display form, VERSIONID
|
||||
the dotted numeric form, AFF_VERSION the short form shown in footers,
|
||||
LIB_VERSION the data/cache format generation. */
|
||||
#define HTTRACK_VERSION "3.49-12"
|
||||
#define HTTRACK_VERSIONID "3.49.12"
|
||||
#define HTTRACK_VERSION "3.49-11"
|
||||
#define HTTRACK_VERSIONID "3.49.11"
|
||||
#define HTTRACK_AFF_VERSION "3.x"
|
||||
#define HTTRACK_LIB_VERSION "2.0"
|
||||
|
||||
@@ -422,10 +422,9 @@ typedef int T_SOC;
|
||||
#endif
|
||||
|
||||
/* Permission bits for created folders and files (mkdir and chmod).
|
||||
PROTECT_FOLDER/FILE are owner-only. With HTS_ACCESS set (the default) the
|
||||
ACCESS_ modes also grant group/other read; otherwise they stay owner-only. */
|
||||
PROTECT_FOLDER is owner-only. With HTS_ACCESS set (the default) the ACCESS_
|
||||
modes also grant group/other read; otherwise they stay owner-only. */
|
||||
#define HTS_PROTECT_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR)
|
||||
#define HTS_PROTECT_FILE (S_IRUSR | S_IWUSR)
|
||||
|
||||
#if HTS_ACCESS
|
||||
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
||||
|
||||
@@ -2007,7 +2007,7 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
|
||||
|
||||
/* Unescape/escape %20 and other */
|
||||
{
|
||||
// NULL when UTF-8 conversion is off (-%T0)
|
||||
// Note: always true (iso-8859-1 as default)
|
||||
const char *const charset = str->page_charset_;
|
||||
const int hasCharset = charset != NULL
|
||||
&& *charset != '\0';
|
||||
@@ -2030,12 +2030,11 @@ int htsparse(htsmoduleStruct * str, htsmoduleStructExtended * stre) {
|
||||
|
||||
// Force to encode non-printable chars (should never happend)
|
||||
escape_remove_control(lien);
|
||||
|
||||
// charset conversion for the URI filename (not the query
|
||||
// string), unless the bytes already are valid UTF-8:
|
||||
// converting those would double-encode them (#180)
|
||||
if (hasCharset && !hts_isCharsetUTF8(charset) &&
|
||||
!hts_isStringUTF8(lien, strlen(lien))) {
|
||||
|
||||
// charset conversion for the URI filename,
|
||||
// and not already UTF-8
|
||||
// (note: not for the query string!)
|
||||
if (hasCharset && !hts_isCharsetUTF8(charset)) {
|
||||
char *const s = hts_convertStringToUTF8(lien, strlen(lien), charset);
|
||||
if (s != NULL) {
|
||||
hts_log_print(opt, LOG_DEBUG,
|
||||
|
||||
@@ -621,105 +621,6 @@ static int st_filtersize(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Mime-type filter verdict via fa_strjoker(type=1): only mime: rules apply. */
|
||||
static int st_filtermime(httrackp *opt, int argc, char **argv) {
|
||||
int verdict;
|
||||
|
||||
(void) opt;
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "filtermime: needs <mime> <filter> [filter...]\n");
|
||||
return 1;
|
||||
}
|
||||
verdict = fa_strjoker(1, &argv[1], argc - 1, argv[0], NULL, NULL, NULL);
|
||||
printf("verdict=%s\n", verdict > 0 ? "allowed"
|
||||
: verdict < 0 ? "forbidden"
|
||||
: "unknown");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* SplitMix64: deterministic, platform-independent case generator. */
|
||||
static uint64_t st_mix64(uint64_t *state) {
|
||||
uint64_t z = (*state += UINT64_C(0x9E3779B97F4A7C15));
|
||||
|
||||
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4B5B9);
|
||||
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
|
||||
return z ^ (z >> 31);
|
||||
}
|
||||
|
||||
/* Differential test: memoized strjoker vs the no-memo oracle on seeded random
|
||||
pattern/subject/size cases must agree on result, *size and *size_flag. */
|
||||
static int st_filtermemo(httrackp *opt, int argc, char **argv) {
|
||||
static const char *const pieces[] = {
|
||||
"a", "b", "A", "c", ".", "/",
|
||||
"?", "*", "*[a]", "*[ab]", "*[a-c]", "*[A-Z]",
|
||||
"*[<5]", "*[>5]", "*(a)", "*(a,b)", "*[file]", "*[path]",
|
||||
"*[name]", "*[param]", "*[]", "*[\\a]", "*[a-"};
|
||||
static const char subject_chars[] = "abAc./?";
|
||||
uint64_t rng = UINT64_C(0x501);
|
||||
int iters = 20000, matched = 0, unmatched = 0;
|
||||
int it;
|
||||
|
||||
(void) opt;
|
||||
if (argc >= 1)
|
||||
sscanf(argv[0], "%d", &iters);
|
||||
for (it = 0; it < iters; it++) {
|
||||
char pat[64], str[16];
|
||||
size_t pl = 0;
|
||||
const int npieces = (int) (st_mix64(&rng) % 5);
|
||||
const int slen = (int) (st_mix64(&rng) % 13);
|
||||
const int szsel = (int) (st_mix64(&rng) % 4); /* none/unknown/3/20 */
|
||||
LLint sz1, sz2, off1, off2;
|
||||
int i, flag1 = 0, flag2 = 0;
|
||||
char *hpat, *hstr;
|
||||
const char *adr;
|
||||
|
||||
for (i = 0; i < npieces; i++) {
|
||||
const char *p =
|
||||
pieces[st_mix64(&rng) % (sizeof(pieces) / sizeof(pieces[0]))];
|
||||
const size_t len = strlen(p);
|
||||
|
||||
if (len >= sizeof(pat) - pl)
|
||||
break;
|
||||
memcpy(pat + pl, p, len);
|
||||
pl += len;
|
||||
}
|
||||
pat[pl] = '\0';
|
||||
for (i = 0; i < slen; i++)
|
||||
str[i] = subject_chars[st_mix64(&rng) % (sizeof(subject_chars) - 1)];
|
||||
str[slen] = '\0';
|
||||
sz1 = sz2 = (szsel <= 1) ? -1 : (szsel == 2) ? 3 : 20;
|
||||
/* exact-size heap copies per run so a sanitizer traps any over-read */
|
||||
hpat = strdupt(pat);
|
||||
hstr = strdupt(str);
|
||||
adr = strjoker(hstr, hpat, szsel ? &sz1 : NULL, szsel ? &flag1 : NULL);
|
||||
off1 = adr != NULL ? (LLint) (adr - hstr) : -1;
|
||||
freet(hpat);
|
||||
freet(hstr);
|
||||
hpat = strdupt(pat);
|
||||
hstr = strdupt(str);
|
||||
adr =
|
||||
strjoker_nomemo(hstr, hpat, szsel ? &sz2 : NULL, szsel ? &flag2 : NULL);
|
||||
off2 = adr != NULL ? (LLint) (adr - hstr) : -1;
|
||||
freet(hpat);
|
||||
freet(hstr);
|
||||
if (off1 != off2 || sz1 != sz2 || flag1 != flag2) {
|
||||
printf("filtermemo MISMATCH pat=[%s] str=[%s] szsel=%d: "
|
||||
"off %d/%d size %d/%d flag %d/%d\n",
|
||||
pat, str, szsel, (int) off1, (int) off2, (int) sz1, (int) sz2,
|
||||
flag1, flag2);
|
||||
return 1;
|
||||
}
|
||||
if (off1 >= 0)
|
||||
matched++;
|
||||
else
|
||||
unmatched++;
|
||||
}
|
||||
/* both polarities must actually occur or the test proves nothing */
|
||||
assertf(matched > 0 && unmatched > 0);
|
||||
printf("filtermemo: %d cases OK\n", iters);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Merged two-form filter verdict via fa_strjoker_dual (see htsfilters.h). */
|
||||
static int st_filterdual(httrackp *opt, int argc, char **argv) {
|
||||
int depth = -1, verdict;
|
||||
@@ -788,30 +689,6 @@ static int st_charset(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_metacharset(httrackp *opt, int argc, char **argv) {
|
||||
char *s;
|
||||
|
||||
(void) opt;
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "metacharset: needs an html string\n");
|
||||
return 1;
|
||||
}
|
||||
s = hts_getCharsetFromMeta(argv[0], strlen(argv[0]));
|
||||
printf("%s\n", s != NULL ? s : "(none)");
|
||||
freet(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_isutf8(httrackp *opt, int argc, char **argv) {
|
||||
(void) opt;
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "isutf8: needs a string\n");
|
||||
return 1;
|
||||
}
|
||||
printf("%d\n", hts_isStringUTF8(argv[0], strlen(argv[0])) ? 1 : 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_idna_encode(httrackp *opt, int argc, char **argv) {
|
||||
char *s;
|
||||
|
||||
@@ -1686,26 +1563,6 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
|
||||
err = 1;
|
||||
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
|
||||
err = 1;
|
||||
#ifndef _WIN32
|
||||
/* the jar holds live session cookies: cookie_save must keep it 0600 */
|
||||
{
|
||||
const char *jar = "st-cookies-jar.txt";
|
||||
struct stat st;
|
||||
|
||||
(void) UNLINK(jar);
|
||||
assertf(cookie_save(&cookie, jar) == 0);
|
||||
assertf(stat(jar, &st) == 0);
|
||||
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
|
||||
assertf(st.st_size > 0); /* mode-only checks would pass an empty jar */
|
||||
/* a pre-existing world-readable jar must be tightened, not kept */
|
||||
assertf(chmod(jar, 0644) == 0);
|
||||
assertf(cookie_save(&cookie, jar) == 0);
|
||||
assertf(stat(jar, &st) == 0);
|
||||
assertf((st.st_mode & 07777) == HTS_PROTECT_FILE);
|
||||
assertf(st.st_size > 0);
|
||||
(void) UNLINK(jar);
|
||||
}
|
||||
#endif
|
||||
printf("cookie-header: %s\n", err ? "FAIL" : "OK");
|
||||
if (err)
|
||||
printf(" got: %s\n", hdr);
|
||||
@@ -2147,19 +2004,6 @@ static int ae_write_collision(const char *path, const unsigned char *src,
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Write src[0..len) to path as-is; 0 on success. */
|
||||
static int ae_write_raw(const char *path, const unsigned char *src,
|
||||
size_t len) {
|
||||
FILE *const f = FOPEN(path, "wb");
|
||||
int ok;
|
||||
|
||||
if (f == NULL)
|
||||
return 1;
|
||||
ok = fwrite(src, 1, len, f) == len;
|
||||
fclose(f);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Compare path's bytes to expect[0..len); 0 if equal. Streams (large files). */
|
||||
static int ae_check_decoded(const char *path, const unsigned char *expect,
|
||||
size_t len) {
|
||||
@@ -2227,46 +2071,6 @@ static int st_acceptencoding(httrackp *opt, int argc, char **argv) {
|
||||
assertf(ae_write_collision(inpath, body, 64) == 0);
|
||||
assertf(hts_zunpack(inpath, outpath) == 64);
|
||||
assertf(ae_check_decoded(outpath, body, 64) == 0);
|
||||
/* Identity fallback (#47): a plain body mislabeled as compressed is kept
|
||||
verbatim, small and multi-chunk (> one 8 KiB fread). */
|
||||
assertf(ae_write_raw(inpath, small, slen) == 0);
|
||||
assertf(hts_zunpack(inpath, outpath) == (int) slen);
|
||||
assertf(ae_check_decoded(outpath, small, slen) == 0);
|
||||
{
|
||||
const size_t ilen = 16 * 1024;
|
||||
unsigned char *idbody = malloct(ilen);
|
||||
|
||||
assertf(idbody != NULL);
|
||||
for (i = 0; i < ilen; i++)
|
||||
idbody[i] = small[i % slen];
|
||||
assertf(ae_write_raw(inpath, idbody, ilen) == 0);
|
||||
assertf(hts_zunpack(inpath, outpath) == (int) ilen);
|
||||
assertf(ae_check_decoded(outpath, idbody, ilen) == 0);
|
||||
freet(idbody);
|
||||
}
|
||||
/* Truncated gzip (CRC+ISIZE cut), zlib (ADLER32 cut) and raw deflate
|
||||
must all still fail, not fall back to a verbatim copy. */
|
||||
{
|
||||
static const struct {
|
||||
int wb;
|
||||
size_t cut;
|
||||
} tr[] = {{16 + MAX_WBITS, 8}, {MAX_WBITS, 4}, {-MAX_WBITS, 5}};
|
||||
|
||||
for (i = 0; i < sizeof(tr) / sizeof(tr[0]); i++) {
|
||||
unsigned char z[512];
|
||||
size_t zlen;
|
||||
FILE *f;
|
||||
|
||||
assertf(ae_write_packed(inpath, tr[i].wb, small, slen) == 0);
|
||||
f = FOPEN(inpath, "rb");
|
||||
assertf(f != NULL);
|
||||
zlen = fread(z, 1, sizeof(z), f);
|
||||
fclose(f);
|
||||
assertf(zlen > tr[i].cut && zlen < sizeof(z));
|
||||
assertf(ae_write_raw(inpath, z, zlen - tr[i].cut) == 0);
|
||||
assertf(hts_zunpack(inpath, outpath) < 0);
|
||||
}
|
||||
}
|
||||
freet(body);
|
||||
}
|
||||
#else
|
||||
@@ -2455,10 +2259,6 @@ static const struct selftest_entry {
|
||||
{"filtersize", "<size> <string> <filter>...",
|
||||
"size-aware filter verdict (negative size = unknown/scan time)",
|
||||
st_filtersize},
|
||||
{"filtermime", "<mime> <filter>...",
|
||||
"mime-type filter verdict (fa_strjoker type=1)", st_filtermime},
|
||||
{"filtermemo", "[iterations]",
|
||||
"memoized vs unmemoized matcher differential", st_filtermemo},
|
||||
{"filterdual", "<string1> <string2> <filter>...",
|
||||
"merged two-form filter verdict (fa_strjoker_dual)", st_filterdual},
|
||||
{"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify},
|
||||
@@ -2471,9 +2271,6 @@ static const struct selftest_entry {
|
||||
{"mime", "<filename>", "MIME type for a filename", st_mime},
|
||||
{"charset", "<charset> <string>",
|
||||
"convert a string to UTF-8 from a charset", st_charset},
|
||||
{"metacharset", "<html>", "extract the <meta> charset from an HTML page",
|
||||
st_metacharset},
|
||||
{"isutf8", "<string>", "is the string valid UTF-8 (1/0)", st_isutf8},
|
||||
{"idna-encode", "<host>", "encode a hostname to IDNA/punycode",
|
||||
st_idna_encode},
|
||||
{"idna-decode", "<host>", "decode an IDNA/punycode hostname",
|
||||
|
||||
@@ -48,7 +48,6 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
/*
|
||||
Unpack file into a new file (gzip, zlib RFC1950 or raw deflate RFC1951).
|
||||
A body provably in no deflate framing is copied verbatim (identity).
|
||||
Return value: size of the new file, or -1 if an error occurred
|
||||
*/
|
||||
/* Note: utf-8 */
|
||||
@@ -69,10 +68,6 @@ int hts_zunpack(char *filename, char *newfile) {
|
||||
((inbuf[0] & 0x0f) == Z_DEFLATED &&
|
||||
(((unsigned) inbuf[0] << 8 | inbuf[1]) % 31) == 0)));
|
||||
int attempt;
|
||||
/* not_deflate: the raw attempt hit a data error, so the body is in no
|
||||
deflate framing at all. env_error: local I/O or memory failure. */
|
||||
hts_boolean not_deflate = HTS_FALSE;
|
||||
hts_boolean env_error = HTS_FALSE;
|
||||
|
||||
/* deflate is ambiguous; on failure retry with the other windowBits */
|
||||
for (attempt = 0; attempt < 2 && ret < 0; attempt++) {
|
||||
@@ -83,20 +78,15 @@ int hts_zunpack(char *filename, char *newfile) {
|
||||
|
||||
if (attempt > 0) {
|
||||
/* rewind input; reopening fpout "wb" discards the partial output */
|
||||
if (fseek(in, 0, SEEK_SET) != 0) {
|
||||
env_error = HTS_TRUE;
|
||||
if (fseek(in, 0, SEEK_SET) != 0)
|
||||
break;
|
||||
}
|
||||
navail = fread(inbuf, 1, sizeof(inbuf), in);
|
||||
}
|
||||
fpout = FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
|
||||
if (fpout == NULL) {
|
||||
env_error = HTS_TRUE;
|
||||
if (fpout == NULL)
|
||||
break;
|
||||
}
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
if (inflateInit2(&strm, windowBits) != Z_OK) {
|
||||
env_error = HTS_TRUE;
|
||||
fclose(fpout);
|
||||
break;
|
||||
}
|
||||
@@ -118,17 +108,12 @@ int hts_zunpack(char *filename, char *newfile) {
|
||||
zerr = inflate(&strm, Z_NO_FLUSH);
|
||||
if (zerr == Z_NEED_DICT || zerr == Z_DATA_ERROR ||
|
||||
zerr == Z_MEM_ERROR || zerr == Z_STREAM_ERROR) {
|
||||
if (zerr == Z_MEM_ERROR || zerr == Z_STREAM_ERROR)
|
||||
env_error = HTS_TRUE;
|
||||
else if (windowBits < 0)
|
||||
not_deflate = HTS_TRUE;
|
||||
ok = HTS_FALSE;
|
||||
break;
|
||||
}
|
||||
produced = sizeof(outbuf) - strm.avail_out;
|
||||
if (produced > 0 &&
|
||||
fwrite(outbuf, 1, produced, fpout) != produced) {
|
||||
env_error = HTS_TRUE;
|
||||
ok = HTS_FALSE;
|
||||
break;
|
||||
}
|
||||
@@ -144,28 +129,6 @@ int hts_zunpack(char *filename, char *newfile) {
|
||||
inflateEnd(&strm);
|
||||
fclose(fpout);
|
||||
}
|
||||
/* keep a mislabeled identity body verbatim only when provably not
|
||||
deflate; truncation or a local failure must keep failing (#47) */
|
||||
if (ret < 0 && !wrapped && not_deflate && !env_error && !ferror(in)) {
|
||||
FILE *const fpout =
|
||||
FOPEN(fconv(catbuff, sizeof(catbuff), newfile), "wb");
|
||||
|
||||
if (fpout != NULL && fseek(in, 0, SEEK_SET) == 0) {
|
||||
int size = 0;
|
||||
|
||||
while ((navail = fread(inbuf, 1, sizeof(inbuf), in)) > 0) {
|
||||
if (fwrite(inbuf, 1, navail, fpout) != navail) {
|
||||
size = -1;
|
||||
break;
|
||||
}
|
||||
size += (int) navail;
|
||||
}
|
||||
if (size >= 0 && !ferror(in))
|
||||
ret = size;
|
||||
}
|
||||
if (fpout != NULL)
|
||||
fclose(fpout);
|
||||
}
|
||||
fclose(in);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,64 +36,3 @@ test "$(httrack -O /dev/null -#test=charset 'no-such-charset-xyz' 'café' 2>/dev
|
||||
# malformed UTF-8 (lone continuation byte, truncated lead byte) must not crash
|
||||
runs 'utf-8' $'\x80'
|
||||
runs 'utf-8' $'\xc3'
|
||||
|
||||
# hts_getCharsetFromMeta: <meta> charset extraction, HTML5 and legacy forms.
|
||||
# -#test=metacharset <html> prints the charset, or "(none)".
|
||||
meta() {
|
||||
test "$(httrack -O /dev/null -#test=metacharset "$1")" == "$2" || exit 1
|
||||
}
|
||||
|
||||
# HTML5 form, quoting/spacing flavors
|
||||
meta '<meta charset="utf-8">' 'utf-8'
|
||||
meta "<meta charset='utf-8'>" 'utf-8'
|
||||
meta '<meta charset=utf-8>' 'utf-8'
|
||||
meta '<meta charset=utf-8 />' 'utf-8'
|
||||
meta '<meta charset=utf-8/>' 'utf-8'
|
||||
# charset labels are case-insensitive downstream; only require case-insensitive match
|
||||
test "$(httrack -O /dev/null -#test=metacharset '<META CHARSET=UTF-8>' | tr '[:upper:]' '[:lower:]')" == 'utf-8' || exit 1
|
||||
meta '<html><head><meta charset = "utf-8" ></head>' 'utf-8'
|
||||
|
||||
# legacy http-equiv form, attributes in any order
|
||||
meta '<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">' 'iso-8859-1'
|
||||
meta '<meta content="text/html; charset=gb2312" http-equiv="content-type">' 'gb2312'
|
||||
meta '<meta http-equiv=Content-Type content="text/html;charset=euc-jp">' 'euc-jp'
|
||||
meta '<meta http-equiv="Content-Type" content="text/html; charset = koi8-r ">' 'koi8-r'
|
||||
meta '<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">' 'utf-8'
|
||||
meta '<meta http-equiv=content-type content=text/html;charset=utf-8 />' 'utf-8'
|
||||
meta '<meta http-equiv=content-type content=text/html;charset=utf-8>' 'utf-8'
|
||||
|
||||
# first resolvable meta wins; unrelated metas are skipped
|
||||
meta '<meta name="viewport" content="width=1"><meta charset="koi8-r">' 'koi8-r'
|
||||
meta '<meta charset="utf-8"><meta http-equiv="Content-Type" content="text/html; charset=gb2312">' 'utf-8'
|
||||
|
||||
# duplicate attribute: first wins, as in browsers
|
||||
meta '<meta http-equiv="Content-Type" content="text/html; charset=big5" http-equiv="refresh">' 'big5'
|
||||
|
||||
# negatives: wrong tag/attribute, empty value
|
||||
meta '<p>x</p>' '(none)'
|
||||
meta '<meta name="desc" content="charset=bogus">' '(none)'
|
||||
meta '<meta http-equiv="refresh" content="0; url=x.html">' '(none)'
|
||||
meta '<body charset="utf-8">' '(none)'
|
||||
meta '<meta charset="">' '(none)'
|
||||
|
||||
# hostile truncations: no crash, no match
|
||||
meta '<meta charset="utf-8' '(none)'
|
||||
meta '<meta charset=' '(none)'
|
||||
meta '<meta ' '(none)'
|
||||
meta '<meta' '(none)'
|
||||
|
||||
# hts_isStringUTF8: valid-UTF-8 probe (guards the #180 link conversion).
|
||||
utf8() {
|
||||
test "$(httrack -O /dev/null -#test=isutf8 "$1")" == "$2" || exit 1
|
||||
}
|
||||
utf8 'plain' '1'
|
||||
utf8 'café' '1'
|
||||
utf8 '统计' '1'
|
||||
utf8 "$(printf 'caf\xe9')" '0' # latin-1 accent
|
||||
utf8 "$(printf '\xc3')" '0' # truncated lead byte
|
||||
utf8 "$(printf '\xa9')" '0' # lone continuation byte
|
||||
utf8 "$(printf '\xf0\x9f\x98\x80')" '1' # 4-byte emoji
|
||||
utf8 "$(printf '\xc0\xaf')" '0' # overlong (latin-1 "À¯" must stay convertible)
|
||||
utf8 "$(printf '\xed\xa0\x80')" '0' # UTF-16 surrogate
|
||||
utf8 "$(printf '\xf4\x90\x80\x80')" '0' # above U+10FFFF
|
||||
utf8 "$(printf '\xf8\x88\x80\x80\x80')" '0' # legacy 5-byte form
|
||||
|
||||
@@ -131,64 +131,3 @@ match '*.aspx' 'page.aspx?y=2'
|
||||
# empty subject against a unique-match allow-all pattern (heap over-read regress)
|
||||
nomatch_empty '*(('
|
||||
nomatch_empty '**(('
|
||||
|
||||
# near-miss negatives: word order, exact tails, range boundaries, class case
|
||||
nomatch 'foo*bar' 'foobaz'
|
||||
nomatch '*foo*bar' 'barfoo' # both words present, wrong order
|
||||
nomatch '*.gif' 'a.gifx' # trailing junk after the extension
|
||||
nomatch '*.gif' 'a.gi'
|
||||
nomatch '*[b-y]' 'a'
|
||||
nomatch '*[b-y]' 'z'
|
||||
nomatch '*[a-z].txt' 'B.txt' # classes are case-sensitive...
|
||||
match 'FoO*' 'foobar' # ...literals are not
|
||||
|
||||
# a star may match an empty run, but never skip a required literal
|
||||
match '*[0-9].gif' '.gif'
|
||||
nomatch '*[0-9].gif' 'x.gif'
|
||||
|
||||
# a descending range *[c-a] is an empty class, not c..a reversed
|
||||
match '*[c-a]x' 'x'
|
||||
nomatch '*[c-a]x' 'cx'
|
||||
|
||||
# *[param] is all-or-nothing on '?': spans everything after one, else nothing
|
||||
match 'a*[param]' 'a?x=1'
|
||||
nomatch 'a*[param]' 'ab?x=1'
|
||||
|
||||
# the latest matching rule wins, regardless of sign
|
||||
fsize 'verdict=forbidden size_flag=0' -1 a.gif '+*.gif' '-*.gif'
|
||||
fsize 'verdict=allowed size_flag=0' -1 a.gif '-*.gif' '+*.gif'
|
||||
|
||||
# a failed size test sets size_flag but leaves the rule unmatched
|
||||
fsize 'verdict=unknown size_flag=1' 5 foo.jpg '-*.jpg*[>10]'
|
||||
|
||||
# mime filters (-#test=filtermime = fa_strjoker type=1): only mime: rules
|
||||
# apply to a mime type, and mime: rules never apply to a URL
|
||||
fmime() {
|
||||
local want="$1"
|
||||
shift
|
||||
test "$(httrack -O /dev/null -#test=filtermime "$@")" == "verdict=$want" || exit 1
|
||||
}
|
||||
fmime allowed 'image/png' '+mime:image/*'
|
||||
fmime unknown 'text/html' '+mime:image/*'
|
||||
fmime forbidden 'image/png' '+mime:image/*' '-mime:image/png' # last wins
|
||||
fmime allowed 'image/png' '-mime:image/png' '+mime:image/*'
|
||||
fmime unknown 'image/png' '+image/*' # URL rule: skipped
|
||||
fsize 'verdict=unknown size_flag=0' -1 'image/png' '+mime:image/*' # and vice versa
|
||||
|
||||
# memoized vs no-memo differential over seeded random patterns and subjects,
|
||||
# matching and non-matching (the self-test asserts both polarities occurred)
|
||||
test "$(httrack -O /dev/null -#test=filtermemo)" == "filtermemo: 20000 cases OK" || exit 1
|
||||
|
||||
# star-heavy non-match must stay polynomial (#501); 400 chars also exercises
|
||||
# the heap-allocated memo. timeout (where available) turns a hang into a FAIL.
|
||||
with_timeout() {
|
||||
if command -v timeout >/dev/null; then timeout 60 "$@"; else "$@"; fi
|
||||
}
|
||||
pat=$(printf '*[a]%.0s' $(seq 1 12))b
|
||||
subj=$(printf 'a%.0s' $(seq 1 400))
|
||||
test "$(with_timeout httrack -O /dev/null -#test=filter "$pat" "$subj")" == "$subj does NOT match $pat" || exit 1
|
||||
|
||||
# a later star's class dead-end must re-extend the earlier star, not give up
|
||||
# (guards against a single-backtrack-point matcher rewrite)
|
||||
match '*[aX]X*[a]Y' 'aXaXaY'
|
||||
nomatch '*[aX]X*[a]Y' 'aXbXaY'
|
||||
|
||||
@@ -22,9 +22,9 @@ bash crawl-test.sh \
|
||||
httrack http://ut.httrack.com/unicode-links/utf8.html
|
||||
|
||||
bash crawl-test.sh \
|
||||
--errors 2 --files 9 \
|
||||
--found ut.httrack.com/unicode-links/café3860.html \
|
||||
--found ut.httrack.com/unicode-links/café9fa8.html \
|
||||
--errors 4 --files 7 \
|
||||
--found ut.httrack.com/unicode-links/café3860.html \
|
||||
--found ut.httrack.com/unicode-links/café9fa8.html \
|
||||
--found ut.httrack.com/unicode-links/café30f4.html \
|
||||
--found ut.httrack.com/unicode-links/café5e1f.html \
|
||||
--found ut.httrack.com/unicode-links/café7b30.html \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Diverse seeded /big/ crawl: 12 pattern families, decoy absence, update pass
|
||||
# must 304-revalidate. 361 = 1 index + 96 pages + 192 imgs + 5 shared + 61
|
||||
# must 304-revalidate. 360 = 1 index + 96 pages + 192 imgs + 5 shared + 60
|
||||
# family + 6 singles; the 4 planted errors write -o1 pages, not counted.
|
||||
|
||||
set -euo pipefail
|
||||
@@ -9,7 +9,7 @@ set -euo pipefail
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --rerun \
|
||||
--errors 4 --files 361 \
|
||||
--errors 4 --files 360 \
|
||||
--found 'big/p/95.html' \
|
||||
--found 'big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png' \
|
||||
--found 'big/a/f2-2x.png' \
|
||||
@@ -40,7 +40,6 @@ bash "$top_srcdir/tests/local-crawl.sh" --rerun \
|
||||
--not-found 'big/x/bj.jar' \
|
||||
--not-found 'big/x/is1.png' \
|
||||
--not-found 'big/x/concat.html' \
|
||||
--file-matches 'big/f1/gzid.html' '<p>gzid</p>' \
|
||||
--file-matches 'big/p/2.html' 'srcset="\.\./a/f2-1x\.png 1x, \.\./a/f2-2x\.png 2x"' \
|
||||
--file-matches 'big/a/blk2.css' 'url\(blk2-bg\.png\)' \
|
||||
--file-matches 'big/p/5.html' "document\\.write\\('<a href=\"\\.\\./f5/dw\\.html\"" \
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# #180: raw non-ASCII hrefs must reach the wire UTF-8 percent-encoded whatever
|
||||
# the declared page charset; each PDF exists only at its exact UTF-8 path.
|
||||
|
||||
set -euo pipefail
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" --errors 0 --rerun \
|
||||
--found 'charset/header/统计大数据服务平台.pdf' \
|
||||
--found 'charset/meta5/统计大数据服务平台.pdf' \
|
||||
--found 'charset/metaeq/统计大数据服务平台.pdf' \
|
||||
--found 'charset/none/统计大数据服务平台.pdf' \
|
||||
--found 'charset/latin1hdr/统计大数据服务平台.pdf' \
|
||||
--found 'charset/latin1real/café.pdf' \
|
||||
--found 'charset/metalatin1/déjà.pdf' \
|
||||
--found 'charset/latin1ovl/À¡x.pdf' \
|
||||
--found 'charset/priority/nuée.pdf' \
|
||||
--found 'charset/preenc/统计大数据服务平台.pdf' \
|
||||
--found 'charset/bom/统计大数据服务平台.pdf' \
|
||||
httrack 'BASEURL/charset/index.html'
|
||||
@@ -109,7 +109,6 @@ TESTS = \
|
||||
37_local-cache-outage.test \
|
||||
38_local-update-304.test \
|
||||
39_local-delayed-cancel.test \
|
||||
40_local-why.test \
|
||||
41_local-utf8-link.test
|
||||
40_local-why.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -374,7 +374,6 @@ def big_index(port):
|
||||
'<img src="/big/a/d1/d2/d3/d4/d5/d6/d7/d8/deep.png">'
|
||||
'<a href="/big/f1/long.html?x=%s">long</a>'
|
||||
'<a href="/big/f1/gzok.html">gzok</a>'
|
||||
'<a href="/big/f1/gzid.html">gzid</a>'
|
||||
'<a href="//127.0.0.1:%d/big/f1/protorel.html">protorel</a>'
|
||||
'<a href="http://127.0.0.1:%d/big/f1/abshost.html">abshost</a>'
|
||||
'<a href="/big/e/404.html">e404</a>'
|
||||
@@ -400,7 +399,6 @@ BIG_SIMPLE_PAGES = {
|
||||
"/big/f1/dir/": "dir index",
|
||||
"/big/f1/long.html": "long",
|
||||
"/big/f1/gzok.html": "gzok",
|
||||
"/big/f1/gzid.html": "gzid",
|
||||
"/big/f1/protorel.html": "protorel",
|
||||
"/big/f1/abshost.html": "abshost",
|
||||
"/big/f5/dw.html": "dw target",
|
||||
@@ -662,97 +660,6 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
def route_intl_page(self):
|
||||
self.send_raw(b"<html><body>accented page</body></html>\n", "text/html")
|
||||
|
||||
# Raw non-ASCII href matrix (#180): each variant declares the page charset
|
||||
# differently; the PDF exists only at its exact UTF-8 path, so a
|
||||
# mis-decoded link 404s.
|
||||
CHARSET_CJK = "统计大数据服务平台.pdf"
|
||||
# variant -> (index Content-Type, <head> bytes, href bytes, pdf name)
|
||||
CHARSET_VARIANTS = {
|
||||
"header": ("text/html; charset=utf-8", b"", CHARSET_CJK.encode(), CHARSET_CJK),
|
||||
"meta5": (
|
||||
"text/html",
|
||||
b'<meta charset="utf-8">',
|
||||
CHARSET_CJK.encode(),
|
||||
CHARSET_CJK,
|
||||
),
|
||||
"metaeq": (
|
||||
"text/html",
|
||||
b'<meta http-equiv="Content-Type" content="text/html; charset=utf-8">',
|
||||
CHARSET_CJK.encode(),
|
||||
CHARSET_CJK,
|
||||
),
|
||||
"none": ("text/html", b"", CHARSET_CJK.encode(), CHARSET_CJK),
|
||||
"latin1hdr": (
|
||||
"text/html; charset=iso-8859-1",
|
||||
b"",
|
||||
CHARSET_CJK.encode(),
|
||||
CHARSET_CJK,
|
||||
),
|
||||
# genuine latin-1 href: the charset conversion must still apply
|
||||
"latin1real": (
|
||||
"text/html; charset=iso-8859-1",
|
||||
b"",
|
||||
"café.pdf".encode("latin-1"),
|
||||
"café.pdf",
|
||||
),
|
||||
# latin-1 declared by META only: the meta parser is load-bearing
|
||||
"metalatin1": (
|
||||
"text/html",
|
||||
b'<meta charset="iso-8859-1">',
|
||||
"déjà.pdf".encode("latin-1"),
|
||||
"déjà.pdf",
|
||||
),
|
||||
# latin-1 bytes that form an overlong UTF-8 shape: strict validation
|
||||
# must still convert them
|
||||
"latin1ovl": (
|
||||
"text/html; charset=iso-8859-1",
|
||||
b"",
|
||||
"À¡x.pdf".encode("latin-1"),
|
||||
"À¡x.pdf",
|
||||
),
|
||||
# header wins over meta: latin-1 href only resolves if iso-8859-1 is kept
|
||||
"priority": (
|
||||
"text/html; charset=iso-8859-1",
|
||||
b'<meta charset="utf-8">',
|
||||
"nuée.pdf".encode("latin-1"),
|
||||
"nuée.pdf",
|
||||
),
|
||||
"preenc": ("text/html", b"", quote(CHARSET_CJK).encode(), CHARSET_CJK),
|
||||
"bom": ("text/html", b"", CHARSET_CJK.encode(), CHARSET_CJK),
|
||||
}
|
||||
|
||||
def route_charset(self):
|
||||
path = unquote(urlsplit(self.path).path)
|
||||
parts = path.split("/")
|
||||
if path == "/charset/index.html":
|
||||
self.send_html(
|
||||
"".join(
|
||||
'\t<a href="%s/index.html">%s</a>\n' % (v, v)
|
||||
for v in self.CHARSET_VARIANTS
|
||||
)
|
||||
)
|
||||
return
|
||||
if len(parts) == 4 and parts[2] in self.CHARSET_VARIANTS:
|
||||
ctype, head, href, pdf = self.CHARSET_VARIANTS[parts[2]]
|
||||
if parts[3] == "index.html":
|
||||
body = (
|
||||
b"<html><head>"
|
||||
+ head
|
||||
+ b'</head><body><a href="'
|
||||
+ href
|
||||
+ b'">doc</a></body></html>'
|
||||
)
|
||||
if parts[2] == "bom":
|
||||
body = b"\xef\xbb\xbf" + body
|
||||
self.send_raw(body, ctype)
|
||||
return
|
||||
if parts[3] == pdf:
|
||||
self.send_raw(self.FAKE_PDF, "application/pdf")
|
||||
return
|
||||
self.send_response(404)
|
||||
self.send_header("Content-Length", "0")
|
||||
self.end_headers()
|
||||
|
||||
# resume / 416 loop (#206): the first GET stalls after a prefix so the crawl
|
||||
# can be interrupted (partial + temp-ref); every later request is 416.
|
||||
RESUME_PREFIX = b"PARTIAL-" + b"x" * 4096 # flushed before the stall
|
||||
@@ -1172,13 +1079,6 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"text/html",
|
||||
extra=[("Content-Encoding", "gzip")],
|
||||
)
|
||||
elif path == "/big/f1/gzid.html":
|
||||
# Plain body mislabeled as gzip: identity fallback keeps it (#47)
|
||||
self.big_send(
|
||||
body,
|
||||
"text/html",
|
||||
extra=[("Content-Encoding", "gzip")],
|
||||
)
|
||||
else:
|
||||
self.big_send(body, "text/html")
|
||||
elif path == "/big/f1/list.html":
|
||||
@@ -1294,9 +1194,6 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
if path.startswith("/big/"):
|
||||
self.route_big()
|
||||
return True
|
||||
if path.startswith("/charset/"):
|
||||
self.route_charset()
|
||||
return True
|
||||
# Match percent-encoded paths (accented #157 route) by their decoded form.
|
||||
handler = self.ROUTES.get(path) or self.ROUTES.get(unquote(path))
|
||||
if handler is not None:
|
||||
|
||||
Reference in New Issue
Block a user