mirror of
https://github.com/xroche/httrack.git
synced 2026-07-11 11:26:31 +03:00
Compare commits
11 Commits
p3-3-codeq
...
fast-cache
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caf210b295 | ||
|
|
9105732112 | ||
|
|
9c01812141 | ||
|
|
7a02d5e411 | ||
|
|
3c7e9fa46d | ||
|
|
a707d4b845 | ||
|
|
65c1016a4e | ||
|
|
fd2252dc8c | ||
|
|
53a257448f | ||
|
|
8ba3cb6c03 | ||
|
|
b9afe755bd |
63
.github/workflows/codeql.yml
vendored
Normal file
63
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# CodeQL static analysis (C). The security-extended suite covers the classes
|
||||||
|
# this codebase actually fights: overflows, tainted-size allocs, format bugs.
|
||||||
|
name: CodeQL
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
# Weekly re-scan of master so new/updated queries land without a push.
|
||||||
|
- cron: "17 4 * * 1"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: analyze (c-cpp)
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
# Upload findings to the repo's code-scanning dashboard.
|
||||||
|
security-events: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends \
|
||||||
|
build-essential autoconf automake libtool autoconf-archive \
|
||||||
|
zlib1g-dev libssl-dev
|
||||||
|
|
||||||
|
- uses: github/codeql-action/init@v4
|
||||||
|
with:
|
||||||
|
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
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
autoreconf -fi
|
||||||
|
./configure
|
||||||
|
make -j"$(nproc)"
|
||||||
|
|
||||||
|
- uses: github/codeql-action/analyze@v4
|
||||||
|
with:
|
||||||
|
category: "/language:c-cpp"
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -34,8 +34,9 @@ Makefile
|
|||||||
*.so.*
|
*.so.*
|
||||||
*.a
|
*.a
|
||||||
|
|
||||||
# make dist output.
|
# make dist output; dist/ holds httrack-gh-release staging artifacts.
|
||||||
/httrack-*.tar.gz
|
/httrack-*.tar.gz
|
||||||
|
/dist/
|
||||||
|
|
||||||
# Editor / autotools backup files.
|
# Editor / autotools backup files.
|
||||||
*~
|
*~
|
||||||
|
|||||||
10
configure.ac
10
configure.ac
@@ -102,7 +102,8 @@ AX_CHECK_COMPILE_FLAG([-Wignored-qualifiers], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -
|
|||||||
AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"])
|
AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"])
|
||||||
AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"])
|
AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"])
|
||||||
AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"])
|
AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"])
|
||||||
AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])
|
AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector-strong"],
|
||||||
|
[AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])])
|
||||||
AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"])
|
AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"])
|
||||||
AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"])
|
AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"])
|
||||||
AX_CHECK_LINK_FLAG([-Wl,--discard-all], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--discard-all"])
|
AX_CHECK_LINK_FLAG([-Wl,--discard-all], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--discard-all"])
|
||||||
@@ -110,6 +111,13 @@ AX_CHECK_LINK_FLAG([-Wl,--no-undefined], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,
|
|||||||
AX_CHECK_LINK_FLAG([-Wl,-z,relro,-z,now], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,relro,-z,now"])
|
AX_CHECK_LINK_FLAG([-Wl,-z,relro,-z,now], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,relro,-z,now"])
|
||||||
AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,noexecstack"])
|
AX_CHECK_LINK_FLAG([-Wl,-z,noexecstack], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,noexecstack"])
|
||||||
|
|
||||||
|
# Fortify libc calls (=3, else =2) unless the toolchain predefines it; skip
|
||||||
|
# sanitizer builds, whose interceptors want the unfortified calls.
|
||||||
|
case "$CFLAGS" in
|
||||||
|
*-fsanitize=*) ;;
|
||||||
|
*) AX_ADD_FORTIFY_SOURCE ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Force libc back into DT_NEEDED for libraries that reach it only through
|
# Force libc back into DT_NEEDED for libraries that reach it only through
|
||||||
# libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style
|
# libhttrack (libhtsjava, the libtest callbacks), but only with a GNU-style
|
||||||
# linker; Apple ld rejects these flags and links libSystem unconditionally.
|
# linker; Apple ld rejects these flags and links libSystem unconditionally.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# libFuzzer harnesses; built only with --enable-fuzzers (requires clang).
|
# libFuzzer harnesses; built only with --enable-fuzzers (requires clang).
|
||||||
if FUZZERS
|
if FUZZERS
|
||||||
noinst_PROGRAMS = fuzz-charset fuzz-meta fuzz-idna fuzz-entities \
|
noinst_PROGRAMS = fuzz-charset fuzz-meta fuzz-idna fuzz-entities \
|
||||||
fuzz-unescape fuzz-filters fuzz-url
|
fuzz-unescape fuzz-filters fuzz-url fuzz-header fuzz-cachendx
|
||||||
endif
|
endif
|
||||||
|
|
||||||
AM_CPPFLAGS = \
|
AM_CPPFLAGS = \
|
||||||
@@ -23,6 +23,8 @@ fuzz_entities_SOURCES = fuzz-entities.c fuzz.h
|
|||||||
fuzz_unescape_SOURCES = fuzz-unescape.c fuzz.h
|
fuzz_unescape_SOURCES = fuzz-unescape.c fuzz.h
|
||||||
fuzz_filters_SOURCES = fuzz-filters.c fuzz.h
|
fuzz_filters_SOURCES = fuzz-filters.c fuzz.h
|
||||||
fuzz_url_SOURCES = fuzz-url.c fuzz.h
|
fuzz_url_SOURCES = fuzz-url.c fuzz.h
|
||||||
|
fuzz_header_SOURCES = fuzz-header.c fuzz.h
|
||||||
|
fuzz_cachendx_SOURCES = fuzz-cachendx.c fuzz.h
|
||||||
|
|
||||||
# List corpus files explicitly: automake does not expand EXTRA_DIST globs.
|
# List corpus files explicitly: automake does not expand EXTRA_DIST globs.
|
||||||
EXTRA_DIST = README.md run-fuzzers.sh \
|
EXTRA_DIST = README.md run-fuzzers.sh \
|
||||||
@@ -34,5 +36,10 @@ EXTRA_DIST = README.md run-fuzzers.sh \
|
|||||||
corpus/unescape/percent.txt \
|
corpus/unescape/percent.txt \
|
||||||
corpus/filters/filter.bin corpus/filters/filter-size.bin \
|
corpus/filters/filter.bin corpus/filters/filter-size.bin \
|
||||||
corpus/filters/regress-empty-subject-unique.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/http-url.txt corpus/url/relative-path.txt \
|
||||||
corpus/url/regress-file-empty-path.txt corpus/url/regress-long-path-abort.txt
|
corpus/url/regress-file-empty-path.txt corpus/url/regress-long-path-abort.txt \
|
||||||
|
corpus/header/full-response.txt corpus/header/redirect.txt \
|
||||||
|
corpus/cachendx/new-format.txt corpus/cachendx/old-format.txt \
|
||||||
|
corpus/cachendx/regress-overadvance.bin \
|
||||||
|
corpus/cachendx/regress-truncated-entry.bin
|
||||||
|
|||||||
7
fuzz/corpus/cachendx/new-format.txt
Normal file
7
fuzz/corpus/cachendx/new-format.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
8
|
||||||
|
CACHE-1.1
|
||||||
|
28
|
||||||
|
Mon, 01 Jan 2024 00:00:00 GMT
|
||||||
|
www.example.com
|
||||||
|
/index.html
|
||||||
|
123
|
||||||
5
fuzz/corpus/cachendx/old-format.txt
Normal file
5
fuzz/corpus/cachendx/old-format.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
3
|
||||||
|
1.0
|
||||||
|
www.example.com
|
||||||
|
/page
|
||||||
|
5
|
||||||
2
fuzz/corpus/cachendx/regress-overadvance.bin
Normal file
2
fuzz/corpus/cachendx/regress-overadvance.bin
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
32768
|
||||||
|
CACHE-1.1
|
||||||
5
fuzz/corpus/cachendx/regress-truncated-entry.bin
Normal file
5
fuzz/corpus/cachendx/regress-truncated-entry.bin
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
8
|
||||||
|
CACHE-1.1
|
||||||
|
1
|
||||||
|
x
|
||||||
|
www.example.com
|
||||||
BIN
fuzz/corpus/filters/redos-star-classes.bin
Normal file
BIN
fuzz/corpus/filters/redos-star-classes.bin
Normal file
Binary file not shown.
10
fuzz/corpus/header/full-response.txt
Normal file
10
fuzz/corpus/header/full-response.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Content-Length: 1234
|
||||||
|
Content-Encoding: gzip
|
||||||
|
Last-Modified: Mon, 01 Jan 2024 00:00:00 GMT
|
||||||
|
Etag: "abc"
|
||||||
|
Location: http://example.com/x
|
||||||
|
Set-Cookie: ID=42; path=/; domain=.example.com
|
||||||
|
Content-Range: bytes 0-99/100
|
||||||
|
Transfer-Encoding: chunked
|
||||||
3
fuzz/corpus/header/redirect.txt
Normal file
3
fuzz/corpus/header/redirect.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
HTTP/1.0 301 Moved
|
||||||
|
Location: /elsewhere
|
||||||
|
Content-Disposition: attachment; filename="a.pdf"
|
||||||
65
fuzz/fuzz-cachendx.c
Normal file
65
fuzz/fuzz-cachendx.c
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/*
|
||||||
|
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||||
|
Copyright (C) 2026 Xavier Roche and other contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Ethical use: we kindly ask that you NOT use this software to harvest email
|
||||||
|
addresses or to collect any other private information about people. Doing so
|
||||||
|
would dishonor our work and waste the many hours we have spent on it.
|
||||||
|
|
||||||
|
Please visit our Website: http://www.httrack.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Fuzz the cache-index (.ndx) parser: a corrupt or truncated index must not
|
||||||
|
walk the length-prefixed cache_brstr/cache_binput scan past the buffer.
|
||||||
|
Mirrors the -#C cache-listing scan (htscoremain.c). */
|
||||||
|
#include "fuzz.h"
|
||||||
|
#include "htscache.h"
|
||||||
|
#include "htslib.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
|
char *buf = fuzz_strdup(data, size);
|
||||||
|
const char *const end = buf + size;
|
||||||
|
char firstline[256];
|
||||||
|
char *a = buf;
|
||||||
|
|
||||||
|
/* header: two length-prefixed fields (version, last-modified) */
|
||||||
|
a += cache_brstr(a, firstline, sizeof(firstline));
|
||||||
|
a += cache_brstr(a, firstline, sizeof(firstline));
|
||||||
|
|
||||||
|
/* body: newline-delimited host/file/position triples; the length-prefixed
|
||||||
|
scan must stay inside the buffer */
|
||||||
|
while (a != NULL && a < end) {
|
||||||
|
char BIGSTK line[HTS_URLMAXSIZE * 2];
|
||||||
|
char linepos[256];
|
||||||
|
int pos;
|
||||||
|
|
||||||
|
a = strchr(a + 1, '\n');
|
||||||
|
if (a == NULL)
|
||||||
|
break;
|
||||||
|
a++;
|
||||||
|
a += cache_binput(a, end, line, HTS_URLMAXSIZE);
|
||||||
|
a += cache_binput(a, end, line + strlen(line), HTS_URLMAXSIZE);
|
||||||
|
a += cache_binput(a, end, linepos, 200);
|
||||||
|
sscanf(linepos, "%d", &pos);
|
||||||
|
(void) pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
freet(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
74
fuzz/fuzz-header.c
Normal file
74
fuzz/fuzz-header.c
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/*
|
||||||
|
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||||
|
Copyright (C) 2026 Xavier Roche and other contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Ethical use: we kindly ask that you NOT use this software to harvest email
|
||||||
|
addresses or to collect any other private information about people. Doing so
|
||||||
|
would dishonor our work and waste the many hours we have spent on it.
|
||||||
|
|
||||||
|
Please visit our Website: http://www.httrack.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Fuzz the HTTP response-header parser (htslib.c): treatfirstline on the
|
||||||
|
status line, treathead on each following header. Both consume raw bytes
|
||||||
|
off the wire and copy fields into fixed htsblk buffers; treathead also
|
||||||
|
mutates its line in place and drives cookie parsing. */
|
||||||
|
#include "fuzz.h"
|
||||||
|
#include "htslib.h"
|
||||||
|
#include "htsbauth.h"
|
||||||
|
|
||||||
|
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||||
|
char *buf = fuzz_strdup(data, size);
|
||||||
|
htsblk r;
|
||||||
|
t_cookie *cookie = calloct(1, sizeof(*cookie));
|
||||||
|
char *line = malloct(size + 1);
|
||||||
|
char *p = buf;
|
||||||
|
int first = 1;
|
||||||
|
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
cookie->max_len = (int) sizeof(cookie->data);
|
||||||
|
r.location = malloct(HTS_URLMAXSIZE * 2);
|
||||||
|
r.location[0] = '\0';
|
||||||
|
|
||||||
|
/* feed one header line at a time, as the receive loop does */
|
||||||
|
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';
|
||||||
|
if (first) {
|
||||||
|
treatfirstline(&r, line);
|
||||||
|
first = 0;
|
||||||
|
} else {
|
||||||
|
treathead(cookie, "www.example.com", "/", &r, line);
|
||||||
|
}
|
||||||
|
p = (nl != NULL) ? nl + 1 : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
freet(r.location);
|
||||||
|
freet(line);
|
||||||
|
freet(cookie);
|
||||||
|
freet(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
# Drive every built harness against its seed corpus.
|
# 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> check deterministic replay (CI smoke)
|
||||||
# run-fuzzers.sh <build-fuzz-dir> [seconds] timed mutation run (discovery)
|
# run-fuzzers.sh <build-fuzz-dir> [seconds] timed mutation run (discovery)
|
||||||
# Replay is crash/leak-only and never mutates, so it can't hit strjoker's
|
# Replay is crash/leak-only and never mutates; the per-unit -timeout guards
|
||||||
# catastrophic backtracking; the timed mode can, hence the per-unit -timeout.
|
# both modes against pathological slowdowns (e.g. pre-#501 strjoker).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
srcdir=$(cd "$(dirname "$0")" && pwd)
|
srcdir=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
|||||||
119
m4/ax_add_fortify_source.m4
Normal file
119
m4/ax_add_fortify_source.m4
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# ===========================================================================
|
||||||
|
# https://www.gnu.org/software/autoconf-archive/ax_add_fortify_source.html
|
||||||
|
# ===========================================================================
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# AX_ADD_FORTIFY_SOURCE
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
#
|
||||||
|
# Check whether -D_FORTIFY_SOURCE=2 can be added to CPPFLAGS without macro
|
||||||
|
# redefinition warnings, other cpp warnings or linker. Some distributions
|
||||||
|
# (such as Ubuntu or Gentoo Linux) enable _FORTIFY_SOURCE globally in
|
||||||
|
# their compilers, leading to unnecessary warnings in the form of
|
||||||
|
#
|
||||||
|
# <command-line>:0:0: error: "_FORTIFY_SOURCE" redefined [-Werror]
|
||||||
|
# <built-in>: note: this is the location of the previous definition
|
||||||
|
#
|
||||||
|
# which is a problem if -Werror is enabled. This macro checks whether
|
||||||
|
# _FORTIFY_SOURCE is already defined, and if not, adds -D_FORTIFY_SOURCE=2
|
||||||
|
# to CPPFLAGS.
|
||||||
|
#
|
||||||
|
# Newer mingw-w64 msys2 package comes with a bug in
|
||||||
|
# headers-git-7.0.0.5546.d200317d-1. It broke -D_FORTIFY_SOURCE support,
|
||||||
|
# and would need -lssp or -fstack-protector. See
|
||||||
|
# https://github.com/msys2/MINGW-packages/issues/5803. Try to actually
|
||||||
|
# link it.
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# Copyright (c) 2017 David Seifert <soap@gentoo.org>
|
||||||
|
# Copyright (c) 2019, 2023 Reini Urban <rurban@cpan.org>
|
||||||
|
#
|
||||||
|
# Copying and distribution of this file, with or without modification, are
|
||||||
|
# permitted in any medium without royalty provided the copyright notice
|
||||||
|
# and this notice are preserved. This file is offered as-is, without any
|
||||||
|
# warranty.
|
||||||
|
|
||||||
|
#serial 10
|
||||||
|
|
||||||
|
AC_DEFUN([AX_ADD_FORTIFY_SOURCE],[
|
||||||
|
ac_save_cflags=$CFLAGS
|
||||||
|
ac_cwerror_flag=yes
|
||||||
|
AX_CHECK_COMPILE_FLAG([-Werror],[CFLAGS="$CFLAGS -Werror"])
|
||||||
|
ax_add_fortify_3_failed=
|
||||||
|
AC_MSG_CHECKING([whether to add -D_FORTIFY_SOURCE=3 to CPPFLAGS])
|
||||||
|
AC_LINK_IFELSE([
|
||||||
|
AC_LANG_PROGRAM([],
|
||||||
|
[[
|
||||||
|
#ifndef _FORTIFY_SOURCE
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
_FORTIFY_SOURCE_already_defined;
|
||||||
|
#endif
|
||||||
|
]]
|
||||||
|
)],
|
||||||
|
AC_LINK_IFELSE([
|
||||||
|
AC_LANG_SOURCE([[
|
||||||
|
#define _FORTIFY_SOURCE 3
|
||||||
|
#include <string.h>
|
||||||
|
int main(void) {
|
||||||
|
char *s = " ";
|
||||||
|
strcpy(s, "x");
|
||||||
|
return strlen(s)-1;
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
)],
|
||||||
|
[
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
CFLAGS=$ac_save_cflags
|
||||||
|
CPPFLAGS="$CPPFLAGS -D_FORTIFY_SOURCE=3"
|
||||||
|
], [
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
ax_add_fortify_3_failed=1
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
ax_add_fortify_3_failed=1
|
||||||
|
])
|
||||||
|
if test -n "$ax_add_fortify_3_failed"
|
||||||
|
then
|
||||||
|
AC_MSG_CHECKING([whether to add -D_FORTIFY_SOURCE=2 to CPPFLAGS])
|
||||||
|
AC_LINK_IFELSE([
|
||||||
|
AC_LANG_PROGRAM([],
|
||||||
|
[[
|
||||||
|
#ifndef _FORTIFY_SOURCE
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
_FORTIFY_SOURCE_already_defined;
|
||||||
|
#endif
|
||||||
|
]]
|
||||||
|
)],
|
||||||
|
AC_LINK_IFELSE([
|
||||||
|
AC_LANG_SOURCE([[
|
||||||
|
#define _FORTIFY_SOURCE 2
|
||||||
|
#include <string.h>
|
||||||
|
int main(void) {
|
||||||
|
char *s = " ";
|
||||||
|
strcpy(s, "x");
|
||||||
|
return strlen(s)-1;
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
)],
|
||||||
|
[
|
||||||
|
AC_MSG_RESULT([yes])
|
||||||
|
CFLAGS=$ac_save_cflags
|
||||||
|
CPPFLAGS="$CPPFLAGS -D_FORTIFY_SOURCE=2"
|
||||||
|
], [
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
CFLAGS=$ac_save_cflags
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[
|
||||||
|
AC_MSG_RESULT([no])
|
||||||
|
CFLAGS=$ac_save_cflags
|
||||||
|
])
|
||||||
|
fi
|
||||||
|
])
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" This file is generated by man/makeman.sh; do not edit by hand.
|
.\" This file is generated by man/makeman.sh; do not edit by hand.
|
||||||
.\" SPDX-License-Identifier: GPL-3.0-or-later
|
.\" SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
.TH httrack 1 "27 June 2026" "httrack website copier"
|
.TH httrack 1 "07 July 2026" "httrack website copier"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
httrack \- offline browser : copy websites to a local directory
|
httrack \- offline browser : copy websites to a local directory
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -51,6 +51,7 @@ httrack \- offline browser : copy websites to a local directory
|
|||||||
[ \fB\-%T, \-\-utf8\-conversion\fR ]
|
[ \fB\-%T, \-\-utf8\-conversion\fR ]
|
||||||
[ \fB\-bN, \-\-cookies[=N]\fR ]
|
[ \fB\-bN, \-\-cookies[=N]\fR ]
|
||||||
[ \fB\-%K, \-\-cookies\-file\fR ]
|
[ \fB\-%K, \-\-cookies\-file\fR ]
|
||||||
|
[ \fB\-%Y, \-\-why\fR ]
|
||||||
[ \fB\-u, \-\-check\-type[=N]\fR ]
|
[ \fB\-u, \-\-check\-type[=N]\fR ]
|
||||||
[ \fB\-j, \-\-parse\-java[=N]\fR ]
|
[ \fB\-j, \-\-parse\-java[=N]\fR ]
|
||||||
[ \fB\-sN, \-\-robots[=N]\fR ]
|
[ \fB\-sN, \-\-robots[=N]\fR ]
|
||||||
@@ -218,6 +219,8 @@ links conversion to UTF\-8 (\-\-utf8\-conversion)
|
|||||||
accept cookies in cookies.txt (0=do not accept,* 1=accept) (\-\-cookies[=N])
|
accept cookies in cookies.txt (0=do not accept,* 1=accept) (\-\-cookies[=N])
|
||||||
.IP \-%K
|
.IP \-%K
|
||||||
load extra cookies from a Netscape cookies.txt (\-\-cookies\-file <param>)
|
load extra cookies from a Netscape cookies.txt (\-\-cookies\-file <param>)
|
||||||
|
.IP \-%Y
|
||||||
|
explain which filter rule accepts or rejects a URL, then exit (\-\-why <param>)
|
||||||
.IP \-u
|
.IP \-u
|
||||||
check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N])
|
check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always) (\-\-check\-type[=N])
|
||||||
.IP \-j
|
.IP \-j
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ const char *hts_optalias[][4] = {
|
|||||||
"strip [host/pattern=]key1,key2,... from URLs"},
|
"strip [host/pattern=]key1,key2,... from URLs"},
|
||||||
{"cookies-file", "-%K", "param1",
|
{"cookies-file", "-%K", "param1",
|
||||||
"load extra cookies from a Netscape cookies.txt"},
|
"load extra cookies from a Netscape cookies.txt"},
|
||||||
|
{"why", "-%Y", "param1",
|
||||||
|
"explain which filter rule accepts or rejects a URL, then exit"},
|
||||||
{"pause", "-%G", "param1",
|
{"pause", "-%G", "param1",
|
||||||
"random pause of MIN[:MAX] seconds between files"},
|
"random pause of MIN[:MAX] seconds between files"},
|
||||||
{"generate-errors", "-o", "single", ""},
|
{"generate-errors", "-o", "single", ""},
|
||||||
|
|||||||
169
src/htsback.c
169
src/htsback.c
@@ -1646,138 +1646,104 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// tester cache
|
// tester cache
|
||||||
if ((strcmp(adr, "file://")) /* pas fichier */
|
if ((strcmp(adr, "file://")) /* pas fichier */
|
||||||
&&((!test) || (cache->type == 1)) /* cache prioritaire, laisser passer en test! */
|
&& ((!test) ||
|
||||||
&&((strnotempty(save)) || (strcmp(fil, "/robots.txt") == 0))) { // si en test on ne doit pas utiliser le cache sinon telescopage avec le 302..
|
(cache->type == 1)) /* cache prioritaire, laisser passer en test! */
|
||||||
#if HTS_FAST_CACHE
|
&&
|
||||||
|
((strnotempty(save)) || (strcmp(fil, "/robots.txt") ==
|
||||||
|
0))) { // si en test on ne doit pas utiliser le
|
||||||
|
// cache sinon telescopage avec le 302..
|
||||||
intptr_t hash_pos;
|
intptr_t hash_pos;
|
||||||
int hash_pos_return = 0;
|
int hash_pos_return = 0;
|
||||||
#else
|
|
||||||
char *a = NULL;
|
|
||||||
#endif
|
|
||||||
#if HTS_FAST_CACHE
|
|
||||||
if (cache->hashtable) {
|
if (cache->hashtable) {
|
||||||
#else
|
|
||||||
if (cache->use) {
|
|
||||||
#endif
|
|
||||||
char BIGSTK buff[HTS_URLMAXSIZE * 4];
|
char BIGSTK buff[HTS_URLMAXSIZE * 4];
|
||||||
|
|
||||||
#if HTS_FAST_CACHE
|
|
||||||
strcpybuff(buff, adr);
|
strcpybuff(buff, adr);
|
||||||
strcatbuff(buff, fil);
|
strcatbuff(buff, fil);
|
||||||
hash_pos_return = coucal_read(cache->hashtable, buff, &hash_pos);
|
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
|
// negative values when data is not in cache
|
||||||
if (hash_pos_return < 0) {
|
if (hash_pos_return < 0) {
|
||||||
#else
|
if (!test) { // not test mode
|
||||||
if (a) {
|
/* note: no check with IS_DELAYED_EXT() enabled - postcheck by
|
||||||
#endif
|
* client please! */
|
||||||
if (!test) { // non mode test
|
if (save[0] != '\0' && !IS_DELAYED_EXT(save) &&
|
||||||
#if HTS_FAST_CACHE==0
|
fsize_utf8(fconv(catbuff, sizeof(catbuff), save)) <=
|
||||||
int pos = -1;
|
0) { // final file missing or empty
|
||||||
|
int found = 0;
|
||||||
|
|
||||||
a += strlen(buff);
|
/* It is possible that the file has been moved due to changes in
|
||||||
sscanf(a, "%d", &pos); // lire position
|
* build structure */
|
||||||
#endif
|
{
|
||||||
|
char BIGSTK previous_save[HTS_URLMAXSIZE * 2];
|
||||||
|
htsblk r;
|
||||||
|
|
||||||
#if HTS_FAST_CACHE==0
|
previous_save[0] = '\0';
|
||||||
if (pos < 0) { // pas de mise en cache data, vérifier existence
|
r = cache_readex(opt, cache, adr, fil, /*head */ NULL,
|
||||||
#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) */
|
/*bound to back[p] (temporary) */
|
||||||
back[p].location_buffer, previous_save, /*ro */
|
back[p].location_buffer, previous_save, /*ro */
|
||||||
1);
|
1);
|
||||||
/* Is supposed to be on disk only */
|
/* Is supposed to be on disk only */
|
||||||
if (r.is_write && previous_save[0] != '\0') {
|
if (r.is_write && previous_save[0] != '\0') {
|
||||||
/* Exists, but with another (old) filename: rename (almost) silently */
|
/* Exists, but with another (old) filename: rename (almost)
|
||||||
if (strcmp(previous_save, save) != 0
|
* silently */
|
||||||
&& fexist_utf8(fconv(catbuff, sizeof(catbuff), previous_save))) {
|
if (strcmp(previous_save, save) != 0 &&
|
||||||
rename(fconv(catbuff, sizeof(catbuff), previous_save),
|
fexist_utf8(
|
||||||
fconv(catbuff2, sizeof(catbuff2), save));
|
fconv(catbuff, sizeof(catbuff), previous_save))) {
|
||||||
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
|
rename(fconv(catbuff, sizeof(catbuff), previous_save),
|
||||||
found = 1;
|
fconv(catbuff2, sizeof(catbuff2), save));
|
||||||
hts_log_print(opt, LOG_DEBUG,
|
if (fexist_utf8(fconv(catbuff, sizeof(catbuff), save))) {
|
||||||
"File '%s' has been renamed since last mirror to '%s' ; applying changes",
|
found = 1;
|
||||||
previous_save, save);
|
hts_log_print(opt, LOG_DEBUG,
|
||||||
} else {
|
"File '%s' has been renamed since last "
|
||||||
hts_log_print(opt, LOG_ERROR,
|
"mirror to '%s' ; applying changes",
|
||||||
"Could not rename '%s' to '%s' ; will have to retransfer it",
|
previous_save, save);
|
||||||
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 ? */
|
/* Not found ? */
|
||||||
if (!found) {
|
if (!found) {
|
||||||
#if HTS_FAST_CACHE
|
// invalidate: gone from disk, force a refetch
|
||||||
hash_pos_return = 0;
|
hash_pos_return = 0;
|
||||||
#else
|
if (opt->norecatch) {
|
||||||
a = NULL;
|
if (!fexist_utf8(fconv(
|
||||||
#endif
|
catbuff, sizeof(catbuff),
|
||||||
// dévalider car non présent sur disque dans structure originale!!!
|
save))) { // declared but missing: user erased it
|
||||||
// sinon, le fichier est ok à priori, mais on renverra un if-modified-since pour
|
FILE *fp =
|
||||||
// en être sûr
|
FOPEN(fconv(catbuff, sizeof(catbuff), save), "wb");
|
||||||
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)
|
if (fp)
|
||||||
fclose(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,
|
hts_log_print(opt, LOG_WARNING,
|
||||||
"Previous file '%s' not found (erased by user ?), recatching: %s%s",
|
"Previous file '%s' not found (erased by "
|
||||||
|
"user ?), ignoring: %s%s",
|
||||||
save, back[p].url_adr, back[p].url_fil);
|
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
|
}
|
||||||
#if HTS_FAST_CACHE==0
|
} // fsize() <= 0
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
} else {
|
} else {
|
||||||
#if HTS_FAST_CACHE
|
|
||||||
hash_pos_return = 0;
|
hash_pos_return = 0;
|
||||||
#else
|
|
||||||
a = NULL;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existe pas en cache, ou bien pas de cache présent
|
if (hash_pos_return) { // in cache, with data
|
||||||
#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
|
const int cache_is_prioritary = cache->type == 1
|
||||||
|| opt->state.stop != 0;
|
|| opt->state.stop != 0;
|
||||||
if (cache_is_prioritary) { // cache prioritaire (pas de test if-modified..)
|
if (cache_is_prioritary) { // cache prioritaire (pas de test if-modified..)
|
||||||
@@ -1898,7 +1864,8 @@ int back_add(struct_back * sback, httrackp * opt, cache_back * cache, const char
|
|||||||
#endif
|
#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) {
|
else if (back_unserialize_ref(opt, adr, fil, &itemback) == 0) {
|
||||||
const off_t file_size = fsize_utf8(itemback->url_sav);
|
const off_t file_size = fsize_utf8(itemback->url_sav);
|
||||||
|
|
||||||
|
|||||||
@@ -307,14 +307,25 @@ int cookie_load(t_cookie * cookie, const char *fpath, const char *name) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// écrire cookies.txt
|
/* Write cookies.txt; returns 0 on success. The jar holds live session
|
||||||
// !=0 : erreur
|
cookies, so keep it owner-only on Unix (Windows inherits folder ACLs). */
|
||||||
int cookie_save(t_cookie * cookie, const char *name) {
|
int cookie_save(t_cookie * cookie, const char *name) {
|
||||||
char catbuff[CATBUFF_SIZE];
|
char catbuff[CATBUFF_SIZE];
|
||||||
|
|
||||||
if (strnotempty(cookie->data)) {
|
if (strnotempty(cookie->data)) {
|
||||||
char BIGSTK line[8192];
|
char BIGSTK line[8192];
|
||||||
|
#ifdef _WIN32
|
||||||
FILE *fp = fopen(fconv(catbuff, sizeof(catbuff), name), "wb");
|
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) {
|
if (fp) {
|
||||||
char *a = cookie->data;
|
char *a = cookie->data;
|
||||||
|
|||||||
980
src/htscache.c
980
src/htscache.c
File diff suppressed because it is too large
Load Diff
@@ -89,14 +89,11 @@ typedef enum {
|
|||||||
the involved files are absent. */
|
the involved files are absent. */
|
||||||
void hts_cache_reconcile(httrackp *opt, hts_cache_reconcile_mode mode);
|
void hts_cache_reconcile(httrackp *opt, hts_cache_reconcile_mode mode);
|
||||||
|
|
||||||
int cache_writedata(FILE * cache_ndx, FILE * cache_dat, const char *str1,
|
|
||||||
const char *str2, char *outbuff, int len);
|
|
||||||
int cache_readdata(cache_back * cache, const char *str1, const char *str2,
|
|
||||||
char **inbuff, int *len);
|
|
||||||
|
|
||||||
void cache_rstr(FILE *fp, char *s, size_t s_size);
|
void cache_rstr(FILE *fp, char *s, size_t s_size);
|
||||||
char *cache_rstr_addr(FILE * fp);
|
char *cache_rstr_addr(FILE * fp);
|
||||||
int cache_brstr(char *adr, char *s, size_t s_size);
|
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_brint(char *adr, int *i);
|
int cache_brint(char *adr, int *i);
|
||||||
void cache_rint(FILE * fp, int *i);
|
void cache_rint(FILE * fp, int *i);
|
||||||
void cache_rLLint(FILE * fp, LLint * i);
|
void cache_rLLint(FILE * fp, LLint * i);
|
||||||
|
|||||||
@@ -78,14 +78,6 @@ static void selftest_open_for_read(cache_back *cache, httrackp *opt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void selftest_close(cache_back *cache) {
|
static void selftest_close(cache_back *cache) {
|
||||||
if (cache->dat != NULL) {
|
|
||||||
fclose(cache->dat);
|
|
||||||
cache->dat = NULL;
|
|
||||||
}
|
|
||||||
if (cache->ndx != NULL) {
|
|
||||||
fclose(cache->ndx);
|
|
||||||
cache->ndx = NULL;
|
|
||||||
}
|
|
||||||
if (cache->zipOutput != NULL) {
|
if (cache->zipOutput != NULL) {
|
||||||
zipClose(cache->zipOutput,
|
zipClose(cache->zipOutput,
|
||||||
"Created by HTTrack Website Copier (cache self-test)");
|
"Created by HTTrack Website Copier (cache self-test)");
|
||||||
@@ -989,26 +981,15 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
|||||||
failures +=
|
failures +=
|
||||||
reconcile_expect(opt, "hts-cache/old.zip", SOLID, "promote-zip-noop");
|
reconcile_expect(opt, "hts-cache/old.zip", SOLID, "promote-zip-noop");
|
||||||
|
|
||||||
/* PROMOTE: a pure-legacy old generation is promoted too (was dead when no
|
/* PROMOTE: the removed pre-3.31 legacy pair is left alone */
|
||||||
zip cache existed) */
|
|
||||||
reconcile_wipe(opt);
|
reconcile_wipe(opt);
|
||||||
reconcile_put(opt, "hts-cache/old.dat", SOLID);
|
reconcile_put(opt, "hts-cache/old.dat", SOLID);
|
||||||
reconcile_put(opt, "hts-cache/old.ndx", TINY);
|
reconcile_put(opt, "hts-cache/old.ndx", TINY);
|
||||||
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
|
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.dat", SOLID, "promote-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.dat", -1, "promote-dat");
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.ndx", TINY, "promote-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.ndx", -1, "promote-dat");
|
||||||
failures += reconcile_expect(opt, "hts-cache/old.dat", -1, "promote-dat");
|
failures += reconcile_expect(opt, "hts-cache/old.dat", SOLID, "promote-dat");
|
||||||
|
failures += reconcile_expect(opt, "hts-cache/old.ndx", TINY, "promote-dat");
|
||||||
/* PROMOTE: a half-written legacy new pair is replaced by the old pair */
|
|
||||||
reconcile_wipe(opt);
|
|
||||||
reconcile_put(opt, "hts-cache/new.dat", TINY);
|
|
||||||
reconcile_put(opt, "hts-cache/old.dat", SOLID);
|
|
||||||
reconcile_put(opt, "hts-cache/old.ndx", TINY);
|
|
||||||
hts_cache_reconcile(opt, CACHE_RECONCILE_PROMOTE);
|
|
||||||
failures +=
|
|
||||||
reconcile_expect(opt, "hts-cache/new.dat", SOLID, "promote-dat-partial");
|
|
||||||
failures +=
|
|
||||||
reconcile_expect(opt, "hts-cache/new.ndx", TINY, "promote-dat-partial");
|
|
||||||
|
|
||||||
/* INTERRUPTED: no lock file, no action */
|
/* INTERRUPTED: no lock file, no action */
|
||||||
reconcile_wipe(opt);
|
reconcile_wipe(opt);
|
||||||
@@ -1058,7 +1039,7 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
|||||||
failures +=
|
failures +=
|
||||||
reconcile_expect(opt, "hts-cache/new.zip", MID, "interrupted-bignew");
|
reconcile_expect(opt, "hts-cache/new.zip", MID, "interrupted-bignew");
|
||||||
|
|
||||||
/* INTERRUPTED: the legacy pair follows the same size rule (was dead code) */
|
/* INTERRUPTED: the removed pre-3.31 legacy pair is left alone */
|
||||||
reconcile_wipe(opt);
|
reconcile_wipe(opt);
|
||||||
reconcile_put(opt, "hts-in_progress.lock", 0);
|
reconcile_put(opt, "hts-in_progress.lock", 0);
|
||||||
reconcile_put(opt, "hts-cache/new.dat", TINY);
|
reconcile_put(opt, "hts-cache/new.dat", TINY);
|
||||||
@@ -1067,9 +1048,13 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
|||||||
reconcile_put(opt, "hts-cache/old.ndx", MID);
|
reconcile_put(opt, "hts-cache/old.ndx", MID);
|
||||||
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
hts_cache_reconcile(opt, CACHE_RECONCILE_INTERRUPTED);
|
||||||
failures +=
|
failures +=
|
||||||
reconcile_expect(opt, "hts-cache/new.dat", SOLID, "interrupted-dat");
|
reconcile_expect(opt, "hts-cache/new.dat", TINY, "interrupted-dat");
|
||||||
failures +=
|
failures +=
|
||||||
reconcile_expect(opt, "hts-cache/new.ndx", MID, "interrupted-dat");
|
reconcile_expect(opt, "hts-cache/new.ndx", TINY, "interrupted-dat");
|
||||||
|
failures +=
|
||||||
|
reconcile_expect(opt, "hts-cache/old.dat", SOLID, "interrupted-dat");
|
||||||
|
failures +=
|
||||||
|
reconcile_expect(opt, "hts-cache/old.ndx", MID, "interrupted-dat");
|
||||||
|
|
||||||
/* ROLLBACK: the old zip generation is restored (a zip cache used to lose
|
/* ROLLBACK: the old zip generation is restored (a zip cache used to lose
|
||||||
its only good generation here) */
|
its only good generation here) */
|
||||||
@@ -1089,17 +1074,18 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
|||||||
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-lst");
|
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-lst");
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-txt");
|
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-txt");
|
||||||
|
|
||||||
/* ROLLBACK: full legacy generation incl. sidecars (historical behavior) */
|
/* ROLLBACK: sidecars restored, the removed pre-3.31 pair left alone */
|
||||||
reconcile_wipe(opt);
|
reconcile_wipe(opt);
|
||||||
reconcile_put(opt, "hts-cache/new.dat", TINY);
|
reconcile_put(opt, "hts-cache/new.dat", TINY);
|
||||||
reconcile_put(opt, "hts-cache/new.ndx", TINY);
|
|
||||||
reconcile_put(opt, "hts-cache/old.dat", SOLID);
|
reconcile_put(opt, "hts-cache/old.dat", SOLID);
|
||||||
reconcile_put(opt, "hts-cache/old.ndx", MID);
|
reconcile_put(opt, "hts-cache/old.ndx", MID);
|
||||||
reconcile_put(opt, "hts-cache/old.lst", MID);
|
reconcile_put(opt, "hts-cache/old.lst", MID);
|
||||||
reconcile_put(opt, "hts-cache/old.txt", MID);
|
reconcile_put(opt, "hts-cache/old.txt", MID);
|
||||||
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
|
hts_cache_reconcile(opt, CACHE_RECONCILE_ROLLBACK);
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.dat", SOLID, "rollback-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.dat", TINY, "rollback-dat");
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.ndx", MID, "rollback-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.ndx", -1, "rollback-dat");
|
||||||
|
failures += reconcile_expect(opt, "hts-cache/old.dat", SOLID, "rollback-dat");
|
||||||
|
failures += reconcile_expect(opt, "hts-cache/old.ndx", MID, "rollback-dat");
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.lst", MID, "rollback-dat");
|
||||||
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-dat");
|
failures += reconcile_expect(opt, "hts-cache/new.txt", MID, "rollback-dat");
|
||||||
|
|
||||||
@@ -1113,6 +1099,101 @@ int cache_reconcile_selftest(httrackp *opt, const char *dir) {
|
|||||||
return failures;
|
return failures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The pre-3.31 .dat/.ndx import was removed: cache_init must refuse the
|
||||||
|
legacy pair, leave the files in place, and not flag an update run. */
|
||||||
|
int cache_legacy_refused_selftest(httrackp *opt, const char *dir) {
|
||||||
|
int failures = 0;
|
||||||
|
int variant;
|
||||||
|
|
||||||
|
selftest_tag = "cache-legacy";
|
||||||
|
golden_setup(opt, dir);
|
||||||
|
#ifdef _WIN32
|
||||||
|
mkdir(reconcile_st_path(opt, "hts-cache"));
|
||||||
|
#else
|
||||||
|
mkdir(reconcile_st_path(opt, "hts-cache"), HTS_PROTECT_FOLDER);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* variant 0: old.dat/old.ndx (--update layout); 1: new.dat/new.ndx (ro) */
|
||||||
|
for (variant = 0; variant < 2; variant++) {
|
||||||
|
cache_back cache;
|
||||||
|
const char *const dat =
|
||||||
|
variant == 0 ? "hts-cache/old.dat" : "hts-cache/new.dat";
|
||||||
|
const char *const ndx =
|
||||||
|
variant == 0 ? "hts-cache/old.ndx" : "hts-cache/new.ndx";
|
||||||
|
|
||||||
|
reconcile_wipe(opt);
|
||||||
|
reconcile_put(opt, dat, 4096);
|
||||||
|
reconcile_put(opt, ndx, 4096);
|
||||||
|
opt->is_update = 0;
|
||||||
|
selftest_open(&cache, opt, /*ro*/ variant == 1);
|
||||||
|
if (cache_readable(&cache)) {
|
||||||
|
printf("cache-legacy: variant %d imported a removed format\n", variant);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
if (coucal_nitems(cache.hashtable) != 0) { /* no phantom index entries */
|
||||||
|
printf("cache-legacy: variant %d imported index entries\n", variant);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
if (opt->is_update) {
|
||||||
|
printf("cache-legacy: variant %d flagged an update\n", variant);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
if (fsize(reconcile_st_path(opt, dat)) != 4096 ||
|
||||||
|
fsize(reconcile_st_path(opt, ndx)) != 4096) {
|
||||||
|
printf("cache-legacy: variant %d touched the legacy files\n", variant);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
if (cache.lst != NULL) {
|
||||||
|
fclose(cache.lst);
|
||||||
|
cache.lst = NULL;
|
||||||
|
}
|
||||||
|
if (cache.txt != NULL) {
|
||||||
|
fclose(cache.txt);
|
||||||
|
cache.txt = NULL;
|
||||||
|
}
|
||||||
|
selftest_close(&cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a healthy zip must win over stray legacy files, with no refusal */
|
||||||
|
{
|
||||||
|
cache_back cache;
|
||||||
|
const char *const body = "<html>zip wins</html>";
|
||||||
|
|
||||||
|
reconcile_wipe(opt);
|
||||||
|
selftest_open_for_write(&cache, opt);
|
||||||
|
store_entry(opt, &cache, "example.com", "/", "example.com/index.html", 200,
|
||||||
|
"OK", "text/html", "utf-8", "", "", "", "", body, strlen(body));
|
||||||
|
selftest_close(&cache);
|
||||||
|
reconcile_put(opt, "hts-cache/old.ndx", 4096);
|
||||||
|
reconcile_put(opt, "hts-cache/old.dat", 4096);
|
||||||
|
selftest_open_for_read(&cache, opt);
|
||||||
|
if (!cache_readable(&cache)) {
|
||||||
|
printf("cache-legacy: stray legacy files shadowed the zip cache\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
failures +=
|
||||||
|
check_entry(opt, &cache, "example.com", "/", 200, "OK", "text/html",
|
||||||
|
"utf-8", "", "", "", "", body, strlen(body));
|
||||||
|
if (fsize(reconcile_st_path(opt, "hts-cache/old.ndx")) != 4096 ||
|
||||||
|
fsize(reconcile_st_path(opt, "hts-cache/old.dat")) != 4096) {
|
||||||
|
printf("cache-legacy: zip-wins pass touched the legacy files\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
if (cache.lst != NULL) {
|
||||||
|
fclose(cache.lst);
|
||||||
|
cache.lst = NULL;
|
||||||
|
}
|
||||||
|
if (cache.txt != NULL) {
|
||||||
|
fclose(cache.txt);
|
||||||
|
cache.txt = NULL;
|
||||||
|
}
|
||||||
|
selftest_close(&cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
reconcile_wipe(opt);
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- read-side corruption injection --------------------------------------- */
|
/* --- read-side corruption injection --------------------------------------- */
|
||||||
|
|
||||||
/* canary read back intact after each corruption; victim gets the byte surgery
|
/* canary read back intact after each corruption; victim gets the byte surgery
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ int cache_write_failure_selftest(httrackp *opt, const char *dir);
|
|||||||
under <dir>. Returns the failed-check count. */
|
under <dir>. Returns the failed-check count. */
|
||||||
int cache_reconcile_selftest(httrackp *opt, const char *dir);
|
int cache_reconcile_selftest(httrackp *opt, const char *dir);
|
||||||
|
|
||||||
|
/* Verify cache_init refuses a pre-3.31 .dat/.ndx cache without touching it.
|
||||||
|
Returns the number of failed checks (0 = pass). */
|
||||||
|
int cache_legacy_refused_selftest(httrackp *opt, const char *dir);
|
||||||
|
|
||||||
/* Inject read-side corruption (zip byte surgery: bad size, header, deflate)
|
/* Inject read-side corruption (zip byte surgery: bad size, header, deflate)
|
||||||
under <dir> and assert every case degrades to STATUSCODE_INVALID without
|
under <dir> and assert every case degrades to STATUSCODE_INVALID without
|
||||||
tainting a sibling entry. */
|
tainting a sibling entry. */
|
||||||
|
|||||||
@@ -76,9 +76,6 @@ Please visit our Website: http://www.httrack.com
|
|||||||
// always direct-to-disk (0/1)
|
// always direct-to-disk (0/1)
|
||||||
#define HTS_DIRECTDISK_ALWAYS 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
|
// le > peut être considéré comme un tag de fermeture de commentaire (<!-- > est
|
||||||
// valide)
|
// valide)
|
||||||
#define GT_ENDS_COMMENT 1
|
#define GT_ENDS_COMMENT 1
|
||||||
|
|||||||
@@ -134,18 +134,6 @@ RUN_CALLBACK0(opt, end); \
|
|||||||
back_delete_all(opt, &cache, sback); \
|
back_delete_all(opt, &cache, sback); \
|
||||||
back_free(&sback); \
|
back_free(&sback); \
|
||||||
checkrobots_free(&robots); \
|
checkrobots_free(&robots); \
|
||||||
if (cache.use) { \
|
|
||||||
freet(cache.use); \
|
|
||||||
cache.use = NULL; \
|
|
||||||
} \
|
|
||||||
if (cache.dat) { \
|
|
||||||
fclose(cache.dat); \
|
|
||||||
cache.dat = NULL; \
|
|
||||||
} \
|
|
||||||
if (cache.ndx) { \
|
|
||||||
fclose(cache.ndx); \
|
|
||||||
cache.ndx = NULL; \
|
|
||||||
} \
|
|
||||||
if (cache.zipOutput) { \
|
if (cache.zipOutput) { \
|
||||||
zipClose(cache.zipOutput, \
|
zipClose(cache.zipOutput, \
|
||||||
"Created by HTTrack Website Copier/" HTTRACK_VERSION); \
|
"Created by HTTrack Website Copier/" HTTRACK_VERSION); \
|
||||||
@@ -155,10 +143,6 @@ RUN_CALLBACK0(opt, end); \
|
|||||||
unzClose(cache.zipInput); \
|
unzClose(cache.zipInput); \
|
||||||
cache.zipInput = NULL; \
|
cache.zipInput = NULL; \
|
||||||
} \
|
} \
|
||||||
if (cache.olddat) { \
|
|
||||||
fclose(cache.olddat); \
|
|
||||||
cache.olddat = NULL; \
|
|
||||||
} \
|
|
||||||
if (cache.lst) { \
|
if (cache.lst) { \
|
||||||
fclose(cache.lst); \
|
fclose(cache.lst); \
|
||||||
cache.lst = opt->state.strc.lst = NULL; \
|
cache.lst = opt->state.strc.lst = NULL; \
|
||||||
@@ -488,32 +472,11 @@ void hts_finish_makeindex(httrackp *opt, int *makeindex_done,
|
|||||||
*makeindex_done = 1;
|
*makeindex_done = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Flush the parsed HTML output buffer to disk, skipping the rewrite when the
|
/* Flush the parsed HTML output buffer to disk. */
|
||||||
* on-disk MD5 is unchanged. */
|
|
||||||
void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
|
void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
|
||||||
FILE **fp, const char *ht_buff, size_t ht_len,
|
FILE **fp, const char *ht_buff, size_t ht_len,
|
||||||
const char *adr, const char *fil, const char *save) {
|
const char *adr, const char *fil, const char *save) {
|
||||||
char digest[32 + 2];
|
{
|
||||||
off_t fsize_old =
|
|
||||||
fsize(fconv(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), save));
|
|
||||||
int ok = 0;
|
|
||||||
|
|
||||||
digest[0] = '\0';
|
|
||||||
domd5mem(ht_buff, ht_len, digest, 1);
|
|
||||||
if (fsize_old == (off_t) ht_len) {
|
|
||||||
int mlen = 0;
|
|
||||||
char *mbuff;
|
|
||||||
|
|
||||||
cache_readdata(cache, "//[HTML-MD5]//", save, &mbuff, &mlen);
|
|
||||||
if (mlen)
|
|
||||||
mbuff[mlen] = '\0';
|
|
||||||
if ((mlen == 32) && (strcmp(((mbuff != NULL) ? mbuff : ""), digest) == 0)) {
|
|
||||||
ok = 1;
|
|
||||||
hts_log_print(opt, LOG_DEBUG, "File not re-written (md5): %s", save);
|
|
||||||
}
|
|
||||||
freet(mbuff);
|
|
||||||
}
|
|
||||||
if (!ok) {
|
|
||||||
file_notify(opt, adr, fil, save, 1, 1, r->notmodified);
|
file_notify(opt, adr, fil, save, 1, 1, r->notmodified);
|
||||||
*fp = filecreate(&opt->state.strc, save);
|
*fp = filecreate(&opt->state.strc, save);
|
||||||
if (*fp) {
|
if (*fp) {
|
||||||
@@ -545,13 +508,7 @@ void hts_finish_html_file(httrackp *opt, cache_back *cache, htsblk *r,
|
|||||||
if (fcheck)
|
if (fcheck)
|
||||||
hts_log_print(opt, LOG_ERROR, "* * Fatal write error, giving up");
|
hts_log_print(opt, LOG_ERROR, "* * Fatal write error, giving up");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
file_notify(opt, adr, fil, save, 0, 0, r->notmodified);
|
|
||||||
filenote(&opt->state.strc, save, NULL);
|
|
||||||
}
|
}
|
||||||
if (cache->ndx)
|
|
||||||
cache_writedata(cache->ndx, cache->dat, "//[HTML-MD5]//", save, digest,
|
|
||||||
(int) strlen(digest));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* does it look like XML ? (SVG et al.) */
|
/* does it look like XML ? (SVG et al.) */
|
||||||
@@ -846,6 +803,52 @@ int httpmirror(char *url1, httrackp * opt) {
|
|||||||
}
|
}
|
||||||
} // while
|
} // while
|
||||||
|
|
||||||
|
/* --why: print which filter rule decides for this URL, then stop */
|
||||||
|
if (StringNotEmpty(opt->why_url)) {
|
||||||
|
char BIGSTK url[HTS_URLMAXSIZE * 2];
|
||||||
|
lien_adrfil af;
|
||||||
|
|
||||||
|
if (strstr(StringBuff(opt->why_url), ":/") == NULL)
|
||||||
|
snprintf(url, sizeof(url), "http://%s", StringBuff(opt->why_url));
|
||||||
|
else
|
||||||
|
strcpybuff(url, StringBuff(opt->why_url));
|
||||||
|
if (ident_url_absolute(url, &af) < 0) {
|
||||||
|
printf("--why: unable to parse URL %s" LF, StringBuff(opt->why_url));
|
||||||
|
} else {
|
||||||
|
char BIGSTK l[HTS_URLMAXSIZE * 2], lfull[HTS_URLMAXSIZE * 2];
|
||||||
|
int jok, jokDepth = 0;
|
||||||
|
|
||||||
|
/* the two forms the wizard tests */
|
||||||
|
strcpybuff(l, jump_identification_const(af.adr));
|
||||||
|
if (*af.fil != '/')
|
||||||
|
strcatbuff(l, "/");
|
||||||
|
strcatbuff(l, af.fil);
|
||||||
|
if (!link_has_authority(af.adr))
|
||||||
|
strcpybuff(lfull, "http://");
|
||||||
|
else
|
||||||
|
lfull[0] = '\0';
|
||||||
|
strcatbuff(lfull, af.adr);
|
||||||
|
if (*af.fil != '/')
|
||||||
|
strcatbuff(lfull, "/");
|
||||||
|
strcatbuff(lfull, af.fil);
|
||||||
|
jok = fa_strjoker_dual(/*url */ 0, filters, filptr, lfull, l, NULL,
|
||||||
|
NULL, &jokDepth);
|
||||||
|
if (jok > 0)
|
||||||
|
printf("%s: accepted by rule #%d (%s)" LF, url, jokDepth + 1,
|
||||||
|
filters[jokDepth]);
|
||||||
|
else if (jok < 0)
|
||||||
|
printf("%s: rejected by rule #%d (%s)" LF, url, jokDepth + 1,
|
||||||
|
filters[jokDepth]);
|
||||||
|
else
|
||||||
|
printf("%s: no filter rule matches; the wizard decides "
|
||||||
|
"(same-site links are followed by default)" LF,
|
||||||
|
url);
|
||||||
|
}
|
||||||
|
freet(primary);
|
||||||
|
XH_extuninit;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* load URL file list */
|
/* load URL file list */
|
||||||
/* OPTIMIZED for fast load */
|
/* OPTIMIZED for fast load */
|
||||||
if (StringNotEmpty(opt->filelist)) {
|
if (StringNotEmpty(opt->filelist)) {
|
||||||
|
|||||||
@@ -193,8 +193,7 @@ struct cache_back {
|
|||||||
/* */
|
/* */
|
||||||
int type;
|
int type;
|
||||||
int ro; /**< read-only: no new cache is written */
|
int ro; /**< read-only: no new cache is written */
|
||||||
FILE *dat, *ndx, *olddat; /**< new data, new index, old data files */
|
|
||||||
char *use; /**< in-memory list of cached adr+fil keys */
|
|
||||||
FILE *lst; /**< file list, used for purge */
|
FILE *lst; /**< file list, used for purge */
|
||||||
FILE *txt; /**< human-readable file list (info) */
|
FILE *txt; /**< human-readable file list (info) */
|
||||||
char lastmodified[256];
|
char lastmodified[256];
|
||||||
@@ -288,12 +287,12 @@ struct filecreate_params {
|
|||||||
|
|
||||||
/* True if a new cache is being written (plain or zip backend). */
|
/* True if a new cache is being written (plain or zip backend). */
|
||||||
HTS_STATIC int cache_writable(cache_back * cache) {
|
HTS_STATIC int cache_writable(cache_back * cache) {
|
||||||
return (cache != NULL && (cache->dat != NULL || cache->zipOutput != NULL));
|
return (cache != NULL && cache->zipOutput != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* True if an old cache is available to read (plain or zip backend). */
|
/* True if an old cache is available to read (plain or zip backend). */
|
||||||
HTS_STATIC int cache_readable(cache_back * cache) {
|
HTS_STATIC int cache_readable(cache_back * cache) {
|
||||||
return (cache != NULL && (cache->olddat != NULL || cache->zipInput != NULL));
|
return (cache != NULL && cache->zipInput != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1803,6 +1803,23 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
|||||||
StringCopy(opt->cookies_file, argv[na]);
|
StringCopy(opt->cookies_file, argv[na]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'Y': // why: explain the filter verdict for a URL, no crawl
|
||||||
|
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
|
||||||
|
HTS_PANIC_PRINTF("Option why needs a blank space and a URL");
|
||||||
|
printf(
|
||||||
|
"Example: --why \"http://www.example.com/file.zip\"\n");
|
||||||
|
htsmain_free();
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
na++;
|
||||||
|
if (strlen(argv[na]) >= HTS_URLMAXSIZE) {
|
||||||
|
HTS_PANIC_PRINTF("why URL too long");
|
||||||
|
htsmain_free();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
StringCopy(opt->why_url, argv[na]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'G': // pause: randomized inter-file delay MIN[:MAX] seconds
|
case 'G': // pause: randomized inter-file delay MIN[:MAX] seconds
|
||||||
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
|
if ((na + 1 >= argc) || (argv[na + 1][0] == '-')) {
|
||||||
HTS_PANIC_PRINTF("Option pause needs a blank space and a "
|
HTS_PANIC_PRINTF("Option pause needs a blank space and a "
|
||||||
@@ -1936,18 +1953,20 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
|||||||
char BIGSTK url[HTS_URLMAXSIZE * 2];
|
char BIGSTK url[HTS_URLMAXSIZE * 2];
|
||||||
char linepos[256];
|
char linepos[256];
|
||||||
int pos;
|
int pos;
|
||||||
char *cacheNdx =
|
LLint cacheNdxLen = 0;
|
||||||
readfile(fconcat
|
char *cacheNdx = readfile2(
|
||||||
(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), StringBuff(opt->path_log),
|
fconcat(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt),
|
||||||
"hts-cache/new.ndx"));
|
StringBuff(opt->path_log), "hts-cache/new.ndx"),
|
||||||
|
&cacheNdxLen);
|
||||||
cache_init(&cache, opt); /* load cache */
|
cache_init(&cache, opt); /* load cache */
|
||||||
if (cacheNdx != NULL) {
|
if (cacheNdx != NULL) {
|
||||||
char firstline[256];
|
char firstline[256];
|
||||||
char *a = cacheNdx;
|
char *a = cacheNdx;
|
||||||
|
const char *const end = cacheNdx + cacheNdxLen;
|
||||||
|
|
||||||
a += cache_brstr(a, firstline, sizeof(firstline));
|
a += cache_brstr(a, firstline, sizeof(firstline));
|
||||||
a += cache_brstr(a, firstline, sizeof(firstline));
|
a += cache_brstr(a, firstline, sizeof(firstline));
|
||||||
while(a != NULL) {
|
while (a != NULL && a < end) {
|
||||||
a = strchr(a + 1, '\n'); /* start of line */
|
a = strchr(a + 1, '\n'); /* start of line */
|
||||||
if (a) {
|
if (a) {
|
||||||
htsblk r;
|
htsblk r;
|
||||||
@@ -1955,15 +1974,15 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
|||||||
/* */
|
/* */
|
||||||
a++;
|
a++;
|
||||||
/* read "host/file" */
|
/* read "host/file" */
|
||||||
a += binput(a, afs.af.adr, HTS_URLMAXSIZE);
|
a += cache_binput(a, end, afs.af.adr, HTS_URLMAXSIZE);
|
||||||
a += binput(a, afs.af.fil, HTS_URLMAXSIZE);
|
a += cache_binput(a, end, afs.af.fil, HTS_URLMAXSIZE);
|
||||||
url[0] = '\0';
|
url[0] = '\0';
|
||||||
if (!link_has_authority(afs.af.adr))
|
if (!link_has_authority(afs.af.adr))
|
||||||
strcatbuff(url, "http://");
|
strcatbuff(url, "http://");
|
||||||
strcatbuff(url, afs.af.adr);
|
strcatbuff(url, afs.af.adr);
|
||||||
strcatbuff(url, afs.af.fil);
|
strcatbuff(url, afs.af.fil);
|
||||||
/* read position */
|
/* read position */
|
||||||
a += binput(a, linepos, 200);
|
a += cache_binput(a, end, linepos, 200);
|
||||||
sscanf(linepos, "%d", &pos);
|
sscanf(linepos, "%d", &pos);
|
||||||
if (!hasFilter
|
if (!hasFilter
|
||||||
|| (strjoker(url, filter, NULL, NULL) != NULL)
|
|| (strjoker(url, filter, NULL, NULL) != NULL)
|
||||||
|
|||||||
118
src/htsfilters.c
118
src/htsfilters.c
@@ -48,6 +48,7 @@ Please visit our Website: http://www.httrack.com
|
|||||||
#include "htsbase.h"
|
#include "htsbase.h"
|
||||||
#include "htslib.h"
|
#include "htslib.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <stdint.h>
|
||||||
/* END specific definitions */
|
/* END specific definitions */
|
||||||
|
|
||||||
// à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé
|
// à partir d'un tableau de {"+*.toto","-*.zip","+*.tata"} définit si nom est autorisé
|
||||||
@@ -94,16 +95,107 @@ int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * siz
|
|||||||
return verdict;
|
return verdict;
|
||||||
}
|
}
|
||||||
|
|
||||||
// supercomparateur joker (tm)
|
int fa_strjoker_dual(int type, char **filters, int nfil, const char *nom1,
|
||||||
// compare a et b (b=avec joker dedans), case insensitive [voir CI]
|
const char *nom2, LLint *size, int *size_flag,
|
||||||
// renvoi l'adresse de la première lettre de la chaine
|
int *depth) {
|
||||||
// (càd *[..]toto.. renvoi adresse de toto dans la chaine)
|
int depth1 = 0, depth2 = 0;
|
||||||
// accepte les délires du genre www.*.*/ * / * truc*.*
|
int flag1 = 0, flag2 = 0;
|
||||||
// cet algo est 'un peu' récursif mais ne consomme pas trop de tm
|
LLint sz1 = 0, sz2 = 0;
|
||||||
// * = toute lettre
|
int jok1, jok2, use1;
|
||||||
// --?-- : spécifique à HTTrack et aux ?
|
|
||||||
|
if (size) {
|
||||||
|
sz1 = *size;
|
||||||
|
sz2 = *size;
|
||||||
|
}
|
||||||
|
jok1 = fa_strjoker(type, filters, nfil, nom1, size ? &sz1 : NULL,
|
||||||
|
size_flag ? &flag1 : NULL, &depth1);
|
||||||
|
jok2 = fa_strjoker(type, filters, nfil, nom2, size ? &sz2 : NULL,
|
||||||
|
size_flag ? &flag2 : NULL, &depth2);
|
||||||
|
use1 = jok2 == 0 || (jok1 != 0 && depth1 >= depth2);
|
||||||
|
if (size)
|
||||||
|
*size = use1 ? sz1 : sz2;
|
||||||
|
if (size_flag)
|
||||||
|
*size_flag = use1 ? flag1 : flag2;
|
||||||
|
if (depth)
|
||||||
|
*depth = use1 ? depth1 : depth2;
|
||||||
|
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
|
||||||
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
||||||
LLint *size, int *size_flag) {
|
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(joker) == 0) { // fin de chaine joker
|
||||||
if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok
|
if (strnotempty(chaine) == 0) // fin aussi pour la chaine: ok
|
||||||
return chaine;
|
return chaine;
|
||||||
@@ -277,7 +369,7 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
|||||||
|
|
||||||
// tester sans le joker (pas ()+ mais ()*)
|
// tester sans le joker (pas ()+ mais ()*)
|
||||||
if (!unique) {
|
if (!unique) {
|
||||||
if ((adr = strjoker(chaine, joker + jmp, size, size_flag))) {
|
if ((adr = strjoker_rec(memo, chaine, joker + jmp, size, size_flag))) {
|
||||||
return adr;
|
return adr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,7 +381,8 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
|||||||
max = strnotempty(chaine) ? 1 : 0; /* empty chaine: no char to eat */
|
max = strnotempty(chaine) ? 1 : 0; /* empty chaine: no char to eat */
|
||||||
while(i < (int) max) {
|
while(i < (int) max) {
|
||||||
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
|
if (pass[(int) (unsigned char) chaine[i]]) { // caractère autorisé
|
||||||
if ((adr = strjoker(chaine + i + 1, joker + jmp, size, size_flag))) {
|
if ((adr = strjoker_rec(memo, chaine + i + 1, joker + jmp, size,
|
||||||
|
size_flag))) {
|
||||||
return adr;
|
return adr;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@@ -299,7 +392,8 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
|||||||
|
|
||||||
// tester chaîne vide
|
// tester chaîne vide
|
||||||
if (i != max + 2) // avant c'est ok
|
if (i != max + 2) // avant c'est ok
|
||||||
if ((adr = strjoker(chaine + max, joker + jmp, size, size_flag)))
|
if ((adr = strjoker_rec(memo, chaine + max, joker + jmp, size,
|
||||||
|
size_flag)))
|
||||||
return adr;
|
return adr;
|
||||||
|
|
||||||
return NULL; // perdu
|
return NULL; // perdu
|
||||||
@@ -321,7 +415,7 @@ HTS_INLINE const char *strjoker(const char *chaine, const char *joker,
|
|||||||
// comparaison ok?
|
// comparaison ok?
|
||||||
if (ok) {
|
if (ok) {
|
||||||
// continuer la comparaison.
|
// continuer la comparaison.
|
||||||
if (strjoker(chaine + jmp, joker + jmp, size, size_flag))
|
if (strjoker_rec(memo, chaine + jmp, joker + jmp, size, size_flag))
|
||||||
return chaine; // retourner 1e lettre
|
return chaine; // retourner 1e lettre
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,17 @@ Please visit our Website: http://www.httrack.com
|
|||||||
|
|
||||||
int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * size,
|
int fa_strjoker(int type, char **filters, int nfil, const char *nom, LLint * size,
|
||||||
int *size_flag, int *depth);
|
int *size_flag, int *depth);
|
||||||
|
/* fa_strjoker() on both URL forms the engine builds (nom1 full, nom2 without
|
||||||
|
scheme); the match latest in the list wins, a "don't know" verdict defers.
|
||||||
|
Returns the merged verdict; the out-params carry the winner's values. */
|
||||||
|
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,
|
HTS_INLINE const char *strjoker(const char *chaine, const char *joker, LLint * size,
|
||||||
int *size_flag);
|
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);
|
const char *strjokerfind(const char *chaine, const char *joker);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -422,9 +422,10 @@ typedef int T_SOC;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Permission bits for created folders and files (mkdir and chmod).
|
/* Permission bits for created folders and files (mkdir and chmod).
|
||||||
PROTECT_FOLDER is owner-only. With HTS_ACCESS set (the default) the ACCESS_
|
PROTECT_FOLDER/FILE are owner-only. With HTS_ACCESS set (the default) the
|
||||||
modes also grant group/other read; otherwise they stay owner-only. */
|
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_FOLDER (S_IRUSR | S_IWUSR | S_IXUSR)
|
||||||
|
#define HTS_PROTECT_FILE (S_IRUSR | S_IWUSR)
|
||||||
|
|
||||||
#if HTS_ACCESS
|
#if HTS_ACCESS
|
||||||
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
#define HTS_ACCESS_FILE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ void help(const char *app, int more) {
|
|||||||
infomsg("Spider options:");
|
infomsg("Spider options:");
|
||||||
infomsg(" bN accept cookies in cookies.txt (0=do not accept,* 1=accept)");
|
infomsg(" bN accept cookies in cookies.txt (0=do not accept,* 1=accept)");
|
||||||
infomsg(" %K load extra cookies from a Netscape cookies.txt");
|
infomsg(" %K load extra cookies from a Netscape cookies.txt");
|
||||||
|
infomsg(" %Y explain which filter rule accepts or rejects a URL, then exit");
|
||||||
infomsg
|
infomsg
|
||||||
(" u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)");
|
(" u check document type if unknown (cgi,asp..) (u0 don't check, * u1 check but /, u2 check always)");
|
||||||
infomsg
|
infomsg
|
||||||
|
|||||||
10
src/htslib.c
10
src/htslib.c
@@ -1562,7 +1562,7 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
|
|||||||
}
|
}
|
||||||
// An empty/whitespace Content-Type value yields no token: keep the
|
// An empty/whitespace Content-Type value yields no token: keep the
|
||||||
// sentinel default rather than reading an uninitialized tempo.
|
// sentinel default rather than reading an uninitialized tempo.
|
||||||
if (sscanf(rcvd + p, "%s", tempo) == 1) {
|
if (sscanf(rcvd + p, "%1099s", tempo) == 1) { // tempo[1100], server data
|
||||||
if (strlen(tempo) < sizeof(retour->contenttype) - 2) // pas trop long!!
|
if (strlen(tempo) < sizeof(retour->contenttype) - 2) // pas trop long!!
|
||||||
strcpybuff(retour->contenttype, tempo);
|
strcpybuff(retour->contenttype, tempo);
|
||||||
else
|
else
|
||||||
@@ -1657,11 +1657,11 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
|
|||||||
if (a)
|
if (a)
|
||||||
*a = '\0';
|
*a = '\0';
|
||||||
}
|
}
|
||||||
sscanf(a, "%s", tempo);
|
// Bound to tempo[1100]; no token => leave empty, don't read uninit tempo.
|
||||||
if (strlen(tempo) < 64) // pas trop long!!
|
if (sscanf(a, "%1099s", tempo) == 1 && strlen(tempo) < 64)
|
||||||
strcpybuff(retour->contentencoding, tempo);
|
strcpybuff(retour->contentencoding, tempo);
|
||||||
else
|
else
|
||||||
retour->contentencoding[0] = '\0'; // erreur
|
retour->contentencoding[0] = '\0';
|
||||||
#if HTS_USEZLIB
|
#if HTS_USEZLIB
|
||||||
/* Check known encodings */
|
/* Check known encodings */
|
||||||
if (retour->contentencoding[0]) {
|
if (retour->contentencoding[0]) {
|
||||||
@@ -6006,6 +6006,7 @@ HTSEXT_API httrackp *hts_create_opt(void) {
|
|||||||
StringCopy(opt->footer, HTS_DEFAULT_FOOTER);
|
StringCopy(opt->footer, HTS_DEFAULT_FOOTER);
|
||||||
StringCopy(opt->strip_query, "");
|
StringCopy(opt->strip_query, "");
|
||||||
StringCopy(opt->cookies_file, "");
|
StringCopy(opt->cookies_file, "");
|
||||||
|
StringCopy(opt->why_url, "");
|
||||||
opt->pause_min_ms = 0;
|
opt->pause_min_ms = 0;
|
||||||
opt->pause_max_ms = 0;
|
opt->pause_max_ms = 0;
|
||||||
opt->ftp_proxy = HTS_TRUE;
|
opt->ftp_proxy = HTS_TRUE;
|
||||||
@@ -6153,6 +6154,7 @@ HTSEXT_API void hts_free_opt(httrackp * opt) {
|
|||||||
StringFree(opt->mod_blacklist);
|
StringFree(opt->mod_blacklist);
|
||||||
StringFree(opt->strip_query);
|
StringFree(opt->strip_query);
|
||||||
StringFree(opt->cookies_file);
|
StringFree(opt->cookies_file);
|
||||||
|
StringFree(opt->why_url);
|
||||||
|
|
||||||
StringFree(opt->path_html);
|
StringFree(opt->path_html);
|
||||||
StringFree(opt->path_html_utf8);
|
StringFree(opt->path_html_utf8);
|
||||||
|
|||||||
@@ -536,6 +536,8 @@ struct httrackp {
|
|||||||
(--cookies-file) */
|
(--cookies-file) */
|
||||||
int pause_min_ms; /**< inter-file pause lower bound, ms (0=off, #185) */
|
int pause_min_ms; /**< inter-file pause lower bound, ms (0=off, #185) */
|
||||||
int pause_max_ms; /**< inter-file pause upper bound, ms */
|
int pause_max_ms; /**< inter-file pause upper bound, ms */
|
||||||
|
String why_url; /**< URL to diagnose (--why): print the deciding filter rule
|
||||||
|
and exit without crawling */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Running statistics for a mirror. */
|
/* Running statistics for a mirror. */
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ Please visit our Website: http://www.httrack.com
|
|||||||
#include "htsdefines.h"
|
#include "htsdefines.h"
|
||||||
#include "htslib.h"
|
#include "htslib.h"
|
||||||
#include "htsparse.h"
|
#include "htsparse.h"
|
||||||
|
#include "htscache.h"
|
||||||
#include "htscache_selftest.h"
|
#include "htscache_selftest.h"
|
||||||
#include "htsdns_selftest.h"
|
#include "htsdns_selftest.h"
|
||||||
#include "htscharset.h"
|
#include "htscharset.h"
|
||||||
@@ -620,6 +621,125 @@ static int st_filtersize(httrackp *opt, int argc, char **argv) {
|
|||||||
return 0;
|
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;
|
||||||
|
|
||||||
|
(void) opt;
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"filterdual: needs <string1> <string2> <filter> [filter...]\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
verdict = fa_strjoker_dual(0, &argv[2], argc - 2, argv[0], argv[1], NULL,
|
||||||
|
NULL, &depth);
|
||||||
|
printf("verdict=%s rule=%d\n",
|
||||||
|
verdict > 0 ? "allowed"
|
||||||
|
: verdict < 0 ? "forbidden"
|
||||||
|
: "unknown",
|
||||||
|
depth);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int st_simplify(httrackp *opt, int argc, char **argv) {
|
static int st_simplify(httrackp *opt, int argc, char **argv) {
|
||||||
(void) opt;
|
(void) opt;
|
||||||
if (argc < 1) {
|
if (argc < 1) {
|
||||||
@@ -1143,6 +1263,26 @@ static int st_header(httrackp *opt, int argc, char **argv) {
|
|||||||
treathead(NULL, "www.example.com", "/", &r, line);
|
treathead(NULL, "www.example.com", "/", &r, line);
|
||||||
}
|
}
|
||||||
printf("contenttype=%s cdispo=%s\n", r.contenttype, r.cdispo);
|
printf("contenttype=%s cdispo=%s\n", r.contenttype, r.cdispo);
|
||||||
|
printf("contentencoding=%s\n", r.contentencoding);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An over-long header value must not overflow treathead's tempo[1100]. */
|
||||||
|
static int st_headerlong(httrackp *opt, int argc, char **argv) {
|
||||||
|
htsblk r;
|
||||||
|
char BIGSTK line[HTS_URLMAXSIZE * 2];
|
||||||
|
const char *const name = argc >= 1 ? argv[0] : "Content-Type:";
|
||||||
|
const int pad = 1500; /* > tempo[1100] */
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
(void) opt;
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
n = (size_t) snprintf(line, sizeof(line), "%s ", name);
|
||||||
|
memset(line + n, 'a', pad);
|
||||||
|
line[n + pad] = '\0';
|
||||||
|
treathead(NULL, "www.example.com", "/", &r, line);
|
||||||
|
printf("contenttype_len=%d contentencoding_len=%d\n",
|
||||||
|
(int) strlen(r.contenttype), (int) strlen(r.contentencoding));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1350,6 +1490,78 @@ static int st_cache(httrackp *opt, int argc, char **argv) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A corrupt cache index (.ndx) must not walk the length-prefixed scan past
|
||||||
|
the buffer. Checks the two primitives the loader is built from. */
|
||||||
|
static int st_cacheindex(httrackp *opt, int argc, char **argv) {
|
||||||
|
int fail = 0;
|
||||||
|
|
||||||
|
(void) opt;
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
/* A length prefix that overstates the bytes present must bound the advance
|
||||||
|
to the buffer, not trust the declared length. */
|
||||||
|
{
|
||||||
|
static const char src[] = "32768\nCACHE-1.1";
|
||||||
|
const size_t len = sizeof(src) - 1;
|
||||||
|
char *buf = malloct(len + 1);
|
||||||
|
char s[256];
|
||||||
|
int off;
|
||||||
|
|
||||||
|
memcpy(buf, src, len + 1);
|
||||||
|
off = cache_brstr(buf, s, sizeof(s));
|
||||||
|
if (off > (int) len) {
|
||||||
|
printf("cacheindex: over-advance off=%d len=%d\n", off, (int) len);
|
||||||
|
fail = 1;
|
||||||
|
}
|
||||||
|
if (strcmp(s, "CACHE-1.1") != 0) {
|
||||||
|
printf("cacheindex: value=%s\n", s);
|
||||||
|
fail = 1;
|
||||||
|
}
|
||||||
|
freet(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cache_binput reads a field while in bounds, but refuses one starting at
|
||||||
|
or past end-of-buffer. */
|
||||||
|
{
|
||||||
|
char buf[8] = "ab\ncd";
|
||||||
|
const char *const end = buf + 5;
|
||||||
|
char s[16];
|
||||||
|
|
||||||
|
if (cache_binput(buf, end, s, sizeof(s)) != 3 || strcmp(s, "ab") != 0)
|
||||||
|
fail = 1; /* normal read: "ab" then the '\n', 3 bytes consumed */
|
||||||
|
if (cache_binput(end, end, s, sizeof(s)) != 0 || s[0] != '\0')
|
||||||
|
fail = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drive the full loader scan over a truncated index: a declared length that
|
||||||
|
overshoots plus a half-written entry. ASan aborts here on the pre-fix
|
||||||
|
scan; the cursor must never leave the buffer. */
|
||||||
|
{
|
||||||
|
static const char src[] = "9\nCACHE-1.1\n99\nwww.example.com\n/a";
|
||||||
|
const size_t len = sizeof(src) - 1;
|
||||||
|
char *buf = malloct(len + 1);
|
||||||
|
const char *const end = buf + len;
|
||||||
|
char line[256];
|
||||||
|
char *a = buf;
|
||||||
|
|
||||||
|
memcpy(buf, src, len + 1);
|
||||||
|
a += cache_brstr(a, line, sizeof(line));
|
||||||
|
a += cache_brstr(a, line, sizeof(line));
|
||||||
|
while (a != NULL && a < end) {
|
||||||
|
a = strchr(a + 1, '\n');
|
||||||
|
if (a == NULL)
|
||||||
|
break;
|
||||||
|
a++;
|
||||||
|
a += cache_binput(a, end, line, sizeof(line));
|
||||||
|
}
|
||||||
|
freet(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("cacheindex: %s\n", fail ? "FAIL" : "OK");
|
||||||
|
return fail;
|
||||||
|
}
|
||||||
|
|
||||||
static int st_cache_golden(httrackp *opt, int argc, char **argv) {
|
static int st_cache_golden(httrackp *opt, int argc, char **argv) {
|
||||||
int regen, err;
|
int regen, err;
|
||||||
|
|
||||||
@@ -1387,6 +1599,18 @@ static int st_cache_corrupt(httrackp *opt, int argc, char **argv) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int st_cache_legacy(httrackp *opt, int argc, char **argv) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (argc < 1) {
|
||||||
|
fprintf(stderr, "cache-legacy: needs a directory\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
err = cache_legacy_refused_selftest(opt, argv[0]);
|
||||||
|
printf("cache-legacy: %s\n", err ? "FAIL" : "OK");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
static int st_reconcile(httrackp *opt, int argc, char **argv) {
|
static int st_reconcile(httrackp *opt, int argc, char **argv) {
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
@@ -1438,6 +1662,26 @@ static int st_cookies(httrackp *opt, int argc, char **argv) {
|
|||||||
err = 1;
|
err = 1;
|
||||||
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
|
if (strstr(hdr, "junk") != NULL) // wrong-domain cookie leaked
|
||||||
err = 1;
|
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");
|
printf("cookie-header: %s\n", err ? "FAIL" : "OK");
|
||||||
if (err)
|
if (err)
|
||||||
printf(" got: %s\n", hdr);
|
printf(" got: %s\n", hdr);
|
||||||
@@ -2134,6 +2378,12 @@ static const struct selftest_entry {
|
|||||||
{"filtersize", "<size> <string> <filter>...",
|
{"filtersize", "<size> <string> <filter>...",
|
||||||
"size-aware filter verdict (negative size = unknown/scan time)",
|
"size-aware filter verdict (negative size = unknown/scan time)",
|
||||||
st_filtersize},
|
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},
|
{"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify},
|
||||||
{"stripquery", "", "--strip-query pattern/key stripping self-test",
|
{"stripquery", "", "--strip-query pattern/key stripping self-test",
|
||||||
st_stripquery},
|
st_stripquery},
|
||||||
@@ -2163,17 +2413,24 @@ static const struct selftest_entry {
|
|||||||
{"identurl", "<url>", "split an absolute URL into (adr, fil)", st_identurl},
|
{"identurl", "<url>", "split an absolute URL into (adr, fil)", st_identurl},
|
||||||
{"header", "<raw-header-line> ...", "response header-line parsing",
|
{"header", "<raw-header-line> ...", "response header-line parsing",
|
||||||
st_header},
|
st_header},
|
||||||
|
{"headerlong", "[header-name:]",
|
||||||
|
"over-long header value must not overflow the parse scratch",
|
||||||
|
st_headerlong},
|
||||||
{"savename", "<fil> <content-type> [key=value ...]",
|
{"savename", "<fil> <content-type> [key=value ...]",
|
||||||
"local save-name for a URL", st_savename},
|
"local save-name for a URL", st_savename},
|
||||||
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
|
{"sniff", "<content-type> <hex:..|text>", "MIME magic consistency",
|
||||||
st_sniff},
|
st_sniff},
|
||||||
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
|
{"cache", "<dir>", "cache read/write round-trip self-test", st_cache},
|
||||||
|
{"cacheindex", "", "cache-index (.ndx) parse must stay in bounds",
|
||||||
|
st_cacheindex},
|
||||||
{"cache-golden", "<dir> [regen]", "frozen cache-format read self-test",
|
{"cache-golden", "<dir> [regen]", "frozen cache-format read self-test",
|
||||||
st_cache_golden},
|
st_cache_golden},
|
||||||
{"cache-writefail", "<dir>", "cache write-failure handling self-test",
|
{"cache-writefail", "<dir>", "cache write-failure handling self-test",
|
||||||
st_cache_writefail},
|
st_cache_writefail},
|
||||||
{"reconcile", "<dir>", "cache generation reconcile policy self-test",
|
{"reconcile", "<dir>", "cache generation reconcile policy self-test",
|
||||||
st_reconcile},
|
st_reconcile},
|
||||||
|
{"cache-legacy", "<dir>", "pre-3.31 legacy cache refusal self-test",
|
||||||
|
st_cache_legacy},
|
||||||
{"cache-corrupt", "<dir>", "cache read-side corruption self-test",
|
{"cache-corrupt", "<dir>", "cache read-side corruption self-test",
|
||||||
st_cache_corrupt},
|
st_cache_corrupt},
|
||||||
{"dns", "", "DNS resolver/cache self-test", st_dns},
|
{"dns", "", "DNS resolver/cache self-test", st_dns},
|
||||||
|
|||||||
@@ -524,28 +524,11 @@ static int hts_acceptlink_(httrackp * opt, int ptr,
|
|||||||
|
|
||||||
// filters, 0=sait pas 1=ok -1=interdit
|
// filters, 0=sait pas 1=ok -1=interdit
|
||||||
{
|
{
|
||||||
int jokDepth1 = 0, jokDepth2 = 0;
|
int jokDepth = 0;
|
||||||
int jok1 = 0, jok2 = 0;
|
|
||||||
|
|
||||||
jok1 =
|
jok = fa_strjoker_dual(/*url */ 0, _FILTERS, *_FILTERS_PTR, lfull, l,
|
||||||
fa_strjoker( /*url */ 0, _FILTERS, *_FILTERS_PTR, lfull, NULL, NULL,
|
NULL, NULL, &jokDepth);
|
||||||
&jokDepth1);
|
mdepth = _FILTERS[jokDepth];
|
||||||
jok2 =
|
|
||||||
fa_strjoker( /*url */ 0, _FILTERS, *_FILTERS_PTR, l, NULL, NULL,
|
|
||||||
&jokDepth2);
|
|
||||||
if (jok2 == 0) { // #2 doesn't know
|
|
||||||
jok = jok1; // then, use #1
|
|
||||||
mdepth = _FILTERS[jokDepth1];
|
|
||||||
} else if (jok1 == 0) { // #1 doesn't know
|
|
||||||
jok = jok2; // then, use #2
|
|
||||||
mdepth = _FILTERS[jokDepth2];
|
|
||||||
} else if (jokDepth1 >= jokDepth2) { // #1 matching rule is "after" #2, then it is prioritary
|
|
||||||
jok = jok1;
|
|
||||||
mdepth = _FILTERS[jokDepth1];
|
|
||||||
} else { // #2 matching rule is "after" #1, then it is prioritary
|
|
||||||
jok = jok2;
|
|
||||||
mdepth = _FILTERS[jokDepth2];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (jok == 1) { // autorisé
|
if (jok == 1) { // autorisé
|
||||||
@@ -965,34 +948,10 @@ int hts_testlinksize(httrackp * opt, const char *adr, const char *fil, LLint siz
|
|||||||
|
|
||||||
// filters, 0=sait pas 1=ok -1=interdit
|
// filters, 0=sait pas 1=ok -1=interdit
|
||||||
{
|
{
|
||||||
int jokDepth1 = 0, jokDepth2 = 0;
|
sz = size;
|
||||||
int jok1 = 0, jok2 = 0;
|
jok = fa_strjoker_dual(/*url */ 0, *opt->filters.filters,
|
||||||
LLint sz1 = size, sz2 = size;
|
*opt->filters.filptr, lfull, l, &sz, &size_flag,
|
||||||
int size_flag1 = 0, size_flag2 = 0;
|
NULL);
|
||||||
|
|
||||||
jok1 =
|
|
||||||
fa_strjoker( /*url */ 0, *opt->filters.filters, *opt->filters.filptr,
|
|
||||||
lfull, &sz1, &size_flag1, &jokDepth1);
|
|
||||||
jok2 =
|
|
||||||
fa_strjoker( /*url */ 0, *opt->filters.filters, *opt->filters.filptr,
|
|
||||||
l, &sz2, &size_flag2, &jokDepth2);
|
|
||||||
if (jok2 == 0) { // #2 doesn't know
|
|
||||||
jok = jok1; // then, use #1
|
|
||||||
sz = sz1;
|
|
||||||
size_flag = size_flag1;
|
|
||||||
} else if (jok1 == 0) { // #1 doesn't know
|
|
||||||
jok = jok2; // then, use #2
|
|
||||||
sz = sz2;
|
|
||||||
size_flag = size_flag2;
|
|
||||||
} else if (jokDepth1 >= jokDepth2) { // #1 matching rule is "after" #2, then it is prioritary
|
|
||||||
jok = jok1;
|
|
||||||
sz = sz1;
|
|
||||||
size_flag = size_flag1;
|
|
||||||
} else { // #2 matching rule is "after" #1, then it is prioritary
|
|
||||||
jok = jok2;
|
|
||||||
sz = sz2;
|
|
||||||
size_flag = size_flag2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// log
|
// log
|
||||||
|
|||||||
10
tests/01_engine-cacheindex.test
Executable file
10
tests/01_engine-cacheindex.test
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Cache-index (.ndx) parse must stay inside the buffer on a corrupt length
|
||||||
|
# prefix (ASan aborts here on the pre-fix binary; the OK check gates the
|
||||||
|
# non-sanitized build too).
|
||||||
|
|
||||||
|
test "$(httrack -O /dev/null -#test=cacheindex)" == 'cacheindex: OK'
|
||||||
@@ -131,3 +131,64 @@ match '*.aspx' 'page.aspx?y=2'
|
|||||||
# empty subject against a unique-match allow-all pattern (heap over-read regress)
|
# empty subject against a unique-match allow-all pattern (heap over-read regress)
|
||||||
nomatch_empty '*(('
|
nomatch_empty '*(('
|
||||||
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'
|
||||||
|
|||||||
20
tests/01_engine-filterdual.test
Normal file
20
tests/01_engine-filterdual.test
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# fa_strjoker_dual: merged verdict on the two URL forms the engine tests;
|
||||||
|
# the match latest in the filter list wins, an unknown verdict defers.
|
||||||
|
|
||||||
|
fdual() {
|
||||||
|
local want="$1"
|
||||||
|
shift
|
||||||
|
test "$(httrack -O /dev/null -#test=filterdual "$@")" == "$want" || exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fdual 'verdict=unknown rule=0' zzz yyy '+a*' # neither form matches
|
||||||
|
fdual 'verdict=forbidden rule=0' aaa zzz '-a*' # only form 1 matches
|
||||||
|
fdual 'verdict=forbidden rule=0' zzz aaa '-a*' # only form 2 matches
|
||||||
|
fdual 'verdict=forbidden rule=1' a b '+a*' '-b*' # later rule wins (form 2)
|
||||||
|
fdual 'verdict=forbidden rule=1' b a '+a*' '-b*' # later rule wins (form 1)
|
||||||
|
fdual 'verdict=allowed rule=0' a aa '+a*' # tie: form 1 wins
|
||||||
@@ -27,3 +27,16 @@ hdr 'contenttype= cdispo=report.pdf' \
|
|||||||
# Path components in the filename are dropped on the wire (RFC 2616).
|
# Path components in the filename are dropped on the wire (RFC 2616).
|
||||||
hdr 'contenttype= cdispo=evil.pdf' \
|
hdr 'contenttype= cdispo=evil.pdf' \
|
||||||
'Content-Disposition: attachment; filename="../../evil.pdf"'
|
'Content-Disposition: attachment; filename="../../evil.pdf"'
|
||||||
|
|
||||||
|
# An over-long header value must not overflow the parse scratch (ASan aborts
|
||||||
|
# here on the pre-fix binary).
|
||||||
|
test "$(httrack -O /dev/null -#test=headerlong 'Content-Type:')" \
|
||||||
|
== 'contenttype_len=32 contentencoding_len=0'
|
||||||
|
test "$(httrack -O /dev/null -#test=headerlong 'Content-Encoding:')" \
|
||||||
|
== 'contenttype_len=0 contentencoding_len=0'
|
||||||
|
|
||||||
|
# An empty Content-Encoding yields no token: leave it empty, don't read the
|
||||||
|
# uninitialized scratch (MSan aborts here on the pre-fix binary).
|
||||||
|
enc() { httrack -O /dev/null -#test=header "$1" | grep '^contentencoding='; }
|
||||||
|
test "$(enc 'Content-Encoding:')" == 'contentencoding='
|
||||||
|
test "$(enc 'Content-Encoding: gzip')" == 'contentencoding=gzip'
|
||||||
|
|||||||
23
tests/01_zlib-cache-legacy.test
Normal file
23
tests/01_zlib-cache-legacy.test
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# The pre-3.31 .dat/.ndx cache import was removed: cache_init must refuse the
|
||||||
|
# legacy pair and leave the files untouched (httrack -#test=cache-legacy <dir>).
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
dir=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$dir"' EXIT
|
||||||
|
|
||||||
|
# the refusal errors land on stdout (no log file); pin them and the verdict
|
||||||
|
out=$(httrack -#test=cache-legacy "$dir" 2>/dev/null)
|
||||||
|
cnt=$(printf '%s\n' "$out" | grep -c 'no longer supported' || true)
|
||||||
|
test "$cnt" = 2 || {
|
||||||
|
echo "expected 2 refusal messages, got $cnt" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
out=$(printf '%s\n' "$out" | tail -n1)
|
||||||
|
|
||||||
|
test "$out" = "cache-legacy: OK" || {
|
||||||
|
echo "expected 'cache-legacy: OK', got: $out" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
35
tests/40_local-why.test
Normal file
35
tests/40_local-why.test
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# --why: report which +/- filter rule decides for a URL, without crawling.
|
||||||
|
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmpdir"' EXIT
|
||||||
|
|
||||||
|
why() {
|
||||||
|
local want="$1" out
|
||||||
|
shift
|
||||||
|
out="$(httrack -O "$tmpdir/p" -q "$@" |
|
||||||
|
grep -E 'accepted|rejected|no filter|unable')"
|
||||||
|
test "$out" == "$want" || {
|
||||||
|
echo "want: $want"
|
||||||
|
echo "got : $out"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base=http://www.example.com
|
||||||
|
why "$base/x.zip: rejected by rule #2 (-*.zip)" \
|
||||||
|
--why "$base/x.zip" "$base/" '+*.png' '-*.zip'
|
||||||
|
why "$base/a.png: accepted by rule #1 (+*.png)" \
|
||||||
|
--why "$base/a.png" "$base/" '+*.png' '-*.zip'
|
||||||
|
why "$base/i.html: no filter rule matches; the wizard decides (same-site links are followed by default)" \
|
||||||
|
--why "$base/i.html" "$base/" '+*.png' '-*.zip'
|
||||||
|
# scheme-less input gets the same http:// default as primary URLs
|
||||||
|
why "$base/x.zip: rejected by rule #2 (-*.zip)" \
|
||||||
|
--why "www.example.com/x.zip" "$base/" '+*.png' '-*.zip'
|
||||||
|
# the rule latest in the list decides
|
||||||
|
why "$base/x.zip: accepted by rule #3 (+*x.zip)" \
|
||||||
|
--why "$base/x.zip" "$base/" '+*.png' '-*.zip' '+*x.zip'
|
||||||
@@ -35,8 +35,10 @@ TESTS = \
|
|||||||
01_engine-entities.test \
|
01_engine-entities.test \
|
||||||
01_engine-filelist.test \
|
01_engine-filelist.test \
|
||||||
01_engine-filter.test \
|
01_engine-filter.test \
|
||||||
|
01_engine-filterdual.test \
|
||||||
01_engine-ftp-line.test \
|
01_engine-ftp-line.test \
|
||||||
01_engine-ftp-userpass.test \
|
01_engine-ftp-userpass.test \
|
||||||
|
01_engine-cacheindex.test \
|
||||||
01_engine-hashtable.test \
|
01_engine-hashtable.test \
|
||||||
01_engine-header.test \
|
01_engine-header.test \
|
||||||
01_engine-idna.test \
|
01_engine-idna.test \
|
||||||
@@ -66,6 +68,7 @@ TESTS = \
|
|||||||
01_zlib-acceptencoding.test \
|
01_zlib-acceptencoding.test \
|
||||||
01_zlib-cache.test \
|
01_zlib-cache.test \
|
||||||
01_zlib-cache-corrupt.test \
|
01_zlib-cache-corrupt.test \
|
||||||
|
01_zlib-cache-legacy.test \
|
||||||
01_zlib-cache-golden.test \
|
01_zlib-cache-golden.test \
|
||||||
01_zlib-cache-writefail.test \
|
01_zlib-cache-writefail.test \
|
||||||
01_zlib-savename-cached.test \
|
01_zlib-savename-cached.test \
|
||||||
@@ -105,6 +108,7 @@ TESTS = \
|
|||||||
36_local-bigcrawl.test \
|
36_local-bigcrawl.test \
|
||||||
37_local-cache-outage.test \
|
37_local-cache-outage.test \
|
||||||
38_local-update-304.test \
|
38_local-update-304.test \
|
||||||
39_local-delayed-cancel.test
|
39_local-delayed-cancel.test \
|
||||||
|
40_local-why.test
|
||||||
|
|
||||||
CLEANFILES = check-network_sh.cache
|
CLEANFILES = check-network_sh.cache
|
||||||
|
|||||||
Reference in New Issue
Block a user