mirror of
https://github.com/xroche/httrack.git
synced 2026-06-24 19:17:31 +03:00
Compare commits
3 Commits
fix-delaye
...
dns-multia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bea390973 | ||
|
|
67af1c2f0b | ||
|
|
542d6a56b5 |
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
@@ -232,42 +232,30 @@ jobs:
|
||||
deb:
|
||||
name: deb package (lintian)
|
||||
runs-on: ubuntu-24.04
|
||||
# Build and gate inside Debian sid, the upload target. A Debian dpkg-deb
|
||||
# produces archive-legal xz members (an Ubuntu host defaults to zstd, which
|
||||
# the archive's lintian rejects), and sid's lintian carries the same
|
||||
# data-driven checks (embedded-lib fingerprints and the like) the buildds and
|
||||
# UDD apply -- so issues surface here instead of after upload.
|
||||
container: debian:sid
|
||||
steps:
|
||||
- name: Install packaging toolchain
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates git \
|
||||
build-essential autoconf automake libtool autoconf-archive \
|
||||
zlib1g-dev libssl-dev \
|
||||
debhelper devscripts lintian fakeroot
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install packaging toolchain
|
||||
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 \
|
||||
debhelper devscripts lintian fakeroot
|
||||
|
||||
# --unsigned: CI has no GPG key (also skips the release sig/checksums).
|
||||
# mkdeb builds every package then runs the lintian gate (--fail-on=error,
|
||||
# warning); debuild runs the packaged test pass.
|
||||
# debuild builds every package, then lintian gates on errors.
|
||||
#
|
||||
# DEB_BUILD_OPTIONS trims work CI does not need (release builds via
|
||||
# mkdeb.sh are untouched): noautodbgsym drops the -dbgsym packages whose
|
||||
# LTO payloads are slow to compress and that CI never ships; parallel uses
|
||||
# every core.
|
||||
- name: Build and lint Debian packages
|
||||
# every core. We let debuild run its test pass -- the only one now that
|
||||
# mkdeb no longer runs its own -- so CI exercises the packaged tests.
|
||||
- name: Build Debian packages
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The workspace volume is owned by the host runner uid, but the
|
||||
# container runs as root, so mkdeb's git calls (superproject and the
|
||||
# coucal submodule) trip "dubious ownership"; mark them all safe.
|
||||
git config --global --add safe.directory "*"
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym parallel=$(nproc)"
|
||||
bash tools/mkdeb.sh --unsigned --no-release-artifacts
|
||||
|
||||
|
||||
5
debian/libhttrack3.lintian-overrides
vendored
5
debian/libhttrack3.lintian-overrides
vendored
@@ -1,8 +1,3 @@
|
||||
# The shared libraries ship without a versioned symbols control file (ABI is
|
||||
# tracked via the SONAME plus a >= upstream-version dependency, see debian/rules).
|
||||
libhttrack3: no-symbols-control-file usr/lib/*
|
||||
|
||||
# Bundled, locally patched minizip (src/minizip): it adds a zipFlush() API the
|
||||
# system libminizip lacks (htscache.c flushes the cache .zip so an interrupted
|
||||
# crawl leaves a valid archive), plus Android/old-zlib portability fixes.
|
||||
libhttrack3: embedded-library *libminizip*
|
||||
|
||||
3
debian/proxytrack.lintian-overrides
vendored
3
debian/proxytrack.lintian-overrides
vendored
@@ -1,3 +0,0 @@
|
||||
# Statically linked against httrack's bundled, patched minizip (see src/minizip
|
||||
# and libhttrack3's override): the zipFlush() API is absent from the system one.
|
||||
proxytrack: embedded-library *libminizip*
|
||||
@@ -2468,44 +2468,6 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
htsmain_free();
|
||||
return err;
|
||||
} break;
|
||||
case 'N': { // url_savename name resolution: httrack -#N <fil>
|
||||
// <content-type>
|
||||
if (na + 2 < argc) {
|
||||
lien_adrfilsave afs;
|
||||
cache_back cache;
|
||||
struct_back *sback;
|
||||
hash_struct hash;
|
||||
lien_back headers;
|
||||
|
||||
memset(&afs, 0, sizeof(afs));
|
||||
strcpybuff(afs.af.adr, "www.example.com");
|
||||
strcpybuff(afs.af.fil, argv[na + 1]);
|
||||
|
||||
memset(&cache, 0, sizeof(cache));
|
||||
cache.hashtable = (void *) coucal_new(0);
|
||||
|
||||
sback = back_new(opt, opt->maxsoc * 32 + 1024);
|
||||
hash_init(opt, &hash, opt->urlhack);
|
||||
|
||||
memset(&headers, 0, sizeof(headers));
|
||||
headers.status = 0;
|
||||
headers.r.statuscode = HTTP_OK;
|
||||
strcpybuff(headers.r.contenttype, argv[na + 2]);
|
||||
strcpybuff(headers.url_fil, argv[na + 1]);
|
||||
|
||||
url_savename(&afs, NULL, NULL, NULL, opt, sback, &cache,
|
||||
&hash, 0, 0, &headers);
|
||||
printf("savename: %s\n", afs.save);
|
||||
htsmain_free();
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Option #N requires <fil> <content-type> arguments\n");
|
||||
htsmain_free();
|
||||
return 1;
|
||||
}
|
||||
} break;
|
||||
case 'C': // list cache files : httrack -#C '*spid*.gif' will attempt to find the matching file
|
||||
{
|
||||
int hasFilter = 0;
|
||||
|
||||
104
src/htslib.c
104
src/htslib.c
@@ -4766,51 +4766,64 @@ int hts_read(htsblk * r, char *buff, int size) {
|
||||
// -- Gestion cache DNS --
|
||||
// 'RX98
|
||||
|
||||
// Free a DNS cache record (coucal value handler).
|
||||
static void hts_cache_value_free(coucal_opaque arg, coucal_value value) {
|
||||
void *record = value.ptr;
|
||||
|
||||
(void) arg;
|
||||
freet(record);
|
||||
}
|
||||
|
||||
// opt's DNS cache hashtable, created on first use. Records (t_dnscache*) are
|
||||
// owned by the table and freed by hts_cache_value_free on coucal_delete.
|
||||
coucal hts_cache(httrackp *opt) {
|
||||
// 'capsule' contenant uniquement le cache
|
||||
t_dnscache *hts_cache(httrackp * opt) {
|
||||
assertf(opt != NULL);
|
||||
if (opt->state.dns_cache == NULL) {
|
||||
coucal cache = coucal_new(0);
|
||||
|
||||
coucal_set_name(cache, "dns_cache");
|
||||
coucal_value_set_value_handler(cache, hts_cache_value_free, NULL);
|
||||
opt->state.dns_cache = cache;
|
||||
opt->state.dns_cache = (t_dnscache *) malloct(sizeof(t_dnscache));
|
||||
memset(opt->state.dns_cache, 0, sizeof(t_dnscache));
|
||||
}
|
||||
assertf(opt->state.dns_cache != NULL);
|
||||
/* first entry is NULL */
|
||||
assertf(opt->state.dns_cache->iadr == NULL);
|
||||
return opt->state.dns_cache;
|
||||
}
|
||||
|
||||
// MUST BE LOCKED (coucal is not internally serialized vs FTP/web threads)
|
||||
// Free DNS cache.
|
||||
void hts_cache_free(t_dnscache *const root) {
|
||||
if (root != NULL) {
|
||||
t_dnscache *cache;
|
||||
for(cache = root; cache != NULL; ) {
|
||||
t_dnscache *const next = cache->next;
|
||||
cache->next = NULL;
|
||||
freet(cache);
|
||||
cache = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lock le cache dns pour tout opération d'ajout
|
||||
// plus prudent quand plusieurs threads peuvent écrire dedans..
|
||||
// -1: status? 0: libérer 1:locker
|
||||
|
||||
// MUST BE LOCKED
|
||||
// Look up iadr in the DNS cache, filling out[0..min(count,max)-1].
|
||||
// Returns: -1 not yet tested; 0 negative-cached (not in DNS); >0 address count.
|
||||
static int hts_ghbn_all(coucal cache, const char *const iadr,
|
||||
static int hts_ghbn_all(const t_dnscache *cache, const char *const iadr,
|
||||
SOCaddr *const out, const int max) {
|
||||
void *ptr;
|
||||
|
||||
assertf(out != NULL);
|
||||
assertf(iadr != NULL);
|
||||
if (*iadr == '\0') {
|
||||
return -1;
|
||||
}
|
||||
if (coucal_read_pvoid(cache, iadr, &ptr)) { // ok trouvé
|
||||
const t_dnscache *const record = (const t_dnscache *) ptr;
|
||||
int i;
|
||||
/* first entry is empty */
|
||||
if (cache->iadr == NULL) {
|
||||
cache = cache->next;
|
||||
}
|
||||
for(; cache != NULL; cache = cache->next) {
|
||||
assertf(cache != NULL);
|
||||
assertf(cache->iadr != NULL);
|
||||
assertf(cache->iadr == (const char*) cache + sizeof(t_dnscache));
|
||||
if (strcmp(cache->iadr, iadr) == 0) { // ok trouvé
|
||||
int i;
|
||||
|
||||
assertf(record->host_count <= HTS_MAXADDRNUM);
|
||||
for (i = 0; i < record->host_count && i < max; i++) {
|
||||
assertf(record->host_length[i] <= sizeof(record->host_addr[i]));
|
||||
SOCaddr_copyaddr2(out[i], record->host_addr[i], record->host_length[i]);
|
||||
assertf(cache->host_count <= HTS_MAXADDRNUM);
|
||||
for (i = 0; i < cache->host_count && i < max; i++) {
|
||||
assertf(cache->host_length[i] <= sizeof(cache->host_addr[i]));
|
||||
SOCaddr_copyaddr2(out[i], cache->host_addr[i], cache->host_length[i]);
|
||||
}
|
||||
return cache->host_count;
|
||||
}
|
||||
return record->host_count;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -5075,7 +5088,7 @@ static int hts_dns_resolve_list_(httrackp *opt, const char *_iadr,
|
||||
SOCaddr *const out, const int max,
|
||||
const char **error) {
|
||||
char BIGSTK iadr[HTS_URLMAXSIZE * 2];
|
||||
coucal cache = hts_cache(opt); // le cache dns
|
||||
t_dnscache *cache = hts_cache(opt); // adresse du cache
|
||||
int count;
|
||||
|
||||
assertf(opt != NULL);
|
||||
@@ -5096,10 +5109,13 @@ static int hts_dns_resolve_list_(httrackp *opt, const char *_iadr,
|
||||
if (count >= 0) { // cache hit (0 == negative-cached)
|
||||
return count;
|
||||
} else { // non présent dans le cache dns, tester
|
||||
const size_t iadr_len = strlen(iadr) + 1;
|
||||
SOCaddr resolved[HTS_MAXADDRNUM];
|
||||
t_dnscache *record;
|
||||
int i;
|
||||
|
||||
// find queue
|
||||
for(; cache->next != NULL; cache = cache->next) ;
|
||||
|
||||
#if DEBUGDNS
|
||||
printf("resolving (not cached) %s\n", iadr);
|
||||
#endif
|
||||
@@ -5110,18 +5126,22 @@ static int hts_dns_resolve_list_(httrackp *opt, const char *_iadr,
|
||||
DEBUG_W("gethostbyname done\n");
|
||||
#endif
|
||||
|
||||
/* attempt to store new entry (coucal owns it and dups the host key) */
|
||||
record = malloct(sizeof(t_dnscache));
|
||||
if (record != NULL) {
|
||||
memset(record, 0, sizeof(*record));
|
||||
record->host_count = count;
|
||||
/* attempt to store new entry */
|
||||
cache->next = malloct(sizeof(t_dnscache) + iadr_len);
|
||||
if (cache->next != NULL) {
|
||||
t_dnscache *const next = cache->next;
|
||||
char *const block = (char*) cache->next;
|
||||
char *const str = block + sizeof(t_dnscache);
|
||||
memcpy(str, iadr, iadr_len);
|
||||
next->iadr = str;
|
||||
next->host_count = count;
|
||||
for (i = 0; i < count; i++) {
|
||||
record->host_length[i] = SOCaddr_size(resolved[i]);
|
||||
assertf(record->host_length[i] <= sizeof(record->host_addr[i]));
|
||||
memcpy(record->host_addr[i], &SOCaddr_sockaddr(resolved[i]),
|
||||
record->host_length[i]);
|
||||
next->host_length[i] = SOCaddr_size(resolved[i]);
|
||||
assertf(next->host_length[i] <= sizeof(next->host_addr[i]));
|
||||
memcpy(next->host_addr[i], &SOCaddr_sockaddr(resolved[i]),
|
||||
next->host_length[i]);
|
||||
}
|
||||
coucal_add_pvoid(cache, iadr, record);
|
||||
next->next = NULL;
|
||||
}
|
||||
|
||||
/* copy result to caller (cache store may have failed; result still valid)
|
||||
@@ -5992,14 +6012,14 @@ HTSEXT_API void hts_free_opt(httrackp * opt) {
|
||||
|
||||
/* Cache */
|
||||
if (opt->state.dns_cache != NULL) {
|
||||
coucal root;
|
||||
t_dnscache *root;
|
||||
|
||||
hts_mutexlock(&opt->state.lock);
|
||||
root = opt->state.dns_cache;
|
||||
opt->state.dns_cache = NULL;
|
||||
hts_mutexrelease(&opt->state.lock);
|
||||
|
||||
coucal_delete(&root); // frees records via hts_cache_value_free
|
||||
hts_cache_free(root);
|
||||
}
|
||||
|
||||
/* Cancel chain */
|
||||
|
||||
@@ -147,8 +147,9 @@ struct OLD_htsblk {
|
||||
#define HTS_DEF_FWSTRUCT_t_dnscache
|
||||
typedef struct t_dnscache t_dnscache;
|
||||
#endif
|
||||
// One DNS cache record, stored as a coucal value keyed by hostname.
|
||||
struct t_dnscache {
|
||||
struct t_dnscache *next;
|
||||
const char *iadr;
|
||||
// resolved addresses, in resolver (RFC 6724) order; host_count==0 means the
|
||||
// name does not resolve (negative cache). host_count<=HTS_MAXADDRNUM.
|
||||
int host_count;
|
||||
@@ -244,9 +245,8 @@ HTSEXT_API int check_hostname_dns(const char *const hostname);
|
||||
int ftp_available(void);
|
||||
|
||||
#if HTS_DNSCACHE
|
||||
/* Return opt's DNS cache hashtable (hostname -> t_dnscache record), creating it
|
||||
on first use. Records are owned by the table and freed on coucal_delete. */
|
||||
coucal hts_cache(httrackp *opt);
|
||||
void hts_cache_free(t_dnscache *const cache);
|
||||
t_dnscache *hts_cache(httrackp * opt);
|
||||
#endif
|
||||
|
||||
// outils divers
|
||||
|
||||
@@ -760,9 +760,9 @@ int url_savename(lien_adrfilsave *const afs,
|
||||
strcatbuff(fil, DEFAULT_HTML); // nommer page par défaut (à priori ici html depuis un proxy http)
|
||||
}
|
||||
}
|
||||
// Change the extension? e.g. php3 saved as html, cgi as html or gif/xbm
|
||||
// depending on the resolved type.
|
||||
if (ext_chg && !opt->no_type_change) {
|
||||
// Changer extension?
|
||||
// par exemple, php3 sera sauvé en html, cgi en html ou gif, xbm etc.. selon les cas
|
||||
if (ext_chg && !opt->no_type_change) { // changer ext
|
||||
char *a = fil + strlen(fil) - 1;
|
||||
|
||||
if ((opt->debug > 1) && (opt->log != NULL)) {
|
||||
@@ -774,18 +774,11 @@ int url_savename(lien_adrfilsave *const afs,
|
||||
adr_complete, fil_complete, ext);
|
||||
}
|
||||
if (ext_chg == 1) {
|
||||
// Cut the old extension only when it is the new one or a recognized one;
|
||||
// an unknown trailing ".token" (e.g. /article-1.884291, #115) is part of
|
||||
// the name, not an extension to replace.
|
||||
const char *const old_ext = get_ext(catbuff, sizeof(catbuff), fil);
|
||||
const int known_ext = strfield2(old_ext, ext) || is_knowntype(opt, fil) ||
|
||||
is_dyntype(old_ext) || ishtml_ext(old_ext) != -1;
|
||||
|
||||
while((a > fil) && (*a != '.') && (*a != '/'))
|
||||
a--;
|
||||
if (*a == '.' && known_ext)
|
||||
*a = '\0'; // cut
|
||||
strcatbuff(fil, "."); // re-add the dot
|
||||
if (*a == '.')
|
||||
*a = '\0'; // couper
|
||||
strcatbuff(fil, "."); // recopier point
|
||||
} else {
|
||||
while((a > fil) && (*a != '/'))
|
||||
a--;
|
||||
@@ -793,7 +786,7 @@ int url_savename(lien_adrfilsave *const afs,
|
||||
a++;
|
||||
*a = '\0';
|
||||
}
|
||||
strcatbuff(fil, ext); // append ext/name
|
||||
strcatbuff(fil, ext); // copier ext/nom
|
||||
}
|
||||
// Rechercher premier / et dernier .
|
||||
{
|
||||
|
||||
@@ -241,7 +241,7 @@ struct htsoptstate {
|
||||
char *userhttptype;
|
||||
int verif_backblue_done; /**< backblue.gif/fade.gif already emitted */
|
||||
int verif_external_status;
|
||||
coucal dns_cache; /**< DNS resolution cache: hostname -> t_dnscache record */
|
||||
t_dnscache *dns_cache; /**< DNS resolution cache */
|
||||
int dns_cache_nthreads; /**< number of in-flight DNS resolver threads */
|
||||
/* HTML parsing state */
|
||||
char _hts_errmsg[HTS_CDLMAXSIZE + 256]; /**< last engine error message */
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Local save-name extension resolution (url_savename via -#N <fil> <content-type>).
|
||||
# Asserts on the basename of "savename: <path>".
|
||||
|
||||
name() {
|
||||
out="$(httrack -O /dev/null -#N "$1" "$2" | sed -n 's/^savename: //p')"
|
||||
test "${out##*/}" == "$3" || {
|
||||
echo "FAIL: '$1' '$2' -> '$out' (want '$3')"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# #115: an unknown trailing ".token" is part of the name, keep it and append the type.
|
||||
name '/article-1.884291' 'text/html' 'article-1.884291.html'
|
||||
name '/news/story-12345.987654' 'text/html' 'story-12345.987654.html'
|
||||
|
||||
# Recognized extensions still collapse to the resolved type.
|
||||
name '/page.php' 'text/html' 'page.html'
|
||||
name '/page.asp' 'text/html' 'page.html'
|
||||
name '/foo' 'text/html' 'foo.html'
|
||||
|
||||
# Soft-404 (#267/#408): a binary URL served as HTML is named .html.
|
||||
name '/x.pdf' 'text/html' 'x.html'
|
||||
name '/x.gif' 'text/html' 'x.html'
|
||||
|
||||
# Type agrees with the extension: keep it, no churn, no double extension.
|
||||
name '/x.pdf' 'application/pdf' 'x.pdf'
|
||||
name '/x.jpg' 'image/jpeg' 'x.jpg'
|
||||
name '/x.html' 'text/html' 'x.html'
|
||||
name '/x.js' 'application/x-javascript' 'x.js'
|
||||
name '/types/data.json' 'application/json' 'data.json'
|
||||
@@ -40,7 +40,6 @@ TESTS = \
|
||||
01_engine-parse.test \
|
||||
01_engine-rcfile.test \
|
||||
01_engine-relative.test \
|
||||
01_engine-savename.test \
|
||||
01_engine-simplify.test \
|
||||
01_engine-strsafe.test \
|
||||
02_manpage-regen.test \
|
||||
|
||||
Reference in New Issue
Block a user