Compare commits

...

71 Commits

Author SHA1 Message Date
Xavier Roche
417d4cc467 Test 295 expects a wizard verdict string that #1254 retired (#1267)
An answer that does not forbid now resolves to 0 in hts_wizard_apply_verdict(),
so an accepting answer logs "allowed" where the test still expected "no
verdict". Each PR was green alone; the pair is red.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 16:05:37 +00:00
Xavier Roche
492c239dcd The credit tag in every mirrored page is dated 2014 (#1243)
* The credit tag in every mirrored page is dated 2014

HTTRACK_AFF_AUTHORS reads "[XR&CO'2014]" and lands in every mirrored page's
footer, every generated index and the version banner, so a year nobody has
maintained since 2014 is stamped on the output of a 2026 crawl. Drop the year
rather than move it: the next bump would go stale the same way, and the release
already dates itself through the footer's {date}.

The index templates carried their own copies, which had drifted apart in the
meantime: the compiled-in ones said 2014 and the shipped templates/ ones 2008,
with the web designer's name reversed between them. Both now credit 1998, the
start year the source headers already use, and the name matches the served
WebHTTrack pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Collapse the clang-format note to one line

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Keep the clang-format note under the column limit

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:39:06 +00:00
Xavier Roche
2472f8f417 The per-option argument caps are literals the front ends have to copy
`htscoremain.c` held the caps for six option arguments as bare `254` and `62` literals at the option sites. The GUI front ends carry their own copies of three of them, and nothing links the two, so a changed cap shows up only when a user's mirror dies on a value the GUI accepted.

They move to `htsglobal.h`, which already reaches the front ends through `HTTrackInterface.h`, and keep the exclusive bound the existing checks use, so no boundary shifts by one. Naming all six rather than the three asked for avoids leaving a shared-looking `254` next to them for someone to cross-wire later.

No behavior change: every boundary was probed against master, one byte under and exactly at each cap, with identical results. `299_option-arg-caps.test` reads each limit out of the header rather than copying it, so it pins the engine against `htsglobal.h` instead of against a second copy of the numbers.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:30:24 +00:00
Xavier Roche
f94da62bad Strings added since 3.49 are still English in every catalog (#1255)
`LANG_M10` and `LANG_M11` join `LANG_M1`..`LANG_M9` for the wizard's two domain-scope answers, which #1239 left hardcoded in the CLI menu with no key for WinHTTrack (xroche/httrack-windows#96). They are worded exactly like that menu, and test 62 now requires both to appear verbatim in `English.txt`, `lang.def` and `src/httrack.c`, so the front ends cannot drift the way #1117 warned about.

Two other sets were still English everywhere: the host-alias field and tooltip #1166 copied into every catalog, and the fifteen Android strings that had only ever reached Danish, French and Brazilian Portuguese, which `tests/62_lang-integrity.test` waived by name. Both are translated across the thirty catalogs now and the waiver is gone, so the next gap reds the test.

Each file keeps its own on-disk charset and line endings. Uzbek stays LF, Romanian still spells s-comma, t-comma and a-breve with the Latin-1 lookalikes it uses throughout, and the catalogs whose charset has no typographic quote pair take ASCII ones. Three mutants prove test 62 catches drift: a dropped msgid, a stray UTF-8 byte in a Latin-1 file, and `LANG_M10` pointed at a msgid missing from `English.txt`.

A review pass over the translations themselves fixed a feminine pronoun standing in for the masculine host in the Czech, Slovak, Croatian and Slovene scope answers, plus per-language defects in nine more catalogs. `Chinese-BIG5` also spelled `Ignore all` with the wrong character in the same wizard block; that is corrected here.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 14:03:24 +00:00
Xavier Roche
2294aa4019 The footer field names are copied by hand in three places (#1252)
The ten `-%F` footer field names were written out twice inside the formatter's own reach, in htsparse.c and in the htsselftest.c fixture, and a third time in WinHTTrack's preset validator with nothing to link against: `hts_footer_format` sits behind `HTS_INTERNAL_BYTECODE` and the field table was function-local. That third copy could catch a GUI typo and nothing else; an engine rename sailed straight past it.

The formatter's two callers now share one table in htstools.c and pass a positional `values[]` indexed by a new `hts_footer_field_id`, and front ends get `HTSEXT_API hts_boolean hts_footer_field_ok(const char *name)` to test a name against it. The export is additive and `hts_footer_format` is internal, so nothing about the ABI moves. The help text, man page and cmdguide.html still list the names by hand; that is #1256, not this PR.

Asked for by the httrack-windows session, which wires the GUI half once this lands.

Review caught the trap in the positional design and the second commit closes it: reordering the enum without touching the name table passed all 326 tests, because the named path resolves by name while the crawl assigns by enum symbol, so `{mime}` would have carried the charset. The fixture now fills `values[]` by id the way htsparse.c does, which makes the test's expected strings pin each name to its slot. A name missing from the table is a compile error instead of a NULL slot. Both mutants were confirmed to red, and a dropped name to fail the build.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:54:22 +00:00
Xavier Roche
4729035a6e doit.log writes a leading quote raw and swallows the options after it
httrack wrote an argument into `hts-cache/doit.log` unquoted and unescaped whenever its first character was already a quote. Against the raw command line that guard makes sense. But by the time the writer runs, the `-O` pass has stripped one layer of surrounding quotes in place, so a leading quote is data, not syntax. Written raw it opens a quote that never closes, and the next run pulls everything after it into that one argument.

A footer of `"<!-- x -->` loses every option recorded after `-%F`. The reprise then rewrites doit.log from the corrupted parse, so the loss carries into every later update, and the mirrored page footer differs between the first run and the second. Reaching it takes two leading quotes on the command line, since the `-O` pass eats one pair, which is the shape the GUI front ends produce when they wrap an argument in literal quotes (xroche/httrack-windows#122).

One class is not fixed and changes failure mode, so it is worth stating plainly. A plain argument (not an option value) whose value starts with a quote and does not end with one now aborts the reprise with `Missing quote`, where master silently dropped the quote instead. The abort precedes the writer, so the no-argument update stays dead until the mirror is re-run with its URL. Option values are unaffected: they bypass the second strip pass. It takes three leading quotes typed by hand to reach, since the single-pair shape a front end emits already panics on the first run, on master too. #1263 has the asymmetry behind it.

Section 4 of `01_engine-doitlog.test` checks the footer on the mirrored page rather than the doit.log text alone, so a writer and reader that agree on a wrong value still fail it. A differential over 11 argv shapes moves only the leading-quote cases.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 13:45:32 +00:00
Xavier Roche
ed30cde2a4 A wizard session leaves no record in hts-log.txt (#1249)
Two problems, same log. Driving the wizard by hand left no trace: hts-log.txt recorded the crawl going on around the questions, but not what was asked or what the user replied, so a session could not be reviewed afterwards. The levels themselves were mislabelled too. `hts_log_vprint` mapped LOG_NOTICE onto the "warning" prefix, so every notice read as a warning, while the footer's message count tracked LOG_INFO, which the default `opt->debug` filters out. That count was always zero.

The engine now writes one line per wizard answer at LOG_NOTICE: the link, the raw reply, the parsed answer number, the verdict, and the filters that answer inserted, read back from the slots they were written to. `ask_continue()` gets the same line for the yes/no prompts, and nothing is logged unless a front end registered a callback to answer with, so a non-interactive crawl gains no noise. LOG_NOTICE also gets its own arm in the level switch, mapping to "info", so notices print `Info:` and move from the warning column of `(N errors, N warnings, N messages)` to the message one. `html/faq.html` already documents the robots.txt notice as an `Info:` line. Two notice sites report a failure rather than progress, and this promotes them to LOG_WARNING so they render as before.

`tests/295_wizard-log.test` drives three answers through a `file://` crawl under `-W` and checks the line each one produces, the two-filter host-scope answer included.


Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-08-14 13:53:28 +02:00
Xavier Roche
5e8fea2a72 A link the wizard authorized is mirrored under its .delayed placeholder name (#1254)
Answer the wizard with anything that takes the link (4, 5, 6, 7, 50, or a host-scope include) and the verdict came back undecided at -1 rather than 0. The parser records a link on `!= 1` but only resolves its delayed type on `== 0`, so an authorized link went into the hash still called `<base>.<hexid>.delayed`. Every later reference gets that stored name back, `hts_wait_delayed()` then finds the link's own record and gives up ("Duplicate entry in hts_wait_delayed() cancelled", then "link is probably looping, type unknown, aborting"), and the page lands in the mirror as a placeholder file no browser opens. Answering "mirror this domain and below" on httrack.com is enough.

The question is only asked while the verdict is undecided, so an answer that did not forbid has authorized. `hts_wizard_apply_verdict()` now resolves that once at the end instead of in six cases plus the scope-include branch, and it still never overturns a verdict the crawl had already computed. An unreadable reply (-999) used to arrive here undecided too; #1259 makes it refuse the link, so it reaches this normalization already set to 1 and the two changes compose in either merge order.

Two other effects move with it, both in the same direction. `htsAddLink()` (htscore.c:3786) gates on `if (!forbidden_url)`, so under -1 a link handed back by an external module parser was never saved: it logged "(module): file not caught" and shipped as an absolute external URL. It is now saved and rewritten local, and the function returns 1 where it returned 0. The `check_link` callback also sees this value, and html/plug.html documents -1 as "no decision has yet been taken by the engine", so a plugin keyed on -1 used to get a say on wizard-answered links and now defers. The user answering is a decision, so I read that as a correction rather than a regression, but it crosses the public plugin surface.

The two gates are unchanged. Outside `-W` the verdict reaching them is already 0 or 1, so aligning them would change no link I could build. `--addurl` is a second door onto the same state, filed as #1253. The "looks like binary" and octet-stream pair from the field log did not reproduce here and is untouched.

tests/296 crawls two references to one authorized foreign link and checks the mirror: the page is there with its body, both hrefs point at it, and no placeholder survives. tests/293's battery now drives every answer from the undecided verdict the question is actually asked in. The mutant that matters most, an unconditional `*forbidden_url = 0` erasing a refusal, is caught by 293 alone. 296 never crawls a link the crawl had already refused, so it cannot see that one.


Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-08-14 13:53:12 +02:00
Xavier Roche
c5c931093a The interactive wizard spins forever when stdin reaches EOF (#1248)
`httrack -W <url> < /dev/null` never returned. At EOF `linput()` hands back an empty line and reports no failure, so the answer loop in `htsshow_query3()` re-prompted forever at 100% CPU. Any non-interactive wizard run hits it: a piped answer script that runs out of answers, or a service whose stdin is closed.

An unanswerable prompt now reads as the `*` answer, which refuses the link and stops asking. No further answer can arrive, and refusing mirrors nothing the user did not ask for. The guard tests both stream flags, since a closed descriptor sets only `ferror` while EOF sets only `feof`. On a closed stdin the old loop printed 19 million prompts and 58 MB in 25 seconds.

tests/294 crawls a page linking twice to a second local server, once with stdin on `/dev/null` and once with it closed. It asserts the question was asked exactly once, so an answer that refuses this link but keeps asking fails, and the foreign host is served rather than a black hole, so an answer that mirrors it fails too. Each case needs its own half of the guard to pass.

Closes #1247

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 11:39:27 +02:00
Xavier Roche
935ea5a89b An LTO build collapses the installed-header symbol test's candidate list (#1246)
Ubuntu's 3.49.21-2 failed on four architectures (amd64, ppc64el, riscv64, s390x) with `207_install-headers-symbols.test` reporting "only 1 symbols reached the link probe, the candidate list is broken". Debian built the same source everywhere. Ubuntu compiles with `-flto=auto -ffat-lto-objects` by default and Debian does not.

The test picks its probe candidates by matching identifiers in the installed headers against the library's hidden symbol names. Under LTO, GCC renames a local to `abortf_.lto_priv.0`, the names stop matching, and the candidate list empties, so the `probed >= 5` floor fires. The floor was right; what fed it was broken. Stripping the clone suffixes before the comparison takes an `-O3 -flto` build from 1 candidate to 58, and an ordinary build from 32 to 65 by recovering its `.isra` and `.constprop` clones.

That floor turns out to be a weak detector on its own: a strip handling only `.lto_priv` still passed while losing 30 of 54 candidates. The canary header now declares one clone-only name per suffix and requires each to be reported, so dropping any single suffix fails the test.

Nothing in CI builds with LTO, which is how this reached the archive. The deb job runs inside debian:sid and gets Debian's flags; every other leg uses configure's defaults. The matrix gains an `-O3 -flto` leg.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:30:21 +00:00
Xavier Roche
3a0cf262d6 The wizard verdict switch is unreachable by any test (#1245)
The verdict half of a wizard answer moves into `hts_wizard_apply_verdict()`, leaving `hts_acceptlink_()` one call where a switch used to be. #1239 made the filter half a testable function and left this one unreachable: `hts_acceptlink_()` is static, and only the interactive `query3` callback gets there.

The self-test drives the new function against a real `httrackp`, so it asserts what an answer does to the crawl rather than what it means. An accepting answer must not clear a refusal the crawl already computed, answer 4 caps the recursion instead of deciding the link, `*` switches the wizard to automatic, and an answer in no known range only warns. Eight mutants each turn tests/293 red, including the three shapes the call site used to hide.

Behavior is unchanged for every int answer, both scope-range boundaries included. The dead commented-out blocks under answers 3 and 4 went with the move. The widened filter reservation is still untested: reaching it needs a crawl driving `query3`.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 10:22:44 +02:00
Xavier Roche
47a41fb39e webhttrack's browser dependency drags httrack toward autoremoval (#1235)
The autoremoval gatherer reads only the first alternative of a disjunction and never looks at Recommends, so webhttrack's `chromium | firefox-esr | www-browser` in Depends put the whole httrack source on chromium's RC bug clock. [#1128867](https://bugs.debian.org/1128867) has 3.49.14-1 marked for removal from testing on 9 September. Nothing here wants chromium in particular: `src/webhttrack.in` searches `x-www-browser`, then `www-browser`, then a dozen binaries by name.

#436 flipped this list the other way round in June, back when firefox-esr's RC bugs were doing the same to us, and six weeks later chromium got one. Whichever real browser leads the list is big and bug-prone, so the disjunction moves to Recommends, where the gatherer cannot follow it. Apt installs Recommends by default, so a normal install is unchanged. The `webhttrack` wrapper does still exit when it finds no browser (`src/webhttrack.in:97`), but the package also ships `htsserver`, which serves the same UI over HTTP to a browser on another machine, so the dependency is strong rather than absolute.

This only beats the removal if it is uploaded and migrates before 9 September. Testing is still on 3.49.14-1, with i386 and riscv64 at Needs-Build.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 07:10:54 +00:00
Xavier Roche
fabbf80ea5 The wizard can only answer about one exact host (#1239)
The interactive wizard could only answer about the exact host it asked
about, so a mirror of www.example.com stopped again at
download.example.com, and Skip All stayed unusable while any unknown
subdomain might still turn up. Reported against the GUI as
xroche/httrack-windows#96.

The engine now enumerates the domain scopes for a host and takes two new
answer ranges over them. hts_wizard_host_scope() does the splitting so a
front end never has to: it derives the scopes from the same question
string it was handed, which may carry a protocol, credentials and a port.
Answers HTS_WIZARD_SCOPE_INCLUDE+k and HTS_WIZARD_SCOPE_EXCLUDE+k then
take or drop the k-th scope. An index is only safe instead of a finished
pattern because of that helper: the menu label and the applied filter come
out of the same code and cannot drift.

Each answer emits two filters, since +*.example.co.uk/* misses the apex,
which also meant widening the filter-array reservation before the answer
switch. It made room for exactly one insert while HT_INSERT_FILTERS0
asserts on overflow, so the second insert would have aborted on the
boundary at filptr == maxfilter - 2. hts_wizard_scope_answer() carries the
range decision for both the filter and the verdict halves, so the two
cannot disagree.

No public suffix list is involved: every suffix down to a two-label domain
is offered and the user picks the boundary. A bare TLD is never offered,
an IP literal has no scopes, and a fully-qualified www.foo.com. stops at
foo.com. instead of offering the root label as com.

Only the CLI and WinHTTrack need the new menu entries. WebHTTrack answers
"" to every question and Android registers no query3 at all, which is
#1237.

Closes #1117

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 08:51:10 +02:00
Xavier Roche
af0e36e326 A -%F footer that exactly fills the page buffer aborts the crawl (#1241)
* A footer that exactly fills the page buffer aborts the crawl

#670 dropped an oversized footer instead of emitting an unterminated buffer,
but left the success path alone: it appends the closing newline with
strcatbuff, which aborts rather than clips, so the one expansion length that
fills tempo exactly has nowhere to put that byte. Reserve the newline before
formatting, so an expansion that no longer fits takes the existing drop path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Format the touched call to clang-format

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Size the sweep's path instead of inheriting the temp one

The footer template caps at 253 chars, so the number of {path} references and
the literal padding both hang off the path's length. On the Windows runner the
temp directory is 80 chars, which pushed the template past the cap and failed
the test on its own guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Sweep both line endings and assert the footer arrives whole

The fixture was LF-only, so strlen(eol) was always 1 and a fix reserving one
byte rather than the eol's length passed while CRLF pages still aborted. The
emitted length was unchecked too, so a clipping fix read as success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fail on a temp path too long, and name the length that died

A skip would red the Windows leg anyway, since it compares the skip set
exactly, while silently dropping the coverage on every other platform. The
wider path leaves more room before that bites. The abort message named the
template, identical at every step, rather than the expansion that aborted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 01:14:40 +00:00
Xavier Roche
2dc30ab490 The default footer still uses the legacy %s form (#1240)
* Default footer uses named fields instead of %s

The named-field footer (-%F "{url}") has been the documented model since #667,
but the default template still carried the positional %s form, so any field a
user added to it was silently ignored: a "%s" anywhere selects legacy mode for
the whole string. WebHTTrack carried its own copy of that literal; it now takes
the engine macro.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Point the WebHTTrack footer test at the macro's own comment

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:55:41 +00:00
Xavier Roche
d681929d1a An abandoned WebHTTrack server never stops, holding the macOS app open (#1236)
* An abandoned WebHTTrack server never stops on its own

The watchdog only started consulting the heartbeat after the launcher died,
and on macOS the launcher waits on "open -W", which returns when the whole
browser quits. Closing the window therefore left htsserver running with the
app bundle open, so the disk image could not be ejected without a force kill.

Bind the session to the user's attention when idle and to the work when busy:
the page now says goodbye on pagehide, a running mirror vetoes every exit so a
crawl is never lost, and the launcher waits on the server rather than on the
browser. /ping was also the one reply served without cache headers, and
smallserver() returned 0 unconditionally, so every clean quit was reported as
"Unable to create the server".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Keep the idle fallback at 120s and settle the test before judging survivors

A backgrounded tab has its timers throttled, so the fallback stays where it
was; promptness is the goodbye's job. The two servers that must stay alive
started after the one whose death ends the wait, so the test judged them
before either had outlived a timeout of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Report the killed server before the pings that failed because of it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Count windows instead of racing a browser's throttled timers

A single pending-goodbye deadline cannot tell "the last window left" from "one
of several left", so closing one of two windows ended a session the other still
had open: a hidden tab's timers are throttled to as little as one wake-up a
minute, and its cancelling ping arrived after the countdown had fired. Each page
now carries an id, pings under it and drops it on pagehide, and the server exits
when the last one is gone.

Liveness is counted in watchdog ticks rather than wall clock, so a suspended
laptop no longer ages a session out from under the user. A bare connection no
longer cancels a departure, since any local peer can open one. And a failed
thread spawn left commandRunning latched, which the new mirror veto would have
turned into a server that never exits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Take a farewell only from a request holding the session id

/ping is a GET, so it clears neither the session-id gate nor the Origin check,
both of which are POST-only. That was harmless while a heartbeat could only
extend a session; ending one on an unauthenticated request handed every local
process, and every page the user visits, a way to close someone's WebHTTrack.
A full window table now refuses newcomers instead of evicting, so a flood of
ids cannot push the real window out either.

Also from review: query_alnum_value left a half-read value in the caller's
buffer when it returned false, the new predicates and flags were plain ints in
a tree that spells booleans hts_boolean, and --ping-timeout re-parsed with the
bare sscanf that #614 cost us on --port.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Prove a flood of window ids cannot evict the real one

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Name the launcher's pid with a type MSVC has

pid_t is POSIX, and the old code only ever named it inside an #ifndef _WIN32
block. Hoisting the parent check into a function signature put it where Windows
compiles it, and webhttrack.vcxproj stopped at seven errors on one line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 14:09:21 +00:00
Xavier Roche
1483289eec debian: record the 3.49.21-2 upload (#1234)
The revision carried the hppa FTBFS fix to the buildds as a quilt patch against
the frozen orig. The patch itself stays out of git, where the fix is already in
the source; only the changelog entry belongs here, so the file stays continuous
for the next upload.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 09:14:28 +00:00
Xavier Roche
1751c4b593 mkdeb.sh reaches debsign before it discovers the key is wrong (#1233)
The key is only resolved when debsign runs, which on a release is after the
tarball has been built. Ask gpg for it at startup instead, and name the
0x-prefixed fingerprint form in the error, since a short id is what people reach
for first and it is the ambiguous one.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 06:21:17 +00:00
Xavier Roche
bad28b629c A lost Windows runner leaves almost no telemetry behind (#1229)
The suite watchdog's commit statuses are the only evidence that survives a lost runner, and most of it was being thrown away: they went to the PR merge commit, whose SHA GitHub collects, leaving 3 of 146 recorded kills with any telemetry at all. They now go to the PR head, which costs us a line in the PR's checks list.

Each status carries more too. Loop lag and the number of posts that failed since one landed separate a box that stopped from a network that broke; `n=`/`f=`/`e=` come from one `GetTcpIPv4Statistics` call; `m=`/`c=`/`a=` are summed from the process array already enumerated for `p=` and `h=`. The cadence halves to 15s, and the in-flight test name is clipped to 30 characters to pay for the new fields. The widest real line measures 103 of the 140 characters GitHub keeps.

The rest closes two unsound spots in the Windows kill paths, neither of which explains the deaths. `stop_server` re-read `/proc/<pid>/winpid` after signalling its target, so the number it handed to `taskkill /F /T` could already belong to a stranger, and a hosted runner reissues a freed PID within a second rather than in theory. Nothing checked the image before firing either. Both the winpid and the image are now read while the target is alive and checked against `tasklist`, with a warning annotation when they disagree, which is also the meter for how often a tree kill would have left our own trees.

Refs #1228.
2026-08-13 08:08:03 +02:00
Xavier Roche
6584fded15 mkdeb.sh regenerates an orig that debian/patches cannot apply to (#1232)
A quilt patch is written against the tarball it will be applied to. Once the fix
it backports is upstream, HEAD carries it too, so an orig regenerated from HEAD
makes the patch fail or apply with fuzz. Require --orig whenever the series file
is non-empty, unsigned builds included: this one breaks the build, not policy.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 05:43:13 +00:00
Xavier Roche
d8f0cec17d Three suite tests fail on an emulated buildd for being slow, not broken (#1231)
* Three suite tests fail on an emulated buildd for being slow, not broken

hppa reported 3.49.21-1 as Build-Attempted with 105, 151 and 269 red, all three
on wall clock under qemu-user: the guard's own diagnostics dump took 31s of a
30s bound, one configure run passed a hard 300s cap, and the pairwise header
sweep outran the 600s harness budget mid-batch.

Each now measures the property instead of the host. 105 times the guard to its
DUMP announcement, leaving the dump (minutes, emulated) out of the bound. 151
watches configure's output for silence rather than capping its total, and skips
when the budget runs out while it is still making progress. The sweep runs in
slices so it can be given up on, paces itself against what 269 hands it, and 269
declares the larger budget its n^2 compiles need, so the emulated leg still runs
it to completion instead of pacing out.

The emulated leg is the CI counterpart of that buildd and was green on the same
commit: it prints test-suite.log now, so a test that skipped rather than ran
stops reading as coverage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fix the review's two skip-masking holes and the unbounded budget raise

A hung configure could reach the budget skip before the silence window closed, so
a wedge reported SKIP; the skip now needs a run that is still writing and enough
budget left for the detector to speak. 151 also paced inside run(), before
accept/reject read the verdict, so a configure that answered wrongly could exit 77
instead of 1: pacing moved to the callers, which judge first.

The per-test raise is bounded and normalized through one budget parser, since bash
test errors rather than compares past intmax and would have left the guard unarmed,
and a leading zero read as octal in arithmetic and decimal in test. Renamed
TEST_TIMEOUT_AT_LEAST so each use site carries the upwards-only rule, documented in
AGENTS.md, and the sweep takes an explicit --budget rather than sniffing the
environment, so the MSVC job cannot report a paced skip as a header break.

Tests for each: 151 drives run() through a configure_cmd seam with a child that
hangs and one that only crawls, 105 requires the DUMP announcement to be seen while
the guard runs (its fallback made the latency bound vacuous) and pins the hostile
budget values, and the sweep counts the units that reached the compiler.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Two of the new assertions passed a mutant; make them bite

Mutation-testing the added tests found two that could not see the bug they were
written for. The verdict check stubbed run() out, so a pacer left inside the real
one stayed invisible: it now drives the real run() through a child that answers
wrongly with the budget spent. The announcement check only asked that the marker
appear while the guard ran, which a driver announcing after the dump still
satisfies: a slow ps widens the dump, and the marker must now precede it.

Writing the first exposed a third: reject takes one argument fewer than accept, so
the extra one reached run() as an env assignment and the child failed to exec,
which made the probe pass on the wrong answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* The verdict probe left the pacer disarmed

It numbered its run past the case count, so the pacer it exists to catch declined
to fire on a negative "steps left", and the mutant that puts pacing back inside
run() survived. Keep cases ahead of the run number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* A killed configure left its child running and hung the macOS job

The macOS leg reported no test failure and then sat until its 20-minute step
timeout: bash 3.2 does not replace the subshell around the child with the child
itself, so killing that subshell left the run alive, and it outlived make check.
Give each run its own process group and kill the group.

The probe that exposed it now covers it: a child that spawns a child of its own,
and nothing of it left running afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 01:01:27 +02:00
Xavier Roche
de484e0670 Release 3.49.21 (#1226)
* Release 3.49.21

Version bumps in configure.ac, htsglobal.h, version.rc and the AppStream
metainfo, plus the curated release notes.

VERSION_INFO goes to 3:14:0, a revision-only bump: httrackp gained a tail
field (host_alias) and #1202 added an export, so nothing moved and nothing
went away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Apply the release review: notes and changelog wording

The changelog entry named upstream flags, which the packaging audience does
not want; the notes instantiated example hostnames and quoted a UI label
where their neighbours stay abstract. The copyright bullet cites #1163 as
well, and the new export is named rather than implied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 16:51:35 +02:00
Xavier Roche
b6d15c7bc8 Pin the host-alias bytes the WinHTTrack validator now depends on (#1224)
hts_host_alias_rule_ok accepts 0x80-0xFF and a leading '-', both by
design, and nothing pinned either. httrack-windows#109 dropped its
mirrored grammar for this exported validator, so a tightened reject set
would break it silently.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 14:31:07 +00:00
Xavier Roche
db22036173 The Windows watchdog test calls a reaped ping a survivor (#1225)
* The Windows watchdog test calls a reaped ping a survivor

58_watchdog has failed the Win32 leg on every master push since the suite
went parallel. The grandchild check reads tasklist one second after the
watchdog fires and asks whether the ping's PID is still listed; neither half
survives sixteen concurrent tests. Match the image name as well as the PID,
and poll for it to go rather than sleeping once.

On a non-timeout failure the driver re-runs the test traced and appends to
the same log, then reports the last 25 lines: for an intermittent failure
that is the run that passed. Keep the failing run's tail before the trace can
append, and report both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Control alive(), and pin the report's order and bound

The image-name conjunct is new, so a predicate that never matches would
call every survivor reaped: prove it fires on a live ping first, and fold
case the way proclib.sh's tasklist matchers already do.

172 now pins the two properties the marker alone left open: the failing
tail must come before the label, and both halves must stay tails.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 16:11:55 +02:00
Xavier Roche
50f151e11b freeport hands out a port it has already released (#1221)
`freeport` binds a socket to port 0, records the number and closes it, so the port is free again before its caller does anything with it. Under `make check -j16` two tests can be handed the same one. `start_proxytrack` already retried; the other two callers did not.

The one that matters is `56_local-proxy-noleak`, which wants a port that stays dead for the whole crawl so that connecting to it is refused. A retry cannot help when the exposure lasts the length of the test: if a sibling binds that number, the decoy proxy answers and the test stops testing its own premise while still reporting green. Such a port is now held rather than sampled, bound and never listened on, so connections are still refused. `htsserver_start` retries in the shape `start_proxytrack` already uses, and `freeport`'s comment now says which of the two a caller owes it.

Test 288 asserts the discriminator rather than the symptom, since an unbound port refuses a connection too: a competing bind must fail while the port is held, and succeed once it is released. Extracting the helper also fixed a leak in 276, whose private copy registered its cleanup inside a command substitution and left a python process sleeping 900s behind every run.

Closes #1218
2026-08-12 12:42:29 +00:00
Xavier Roche
729f823db9 A build path containing "assert" or "panic" fails the crawl tests (#1222)
* Read the crawl's own log, not the command line printed above it

hts-log.txt line 2 echoes the engine's whole command line, absolute paths
included, so --log-found and --log-not-found were matching the paths the run
was given. 36_local-bigcrawl searches for "assert", which made it fail for
anyone building under a directory whose name contains that word; TMPDIR, -O and
the source directory reach that line too.

Only that line is skipped. The banner above it names the URL, which
14_local-https does match, and it is left in place if it ever stops looking
like the echo: a "not found" audit hiding a real match is the worse failure.

Closes #1220

Signed-off-by: Xavier Roche <roche@httrack.com>

* Review: pin the banner too, and cut the comments

The pair of cases constrained only "line 2 gone, line 4 present", so the
first-cut mistake this fixes, dropping the banner as well, passed both.
14_local-https catches it but skips where TLS is not built.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:42:07 +00:00
Xavier Roche
01274a93f5 The WebHTTrack pages hardcode a copyright year the doc pages now compute (#1217)
* Expand the WebHTTrack footer year instead of hardcoding it (#1165)

The 23 pages under html/server/ carried 1998-2026 as literal markup, which
doc-chrome.py cannot reach, so from next New Year the docs and the GUI would
have disagreed with nothing to catch it.

htsserver already resolves a ${prefix:name} family in the templates it serves,
so the year becomes one more of those. Computed per request, it cannot drift,
and no regeneration or check has to watch it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Classify the date: token in the template escaping audit (#1165)

185 requires every ${...} in the server templates to carry the escaping prefix
its position needs, and a bare key in element body is a finding. The year the
server computes is not runtime data and cannot carry markup, so it joins the
translations and build-time constants instead.

Paired both ways: a settings value in element body must still be a finding, or
the exemption is a hole rather than a classification.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fall back to the clock on an unusable SOURCE_DATE_EPOCH, and harden the test

A garbage or over-range override reached time zero, so the footer rendered
1998-1970 rather than ignoring the value. The parse now has to consume the whole
string, and a value gmtime rejects falls back to the real clock.

The test grew what the review found missing: the tree-wide sweep that a single
converted page cannot satisfy, a pinned epoch an hour before New Year UTC read
from 14 hours east of it, so gmtime and localtime give different years, and the
two unusable overrides. Renumbered off 286, which master took meanwhile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Refuse a SOURCE_DATE_EPOCH whose year no footer can show

glibc's gmtime accepts the whole int range of tm_year, so a parsable
SOURCE_DATE_EPOCH could reach the footer as "-2" or overflow the
tm_year + 1900 add. Both break the digits-only promise test 185's
date: exemption rests on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:33:33 +00:00
Xavier Roche
bd765e4ed6 Give the compile-time rules one spelling, HTS_STATIC_ASSERT (#1219)
Three constants were pinned three ways: an enum dividing by zero in htsparse.c,
and two negative-array typedefs in htsarena.h. htsglobal.h already had
HTS_COMPILE_ASSERT for the expression case, so this adds the declaration case
beside it, on the enum form, and moves all three onto it.

Each of the three was checked by breaking what it guards and confirming the
build stops: a non-power-of-two alignment kills both arena rules, and shrinking
SINGLEFILE_MAX_SPAN kills the parse one.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:24:34 +00:00
Xavier Roche
17e3d241fa Windows CI runs from different branches starve each other's runners (#1214)
* Windows CI runs from different branches starve each other's runners

windows-build keys its concurrency group on github.ref, so it only cancels
superseded runs of the same branch. Runs on different branches overlap freely,
and at two platforms each, up to six Windows jobs can share the pool. #732 and
#733 were two Win32 jobs starting 2 seconds apart that both wedged the 45-minute
step timeout and both passed on a rerun of the identical commits; #1126 wedged
the same way; PR #1203's x64 leg lost communication with the server while three
runs were in flight.

A job-level group keyed on matrix.platform caps that at one x64 and one Win32
job across the whole repository. It needs queue: max, because the default queue
holds a single pending entry and cancels it when the next job arrives, which
would trade starvation for cancelled checks. The workflow-level per-ref cancel
stays, so a second push to a branch still drops the older run and frees its
queue slot.

A Windows job takes about 34 minutes, so a third concurrent PR now waits instead
of racing. That wait shrinks once the suite step stops running its tests
serially.

Closes #1213

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Tighten the comment to two lines

Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 10:23:56 +00:00
Xavier Roche
ee20bfbb2b The Windows suite runs its tests one at a time (#1216)
The Windows suite step takes 23.5 minutes because `ci-windows-suite.sh` runs its 238 tests one at a time, while every `make check` on other platforms uses `-j min(2*nproc,16)`. Measured against the Linux suite test for test, Windows is only 1.61x slower at the same work: the gap is the missing `-j`, not the platform. Scheduling the real per-test times gives 3.3 min at `-j8`.

The pool is the small part. Three assumptions in the shared harness had to go first, because each is only safe while one test runs at a time. `kill_tree` fell back to killing every engine image and every `python.exe` on the host when it could not resolve a Windows PID, which under `-jN` kills the other tests' servers; it now takes a PID captured at fork time, and the host-wide sweep needs an explicit opt-in the driver sets only when it runs serially. `dump_crawl_logs` globbed and deleted every `httrack_local.*` in a shared TMPDIR, so each test now gets its own. The between-test reap taskkills by image name, so it runs once after the pool drains, trading per-test leak attribution for parallelism; a wedge is still bounded by the per-test timeout and its tree kill.

`wait -n` is bash 4.3, and macOS drives this script under 3.2, so the pool falls back to the serial path there. Test 286 runs a stub suite both ways and fails if the parallel leg does not beat the serial one; forcing the driver to one worker makes it red.

Closes #1215
2026-08-12 11:46:51 +02:00
Xavier Roche
bde9eadb05 An early exit from hts_main_internal leaks the rebuilt command line (#1210)
* Release the rebuilt command line on every early exit

hts_main_internal allocated it once and freed it only at the end, so each of
the 88 early returns leaked the token chunks and the slot array. It never
mattered much for the binary, which is exiting anyway, but hts_main2 is an
exported entry point and an embedder pays it per call.

htsmain_free() already runs on every one of those paths, and each caller
returns straight after, so the release belongs there.

Closes #1206

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review: tighten the comments

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Name the new leak test in the Windows suite's expected skips

It needs a LeakSanitizer build, so it skips there like 114 does, and that job
fails any change to the skip set rather than let coverage drift silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 09:43:19 +00:00
Xavier Roche
88f0a0174d The Windows build rebuilds OpenSSL from source on every run (#1209)
The vcpkg binary cache has not served a package since 2026-08-04: both legs log `Restored 0 package(s)` and rebuild openssl from source, 7.7 min on x64 and 5.1 min on Win32. The key named only the manifest, so nothing busted it when a runner image orphaned every archive, and actions/cache skips the save on an exact hit. That is why it stayed broken for eight days rather than one run.

Naming every input vcpkg's ABI hash folds in would only make a list that can still come up short, so the restore keys on the run id and prefix-matches instead, the save runs when the archive set changed, and a cache vcpkg matched nothing in is dropped rather than carried forward. Two runs are needed to show it: the first supersedes the dead entry, the second should restore warm and bring the build step to roughly 2 min.

Closes #1208
2026-08-12 09:00:53 +00:00
Xavier Roche
696553b0f9 Three open-coded chunked pools with the same stable-pointer contract (#1212)
* Give the three chunked pools one implementation

htscore.c grew two of them, for link strings and for lien_url records, and
#1205 added a third for the rebuilt command line. All three exist for the same
reason, stated in one of their own comments: "There are references to item
pointers, so we can not just realloc()". All three picked 32768. The growable
primitives the tree already has are no substitute, since String and TypedArray
move their contents and coucal's pool rebases its keys on purpose.

htsarena.h is that pool, once: chunks that are never resized, doubling to a
ceiling, with the alignment and the overflow guards in one place instead of
three. The 32 KB cap on a single link string goes with it, and so does the
assert that used to abort on a longer one.

Closes #1207

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Charge alignment padding where it is needed, not to every allocation

Rounding each size up cost several bytes on every link string a mirror
records, where the pool it replaces packed them. Only the lien_url records
need the alignment, so pad at the allocation that wants it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Pin the arena's alignment assumptions at compile time

Review: the padding takes HTS_ARENA_ALIGN for a power of two and the header
for a multiple of it, and nothing said so. A platform where either stops
holding would have miscompiled quietly; now it does not compile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 08:57:30 +00:00
Xavier Roche
99d494bd39 The resume offer echoes the program path instead of the command line it would run (#1211)
* Quote the command line in the resume offer, instead of the program path

The offer to resume or update a mirror ends with the command line it would
run, but printed argv[0], so it read "OK to Continue httrack /usr/bin/httrack".
It has never worked: the buffer it reads holds the rebuilt command line as
NUL-separated tokens, so the append stopped at the first one.

Printing the rest is not a plain loop over them. HT_PRINT appends through
strcatbuff into a fixed 2 KB buffer and aborts on overflow, which a command
line replayed from doit.log outgrows, so the tokens are clipped to what is
left and closed with an ellipsis.

Closes #1204

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review: tighten the comments

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Bound the clip mark too, so the append needs nothing of its caller

Review: the ' ...' went in through HT_PRINT, which aborts on overflow, so it
leaned on the preamble the caller had written leaving room. Not reachable
today, since every argv element is bounded, but the function should not
depend on that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 08:37:37 +00:00
Xavier Roche
53eace3cfe A cleared box in the wizard cannot disable the index, and --purge-old=1 kills the run (#1201)
* A long option drops the value its short form takes

--index=0 dropped its value and read back as the bare --index, which enables
the index: the alias table calls -I a "single" option, and that class throws an
attached value away. So clearing "create a top index" in the WebHTTrack wizard
could not turn it off, and neither could --noindex.

Two new classes carry the value the short form already accepts: "onoff" for the
flags whose parser reads a 0 suffix (-I0), "level" for those that parse a number
(-%v2). Everything still "single" now refuses an attached value rather than
dropping it. The bare form of every alias is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Assert the emitted short form against the engine, not the table

Review found the value-taking rows had no test tying them to the parser: the
table loop only checks bare forms, which are identical for all three classes,
so a row wrongly marked onoff would emit -x0 and kill the run unseen. 282 now
sweeps every onoff and level row the engine lists and fails on "invalid
option", and asserts the refusal message and the exact flag set each wizard
box emits rather than a substring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Three param rows are booleans in disguise, so --purge-old=1 kills the run

purge-old (-X), httpproxy-ftp (-%f) and extended-parsing (-%P) are classified
param, but their parser cases have the onoff shape: set the flag, then consume
a 0 suffix. The class demands a following token, so --purge-old=1 emitted -X1
and died on "invalid option 1", and --purge-old before a URL ate the URL as its
value and mirrored nothing. A profile carrying NoPurgeOldFiles=0 makes the
wizard write exactly that command line.

They were misclassified rather than newly broken, so they move to onoff with
the rest. No fourth row has this shape: every other param row reads a real
value, and every param1 takes a separate string. help() now annotates an
optional value, which regenerates the man page and its rendered HTML.

Closes #1200

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* The users guide still promises a numeric value for the three flags

fcguide.html carries a pasted copy of the option list, where -X, -%f and -%P
read [=N]. Only 0 was ever meaningful, and help() now says [=0].

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* The class sweep reads the table it is checking

Review found it circular: a row that should take a value but is still single
never appears in the list, which is the #1200 bug exactly. step4.html is an
independent source, so every --name=0 it writes must now resolve to a row whose
class takes one. The sweep also tries =1, the value that emitted -X1 and killed
the run, and a refusal must carry a message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Bound the digit run before indexing it

CodeQL cpp/offset-use-before-range-check: value[i] was read before i < 16
gated it. In bounds either way, but the wrong order for a parser fed user
input. Same boundary: 16 digits accepted, 17 refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 10:21:54 +02:00
Xavier Roche
1dcfcf491f The Android guide screenshots crawl the emulator's host-loopback alias (#1203)
10.0.2.2 is the Android emulator's fixed alias for the host machine's own
127.0.0.1, so the URL field and the progress line showed a reader an address
they cannot use. A grep for the literal loopback address reports the set clean,
which is why #1133's purge left it behind on this platform alone.

Regenerated from httrack-works 3988c35, where the walk now maps www.example.com
onto that alias inside the emulator. Two images move; the other 17 captures
reproduce byte for byte.

Closes #1199

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 07:45:41 +00:00
Xavier Roche
14e9108049 A --host-alias rule may carry bytes no host can, and no front end can call the validator (#1202)
* Reject a newline inside a --host-alias rule, and export the validator

hts_host_alias_rule_ok() did not reject '\n', which the engine uses to join
the rules it accepted, so --host-alias $'a.com=b.com\nc.com' validated as one
rule and hts_host_alias_match() then read it as two, dropping the '='-less
fragment without a word.

The same function moves to httrack-library.h behind HTSEXT_API, so a front end
can reject a malformed rule in its own field instead of mirroring the grammar
and drifting from it.

Closes #1192
Closes #1172

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Reject any control byte and an unparsable canonical scheme in a rule

Exporting the validator makes its reject set a supported contract, so close
the two holes review found beside the '\n' one. A control byte anywhere in a
rule is refused: '\v' and '\f' are not on the argv filter's CR/LF/TAB list and
reached the host the fold writes. The canonical side is cut with
jump_protocol_const rather than a literal "://", the way every consumer cuts
it, so "b.com=x://a.com" no longer folds http://b.com to a host of x://a.com.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 07:15:01 +00:00
Xavier Roche
5c068a3c65 A large .httrackrc aborts the engine on the rebuilt command line's token block (#1205)
* Grow the rebuilt command line's token block instead of aborting on it

The block was sized once, from the current argv plus fsize("config") plus
32 KB of slack, and never grew. The engine reads .httrackrc, httrackrc or
/etc/httrack.conf, never a file called "config", so nothing in that sum
accounted for what the expansion would add; past the slack cmdl_ins ran out
of room and strlcpybuff aborted the process on a config file the user wrote
themselves.

The block becomes a chain of chunks, so it grows the way the slot array has
since #1196. A chunk is never resized, which is what makes the growth safe:
every argv[] slot points into it and stays valid, as does argv_firsturl in
htscoremain.c, where a realloc would have had to rebase them all.

Closes #1198

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Name the chunk fields for what they are, and catch a clipped token

Review follow-ups: blk_size/blk_used named a blk field the chunk list replaced,
CMDL_CHUNK_DATA was a macro where its peer cmdl_room is a function, and the
test's two markers would both survive an implementation that clipped a token to
the room left instead of growing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 07:11:45 +00:00
Xavier Roche
e9336b4d87 Nothing checks that guide.html's hardcoded image sizes match the images (#1193)
* Check guide.html's hardcoded image sizes against the files it ships

Every <img> in the guide carries a width/height pair, re-verified by hand
after each recapture. A screenshot regenerated at another geometry ships
stretched and nothing goes red, which is the failure mode a regeneration is
actually exposed to.

tests/guide-image-check.py parses the tags and compares each declared pair
against PIL's, and catches the two neighbours: an <img> whose file is gone,
and a guide-*.png under html/img/ the guide stopped referencing. 279 drives
it against the tree, then mutates a copy to prove each arm fires.

Closes #1187

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Close the review holes in the guide image check

The distcheck srcdir is read-only and cp -a carried that over, so the first
mutant hit EACCES and leaked its temp tree. Comment stripping, a
case-insensitive tag match and a guard on any src the parse cannot read stop
a defect from being skipped in silence, and a repeated attribute now resolves
the way a browser lays it out.

None of the old mutants touched a PNG, so a checker that never opened a file
passed every one of them. 279 now resizes a shipped image, which is the
defect #1187 is actually about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Read the PNG geometry from the IHDR, so 279 runs on CI

No workflow installs Pillow and no runner image carries it, so the PIL guard
skipped 279 on every leg: a green suite that never ran the check. png-colors.py
already states the rule this broke, that the suite cannot assume Pillow on a
build host.

Twenty-four bytes of header carry what the check needs, so it reads them
itself, verifying the signature and the IHDR type rather than slicing on
faith. A missing checker is now a hard failure: it is in EXTRA_DIST, so its
absence is a packaging bug rather than an environment to tolerate. Three new
arms cover the reader's own refusals, and the image-side arm swaps in another
shipped PNG instead of resizing one through Pillow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 02:35:33 +00:00
Xavier Roche
11c357e73a The installed headers are never compiled standalone or pairwise on MSVC (#1190)
* Compile the installed headers standalone and pairwise on MSVC

205, 206, 207 and 269 all reach the installed header set through automake's
install rule and gate on abs_top_builddir, so all four exit 77 on the Windows
job, which runs the test scripts directly. Nothing else compiles
DevIncludes_DATA the way a consumer does: the DLL build only proves the
engine's own fixed include order, with LIBHTTRACK_EXPORTS set.

Move 269's sweep into tests/install-headers-sweep.sh and drive it from both
legs. 269 hands it what automake installed; a new step in the existing
libhttrack job stages the same list out of the source tree and compiles it with
cl.exe, batching every unit into one response file per language because a spawn
on that runner costs more than the compile. 278 audits the wiring, since no
sweep can notice its own step being dropped.

Closes #1153

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Put the bytecode mode back on the command line, widen the wiring audit

Review found the -U half of the sweep could go vacuous: the mode was
emitted into each unit as a #define, so a -DHTS_INTERNAL_BYTECODE
arriving through TEST_CPPFLAGS made both halves the same compile while
the unit count still read right. Both modes are command-line flags
again, with a synthetic header that must fail in one and compile in the
other.

Also: assert five named headers reach the staged set, so a Makefile.am
reformat the awk mis-parses cannot shrink the sweep quietly; resolve a
../ entry against the build tree alone; and teach 278's audit to reject
a conditional or continue-on-error step or job, a --self-test that never
sweeps, a sweep placed before the build, and an invocation left as a
comment, each with its own control.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Find the vcpkg include root instead of spelling it

The MSBuild integration installs under src\vcpkg_installed\<triplet>\<triplet>,
nesting the triplet twice, so the step threw before it reached cl.exe. Search
for it, and name openssl/ssl.h as the thing that has to be found: htsbasenet.h
includes it by default, and it is the only reason the path is needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 00:28:34 +00:00
Xavier Roche
94d2d79217 A wizard project reload cannot read back a zero or an empty value (#1184)
* Keep a value of zero when a wizard project is reloaded

The winprofile.ini reader turned "0" into an empty value for every key, so a
rate, depth or timeout the user set to zero came back as the wizard's own
default. Only the checkbox keys, which step4.html writes as ${ztest:x:0:1},
need that inverse; everything else now reads its zero back.

Adds tests/274, the first test to reach the reader at all: it drives a real
project reload over HTTP and covers the escape set as well as the coercion.

Closes #1177
Closes #1171

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Let a present winprofile.ini key overwrite the wizard default

An empty value acquired a NULL String, which coucal_readptr refuses, so
${do:copy:} skipped the key and the startup default survived. Also guards the
truncated percent escape both unescape helpers walked past.

Refs #1186

Signed-off-by: Xavier Roche <roche@httrack.com>

* Guard the truncated percent escape in the two remaining copies

htstools.c reads the category out of the same winprofile.ini and proxystrings.h
carries a third copy of the loop, both walking past the terminator on a value
that ends in a bare %.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Default the cache box at startup, not on every step2 render

${do:set:cache:1} ran on each render, so a reloaded profile that cleared the
box had it ticked again by the next visit to step2. Only a fresh session may
take the default, which is what initOn[] is for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Send the cleared cache and URL-hack boxes to the engine

Both read their state with test:, which emits nothing for an empty value, so
an unchecked box left the engine on its default while the box itself rendered
unticked. ztest: is the form the neighbouring cookies and parse-java lines
already use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Assert a fresh wizard session still defaults to caching

Nothing covered the startup default itself, so dropping it from initOn[] left
the suite green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:52:21 +00:00
Xavier Roche
52ac9ee142 A doit.log or a long command line overflows the rebuilt argv (#1196)
* Grow the rebuilt command line instead of guessing 1024 spare slots

htscoremain.c allocated argc + 1024 argv slots for the command line it
rebuilds and never re-checked that headroom, so both writers could run
off the end: cmdl_add on a command line whose --opt=value arguments each
expand to two tokens, and cmdl_ins replaying hts-cache/doit.log, which is
bounded only by the 8000-byte line the reader accepts.

The token count is not knowable up front (config files and doit.log each
add an input-defined number), so the slot array now grows on demand and
argc <= size is re-established before every write. cmdl_add, cmdl_ins and
optinclude_file take the array, its capacity and the token block as one
struct, which is what lets a growth reach every writer.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Cover the config-file writer and refresh the stale macro references

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Address review: cmdl_init/cmdl_free, and a distinct OOM result for config files

The struct was half-initialized between its brace initializer and the two
fields the caller filled in by hand, and three new out-of-memory paths
returned without freeing it. cmdl_init and cmdl_free own both allocations,
so neither state is reachable.

optinclude_file returned "the file was read" on an allocation failure too,
which let the caller proceed with a half-expanded config. It now reports
that case apart, and the caller panics as its sibling writers already do.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <xroche@gmail.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:44:23 +00:00
Xavier Roche
d432772e1e A rule that begins with a dash kills the whole run (#1183)
* Take a rule that begins with a dash (#1179)

--strip-query and --host-alias take a user-written rule, and a rule may
legitimately start with '-'. The engine read it as the next option name,
so the whole run aborted before the mirror and the valid rules beside it
were lost.

Closes #1179

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review fixes: narrow the exemption, and prove the rule's bytes

Restore the diagnostic when a rule option is followed by a real option name,
and pin byte fidelity plus the short and '=' spellings in the test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review fixes: trim the comments added by this change

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Return the computed boolean rather than a ternary over it

House convention: a comparison or && chain is already an hts_boolean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Run the no-parameter probes through an absolute httrack path

noparam cds before invoking it, and make check puts the build's src/ on PATH
relatively, so a bare name found nothing there. An installed /usr/bin/httrack
hid that locally; CI has none and every build leg failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:39:05 +00:00
Xavier Roche
997160c98f The guide's WebHTTrack and Android screenshots predate the fields they describe (#1197)
The WebHTTrack set was shot before #1166 and #1176 landed. Its Spider tab showed neither the Host aliases box nor the four-row Strip query keys that replaced a single-line field, though the surrounding prose already described both. That page is 229 px taller now, and `guide.html`'s hardcoded height moves with it. The Android set predated #97, #114 and #116 the same way.

Captures are httrack-works `1af9fed`, the Android half shot by that session at httrack-android `f3123ad`. Regenerated with `tools/doc-images.py png`.

Seven web captures moved and I checked each. The Spider tab is the real change. Progress and finished carry live crawl counters. The project-name shot picks up a fresh random temp suffix in its base path. Three option tabs differ only by antialiasing on their select widgets, visually identical at a mean channel delta of 39. All 19 Android shots moved because that session recaptured its whole set rather than the four stale tabs, which keeps the version line and clock consistent.

The Windows images belong to #1180, now merged, so the two never overlapped. Every image is re-checked against `guide.html`: 55 of them, one dimension changed, no other drift.

Closes #1194
2026-08-11 22:20:33 +00:00
Xavier Roche
c3a5deef57 freeport reserves the ICP port over TCP, not the UDP it will be bound in (#1185)
* Reserve the ICP port in UDP, and let proxytrack name a bind failure

freeport() reserved both proxytrack ports by binding a plain TCP socket, but
proxytrack opens its ICP socket with SOCK_DGRAM. Windows keeps excluded port
ranges per protocol, so a TCP-derived number can land inside a UDP exclusion
and all three retries lose the same way. It now takes the protocol of each
port it hands out and probes in that one.

The failure was hard to read because proxytrack printed strerror(errno) for a
socket error Winsock reports through WSAGetLastError(), so the one line that
should have named the cause said "No error". It now reports the protocol, the
port and the system message for the call that actually failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Report a resolver failure as one, and pin the probe's protocol to its port

Review of the first commit found smallserver_init() reading errno on the
gethost() path, where getaddrinfo() never sets it: an unresolvable host printed
"cannot bind tcp port N ...: Success (0)". It now carries its own code and
proxytrack says "cannot resolve".

Test 276 grew with it. The socket-type spy logs the port beside the kind, so a
probe that opens a UDP socket and hands back a TCP-drawn number no longer
passes; both proxytrack destinations are exercised, and a probe forced to
collide proves the redraw loop reports exhaustion. Renamed smallserver_init's
"family" parameter to socktype, which is what it carries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:11:01 +00:00
Xavier Roche
b33ef757e5 Nothing runs black or flake8, so .flake8 binds nothing (#1182)
* Reflow tools/doc-images.py and tools/doc-chrome.py with black

Pure black output, no edits by hand. Settles the 14 E501 that .flake8
already forbade.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Gate black and flake8 on the tracked Python files in CI

.flake8 has bound nothing since it arrived: no job ran black, flake8 or
any other Python linter, which is how two tools/ scripts drifted. Fold
both into the existing lint job, over git ls-files rather than a glob
list so a .py in a new directory cannot escape.

Closes #1170

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Split the Python lint into per-tool steps

Review follow-up: shellcheck and shfmt each get their own step so a red
names the tool, and the list moves to a shared env var like SHELL_SCRIPTS.
Guard the empty list too, which flake8 would otherwise accept silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:01:27 +00:00
Xavier Roche
9bb989053d A target that is not a keyword shares one tab, and dark mode prints white on white (#1181)
* html/: target="_new" shares one tab, and dark mode prints white on white

`_new` is not a target keyword, so browsers treat it as a named browsing
context called "new" and every one of the ten hand-written links reuses the
same tab. `_blank` is the keyword they meant. #1157 fixed the generated
footer; these predate it.

doc.css's `@media print` forced a white body but left the palette alone, so
in a browser that keeps matching prefers-color-scheme: dark while printing,
`--ink` stayed #e6e6ee on paper. Reset the tokens instead, which also covers
the panel and anything added later that reads them.

Closes #1164
Closes #1158

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Also fix cache.html's two transposed target="new_"

Same defect one page over, found while reviewing: `new_` is no more a
keyword than `_new`. Test 277 now rejects any target value outside the four
keywords rather than grepping for one misspelling, so a transposition is
caught too, and it reads both quote styles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 20:56:00 +00:00
Xavier Roche
c27d55c345 The guide's WinHTTrack screenshots predate the address, rate and host-alias changes (#1180)
Four of the guide's WinHTTrack images predate changes that shipped in httrack-windows. The walkthrough now crawls www.example.com:8099 instead of the loopback address (#104), a new project no longer starts capped at 25 KB/s (#103), and the Spider tab gained a Host aliases row (#101). Captures are httrack-works 8f63cbc, regenerated with `tools/doc-images.py png`.

Only those four move. The other fourteen Windows captures are byte-identical, and no web or Android image changed, so nothing rode in from a sibling capture set. Geometry is unchanged on all four, so `guide.html`'s hardcoded width and height still match.

The prose needs no addition: #1175 already flipped the Host aliases row to `data-for="win web droid"` ahead of the field shipping. The new image does surface something I have left alone, though. WinHTTrack draws Host aliases as a single-line box, the same height as Sitemap address, while the guide says "one rule per line". The engine walks newline-separated rules, so that control holds exactly one, which is what #1168 fixed on the WebHTTrack side. Whether it should be multi-line is httrack-windows' call, and I could not confirm the limit from source here, so I have written no caveat.
2026-08-11 20:52:32 +00:00
Xavier Roche
40e94df2cc The wizard can only hold one --strip-query rule (#1176)
* Take several strip-query rules in the wizard, one flag each

The engine accumulates --strip-query into a newline-separated list, one rule
per flag, but the wizard bound the option to a single-line input and emitted a
single flag, so a project built there held one rule. The field is a textarea
now and the arglist: directive #1166 added emits one flag per rule.

Closes #1168

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review fixes: exact assertions, and a scoped rule that matches

The winprofile and reopen checks matched substrings, so a merged pair passed
both; they compare the whole value now. 26's scoped rule matched no host, which
left a mutant dropping every scoped rule green on all four legs, and the control
strips a key the fixture never carries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Test the last-match-wins rule the guide now documents, and the escaping

Both legs of the accumulating pair carried the same key, so a union or a
first-match-wins engine passed them; the new leg needs the later rule to
replace the earlier one. 273 posts a rule holding a '<', which raw would close
the box early.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 21:04:30 +02:00
Xavier Roche
c71f37189a Reshoot the Windows progress screenshot on a busy mirror (#1174)
The guide showed a mirror in progress as one connection trickling in a
small file, which is not what the pane looks like in use.
httrack-windows#102 changed the capture walk to serve itself a larger site
over eight connections, so it now shows eight transfers in flight with
their bars part-filled.

Regenerated from httrack-works 82ca70b. guide-win-language moves with it:
its About line carries the engine version, and CI now builds against
3.49-20 where that set was shot at 3.49-19.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 18:14:17 +00:00
Xavier Roche
cb91ecaedc Show the Host aliases row to WinHTTrack readers (#1175)
httrack-windows#101 merged as 2e65f8f, putting the field on WinHTTrack's
Spider page. The row now names all three front ends.

Closes #1173

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 18:13:55 +00:00
Xavier Roche
faa6964d18 The WebHTTrack wizard cannot reach --host-alias (#1166)
* Expose --host-alias in the WebHTTrack wizard and the guide

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review fixes: bash 3.2, the reload binding, and the guide wording

mapfile is bash 4 and macOS runs the suite under 3.2, so the argv read becomes
a plain loop. The test now reopens the option page with the rule stored, which
is what a typo in ${attr:hostalias} would break: the field renders empty and
the project loses the rule on every reload.

The guide row takes the CLI help's own wording (hosts fold onto one) and says
what the validator does with a trailing slash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Take several host-alias rules in the wizard, one flag each

The engine accumulates --host-alias one rule per flag, so a single-line field
could only ever declare one canonical host. The field becomes a textarea and a
new arglist: template directive emits one flag per rule.

Rules separate on any whitespace, the way the URL box separates two URLs,
except beside a ',' or an '=' where the space belongs to the rule the parser
trims it out of: "a.com , b.com = c.com" stays one rule. The profile keeps them
on one ini line with the separator percent-encoded, which unescapeini() turns
back into a line break on reload.

--strip-query has the same shape and is now #1168.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Pin the whitespace shapes the rules box accepts

A table over the runs a user actually types: repeated spaces, a tab, a CRLF
run, leading and trailing whitespace, and the runs beside a ',' or an '=' that
belong to their rule. The empty cases carry the weight, since the engine
refuses an empty rule and a blank line must not reach it as --host-alias "".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Split the rules box on every whitespace the Android app splits on

The wizard's splitter took " \t\r\n" while the Android field uses Java's \s,
so a pasted "a=b\vc=d" was two rules there and one rejected rule here. Widen
it to isspace()'s C-locale set.

The guide's "Strip query keys" row also claimed win and web only, hiding from
the in-app Android guide a field the app has long shown as editStripQuery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Show the Host aliases row to Android readers

httrack-android#116 merged as b90e8ec, so the app has the field the row
describes. The row travels with the engine the app pins, so a build that
carries this guide also carries the field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:48:44 +00:00
Xavier Roche
fce40a5511 The Windows guide screenshots lag the rebaselined captures (#1169)
httrack-works 8ae37ab reshot the Windows set from master, moving two of the
images doc-images.py generates. The mirror-progress counters differ because the
demo site grew from 24 pages at 0.35s to 120 at 0.8s, so the animation capture
added by httrack-windows#100 has a crawl long enough to film. The welcome pane
differs only in the runner's D: volume label; no GUI changed.

Regenerating deliberately, because doc-images.py reads the httrack-works working
tree live: left alone these land in whichever unrelated PR next runs the script.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 15:14:55 +00:00
Xavier Roche
0f3dbcded4 A long URL overflows the dedup key buffer in htshash.c (#1167)
* A long URL overflows the dedup key buffer in htshash.c

The dedup key is the host followed by the path, built with two unbounded
strcpy() into hash->normfil. Both halves come off the wire and each can reach
HTS_URLMAXSIZE * 2, so a hostile URL wrote up to twice what the buffer holds.
The write landed in the next field of the struct, where neither ASan nor
_FORTIFY_SOURCE can see it.

Size the key scratch for what it actually holds, a host plus a path, and clip
rather than abort on anything longer: the key only feeds the hash, equality is
decided by key_adrfil_equals_generic, so a clipped key costs a collision. The
match string hts_query_strip_keys() builds from the same two halves aborted on
the same input; it clips now too.

Closes #1160

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Bound the strip-query match string and let the test own the canary

Review follow-ups on the key fix: use the tree's own strclipbuff() instead of a
hand-rolled clip, size hts_query_strip_keys()'s own host+path buffer so an
in-range URL is never truncated into matching a rule the whole one would not,
and keep an overlong path out of fil_normalized_filtered_ex()'s smaller
scratch.

hash_url_key() is now the key builder the engine itself calls, so the self-test
poisons its own arena and catches an overrun of any size instead of reading a
private field. normfil2 goes back to its old size: it only ever holds one
normalized path.

Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Signed-off-by: Xavier Roche <xroche@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:00:14 +02:00
Xavier Roche
04c75c430a A site served under several hostnames is mirrored once per name (#1156)
Some sites answer on several hostnames with the same content. HTTrack treated each name as its own site. Links to the other name were out of scope by default, and once a filter let them in, the whole tree came down again under a second directory.

`--host-alias "alias[,alias...]=canonical"` (`-%C`, repeatable) names the hosts of one site. They then count as the same address for the travel rules, and each link is folded onto the canonical host in `url_savename`, which every link passes before it is type-probed, named, recorded or fetched. So one host reaches the wire and the cache whichever name linked there. Aliases match with the +/- filter glob and the last matching rule wins; the right side is a single literal host, checked when the option is parsed. Either side may name a scheme: on the right it replaces the link's, for a host that answers on one scheme only (`legacy.example.com=https://example.com`), and on the left it narrows the rule to that scheme (`https://www.example.com=ftp://files.example.com`). A rule may name anything an address holds and nothing it does not, so a path is refused with a diagnostic rather than ignored: scheme, credentials, host and port live in `adr`, a path lives in `fil`. Chains are followed to their end, since the fold has to give the same host however often a link is named; rules pointing in a circle name no canonical host, so those are left alone and reported.

The interaction with the url hacks is the subtle part. The mapping is decoupled from `-%u`, as `--strip-query` already is, so `-%u0` cannot silently turn it off. But the host is matched in the form the dedup layer is using: under the `www.` collapse a rule naming `example.com` also covers `www.example.com`, which `-%u` would otherwise key onto the bare name and away from the canonical one, and with the collapse off the rule matches the name as the link spells it, so `--no-www-dedup` still keeps those apart. The collapse applies to the rule as well as to the host, so either spelling of an alias works under `-%u`.

There is a `-#test=hostalias` self-test, and a crawl test that reaches the second name once per engine path that folds it: a shared page, a page nothing else links to, a redirect, a page whose type is known only after a probe, and a sitemap entry. A control run keeps the alias URL absolute and fetches none of them, and an HTTPS leg covers the case that made the canonical-side scheme necessary. A misdeclared direction, where the canonical host redirects back to the alias, was probed by hand: the existing same-file-redirect handling absorbs it and the crawl ends normally.

Closes #16
2026-08-11 13:07:15 +00:00
Xavier Roche
0e0072ec03 The footer copyright year is pinned to 2026 and will disagree with the site (#1163)
* Compute the footer copyright year instead of pinning it to 2026

The year was hardcoded because --check diffs the regenerated chrome against
the shipped bytes, so computing it would have turned every 1st of January
into a red CI leg. That left httrack.com, which prints date('Y'), and the
shipped pages disagreeing from next New Year with nothing to catch it.

Compute it, and let --check ignore the digits: a page shipped in an earlier
year stays in sync until something else rewrites it, and the next
regeneration restamps all 15. The mask is scoped to the footer region
because contact.html carries a second 1998-YYYY in its licence text, which
still has to match byte for byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Test the year tolerance, and stop it accepting non-ASCII digits

The mask was written \d, which in a Python str pattern matches every Unicode
decimal digit, so a footer carrying Arabic-Indic or fullwidth digits passed
--check where it failed before. [0-9] is what was meant.

Nothing exercised the new branch either. The tree ships the year it was
generated in, so on CI the two sides agree and the tolerance is never
reached; it would have run for the first time on a New Year, in production.
Honour SOURCE_DATE_EPOCH as man/makeman.sh already does, which pins the
stamp and gives the test a seam, and add 271 to drive it: a lagging year
passes, a drifted footer and non-ASCII digits still fail. Each assertion is
paired with the mutant that kills it.

Also rename undated() to mask_footer_year(): it masks one field in one
region, and the old name promised a page-wide date strip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:26:37 +00:00
Xavier Roche
ceab9c834b Nothing in CI builds our only consumer of the installed headers (#1155)
* Add a CI job building HTTraQt against the branch

HTTraQt is the only consumer of our installed headers, so it is the only
thing exercising the public C++ API surface. Twice an httraqt FTBFS is how
we learned we broke source compat, the second time six days late and by
accident (#1150).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Review fixes: m4/ in the filter, weekly run, stray-library shapes

m4 macros feed AM_CPPFLAGS for every installed header, so a change there
can break the consumer without touching src/. The weekly run catches Qt
and runner-image drift, since upstream itself is pinned.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Fetch HTTraQt from our mirror, shallow, by SHA

No PR should depend on SourceForge being up. xroche/httraqt mirrors the
tree with its full history; fetching the pinned SHA directly also drops
the whole-history clone and the separate pin check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Name the mirror for what it is, build infrastructure

xroche/httraqt reads as a claim on a project whose author we have just
asked about restarting it. The -ci-mirror suffix does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Compile the installed headers with exceptions and RTTI off too

Qt permits -fno-exceptions/-fno-rtti, so a consumer's translation unit
may see our headers that way and 206 never did. Additive: the
compiler's default mode keeps its own coverage, and the flag pair is
probed like the -std ones so a compiler without it skips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:59:06 +00:00
dependabot[bot]
082d87e284 Bump github/codeql-action from 4.37.4 to 4.37.6 (#1159)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.37.4 to 4.37.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v4.37.4...v4.37.6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.37.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-11 11:29:44 +00:00
Xavier Roche
58e8aec88f The doc footer drops the patents chicklet, and two pages escape the check (#1157)
* The doc footer drops the patents chicklet, and two pages escape the check

The shipped doc footer had drifted from httrack.com's: no End Software
Patents chicklet, no <small> around the copyright line so it rendered a
size larger than the site's, and a .5rem top margin holding it off the
panel's black edge. doc.css now takes site.css's .sitefooter shape (flex
row, line-height 1.3, no top margin), with the chicklet at the far end.

The chicklet ships as html/images/esp_chicklet.png and is referenced
relatively rather than by the site's absolute src, since these pages are
read off the install directory where that would be a broken image and an
outbound request from a local page. The link points at the canonical
https://endsoftwarepatents.org/innovating-without-patents/, where the
site's http URL lands after two redirects, and uses target="_blank"
because _new is not a target keyword.

The footer also becomes its own doc-chrome region, as the masthead did in
#1154. It used to sit inside the generated bottom region, which index.html
and guide.html do not have: both build their own sidebar and take the
masthead alone, so their footers were hand-maintained and unchecked.
Corrupting index.html's footer text passes --check on master and fails
after this; a page that loses the markers is reported rather than crashing
the generator.

The year stays hardcoded: --check diffs the regenerated chrome against the
shipped bytes, so computing it would red CI every 1st of January.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Report a malformed chrome region per page instead of aborting the run

A page whose region markers are half-present reached region()'s bare
SystemExit, which named no page and stopped the sweep, so a drift on any
later page went unreported. Catch it per page, print the page, and carry
on. A duplicate marker pair is now rejected too: region() substitutes the
first pair only, so a trailing second one shipped unchecked on any region,
the masthead included.

Also add rel="noopener" to the chicklet link, and reword two comments: the
footer's contrast note no longer claims .8em now that <small> nests inside
it, and doc.css points at site.css's .sitefooter as the shape it mirrors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 11:28:05 +00:00
Xavier Roche
ce66c2391a The site menu is injected at a guessed </header>, not a marker (#1154)
* The site menu is injected at a guessed </header>, not a marker

httrack.com serves the html/ pages through a wrapper that injects the site
menu under the masthead, and it locates that spot by finding the first
</header>. Only index.html and guide.html carry a literal
<!-- doc-chrome:masthead -->. The other 13 get their masthead emitted inside
the generated top region, so its content is governed while the marker itself
never reaches the shipped bytes.

doc-chrome.py now emits the markers as part of top, so all 15 pages expose the
same injection point and --check keeps them there. Shipped bytes change by
exactly those two comment lines per page and nothing else, which matters
because the site copies these files verbatim. Two checks make the guarantee
direct rather than resting on "it sits in a generated region": a page carrying
the chrome whose file has lost the marker fails --check, and a page loading
doc.css with no markers at all is now an error instead of dropping out of the
check unnoticed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Reword the three new comments after the review prose pass

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 09:49:17 +00:00
Xavier Roche
49e93b171b Nothing runs the test suite under emulation, so the buildd finds those failures first (#1149)
* Run the suite under qemu-user, where only the buildd does today

cross-arch.yml compiles for the nine ports architectures and stops at make, so
the Debian buildd is the first machine anywhere to execute the suite on one. It
reports by failing the package build, which is how both failures in #1146
reached sid past a green cross leg.

s390x rather than the hppa that reported it: a release architecture, so the
container image and the build-deps come from the main archive instead of
debian-ports, and big-endian at no extra cost. Gated on tests/ changes, since
the harness is where an assumption about the native shape of a process lands.

The script reads /proc/self/cmdline back before building: a runner that stopped
emulating would leave this leg green while covering nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Read the emulation probe off a child, and pin what must pass

qemu answers /proc/self/cmdline from the guest argv it was handed, so a process
asking about itself sees bash and never the interpreter. Only another pid
reaches the host kernel, which is both why the self-check would have failed
forever and why the harness sees the shift at all.

make check exits 0 for an all-SKIP run, and this leg skips the crawl tests by
design, so a container that lost a dependency would report a green covering
nothing. Read the pass count back and pin a floor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* TEMPORARY: revert #1147 to prove this leg reds on what it targets

Backed out in the next commit. Without it the leg cannot be shown to catch
anything, and the issue asks for exactly that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Restore #1147, the control having shown the leg reds

The reverted run failed 105_suite-timeout under emulation, which is what the
hppa buildd reported. 151 skipped there rather than failing: its pacer fires on
this runner, where an emulated configure is far cheaper than on a buildd, so the
leg reproduces the argv-shift failure deterministically and the wall-clock one
only on a slow enough host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 08:56:18 +00:00
Xavier Roche
3ea7a773fa Installed headers break under some include orders (#1152)
A consumer picks its own include order, and five ordered pairs of our installed headers do not compile. Both causes are a guard placed over a block that a later, better-informed include should still have got to run: htsstrings.h kept a partial copy of htsglobal.h's attribute helpers behind the same `#ifndef HTS_UNUSED`, so including it first starved htssafe.h of `HTS_CHECK_RESULT`; htsbasenet.h tested `HTS_USEOPENSSL` before anything had defined it, and its own `HTS_DEFBASENETH` then locked the OpenSSL block out for good, leaving `SSL` undeclared in htsopt.h. Both now include htsglobal.h rather than guess at it.

The first one is not hypothetical. It shipped in 3.49.15 and broke httraqt, our only reverse dependency, as an RC-severity FTBFS ([Debian #1143660](https://bugs.debian.org/1143660)); Debian worked around it downstream by dropping the include and it never reached us, so it is still live in 3.49.20. The second has never been reported and turned up while sweeping for the first.

205 and 206 could not catch either, because they compile each installed header on its own, and on its own every header passes. 269 sweeps all 182 ordered pairs in C and C++ and in both `HTS_INTERNAL_BYTECODE` states, six installed headers gating declarations on that macro. Its control is a synthetic pair that compiles in one order only: it proves the sweep can see the shape it hunts, without asserting that any real header stays broken. Verified by reverting each fix in turn, each failing 269 alone and naming its own pair, and by making the sweep vacuous, which the control catches.

Closes #1150
Closes #1151
2026-08-11 10:32:43 +02:00
Xavier Roche
5175cf986f A caught signal kills every in-flight transfer, and a stopped FTP transfer waits twice (#1145)
`back_wait()` called `select()` and threw the return away. On EINTR Linux leaves the three fd sets exactly as the caller filled them, so every in-flight socket reads back as an exception and the slot dies with a fabricated "Receive Error": any caught signal costs a transfer, and on master a burst of SIGCHLD against a trickling body truncates the mirror. Reading the return is only half of it, because the prompt `^C` people rely on during an HTTP download exists only through that same accident. Nothing on the receive path ever read `opt->state.stop`, so a correct `select()` on its own would leave HTTP waiting out `--timeout` the way FTP does today. `back_wait()` now sweeps every live non-FTP slot when the stop flag is up, sharing the mirror-limit abort loop, and reports the user's stop instead of a socket failure. FTP slots are left alone: the worker thread owns both socket and slot, and finishing one under a live `fwrite()` is a use-after-free. So are the transfers running under a cap, because `back_checkmirror()` raises the same flag the first time `-M` or `--max-time` is reached: while that grace runs the sweep takes only the slots still waiting to connect, and the limit block that owns those transfers ends them with its own status and their WARC record.

On the FTP side a stopped transfer against a silent server waited out `--timeout` twice, once on the data socket and again on the QUIT reply nobody was going to send. Only the second window goes. The data wait keeps its own, since the first `^C` promises to finish pending transfers and clipping that wait truncates transfers still making progress (#1107). With `--timeout=0` a stop stays unbounded, which is what the user asked for, and a second `^C` already exits at once. No thread drain either: teardown is #1051, and a partial drain would hang the exit against that unbounded wait. Tests 262 and 263 cover both halves against a no-signal control arm, and the backstop self-test now pins the wider sweep with the FTP slot as its control.

Closes #1110
Closes #1096

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 08:31:07 +00:00
Xavier Roche
d25dd436ee cleanup_push traps a SIGPIPE the runners never let it see (#1141)
Settles #1136 as (b): PIPE leaves cleanup_push's trap list.

A non-interactive bash cannot trap a signal it inherited as ignored, and that is how the ubuntu runners hand SIGPIPE down, so `trap -p PIPE` reads back `trap -- '' SIGPIPE` and the entry was inert on the very hosts it was meant to cover. `perl -e '$SIG{PIPE}="IGNORE"; system("bash", ...)'` reproduces the CI red exactly: the subject sits out its full 30s sleep and exits 0. The container and macOS legs passed because their process ancestry resets the disposition. Even where it installed the entry bought nothing, since bash runs the EXIT trap for a fatal PIPE and the stack drains either way; only QUIT needs its own trap, because a non-interactive shell ignores it.

256 now reads the trap list back out of a live subject and checks it against the list it knows, so the next signal added without a leg goes red. Putting PIPE back makes that new case fail.

Closes #1136

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 08:18:36 +00:00
Xavier Roche
0fe1e2ba34 An aborted --update run purges against the files it never reached (#1142)
An interrupted run still ran the end-of-mirror purge, comparing old.lst against the truncated new.lst of what it had managed to fetch. Everything it had not reached was deleted, and `--purge-old` is on by default, so no flag has to be set for this.

The gate now reads `opt->state.stop` and `state.exit_xh` once the crawl loop ends, and skips the purge and the `--changes` report when either is set. Reading the cursor is not enough: a stop that lands while a page is in flight makes the parser drop the links that page carries, so a run can reach the end of its list having never queued half the mirror. Every stop route sets `state.stop` (signals, the GUI, and the `--max-time` and `--max-size` caps through `hts_request_stop`), and nothing sets it on a mirror that ran to the end. The report is suppressed for the reason #1061 gives: a run that never walked the list cannot say what is gone.

The rest of the completion sequence stays. `hts_finish_makeindex` and `index_finish` close files the run was writing, `singlefile_process_mirror` only rewrites pages that arrived whole, and `warc_close_opt` commits records for transfers that really happened, where `warc_abort_opt` would throw them away. The summary line is the other change: it says "mirror aborted after" instead of claiming a complete mirror.

Test 261 covers both halves, a SIGTERM during a stalled FTP `--update` and a `--max-time` cap firing while the page carrying the only links to six leaves is still in flight. The complete crawl in between still purges a file it dropped, so a fix that merely silences the purge fails there.

Closes #1109

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 07:47:55 +00:00
Xavier Roche
9bc300296f The Windows suite has no wall-clock bound, so a blown budget reaches the workflow timeout (#1144)
The Windows suite has no wall-clock bound. Its heartbeat kills only a progress log that has gone static, and the suite deadline is read between tests, but every path that follows a blown budget writes a progress line of its own, and each of those re-arms the staticness window. A test starting a second under the deadline can spend the per-test budget, then a traced rerun on a budget of its own, and land on the workflow's 45-minute step timeout. A cancelled step keeps neither its log nor its artifacts, so nobody learns what it was doing.

The heartbeat now takes an absolute cap and kills at 2400s whatever the progress log is doing, and the traced rerun is charged to the suite deadline instead of getting a second budget. 226 asserts the arithmetic against the workflow's own timeout, 171 drives the cap on its virtual clock against a suite that never stops reporting, and both assertions were checked by mutation. I could not reproduce any of this locally, since it needs a Windows runner, so the fix rests on reading the two scripts rather than on a repro.

The artifacts of the runs the issue cites are still live, and they say something else. Both Win32 legs finished the suite on their own terms and kept their step log and artifacts. Run 31397002827 ran 1264s and failed on 79_local-proxytrack-webdav-mime, whose proxytrack bound none of three port pairs; run 31405831680 ran 1060s and failed on 226 tripping over the new name in the driver's comment. 260 ran on both Windows legs of the first run and passed, in 11s on Win32 and 13s on x64, so it goes back into the selection through a crawl-harness category. The one real overrun is the x64 leg of 31405831680, which ran past 44 minutes and was cancelled with nothing surviving. 260 was already renamed out of the globs in that tree, so it was not the cause, and nothing survives to say what was.

Closes #1126

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 07:47:36 +00:00
Xavier Roche
a6315d04a3 Two suite tests fail on Debian's emulated hppa buildd (#1147)
* The hppa buildd kills 151 and hides the engine from 105

Debian's hppa is a qemu-user chroot, and two suite tests fail there in 3.49.20-1.

105 read /proc/<pid>/cmdline's first field as the program name, which binfmt_misc
gives to the interpreter, so its own probe never saw the fake engine start.
proclib already knew that shift; hoist the two regexes it matches on and reach
them from the shell too.

151 was killed at the 600s budget with four cases to go. The pacer reserved 1.5x
the last step for one more step, and its first twelve cases are cheap rejects
while the last four run configure to completion. Reserve for every step left
instead, past the first, which 196 spends warming a shared config.cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

* Order 151's expensive cases first instead of projecting the pacer

Reserving every remaining step at the last one's cost skips a run that fits
whenever one step is slower than its neighbours: a 2x outlier in 196 case 2
projects over four remaining cases and skips a run that ends at 562s of 600.
Keep the one-step-ahead reserve and give 151 the ordering it needs instead --
its four full configures first, so no step left can outrun the reserve the one
before it set.

Also fold proc_program_name onto the awk prologue so the interpreter shift has
one implementation, keep qemu-img-static and friends out of the shift (they feed
kill -9), and pin the reserve's half-step margin, which no fixture covered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>

---------

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 07:06:51 +00:00
Xavier Roche
893b542953 getrandom() is called undeclared when configure finds the function but not its header (#1143)
configure probes `sys/random.h` and `getrandom()` separately, so a libc that exports the symbol without shipping the header reached the call site with no prototype in scope. gcc 14, clang 16 and C23 reject an implicit declaration, so that build fails instead of falling back to /dev/urandom. #1138 guarded the include on both macros and left the call on one; a single macro derived at the include now gates both.

Nothing reached that cell before. There is now a CI leg that configures with `ac_cv_header_sys_random_h=no`, checks the combination really came out that way, and builds: red on master, green here. With getrandom gone it is also the only leg that runs the /dev/urandom fallback. The `enum { x = 1/(cond) }` pin at the call site covers the rest, so if the two guards split again it is a build error even where the compiler would only warn.

`hts_random_bytes` now answers a zero-length ask before reaching any source. The getrandom path already did; the fallback opened /dev/urandom to read nothing, and Windows left it to RtlGenRandom's undocumented behavior.

The new selftest (`-#test=random`, test 267) covers `hts_random_bytes` itself, not the compile break. It draws eight times with a different filler byte each round, so a byte the fill skipped is distinguishable from one it wrote, and it checks that nothing past the ask is touched. Mutants that fill half the buffer, run one byte past the end, return a constant, or scribble on a zero-length ask all fail it. A counter stub passes, since what it asserts is extent and liveness, not entropy quality.

Closes #1139

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 06:44:37 +00:00
Xavier Roche
3232f81373 Wizard domain answers match beyond the domain (#1140)
Three of the wizard's answers built their host filter as `<host>*`, with nothing between the host and the wildcard, so `+foo.com*` also authorized `foo.com.evil.org`. Answering "mirror all links located on the same domain" on a page linking to such a host pulled it into the mirror. Answer 5's allow-the-domain branch had the same shape, and answer 2's exclude over-excluded for the same reason. All three now emit `<host>/*`, and nothing is lost at the bare-host end because both forms the wizard matches against insert a `/` after the host.

This is a visible behavior change: someone who answered "same domain" and relied on the loose match will now see fewer hosts accepted.

To make the emission testable without a crawl, it moved out of the answer switch into `wizard_answer_filter()`, which `-#test=wizardfilter` drives from tests/264. A differential against the old inline code over answers, hosts and paths came back with only the three intended changes; answer 1's collapse of a doubled slash is preserved and pinned by the test.

Closes #1119

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 06:25:06 +00:00
commitmaniac
4a5854f31d htsrandom: the macOS build fails on an include only getrandom needs (#1138)
macOS's sys/random.h uses u_int without defining it, so a bare include does not compile. Autoconf's header test passes anyway, because it prepends sys/types.h before testing. The only consumer of that header here is getrandom(), which macOS does not have, so the include is dead weight on that platform. Guard it on HAVE_GETRANDOM as well, matching the guard already on the call.

Reported by MacPorts while upgrading to 3.49.19. Tested on macOS 10.7.5 with Xcode 4.6.3.

Signed-off-by: commitmaniac <201806046+commitmaniac@users.noreply.github.com>
2026-08-11 07:08:26 +02:00
Xavier Roche
df0fbd4711 Put the URL pane back as the software-centre screenshot (#1137)
#1132 moved to the scan-rules pane because every wizard pane advertised a
loopback address. #1135 reshot the set with the crawl addressed as
www.example.com, and the site now serves those bytes.

Signed-off-by: Xavier Roche <roche@httrack.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 21:40:26 +00:00
238 changed files with 10853 additions and 1415 deletions

View File

@@ -18,18 +18,29 @@ concurrency:
jobs:
build:
name: build (${{ matrix.arch }}, ${{ matrix.cc }})
name: build (${{ matrix.arch }}, ${{ matrix.cc }}${{ matrix.label }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
# cflags is spelled out everywhere: an exported empty CFLAGS reads as
# "set" to configure, which then drops its own -g -O2.
include:
- { arch: x86-64, runner: ubuntu-24.04, cc: gcc }
- { arch: x86-64, runner: ubuntu-24.04, cc: clang }
- { arch: arm64, runner: ubuntu-24.04-arm, cc: gcc }
- { arch: arm64, runner: ubuntu-24.04-arm, cc: clang }
- { arch: x86-64, runner: ubuntu-24.04, cc: gcc, cflags: -g -O2 }
- { arch: x86-64, runner: ubuntu-24.04, cc: clang, cflags: -g -O2 }
- { arch: arm64, runner: ubuntu-24.04-arm, cc: gcc, cflags: -g -O2 }
- { arch: arm64, runner: ubuntu-24.04-arm, cc: clang, cflags: -g -O2 }
# Ubuntu builds httrack this way; no other leg here does.
- {
arch: x86-64,
runner: ubuntu-24.04,
cc: gcc,
label: " -O3 -flto",
cflags: -g -O3 -flto=auto -ffat-lto-objects,
}
env:
CC: ${{ matrix.cc }}
CFLAGS: ${{ matrix.cflags }}
steps:
- uses: actions/checkout@v7
with:
@@ -700,6 +711,47 @@ jobs:
if: failure()
run: cat tests/test-suite.log 2>/dev/null || true
# The cell of #1139: a libc exporting getrandom() without shipping
# <sys/random.h>. Nothing else reaches it, and it is a hard build break when
# the call is not gated on the header. It also runs the /dev/urandom fallback.
no-getrandom:
name: build (getrandom header hidden)
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update || true # a third-party source in the runner image can 403 (#1097)
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
- name: Configure (sys/random.h hidden from configure)
run: |
set -euo pipefail
autoreconf -fi
./configure ac_cv_header_sys_random_h=no
# A libc without getrandom() at all would pass this job vacuously.
grep -q "define HAVE_GETRANDOM 1" config.h
! grep -q "define HAVE_SYS_RANDOM_H" config.h
- name: Build
run: make -j"$(nproc)"
- name: Test
timeout-minutes: 20
run: |
jobs=$(( $(nproc) * 2 )); [ "$jobs" -le 16 ] || jobs=16
make check -j"$jobs"
- name: Print the test log on failure
if: failure()
run: cat tests/test-suite.log 2>/dev/null || true
# OpenSSL 4.0 removes the API 3.x deprecated. Building with that set hidden
# keeps the engine ready for it: a deprecated call fails here rather than the
# day a distro moves. Compile-only, since the headers are what is under test.
@@ -837,6 +889,7 @@ jobs:
fi
lint:
# The name is a required status check on master; renaming it blocks every PR.
name: lint (shellcheck, shfmt)
runs-on: ubuntu-24.04
# Every tracked shell script; the globs expand at run time. Kept here so the
@@ -865,8 +918,13 @@ jobs:
# version; use it rather than fetching a release binary from github.com.
sudo apt-get install -y --no-install-recommends shellcheck shfmt appstream \
appstream-util
# Pinned to the local dev versions: black's stable style moves across
# majors, so the runner image's build would reformat on its own schedule.
pip install --quiet black==25.1.0 flake8==7.1.1
shfmt --version
appstreamcli --version
black --version
flake8 --version
- name: shellcheck
run: shellcheck $SHELL_SCRIPTS
@@ -874,6 +932,22 @@ jobs:
- name: shfmt
run: shfmt -d -i 4 $SHELL_SCRIPTS
# Read the list rather than hand-keeping one: a .py added in a new
# directory then gets checked without anyone widening a glob.
- name: List the tracked Python files
run: |
set -euo pipefail
mapfile -t py < <(git ls-files '*.py')
test "${#py[@]}" -gt 0 # flake8 given no paths exits 0, checking nothing
echo "${#py[@]} tracked Python files"
echo "PYTHON_FILES=${py[*]}" >> "$GITHUB_ENV"
- name: black
run: black --check --diff $PYTHON_FILES
- name: flake8
run: flake8 $PYTHON_FILES
# A misnamed test falls outside the glob "make check" runs, so it goes
# missing without failing anything.
- name: test file names

View File

@@ -38,7 +38,7 @@ jobs:
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev
- uses: github/codeql-action/init@v4.37.4
- uses: github/codeql-action/init@v4.37.6
with:
languages: c-cpp
build-mode: manual
@@ -67,6 +67,6 @@ jobs:
./configure
make -j"$(nproc)"
- uses: github/codeql-action/analyze@v4.37.4
- uses: github/codeql-action/analyze@v4.37.6
with:
category: "/language:c-cpp"

78
.github/workflows/emulated-suite.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
# cross-arch.yml stops at compile for the ports architectures, so the Debian
# buildd is the first machine to run the suite on one, and it reports by failing
# the package build (#1146, #1148). This runs it under qemu-user, where a process
# is not the shape a native one is and every step costs far more.
name: Emulated suite
on:
# The harness is where a native-shape assumption lands, so gate on it rather
# than on every PR: this leg is slow and is not a required check.
pull_request:
paths:
- "tests/**"
- "tools/emulated-suite.sh"
- ".github/workflows/emulated-suite.yml"
push:
branches: [master]
schedule:
- cron: "41 5 * * 1"
workflow_dispatch:
# Least privilege: the workflow only needs to read the repo.
permissions:
contents: read
concurrency:
group: emulated-suite-${{ github.ref }}
cancel-in-progress: true
jobs:
emulated:
name: emulated suite (${{ matrix.arch }})
runs-on: ubuntu-24.04
# The emulated apt, configure and build take the bulk of it, well under the
# 6h ceiling: a wedge should not run that long.
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
# Emulation is what is under test, not parisc: the hppa that reported
# #1146 lives in debian-ports, with no container image and a far more
# fragile archive. s390x is in main, and big-endian, which nothing else
# here covers.
arch: [s390x]
steps:
- name: Register the binfmt handlers
run: |
set -euo pipefail
sudo apt-get update
# Registered with the F flag, so the kernel holds the interpreter open
# and the container needs no copy of it.
sudo apt-get install -y --no-install-recommends qemu-user-static binfmt-support
test -e "/proc/sys/fs/binfmt_misc/qemu-${{ matrix.arch }}" ||
{ ls /proc/sys/fs/binfmt_misc; exit 1; }
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Build and run the suite emulated
run: |
set -euo pipefail
# The build dir is a mounted volume, so the suite log outlives the
# container: a failure here has no detail otherwise.
mkdir -p "$RUNNER_TEMP/emul-bld"
docker run --rm --platform "linux/${{ matrix.arch }}" \
-v "$GITHUB_WORKSPACE:$GITHUB_WORKSPACE" -w "$GITHUB_WORKSPACE" \
-v "$RUNNER_TEMP/emul-bld:/bld" \
-e "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" \
-e "EMULATED_ARCH=${{ matrix.arch }}" \
debian:sid bash ./tools/emulated-suite.sh
- name: Collect the suite log
if: failure()
uses: actions/upload-artifact@v7
with:
name: emulated-suite-${{ matrix.arch }}-log
path: ${{ runner.temp }}/emul-bld/tests/test-suite.log
if-no-files-found: warn

106
.github/workflows/httraqt.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
# Build HTTraQt, the only consumer of our installed headers, against this branch.
name: HTTraQt
# Filtered, so it must never join the required set: a check that a path filter
# skips stays "Expected" forever and blocks the merge.
on:
push:
branches: [master]
paths: ['src/**', 'm4/**', 'configure.ac', '.github/workflows/httraqt.yml']
pull_request:
paths: ['src/**', 'm4/**', 'configure.ac', '.github/workflows/httraqt.yml']
schedule:
- cron: '37 05 * * 1' # upstream is pinned; this catches drift in Qt and the runner image
workflow_dispatch:
permissions:
contents: read
concurrency:
group: httraqt-${{ github.ref }}
cancel-in-progress: true
env:
# Unofficial mirror of the SourceForge tree, so no PR depends on a third-party host.
HTTRAQT_REPO: https://github.com/xroche/httraqt-ci-mirror.git
HTTRAQT_REF: a1175565c61ab9afd7bda2dc96ed1182c2d44ec3 # 1.4.11 + cmake patch, upstream HEAD since 2023-01-06
PREFIX: /usr/local
jobs:
build:
name: build HTTraQt against this branch
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
- name: Install build dependencies
run: |
set -euo pipefail
sudo apt-get update || true # a third-party source in the runner image can 403 (#1097)
sudo apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool autoconf-archive \
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev \
cmake qt6-base-dev qt6-multimedia-dev
- name: Build and install libhttrack
run: |
set -euo pipefail
autoreconf -fi
bld="$RUNNER_TEMP/bld"
mkdir -p "$bld"
(cd "$bld" && "$GITHUB_WORKSPACE/configure" --prefix="$PREFIX" --disable-static)
make -C "$bld" -j"$(nproc)"
sudo make -C "$bld" install
sudo ldconfig
- name: Assert no distribution libhttrack can shadow ours
run: |
set -euo pipefail
# FindHttrack.cmake hardcodes /usr/include/httrack first, so a stray libhttrack-dev would test Debian's headers.
for stray in /usr/include/httrack /usr/lib/libhttrack.so /usr/lib/*/libhttrack.so; do
if [ -e "$stray" ]; then
echo "::error::$stray exists; HTTraQt would build against it, not this branch"
exit 1
fi
done
test -f "$PREFIX/include/httrack/htsglobal.h"
- name: Fetch HTTraQt
run: |
set -euo pipefail
# Fetching the SHA itself needs no pin check afterwards, and keeps the
# tree out of the workspace so our own build cannot pick it up.
rm -rf "$RUNNER_TEMP/httraqt"
git init -q "$RUNNER_TEMP/httraqt"
ok=
for attempt in 1 2 3; do
if git -C "$RUNNER_TEMP/httraqt" fetch -q --depth 1 "$HTTRAQT_REPO" "$HTTRAQT_REF"; then ok=1; break; fi
echo "fetch failed (attempt $attempt)"
[ "$attempt" -eq 3 ] || sleep $((attempt * 15))
done
[ -n "$ok" ] || { echo "::error::cannot reach $HTTRAQT_REPO"; exit 1; }
git -C "$RUNNER_TEMP/httraqt" checkout -q --detach FETCH_HEAD
- name: Configure HTTraQt
run: |
set -euo pipefail
# CMAKE_MINIMUM_REQUIRED is 2.8 upstream, which CMake 4 rejects outright.
# The redirect is load-bearing: a bare MESSAGE() is NOTICE mode, which goes to stderr.
cmake -S "$RUNNER_TEMP/httraqt" -B "$RUNNER_TEMP/httraqt-bld" \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
2>&1 | tee "$RUNNER_TEMP/cmake.log"
# A configure that resolved elsewhere would compile someone else's headers.
grep -q "httrack header directory found: $PREFIX/include/httrack/" "$RUNNER_TEMP/cmake.log"
- name: Build HTTraQt
run: cmake --build "$RUNNER_TEMP/httraqt-bld" -j"$(nproc)"
- name: Assert it linked against the libhttrack we just built
run: |
set -euo pipefail
# Not bin/: CMAKE_RUNTIME_OUTPUT_DIRECTORY is set after ADD_EXECUTABLE, too late.
bin="$RUNNER_TEMP/httraqt-bld/httraqt"
ldd "$bin" | grep -E "libhttrack\.so.* => $PREFIX/lib/" \
|| { ldd "$bin"; exit 1; }

View File

@@ -31,6 +31,11 @@ jobs:
matrix:
platform: [x64, Win32]
configuration: [Release]
# Windows runs from other branches starve this one's runner (#1213), so cap
# them repo-wide at one per platform. queue: max: the default cancels a waiter.
concurrency:
group: windows-build-${{ matrix.platform }}
queue: max
# Redirect vcpkg's default `files` binary cache into the workspace so
# actions/cache can persist it. vcpkg builds openssl/brotli/zlib/zstd from
# source otherwise, several minutes every run.
@@ -67,17 +72,30 @@ jobs:
run: New-Item -ItemType Directory -Force -Path $env:VCPKG_DEFAULT_BINARY_CACHE | Out-Null
# x-gha is gone (vcpkg-tool #1662 dropped it after GitHub changed the cache
# API), so cache the binary archives directly. Keyed on the manifest, which
# carries the builtin-baseline, so a Dependabot bump busts it; restore-keys
# still seeds the unchanged ports' archives, so only the bumped one rebuilds.
- name: Cache vcpkg binary archives
uses: actions/cache@v6
# API), so cache the binary archives directly. An exact key hit skips the save,
# so a cache orphaned by a toolchain bump pins itself, and naming every input
# vcpkg's ABI hash folds in is a list that can come up short: key on the run
# id instead, and restore by prefix.
- name: Restore the vcpkg binary archives
uses: actions/cache/restore@v6
with:
path: ${{ github.workspace }}\vcpkg_cache
key: vcpkg-${{ matrix.platform }}-${{ hashFiles('src/vcpkg.json') }}
key: vcpkg-${{ matrix.platform }}-${{ hashFiles('src/vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }}
restore-keys: |
vcpkg-${{ matrix.platform }}-${{ hashFiles('src/vcpkg.json') }}-
vcpkg-${{ matrix.platform }}-
# Counted before the build, or vcpkg's own writes during it make a cold cache
# look warm.
- name: Count the restored vcpkg archives
shell: pwsh
run: |
$a = @(Get-ChildItem -Recurse -File -Filter *.zip `
$env:VCPKG_DEFAULT_BINARY_CACHE -EA SilentlyContinue)
Write-Host "restored $($a.Count) archive(s)"
$a.FullName | Out-File -FilePath vcpkg-restored.txt
"VCPKG_ARCHIVES_BEFORE=$($a.Count)" | Out-File -FilePath $env:GITHUB_ENV -Append
# The runner image's vcpkg checkout is pinned to some commit; our manifest's
# builtin-baseline is usually newer, so `git show <baseline>:versions/...`
# fails until that commit is local. Fetch exactly the pinned baseline (read
@@ -107,6 +125,50 @@ jobs:
if ($LASTEXITCODE -ne 0) { throw "$proj failed" }
}
# A stale cache and a warm one both restore and rebuild; only archives
# restored and unmatched prove staleness.
- name: Check whether vcpkg could use the restored archives
id: vcpkg
if: ${{ !cancelled() }}
shell: pwsh
run: |
$before = [int]$env:VCPKG_ARCHIVES_BEFORE
$stale = $false
# The log is missing when the build never reached MSBuild.
if (Test-Path msbuild-libhttrack.log) {
$m = Select-String -Path msbuild-libhttrack.log -Pattern 'Restored (\d+) package' |
Select-Object -First 1
if ($m) {
$restored = [int]$m.Matches[0].Groups[1].Value
Write-Host "vcpkg restored $restored of $before archive(s)"
$stale = ($restored -eq 0 -and $before -gt 0)
} else { Write-Host "no vcpkg restore line in the build log" }
} else { Write-Host "no build log" }
if ($stale) {
Write-Host "::warning::stale vcpkg cache: $before archives, none matching this toolchain; every port rebuilt from source"
# Provably dead: drop them rather than carry every past toolchain
# generation in the saved entry forever. -LiteralPath because a piped
# string binds -Path, which globs on a name carrying [ ].
Get-Content vcpkg-restored.txt -EA SilentlyContinue | Where-Object { $_ } |
ForEach-Object { Remove-Item -LiteralPath $_ -Force -EA SilentlyContinue }
}
$after = @(Get-ChildItem -Recurse -File -Filter *.zip `
$env:VCPKG_DEFAULT_BINARY_CACHE -EA SilentlyContinue).Count
Write-Host "$after archive(s) to cache"
# Pruning can leave the count unmoved while replacing every archive, so the
# stale case saves on its own account rather than on growth.
"save=$($stale -or ($after -gt $before))".ToLower() |
Out-File -FilePath $env:GITHUB_OUTPUT -Append
# Only when the archive set changed, so a warm run adds no entry and the
# eviction budget stays for the runs that rebuilt something.
- name: Save the vcpkg binary archives
if: ${{ !cancelled() && steps.vcpkg.outputs.save == 'true' }}
uses: actions/cache/save@v6
with:
path: ${{ github.workspace }}\vcpkg_cache
key: vcpkg-${{ matrix.platform }}-${{ hashFiles('src/vcpkg.json') }}-${{ github.run_id }}-${{ github.run_attempt }}
# WinHTTrack links src\$(Platform)\$(Configuration)\libhttrack.lib, so the
# import lib and the DLL both have to land there under that exact name.
- name: Check the binaries landed where they are expected
@@ -167,6 +229,33 @@ jobs:
throw "CRT mismatch across the boundary: libhttrack.dll=$a httrack.exe=$b"
}
# The DLL build only proves the engine's own fixed include order, the one
# order a consumer need not use, and 205/206/207/269 all gate on an automake
# environment this job has not (#1153).
- name: Compile the installed headers standalone and pairwise
shell: pwsh
timeout-minutes: 20
run: |
$ErrorActionPreference = "Stop"
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vs = & $vswhere -latest -products * -property installationPath | Select-Object -First 1
if (-not $vs) { throw "Visual Studio not found" }
Import-Module "$vs\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
$arch = if ("${{ matrix.platform }}" -eq "x64") { "x64" } else { "x86" }
Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation `
-DevCmdArguments "-arch=$arch -host_arch=x64"
# htsbasenet.h reaches for <openssl/ssl.h> unless a consumer turns it
# off, so the sweep needs what vcpkg installed for the build. Searched,
# not spelled: the install root nests the triplet twice.
$dirs = @(Get-ChildItem -Path "src\vcpkg_installed" -Recurse -Depth 3 `
-Directory -Filter include -ErrorAction SilentlyContinue)
if (-not ($dirs | Where-Object { Test-Path (Join-Path $_.FullName "openssl\ssl.h") })) {
throw "no vcpkg include directory under src\vcpkg_installed holds openssl/ssl.h"
}
$inc = @($dirs | ForEach-Object { "-I" + $_.FullName })
bash tests/install-headers-sweep.sh --backend cl --srcdir . -- @inc
if ($LASTEXITCODE -ne 0) { throw "the installed headers do not compile standalone and pairwise" }
# The engine ships ~90 tests and none had ever run on Windows: "make check"
# is Linux/macOS only. These are the offline ones, driven from Git Bash
# against the native httrack.exe. They subsume the self-tests this step
@@ -181,8 +270,9 @@ jobs:
# Through the environment, never argv, which the process list exposes.
WATCHDOG_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WATCHDOG_REPO: ${{ github.repository }}
# github.sha here is the PR's merge commit, so statuses posted against it stay out of the PR's checks UI.
WATCHDOG_SHA: ${{ github.sha }}
# The PR head, not github.sha: a merge commit is garbage-collected, and
# these statuses are the only trace a lost runner leaves (#1228).
WATCHDOG_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
WATCHDOG_CONTEXT: windows-suite (${{ matrix.platform }}, ${{ matrix.configuration }})
WATCHDOG_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |

View File

@@ -30,6 +30,11 @@ the operational checklist: toolchain, invariants, and how to ship a change.
check`, or `PATH="<bld>/src:$PATH"` for a manual run.
- Give new `.test` scripts `set -e`: the older ones predate the rule, so several
`local-crawl.sh` calls with no `set -e` report PASS on any non-last failure.
- Each test runs under a 600s wall-clock guard that reports a wedge as 124. A test
whose own work outlasts it raises the budget with a `# TEST_TIMEOUT_AT_LEAST: N`
line, at column 0 within its first 40 lines, and paces itself with
`skip_if_out_of_budget` so a host too slow to finish skips instead. The value only
ever raises the budget: nothing can disarm the guard.
- Run teardown with errexit off: `trap 'set +e; cleanup' EXIT`. Under `set -e` a
failing cleanup command becomes the test's exit status (#773). Keep the other
signals on their own `trap` line, or errexit stays off for the rest of the run.

View File

@@ -1,6 +1,6 @@
AC_PREREQ([2.71])
AC_INIT([httrack], [3.49.20], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
AC_INIT([httrack], [3.49.21], [roche+packaging@httrack.com], [httrack], [http://www.httrack.com/])
AC_COPYRIGHT([
HTTrack Website Copier, Offline Browser for Windows and Unix
Copyright (C) 1998-2015 Xavier Roche and other contributors
@@ -29,6 +29,8 @@ AC_CONFIG_SRCDIR(src/httrack.c)
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE([subdir-objects])
# 3:14:0: revision-only bump. httrackp gained a tail field (host_alias) and #1202
# added an export (hts_host_alias_rule_ok); no layout moved, nothing went away.
# 3:13:0: revision-only bump. htsstrings.h's StringRoomTotal grew saturation in
# its macro body; no installed struct moved and no export came or went.
# 3:12:0: revision-only bump. httrackp gained a tail field (singlefile_state) and
@@ -44,7 +46,7 @@ AM_INIT_AUTOMAKE([subdir-objects])
# moves nothing). Soname stays .so.3: HTTrackQt is the only consumer of the installed
# headers, so a libhttrack4 rename isn't worth it.
# (3:0:0 was the htsblk mime-buffer widening, the ABI break that moved .so.2 -> .so.3.)
VERSION_INFO="3:13:0"
VERSION_INFO="3:14:0"
AM_MAINTAINER_MODE
AC_USE_SYSTEM_EXTENSIONS

19
debian/changelog vendored
View File

@@ -1,3 +1,22 @@
httrack (3.49.21-2) unstable; urgency=medium
* Fix the FTBFS on hppa: three suite tests measured the build host rather
than the property they cover, and failed on the qemu-user buildd purely
for being slower there. Patched from upstream, which now skips a step a
host is too slow to finish instead of failing the build
(skip-emulated-host-test-failures.patch).
-- Xavier Roche <xavier@debian.org> Thu, 13 Aug 2026 07:26:38 +0200
httrack (3.49.21-1) unstable; urgency=medium
* New upstream release: a site answering to several hostnames can now be
mirrored once rather than once per name, plus fixes to mirror
interruption, to an update pass that purged files it had never reached,
and to several buffer overflows; full list in history.txt.
-- Xavier Roche <xavier@debian.org> Wed, 12 Aug 2026 15:05:15 +0200
httrack (3.49.20-1) unstable; urgency=medium
* New upstream release: an off-site link that looked like a file could pull

5
debian/control vendored
View File

@@ -29,7 +29,10 @@ Description: Copy websites to your computer (Offline browser)
Package: webhttrack
Architecture: any
Multi-Arch: foreign
Depends: ${misc:Depends}, ${shlibs:Depends}, webhttrack-common, sensible-utils, chromium | firefox-esr | www-browser
Depends: ${misc:Depends}, ${shlibs:Depends}, webhttrack-common, sensible-utils
# Recommends, not Depends: the autoremoval gatherer follows only a disjunction's
# first alternative, which ties the httrack source to whichever browser leads it.
Recommends: firefox-esr | chromium | www-browser
Replaces: webhttrack-common (<< 3.43.9-2)
Breaks: webhttrack-common (<< 3.43.9-2)
Suggests: httrack, httrack-doc

View File

@@ -4,6 +4,24 @@ HTTrack Website Copier release history:
This file lists all changes and fixes that have been made for HTTrack
3.49-21
+ New: --host-alias (-%C) folds the several hostnames of one site onto a single canonical host, so a site that answers under more than one hostname is fetched once instead of once per name (#16)
+ New: the WebHTTrack wizard can set the host-alias rules and the guide documents them for every front end; a malformed rule is refused where it can still be corrected, through a newly exported hts_host_alias_rule_ok() (#1161, #1172, #1192)
+ Fixed: any signal caught during a mirror killed every transfer in flight, so a burst of them silently truncated the mirror; a stop now ends an HTTP transfer as the user's stop rather than a fabricated receive error (#1110)
+ Fixed: stopping an FTP transfer against a silent server waited out --timeout twice, once on the data socket and again on a QUIT reply nobody was going to send (#1096)
+ Fixed: a crawl that ended early ran --update's purge against the files it had never reached, deleting good ones (#1109)
+ Fixed: a long URL overflowed the duplicate-detection key buffer (#1160)
+ Fixed: a long command line, a large doit.log or a large .httrackrc aborted the engine while it rebuilt its arguments (#1189, #1198)
+ Fixed: a scan rule beginning with a dash ended the whole run (#1179)
+ Fixed: --index=0 and --noindex enabled the index rather than disabling it, and --purge-old=1 killed the run; an option that takes no value now refuses one instead of silently dropping it (#1195, #1200)
+ Fixed: reloading a WebHTTrack project lost every zero and every cleared field, so a rate limit of 0 came back as 100 KB/s and a cleared user-agent refilled itself (#1177, #1171, #1186)
+ Fixed: the wizard kept only the last of several --strip-query rules, and its domain answers matched beyond the domain they named (#1168, #1119)
+ Fixed: the offer to resume an interrupted mirror printed the program path instead of the command line it would rerun (#1204)
+ Fixed: the build failed where getrandom() has no declaration, and the installed headers did not compile under some include orders (#1139, #1150, #1151)
+ Changed: the documentation and the WebHTTrack pages compute their copyright year instead of carrying a fixed one (#1163, #1165)
+ Changed: refreshed the guide screenshots for all three interfaces, and repaired the documentation's dark mode and its search-target tabs (#1164, #1158)
+ Changed: multiple internal hardening, build, test and CI improvements
3.49-20
+ Fixed: a link to another host whose name looked like a file was scanned as a page, so one link could mirror the whole foreign site on the referring page's depth budget (#121)
+ Fixed: a size-bounded filter built both of its filter URLs wrong when the path had no leading slash (#1095)

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -534,9 +536,14 @@ And then, put the email address in your pages through:
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -75,8 +77,8 @@ The main advantages of this cache are:
<ul>
<li>One single file for a complete website cache archive</li>
<li>Standard <a href="http://www.pkware.com/products/enterprise/white_papers/appnote.txt" target="_new">ZIP</a> format, that can be easily reused on most platforms and languages</li>
<li>Compressed data with the efficient and opened <a href="http://www.gzip.org/zlib/" target="_new">zlib</a> format</li>
<li>Standard <a href="http://www.pkware.com/products/enterprise/white_papers/appnote.txt" target="_blank">ZIP</a> format, that can be easily reused on most platforms and languages</li>
<li>Compressed data with the efficient and opened <a href="http://www.gzip.org/zlib/" target="_blank">zlib</a> format</li>
</ul>
The cache is made of ZIP files entries ; with one ZIP file entry per fetched URL (successfully or not - errors are also stored).<br />
@@ -84,7 +86,7 @@ For each entry:
<ul>
<li>The ZIP file name is the original URL [<small><a href="#orig">see notes below</a></small>]</li>
<li>The ZIP file contents, <b>if available</b>, is the original (compressed, using the deflate algorythm) data</li>
<li>The ZIP file extra field (in the local file header) contains a list of meta-fields, very similar to the <a href="http://www.ietf.org/rfc/rfc2616.txt?number=2616" target="new_">HTTP</a> headers fields. See also <a href="http://www.ietf.org/rfc/rfc2396.txt?number=2396" target="new_">RFC</a>.</li><br />
<li>The ZIP file extra field (in the local file header) contains a list of meta-fields, very similar to the <a href="http://www.ietf.org/rfc/rfc2616.txt?number=2616" target="_blank">HTTP</a> headers fields. See also <a href="http://www.ietf.org/rfc/rfc2396.txt?number=2396" target="_blank">RFC</a>.</li><br />
<li>The ZIP file timestamp follows the "Last-Modified-Since" field given for this URL, if any</li>
</ul>
@@ -230,9 +232,14 @@ Libraries should generally handle this peculiar format, however.
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -211,9 +213,14 @@ scope: both change what a mirror directory contains.</li>
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -117,6 +119,7 @@ the index" problems disappear.</p>
<tr class="head"><td><b>Option</b></td><td><b>What it controls</b></td></tr>
<tr><td><tt>--depth (-r)</tt></td><td>Maximum link depth. <b>The start page is level 1</b>, so one level of links out is <tt>-r2</tt>, not <tt>-r1</tt>.</td></tr>
<tr><td><tt>--stay-on-same-address (-a), --stay-on-same-domain (-d), --stay-on-same-tld (-l), --go-everywhere (-e)</tt></td><td>How far off the starting host the crawl may travel: same address (host), same principal domain, same top-level domain (for example .com), or everywhere. The default keeps you on the starting host.</td></tr>
<tr><td><tt>--host-alias (-%C)</tt></td><td>Declares that several hostnames serve the same site, for example <tt>--host-alias "www2.example.com,m.example.com=example.com"</tt>. Those names then count as the starting host: their links travel under the same-address rules, are fetched from the canonical host, and are saved once rather than once per name. The left side takes filter-style globs and comma-separated names, the right side one literal host, port included. Either side may name a scheme (<tt>legacy.example.com=https://example.com</tt> reaches a host that only answers over HTTPS, and <tt>https://www.example.com=ftp://files.example.com</tt> matches only the HTTPS form). A path is refused: this maps hosts, not URLs.</td></tr>
<tr><td><tt>--can-go-down (-D), --can-go-up (-U), --stay-on-same-dir (-S), --can-go-up-and-down (-B)</tt></td><td>Directory travel: down into subdirectories only, up to parent directories only, stay in the same directory, or both up and down.</td></tr>
<tr><td><tt>--near (-n)</tt></td><td>Also fetch non-HTML files "near" a followed link, such as an image linked from a page you kept but hosted elsewhere.</td></tr>
<tr><td><tt>--ext-depth (-%e)</tt></td><td>How many levels of external links to follow once the crawl leaves your scope (default 0).</td></tr>
@@ -146,7 +149,9 @@ usually not a scope option at all: it is an off-host redirect. A start URL of
<tt>http://example.com/</tt> that redirects to <tt>https://www.example.com/</tt>
lands you on a different host, and same-host scope stops the crawl there. Start
from the final URL, or add a filter that re-admits the real host (see
<a href="#filters">Filters</a>). The log will show the redirect.</p>
<a href="#filters">Filters</a>). The log will show the redirect. When both names
really are one site, <tt>--host-alias</tt> says so once and the mirror stops
depending on which one you started from.</p>
<p><tt>-n</tt> is the fix for pages that render locally without their images or
stylesheets: it lets HTTrack pull in requisites that sit just outside scope. Note
@@ -503,9 +508,14 @@ all disabled.</small></p>
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -23,12 +23,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -206,9 +208,14 @@ roche at httrack dot com (Xavier ROCHE)<br>
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -103,9 +105,14 @@ compared to the previous mirror. This page describes that file.
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -121,11 +121,17 @@ body {
}
/* Colour inherited: nothing lighter than --ink reaches 4.5:1 on the --field
this sits on, and at .8em it is small text. */
this sits on, and at this size it is small text. */
/* Mirrors site.css's .sitefooter: flush on the panel's black edge, chicklet at the far end. */
footer {
max-width: 68rem;
margin: .5rem auto 0;
margin: 0 auto;
font-size: .8em;
line-height: 1.3;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
@media (prefers-color-scheme: dark) {
@@ -360,8 +366,26 @@ dialog#zoom::backdrop { background: rgba(0, 0, 0, .8); }
z-index: 10;
}
/* Some browsers still apply prefers-color-scheme: dark when printing, so the ink
comes out #e6e6ee on white paper. Reset the palette, not each rule reading it. */
@media print {
:root {
--field: #77b;
--panel: #ccd;
--accent: #99c;
--ink: #111;
--ink-soft: #444;
--rule: #aab;
--link: #00c;
--link-hover: #c00;
--code-bg: #e8e8f2;
--chip: #dde;
--shadow: rgba(0, 0, 0, .25);
}
body { background: #fff; }
.toc, .filter { display: none; }
.wrap { display: block; border: 0; }
/* Not a token, same paper: the dark scheme inverts the wordmark to white. */
.masthead img { filter: none; }
}

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -318,7 +320,7 @@ However, be sure to always download HTTrack from a <b>trusted source</b> (prefer
<br><br><a NAME="QG0c">Q: <strong>This software is 'free', but I bought it from an authorized reseller . What's going on?</strong></a><br>
A: <em>
HTTrack is free (free as in 'freedom') as it is covered by the <a href="http://www.gnu.org/licenses/gpl.txt" target="_new">GNU General Public License (GPL)</a>.
HTTrack is free (free as in 'freedom') as it is covered by the <a href="http://www.gnu.org/licenses/gpl.txt" target="_blank">GNU General Public License (GPL)</a>.
You can freely download it, without paying any fees, copy it to your friends, and modify it if you respect the license.
There are NO official/authorized resellers, because HTTrack is <b>NOT</b> a commercial product.
But you can be charged for duplication fees, or any other services (example: software CDroms or shareware collections, or fees for maintenance),
@@ -881,9 +883,14 @@ A: <em>Feel free to <a href="contact.html">contact us</a>!
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -174,7 +176,7 @@ Action options:
Proxy options:
P proxy use (-P proxy:port or -P user:pass@proxy:port) (--proxy &lt;param&gt;)
%f *use proxy for ftp (f0 don't use) (--httpproxy-ftp[=N])
%f *use proxy for ftp (f0 don't use) (--httpproxy-ftp[=0])
Limits options:
rN set the mirror depth to N (* r9999) (--depth[=N])
@@ -195,7 +197,7 @@ Flow control:
HN host is abandoned if: 0=never, 1=timeout, 2=slow, 3=timeout or slow (--host-control[=N])
Links options:
%P *extended parsing, attempt to parse all links, even in unknown tags or Javascript (%P0 don't use) (--extended-parsing[=N])
%P *extended parsing, attempt to parse all links, even in unknown tags or Javascript (%P0 don't use) (--extended-parsing[=0])
n get non-html files 'near' an html file (ex: an image located outside) (--near)
t test all URLs (even forbidden ones) (--test)
%L &lt;file&gt; add all URL located in this text file (one URL per line) (--list &lt;param&gt;)
@@ -209,7 +211,7 @@ Build options:
%x do not include any password for external password protected websites (%x0 include) (--no-passwords)
%q *include query string for local files (useless, for information purpose only) (%q0 don't include) (--include-query-string)
o *save the server's error pages (404..) (o0 discard them) (--generate-errors)
X *purge old files after update (X0 keep delete) (--purge-old[=N])
X *purge old files after update (X0 keep delete) (--purge-old[=0])
Spider options:
bN accept cookies in cookies.txt (0=do not accept,* 1=accept) (--cookies[=N])
@@ -2669,9 +2671,14 @@ for only 2 simultaneous sesions.
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -416,9 +418,14 @@ See also: The <a href="faq.html#VF1">FAQ</a><br>
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -629,7 +629,7 @@ bottom are the ones that keep you welcome.</p>
<figure>
<img data-for="win" src="img/guide-win-opt-spider.png" alt="WinHTTrack Spider tab" width="524" height="448">
<img data-for="web" src="img/guide-web-opt-spider.png" alt="WebHTTrack Spider tab" width="1024" height="1541">
<img data-for="web" src="img/guide-web-opt-spider.png" alt="WebHTTrack Spider tab" width="1024" height="1770">
<img data-for="droid" src="img/guide-droid-opt-spider.png" alt="Android Spider tab" width="600" height="1300">
</figure>
@@ -703,11 +703,23 @@ bottom are the ones that keep you welcome.</p>
mirror something that needs you logged in, by exporting the session from your browser.</p>
</div>
<div class="opt" data-for="win web">
<div class="opt" data-for="win web droid">
<div class="opt-head"><span class="opt-name">Strip query keys</span><span class="cli">-%g, --strip-query</span></div>
<p>Comma-separated query keys to drop when naming saved files, such as
<code>sid,utm_source</code>. Keeps one page from being saved several times under tracking
parameters that change nothing.</p>
parameters that change nothing. You can scope a rule to the addresses it covers, for example
<code>shop.example.com/*=sid</code>.</p>
<p data-for="web">The box takes one rule per line. Only the last matching rule applies, so keep
the keys for one scope together on a line rather than on lines of their own.</p>
</div>
<div class="opt" data-for="win web droid">
<div class="opt-head"><span class="opt-name">Host aliases</span><span class="cli">-%C, --host-alias</span></div>
<p>Names the other hostnames one site answers to, one rule per line, written
<code>www2.example.com,m.example.com=example.com</code>. They fold onto that host: their links
travel under the same rules and each page is saved once instead of once per name. Either side may
carry a scheme, as in <code>legacy.example.com=https://example.com</code>. A trailing slash is
ignored, but a path is refused: this maps hosts, not addresses.</p>
</div>
</section>
@@ -915,7 +927,12 @@ are how you widen a crawl deliberately.</p>
<dialog id="zoom" aria-label="Enlarged screenshot"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -76,7 +76,7 @@ offline browser : copy websites to a local directory</p>
[ <b>-w, --mirror</b> ] [ <b>-W, --mirror-wizard</b> ] [
<b>-g, --get-files</b> ] [ <b>-i, --continue</b> ] [ <b>-Y,
--mirrorlinks</b> ] [ <b>-P, --proxy</b> ] [ <b>-%f,
--httpproxy-ftp[=N]</b> ] [ <b>-%b, --bind</b> ] [ <b>-rN,
--httpproxy-ftp[=0]</b> ] [ <b>-%b, --bind</b> ] [ <b>-rN,
--depth[=N]</b> ] [ <b>-%eN, --ext-depth[=N]</b> ] [ <b>-mN,
--max-files[=N]</b> ] [ <b>-MN, --max-size[=N]</b> ] [
<b>-EN, --max-time[=N]</b> ] [ <b>-AN, --max-rate[=N]</b> ]
@@ -85,48 +85,50 @@ offline browser : copy websites to a local directory</p>
--sockets[=N]</b> ] [ <b>-TN, --timeout[=N]</b> ] [ <b>-RN,
--retries[=N]</b> ] [ <b>-JN, --min-rate[=N]</b> ] [ <b>-HN,
--host-control[=N]</b> ] [ <b>-%P,
--extended-parsing[=N]</b> ] [ <b>-n, --near</b> ] [ <b>-t,
--extended-parsing[=0]</b> ] [ <b>-n, --near</b> ] [ <b>-t,
--test</b> ] [ <b>-%L, --list</b> ] [ <b>-%S, --urllist</b>
] [ <b>-%m, --sitemap</b> ] [ <b>-NN, --structure[=N]</b> ]
[ <b>-%N, --delayed-type-check</b> ] [ <b>-%D,
--cached-delayed-type-check</b> ] [ <b>-%M, --mime-html</b>
] [ <b>-%Z, --single-file</b> ] [ <b>-LN,
--long-names[=N]</b> ] [ <b>-KN, --keep-links[=N]</b> ] [
<b>-x, --replace-external</b> ] [ <b>-%x,
--disable-passwords</b> ] [ <b>-%q,
--include-query-string</b> ] [ <b>-%g, --strip-query</b> ] [
<b>-o, --generate-errors</b> ] [ <b>-X, --purge-old[=N]</b>
] [ <b>-%p, --preserve</b> ] [ <b>-%T, --utf8-conversion</b>
[ <b>-%N, --delayed-type-check[=N]</b> ] [ <b>-%D,
--cached-delayed-type-check[=0]</b> ] [ <b>-%M,
--mime-html[=0]</b> ] [ <b>-%Z, --single-file[=0]</b> ] [
<b>-LN, --long-names[=N]</b> ] [ <b>-KN,
--keep-links[=N]</b> ] [ <b>-x, --replace-external</b> ] [
<b>-%x, --disable-passwords[=0]</b> ] [ <b>-%q,
--include-query-string[=0]</b> ] [ <b>-%g, --strip-query</b>
] [ <b>-%C, --host-alias</b> ] [ <b>-o,
--generate-errors[=N]</b> ] [ <b>-X, --purge-old[=0]</b> ] [
<b>-%p, --preserve</b> ] [ <b>-%T, --utf8-conversion[=0]</b>
] [ <b>-bN, --cookies[=N]</b> ] [ <b>-%K, --cookies-file</b>
] [ <b>-%Y, --why</b> ] [ <b>-u, --check-type[=N]</b> ] [
<b>-j, --parse-java[=N]</b> ] [ <b>-sN, --robots[=N]</b> ] [
<b>-%h, --http-10</b> ] [ <b>-%k, --keep-alive</b> ] [
<b>-%z, --disable-compression</b> ] [ <b>-%B, --tolerant</b>
] [ <b>-%s, --updatehack</b> ] [ <b>-%u, --urlhack</b> ] [
<b>-%A, --assume</b> ] [ <b>-@iN, --protocol[=N]</b> ] [
<b>-%w, --disable-module</b> ] [ <b>-F, --user-agent</b> ] [
<b>-%R, --referer</b> ] [ <b>-%E, --from</b> ] [ <b>-%F,
--footer</b> ] [ <b>-%l, --language</b> ] [ <b>-%a,
--accept</b> ] [ <b>-%X, --headers</b> ] [ <b>-C,
--cache[=N]</b> ] [ <b>-k, --store-all-in-cache</b> ] [
<b>-%r, --warc</b> ] [ <b>-%d, --changes</b> ] [ <b>-%n,
--do-not-recatch</b> ] [ <b>-%v, --display</b> ] [ <b>-Q,
--do-not-log</b> ] [ <b>-q, --quiet</b> ] [ <b>-z,
--extra-log</b> ] [ <b>-Z, --debug-log</b> ] [ <b>-v,
--verbose</b> ] [ <b>-f, --file-log</b> ] [ <b>-f2,
--single-log</b> ] [ <b>-I, --index</b> ] [ <b>-%i,
--build-top-index</b> ] [ <b>-%I, --search-index</b> ] [
<b>-pN, --priority[=N]</b> ] [ <b>-S, --stay-on-same-dir</b>
] [ <b>-D, --can-go-down</b> ] [ <b>-U, --can-go-up</b> ] [
<b>-%h, --http-10[=0]</b> ] [ <b>-%k, --keep-alive[=0]</b> ]
[ <b>-%z, --disable-compression[=0]</b> ] [ <b>-%B,
--tolerant[=0]</b> ] [ <b>-%s, --updatehack[=0]</b> ] [
<b>-%u, --urlhack[=0]</b> ] [ <b>-%A, --assume</b> ] [
<b>-@iN, --protocol[=N]</b> ] [ <b>-%w, --disable-module</b>
] [ <b>-F, --user-agent</b> ] [ <b>-%R, --referer</b> ] [
<b>-%E, --from</b> ] [ <b>-%F, --footer</b> ] [ <b>-%l,
--language</b> ] [ <b>-%a, --accept</b> ] [ <b>-%X,
--headers</b> ] [ <b>-C, --cache[=N]</b> ] [ <b>-k,
--store-all-in-cache</b> ] [ <b>-%r, --warc</b> ] [ <b>-%d,
--changes[=0]</b> ] [ <b>-%n, --do-not-recatch[=0]</b> ] [
<b>-%v, --display[=N]</b> ] [ <b>-Q, --do-not-log</b> ] [
<b>-q, --quiet</b> ] [ <b>-z, --extra-log</b> ] [ <b>-Z,
--debug-log</b> ] [ <b>-v, --verbose</b> ] [ <b>-f,
--file-log</b> ] [ <b>-f2, --single-log</b> ] [ <b>-I,
--index[=0]</b> ] [ <b>-%i, --build-top-index[=0]</b> ] [
<b>-%I, --search-index[=N]</b> ] [ <b>-pN,
--priority[=N]</b> ] [ <b>-S, --stay-on-same-dir</b> ] [
<b>-D, --can-go-down</b> ] [ <b>-U, --can-go-up</b> ] [
<b>-B, --can-go-up-and-down</b> ] [ <b>-a,
--stay-on-same-address</b> ] [ <b>-d,
--stay-on-same-domain</b> ] [ <b>-l, --stay-on-same-tld</b>
] [ <b>-e, --go-everywhere</b> ] [ <b>-%H,
--debug-headers</b> ] [ <b>-%!,
--disable-security-limits</b> ] [ <b>-V, --userdef-cmd</b> ]
[ <b>-%W, --callback</b> ] [ <b>-y,
--background-on-suspend</b> ] [ <b>-K, --keep-links[=N]</b>
]</p>
--disable-security-limits[=0]</b> ] [ <b>-V,
--userdef-cmd</b> ] [ <b>-%W, --callback</b> ] [ <b>-y,
--background-on-suspend[=0]</b> ] [ <b>-K,
--keep-links[=N]</b> ]</p>
<h2>DESCRIPTION
<a name="DESCRIPTION"></a>
@@ -297,7 +299,7 @@ httrack --continue</b></p>
<p>*use proxy for ftp (f0 don&rsquo;t use)
(--httpproxy-ftp[=N])</p> </td></tr>
(--httpproxy-ftp[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -529,7 +531,7 @@ errors (*R1) (--retries[=N])</p></td></tr>
<p style="margin-top: 1em">*extended parsing, attempt to
parse all links, even in unknown tags or Javascript (%P0
don&rsquo;t use) (--extended-parsing[=N])</p></td></tr>
don&rsquo;t use) (--extended-parsing[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -635,7 +637,7 @@ structure, 1+: see below) (--structure[=N])</p></td></tr>
make any link test but wait for files download to start
instead (experimental) (%N0 don&rsquo;t use, %N1 use for
unknown extensions, * %N2 always use)
(--delayed-type-check)</p> </td></tr>
(--delayed-type-check[=N])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -648,7 +650,7 @@ unknown extensions, * %N2 always use)
<p>cached delayed type check, don&rsquo;t wait for remote
type during updates, to speedup them (%D0 wait, * %D1
don&rsquo;t wait) (--cached-delayed-type-check)</p></td></tr>
don&rsquo;t wait) (--cached-delayed-type-check[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -660,7 +662,7 @@ don&rsquo;t wait) (--cached-delayed-type-check)</p></td></tr>
<p>generate a RFC MIME-encapsulated full-archive (.mht)
(--mime-html)</p> </td></tr>
(--mime-html[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -678,7 +680,7 @@ between pages stay relative; audio and video stay links);
--single-file-max-size N caps each asset (default 10485760
bytes). %M is the better container where a Chromium-family
browser is a given: one archive, no base64 tax on text, a
shared asset stored once (--single-file)</p></td></tr>
shared asset stored once (--single-file[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -740,7 +742,8 @@ absolute URI links, K5 transparent proxy link)
<p>do not include any password for external password
protected websites (%x0 include) (--disable-passwords)</p></td></tr>
protected websites (%x0 include)
(--disable-passwords[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -753,7 +756,7 @@ protected websites (%x0 include) (--disable-passwords)</p></td></tr>
<p>*include query string for local files (useless, for
information purpose only) (%q0 don&rsquo;t include)
(--include-query-string)</p> </td></tr>
(--include-query-string[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -772,13 +775,26 @@ information purpose only) (%q0 don&rsquo;t include)
<td width="4%">
<p>-%C</p></td>
<td width="5%"></td>
<td width="82%">
<p>fold other hostnames of one site onto it
([scheme://]alias[,...]=[scheme://]host) (--host-alias
&lt;param&gt;)</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
<p>-o</p></td>
<td width="5%"></td>
<td width="82%">
<p>*save the server&rsquo;s error pages (404..) (o0 discard
them) (--generate-errors)</p></td></tr>
them) (--generate-errors[=N])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -790,7 +806,7 @@ them) (--generate-errors)</p></td></tr>
<p>*purge old files after update (X0 keep delete)
(--purge-old[=N])</p> </td></tr>
(--purge-old[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -813,7 +829,7 @@ them) (--generate-errors)</p></td></tr>
<td width="82%">
<p>links conversion to UTF-8 (--utf8-conversion)</p></td></tr>
<p>links conversion to UTF-8 (--utf8-conversion[=0])</p></td></tr>
</table>
<h3>Spider options:
@@ -909,7 +925,7 @@ rules)) (--robots[=N])</p></td></tr>
<p>force HTTP/1.0 requests (reduce update features, only
for old servers or proxies) (--http-10)</p></td></tr>
for old servers or proxies) (--http-10[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -922,7 +938,7 @@ for old servers or proxies) (--http-10)</p></td></tr>
<p>use keep-alive if possible, greately reducing latency
for small files and test requests (%k0 don&rsquo;t use)
(--keep-alive)</p> </td></tr>
(--keep-alive[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -934,7 +950,7 @@ for small files and test requests (%k0 don&rsquo;t use)
<p>do not request compressed content (%z0 request)
(--disable-compression)</p> </td></tr>
(--disable-compression[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -946,7 +962,7 @@ for small files and test requests (%k0 don&rsquo;t use)
<p>tolerant requests (accept bogus responses on some
servers, but not standard!) (--tolerant)</p></td></tr>
servers, but not standard!) (--tolerant[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -959,7 +975,7 @@ servers, but not standard!) (--tolerant)</p></td></tr>
<p>update hacks: various hacks to limit re-transfers when
updating (identical size, bogus response..)
(--updatehack)</p> </td></tr>
(--updatehack[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -971,7 +987,7 @@ updating (identical size, bogus response..)
<p>url hacks: various hacks to limit duplicate URLs (strip
//, www.foo.com==foo.com..) (--urlhack)</p></td></tr>
//, www.foo.com==foo.com..) (--urlhack[=0])</p></td></tr>
</table>
<p style="margin-left:18%;">opt out of one url-hack part:
@@ -1186,7 +1202,7 @@ past N bytes, --warc-cdx also writes a sorted CDXJ index,
<p>write hts-changes.json listing what this crawl left new,
changed, unchanged and gone compared to the previous mirror
(--changes)</p> </td></tr>
(--changes[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -1198,7 +1214,7 @@ changed, unchanged and gone compared to the previous mirror
<p>do not re-download locally erased files
(--do-not-recatch)</p> </td></tr>
(--do-not-recatch[=0])</p> </td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -1210,7 +1226,7 @@ changed, unchanged and gone compared to the previous mirror
<p>display on screen filenames downloaded (in realtime) - *
%v1 short version - %v2 full animation (--display)</p></td></tr>
%v1 short version - %v2 full animation (--display[=N])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -1298,7 +1314,7 @@ changed, unchanged and gone compared to the previous mirror
<td width="82%">
<p>*make an index (I0 don&rsquo;t make) (--index)</p></td></tr>
<p>*make an index (I0 don&rsquo;t make) (--index[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -1310,7 +1326,7 @@ changed, unchanged and gone compared to the previous mirror
<p>make a top index for a project folder (* %i0 don&rsquo;t
make) (--build-top-index)</p></td></tr>
make) (--build-top-index[=0])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="4%">
@@ -1322,7 +1338,7 @@ make) (--build-top-index)</p></td></tr>
<p>make an searchable index for this mirror (* %I0
don&rsquo;t make) (--search-index)</p></td></tr>
don&rsquo;t make) (--search-index[=N])</p></td></tr>
</table>
<h3>Expert options:
@@ -1627,7 +1643,7 @@ with -#test=NAME [args])</p></td></tr>
<td width="82%">
<p>wait time (--advanced-wait)</p></td></tr>
<p>wait time (--advanced-wait[=N])</p></td></tr>
<tr valign="top" align="left">
<td width="9%"></td>
<td width="8%">
@@ -1661,7 +1677,7 @@ with -#test=NAME [args])</p></td></tr>
<p style="margin-top: 1em">bypass built-in security limits
aimed to avoid bandwidth abuses (bandwidth, simultaneous
connections) (--disable-security-limits)</p></td></tr>
connections) (--disable-security-limits[=0])</p></td></tr>
</table>
<p style="margin-left:18%;">IMPORTANT NOTE: DANGEROUS
@@ -1711,7 +1727,7 @@ myfoo.so[,myparameters]) (--callback &lt;param&gt;)</p></td></tr>
<p>go to background when suspended (y0 don&rsquo;t)
(--background-on-suspend)</p> </td></tr>
(--background-on-suspend[=0])</p> </td></tr>
</table>
<h3>Details: Option N

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -110,7 +110,12 @@ not own.</p>
</main>
</div>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -73,9 +75,14 @@ You may also want to check the <tt>httrack.c</tt> and <tt>httrack.h<tt> files to
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -279,9 +281,14 @@ httrack --wrapper mylibrary,myparameter-string http://www.example.com
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -155,9 +157,14 @@ Below additional function names that can be defined inside the optional libhttra
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -13,12 +13,14 @@
<body>
<!-- doc-chrome:top -->
<!-- doc-chrome:masthead -->
<a class="skip" href="#main">Skip to content</a>
<header class="masthead">
<img src="images/wordmark.svg" width="400" height="36" alt="HTTrack Website Copier">
<div class="tagline">Free software offline browser</div>
</header>
<!-- /doc-chrome:masthead -->
<div class="wrap">
@@ -198,9 +200,14 @@ Script example:
</div>
<dialog id="zoom" aria-label="Enlarged image"><img src="" alt=""></dialog>
<footer>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</footer>
<!-- /doc-chrome:bottom -->
<!-- doc-chrome:footer -->
<footer>
<small>&copy; 1998-2026 Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small>
<a href="https://endsoftwarepatents.org/innovating-without-patents/" target="_blank" rel="noopener" title="This site is innovating without patents!"><img src="images/esp_chicklet.png" width="91" height="17" alt="End Software Patents"></a>
</footer>
<!-- /doc-chrome:footer -->
</body>
</html>

View File

@@ -92,7 +92,7 @@ ${LANG_K3} : ${HTTRACK_WEB}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -159,7 +159,7 @@ ${do:end-if}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -43,17 +43,29 @@
</developer>
<screenshots>
<screenshot type="default">
<caption>Choosing which files a mirror includes</caption>
<caption>Choosing the addresses and options for a new mirror</caption>
<!-- A guide asset, so every screenshot reshoot refreshes this too.
appstream-network.yml resolves the URL weekly if the name moves.
Not a wizard pane: the walk crawls a loopback server, so every pane
showing the address advertises 127.0.0.1 and an ephemeral port. -->
<image>https://www.httrack.com/html/img/guide-web-opt-scan-rules.png</image>
appstream-network.yml resolves the URL weekly if the name moves; it
cannot see a stale deploy, since the name survives a reshoot. -->
<image>https://www.httrack.com/html/img/guide-web-project-setup.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1"/>
<!-- Newest first; tests/01_engine-version-macros.test enforces it. -->
<releases>
<release version="3.49.21" date="2026-08-12">
<description>
<ul>
<li>A site that answers to several names is mirrored once, instead of once per name</li>
<li>Stopping a mirror ends the transfers under way instead of reporting them as network errors</li>
<li>A mirror that ends early no longer deletes files the update pass never reached</li>
<li>Reloading a project keeps the boxes you cleared and the values you set to zero</li>
<li>Clearing the option to build an index page now turns it off</li>
<li>A very long address, or a very large settings file, no longer stops the mirror</li>
<li>Refreshed screenshots throughout the documentation</li>
</ul>
</description>
</release>
<release version="3.49.20" date="2026-08-10">
<description>
<ul>

View File

@@ -72,7 +72,7 @@ ${html:error}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -61,7 +61,7 @@ function info(str) {
${do:output-mode:html-urlescaped}
href="file://${path}/"
${do:output-mode:}
target="_new">
target="_blank">
${LANG_D8}
</a>
</td></tr>
@@ -96,7 +96,7 @@ ${do:loadhash}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -99,12 +99,12 @@ ${do:end-if}
${LANG_G8} :
${/* an http: page cannot navigate to file:, so the mirror is reached through the server */}
<a href="/website/index.html" target="_new">
<a href="/website/index.html" target="_blank">
${html:path}/${html:projname}
</a></li>
<ul>
<li><a href="/website/index.html" target="_new">${LANG_D8}</a></li>
<li><a href="/website/hts-log.txt" target="_new">${LANG_D4}</a></li>
<li><a href="/website/index.html" target="_blank">${LANG_D8}</a></li>
<li><a href="/website/hts-log.txt" target="_blank">${LANG_D4}</a></li>
</ul>
<form method="POST" action="exit.html" name="form">
@@ -143,7 +143,7 @@ ${html:path}/${html:projname}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -57,7 +57,7 @@ function info(str) {
<table class="tableWidth" border="0" width="100%" cellspacing="0">
<tr><td class="tabCtrl" align="left">
<a style="background:black;color: white" href="about.html" target="_new"
<a style="background:black;color: white" href="about.html" target="_blank"
title='${html:LANG_G21}' onMouseOver="info('${js:LANG_G21}'); return true" onMouseOut="info('&nbsp;'); return true"
>
${LANG_O16}...
@@ -66,7 +66,7 @@ ${LANG_O16}...
<tr><td class="tabCtrl" align="left">
<a style="background:black;color: white"
href="http://www.httrack.com/update.php3?Product=HTTrack&Version=${attr:HTTRACK_VERSIONID}&VersionStr=${attr:HTTRACK_VERSION}&Platform=${attr:HTS_PLATFORM}&LanguageId=${attr:lang}" target="_new"
href="http://www.httrack.com/update.php3?Product=HTTrack&Version=${attr:HTTRACK_VERSIONID}&VersionStr=${attr:HTTRACK_VERSION}&Platform=${attr:HTS_PLATFORM}&LanguageId=${attr:lang}" target="_blank"
title='${html:LANG_O17}' onMouseOver="info('${js:LANG_O17}'); return true" onMouseOut="info('&nbsp;'); return true"
>
${LANG_O17}...
@@ -77,7 +77,7 @@ ${/* show help only if available */}
${do:if-file-exists:html/index.html}
<tr><td class="tabCtrl" align="left">
<a style="background:black;color: white"
href="/index.html" target="_new"
href="/index.html" target="_blank"
title='${html:LANG_TIPHELP}' onMouseOver="info('${js:LANG_TIPHELP}'); return true" onMouseOut="info('&nbsp;'); return true"
>
${LANG_P16}
@@ -103,7 +103,7 @@ ${do:end-if}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -132,7 +132,7 @@ ${LANG_THANKYOU}!
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -158,7 +158,7 @@ ${do:end-if}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -163,7 +163,7 @@ ${LANG_IOPT10}:
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -250,7 +250,7 @@ ${LANG_W3}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -191,7 +191,7 @@ ${listid:build:LISTDEF_3}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -140,7 +140,7 @@ ${do:output-mode:}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -194,7 +194,7 @@ ${listid:travel3:LISTDEF_11}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -189,7 +189,7 @@ ${LANG_I46}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -228,7 +228,7 @@ ${LANG_I64b}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -156,7 +156,7 @@ ${LANG_I43b}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -148,7 +148,7 @@ ${LANG_B13}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -181,9 +181,17 @@ ${LANG_SITEMAPURL}
<br><br>
${LANG_STRIPQUERY}
<input name="stripquery" value="${attr:stripquery}" size="40"
<br>
<textarea name="stripquery" cols="60" rows="4"
title='${html:LANG_STRIPQUERYTIP}' onMouseOver="info('${js:LANG_STRIPQUERYTIP}'); return true" onMouseOut="info('&nbsp;'); return true"
>
>${do:output-mode:html}${stripquery}${do:output-mode:}</textarea>
<br><br>
${LANG_HOSTALIAS}
<br>
<textarea name="hostalias" cols="60" rows="4"
title='${html:LANG_HOSTALIASTIP}' onMouseOver="info('${js:LANG_HOSTALIASTIP}'); return true" onMouseOut="info('&nbsp;'); return true"
>${do:output-mode:html}${hostalias}${do:output-mode:}</textarea>
<br><br>
<input type="checkbox" name="toler" ${checked:toler}
@@ -235,7 +243,7 @@ ${LANG_STRIPQUERY}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -208,7 +208,7 @@ ${listid:logtype:LISTDEF_9}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -1,26 +1,85 @@
// Function aimed to ping the webhttrack server regularly to keep it alive
// If the browser window is closed, the server will eventually shutdown
function ping_server() {
var iframe = document.getElementById('pingiframe');
if (iframe && iframe.src) {
iframe.src = iframe.src;
setTimeout(ping_server, 30000);
}
// Tell the server this window is alive, so an abandoned server stops instead of
// outliving the session. The period is the one htsweb.c sizes its timeout from.
var PING_PERIOD = 5000;
// Identifies this window for as long as it is open. The server counts windows,
// so closing one of two must not read as the session ending.
var PING_WINDOW =
String(Math.random()).replace(/[^0-9]/g, "") + String(new Date().getTime());
function ping_url(extra) {
// Unique, or a cached response would never reach the server again.
return "/ping?w=" + PING_WINDOW + "&t=" + new Date().getTime() +
(extra ? "&" + extra : "");
}
// Create an invisible iframe to hold the server ping result
// Only modern browsers will support that, but old browsers are compatible
// with the legacy "wait for browser PID" mode
if (document && document.createElement && document.body
&& document.body.appendChild && document.getElementById) {
var iframe = document.createElement('iframe');
if (iframe) {
iframe.id = 'pingiframe';
iframe.style.display = "none";
iframe.style.visibility = "hidden";
iframe.width = iframe.height = 0;
iframe.src = "/ping";
document.body.appendChild(iframe);
ping_server();
}
// An iframe is the fallback only: reassigning its src can push a history entry,
// which would turn the Back button into a walk through past heartbeats.
function ping_send(url) {
if (window.fetch) {
fetch(url, {cache : "no-store"});
return true;
}
var iframe = document.getElementById('pingiframe');
if (!iframe) {
return false;
}
iframe.src = url;
return true;
}
function ping_server() {
if (ping_send(ping_url())) {
setTimeout(ping_server, PING_PERIOD);
}
}
// The session id this page carries, empty on the few pages that hold no form.
function ping_sid() {
var f = document.getElementsByName('sid');
return f && f.length ? f[0].value : "";
}
// Closing the window is the common case, and waiting out the timeout for it
// would hold the server open long after the user considers it gone. The server
// takes this only from a request holding the session id, so it goes as a POST;
// a page without one falls back to the timeout.
function ping_leaving() {
var sid = ping_sid();
if (!sid) {
return;
}
var url = ping_url("e=bye");
var body = "sid=" + encodeURIComponent(sid);
var type = "application/x-www-form-urlencoded";
if (navigator.sendBeacon) {
navigator.sendBeacon(url, new Blob([ body ], {type : type}));
} else if (window.XMLHttpRequest) {
// Synchronous: the page is going away, and an async send dies with it.
var x = new XMLHttpRequest();
x.open("POST", url, false);
x.setRequestHeader("Content-Type", type);
x.send(body);
}
}
// Old browsers reach none of this and stay on the legacy "wait for the launcher
// to die" mode.
if (document && document.createElement && document.body &&
document.body.appendChild && document.getElementById) {
if (!window.fetch) {
var iframe = document.createElement('iframe');
if (iframe) {
iframe.id = 'pingiframe';
iframe.style.display = "none";
iframe.style.visibility = "hidden";
iframe.width = iframe.height = 0;
document.body.appendChild(iframe);
}
}
ping_server();
// pagehide, not unload: Safari's back/forward cache never fires unload.
if (window.addEventListener) {
window.addEventListener('pagehide', ping_leaving, false);
}
}

View File

@@ -204,7 +204,7 @@ ${LANG_H20} ${html:info.currentjob}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -99,9 +99,6 @@ ${do:if-file-exists:html/index.html}
${do:end-if}
</tr></table>
${/* Default values for empty settings */}
${do:set:cache:1}
${/* Convert winprofile.ini into internal variables */}
${do:copy:CurrentUrl:urls}
${do:copy:Category:projcateg}
@@ -140,6 +137,7 @@ ${do:copy:KeepWww:keepwww}
${do:copy:KeepSlashes:keepslashes}
${do:copy:KeepQueryOrder:keepqueryorder}
${do:copy:StripQuery:stripquery}
${do:copy:HostAlias:hostalias}
${do:copy:StoreAllInCache:cache2}
${do:copy:Sitemap:sitemap}
${do:copy:SitemapUrl:sitemapurl}
@@ -293,7 +291,7 @@ ${do:end-if:}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -196,7 +196,7 @@ ${do:output-mode:}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -144,7 +144,7 @@ ${do:end-if}${do:output-mode:html}
${test:hidequery:--include-query-string=1:--include-query-string=0}
${test:nopurge:--purge-old=1:--purge-old=0}
\
${test:cache:--cache=0:}
${ztest:cache:--cache=0:}
${test:filter::--priority=0:--priority=1:--priority=2:--priority=7}
${test:travel::--stay-on-same-dir:--can-go-down:--can-go-up:--can-go-up-and-down}
${test:travel2::--stay-on-same-address:--stay-on-same-domain:--stay-on-same-tld:--go-everywhere}
@@ -179,13 +179,14 @@ ${/* -m<n> resets the html limit, so the bare form must precede the -m,<n> one *
${ztest:cookies:--cookies=0:}
${ztest:parsejava:--parse-java=0:}
${test:updhack:--updatehack}
${test:urlhack:--urlhack=0:--urlhack}
${ztest:urlhack:--urlhack=0:--urlhack}
${test:keepwww:--keep-www-prefix}
${test:keepslashes:--keep-double-slashes}
${test:keepqueryorder:--keep-query-order}
${test:cookiesfile:--cookies-file "}${arg:cookiesfile}${test:cookiesfile:"}
${test:pausefiles:--pause "}${arg:pausefiles}${test:pausefiles:"}
${test:stripquery:--strip-query "}${arg:stripquery}${test:stripquery:"}
${arglist:stripquery:--strip-query}
${arglist:hostalias:--host-alias}
${test:toler:--tolerant}
${test:http10:--http-10}
${test:cache2:--store-all-in-cache}
@@ -202,8 +203,8 @@ ${/* -m<n> resets the html limit, so the bare form must precede the -m,<n> one *
${test:norecatch:--do-not-recatch}
${test:logf:--single-log}
${test:logtype:::--extra-log:--debug-log}
${test:index:--index=0:}
${test:index2:--search-index=0:--search-index}
${ztest:index:--index=0:}
${ztest:index2:--search-index=0:--search-index}
${test:prox:--proxy "}${do:if-not-empty:prox}${test:proxytype::socks5:connect}${test:proxytype:\3A//}${do:end-if}${do:output-mode:html}${arg:prox}${test:prox:\3A}${arg:portprox}${test:prox:"}
${test:ftpprox:--httpproxy-ftp=0:--httpproxy-ftp}
</textarea>
@@ -248,6 +249,7 @@ KeepWww=${ztest:keepwww:0:1}
KeepSlashes=${ztest:keepslashes:0:1}
KeepQueryOrder=${ztest:keepqueryorder:0:1}
StripQuery=${stripquery}
HostAlias=${hostalias}
StoreAllInCache=${ztest:cache2:0:1}
Sitemap=${ztest:sitemap:0:1}
SitemapUrl=${sitemapurl}
@@ -346,7 +348,7 @@ ${do:output-mode:}
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
<tr>
<td id="footer"><small><small>&copy; 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
<td id="footer"><small><small>&copy; 1998-${date:year} Xavier Roche &amp; other contributors - Web Design: Leto Kauler.</small></small></td>
</tr>
</table>

View File

@@ -740,6 +740,10 @@ LANG_M8
Mirror domain
LANG_M9
Ignore all
LANG_M10
Mirror %s and every host below it
LANG_M11
Ignore %s and every host below it
LANG_N1
Wizard query
LANG_N2
@@ -1034,6 +1038,10 @@ LANG_STRIPQUERY
Strip query keys:
LANG_STRIPQUERYTIP
Comma-separated query keys to drop from the saved-file naming (e.g. sid,utm_source).
LANG_HOSTALIAS
Host aliases:
LANG_HOSTALIASTIP
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
LANG_WARC
Write a WARC archive of the crawl
LANG_WARCTIP

View File

@@ -946,6 +946,36 @@ Server terminated
Ñúðâúðúò íå îòãîâàðÿ
A fatal error has occurred during this mirror
Ôàòàëíà ãðåøêà ïðè ñúçäàâàíåòî íà òîçè îãëåäàëåí ñàéò
View Documentation
Ïðåãëåä íà äîêóìåíòàöèÿòà
Go To HTTrack Website
Êúì óåáñàéòà íà HTTrack
Go To HTTrack Forum
Êúì ôîðóìà íà HTTrack
View License
Ïðåãëåä íà ëèöåíçà
Beware: you local browser might be unable to browse files with embedded filenames
Âíèìàíèå: âàøèÿò ëîêàëåí áðàóçúð ìîæå äà íå óñïåå äà îòâîðè ôàéëîâå ñ âãðàäåíè èìåíà íà ôàéëîâå
Recreated HTTrack internal cached resources
Âúòðåøíèòå êåøèðàíè ðåñóðñè íà HTTrack áÿõà ñúçäàäåíè íàíîâî
Could not create internal cached resources
Âúòðåøíèòå êåøèðàíè ðåñóðñè íå ìîæàõà äà áúäàò ñúçäàäåíè
Could not get the system external storage directory
Ñèñòåìíàòà âúíøíà ïàìåò íå ìîæà äà áúäå íàìåðåíà
Could not write to:
Íå ìîæå äà ñå ïèøå â:
Read-only media (SDCARD)
Íîñèòåë ñàìî çà ÷åòåíå (SDCARD)
No storage media (SDCARD)
Íÿìà íîñèòåë çà ñúõðàíåíèå (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Äîêàòî òîçè ïðîáëåì íå áúäå îòñòðàíåí, HTTrack ìîæå äà íå óñïåå äà ñâàëÿ óåáñàéòîâå
HTTrack: mirror '%s' stopped!
HTTrack: îãëåäàëíîòî êîïèå „%s“ áåøå ñïðÿíî!
Click on this notification to restart the interrupted mirror
Íàòèñíåòå òîâà èçâåñòèå, çà äà ïðîäúëæèòå ïðåêúñíàòîòî îãëåäàëíî êîïèå
HTTrack: could not save profile for '%s'!
HTTrack: ïðîôèëúò çà „%s“ íå ìîæà äà áúäå çàïàçåí!
Proxy type:
Òèï íà ïðîêñè:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Íàé-ãîëÿì ðàçìåð íà WARC ñåãìåíò:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Çàïî÷âàíå íà íîâ WARC ñåãìåíò, êîãàòî òåêóùèÿò íàäõâúðëè òîçè áðîé áàéòîâå; îñòàâåòå ïðàçíî èëè 0 çà åäèí ôàéë.
Host aliases:
Ïñåâäîíèìè íà õîñòà:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Äðóãè èìåíà íà õîñòîâå, êîèòî îáñëóæâàò ñúùèÿ ñàéò, îáåäèíåíè â åäíî, ïî åäíî ïðàâèëî íà ðåä (íàïð. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Íàïðàâè îãëåäàëíî êîïèå íà %s è íà âñè÷êè õîñòîâå ïîä íåãî
Ignore %s and every host below it
Èãíîðèðàé %s è âñè÷êè õîñòîâå ïîä íåãî

View File

@@ -946,6 +946,36 @@ Server terminated
Servidor desconectado
A fatal error has occurred during this mirror
Ha ocurrido un error fatal durante esta copia
View Documentation
Ver la documentación
Go To HTTrack Website
Ir al sitio web de HTTrack
Go To HTTrack Forum
Ir al foro de HTTrack
View License
Ver la licencia
Beware: you local browser might be unable to browse files with embedded filenames
Atención: puede que su navegador local no pueda abrir archivos con nombres de archivo incrustados
Recreated HTTrack internal cached resources
Recursos internos en caché de HTTrack recreados
Could not create internal cached resources
No se han podido crear los recursos internos en caché
Could not get the system external storage directory
No se ha podido acceder al almacenamiento externo del sistema
Could not write to:
No se ha podido escribir en:
Read-only media (SDCARD)
Soporte de solo lectura (SDCARD)
No storage media (SDCARD)
No hay soporte de almacenamiento (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Puede que HTTrack no pueda descargar sitios web mientras no se corrija este problema
HTTrack: mirror '%s' stopped!
HTTrack: ¡el volcado «%s» se ha detenido!
Click on this notification to restart the interrupted mirror
Pulse esta notificación para reanudar el volcado interrumpido
HTTrack: could not save profile for '%s'!
HTTrack: ¡no se ha podido guardar el perfil de «%s»!
Proxy type:
Tipo de proxy:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Tamaño máximo de segmento WARC:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Empezar un nuevo segmento WARC cuando el actual supere este número de bytes; déjelo en blanco o 0 para un solo archivo.
Host aliases:
Alias de host:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Otros nombres de host que sirven este mismo sitio, unificados en uno solo, una regla por línea (p. ej. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Volcar %s y todos los hosts por debajo
Ignore %s and every host below it
Ignorar %s y todos los hosts por debajo

View File

@@ -946,6 +946,36 @@ Server terminated
A fatal error has occurred during this mirror
View Documentation
Zobrazit dokumentaci
Go To HTTrack Website
Pøejít na web HTTrack
Go To HTTrack Forum
Pøejít na fórum HTTrack
View License
Zobrazit licenci
Beware: you local browser might be unable to browse files with embedded filenames
Pozor: váš místní prohlížeè nemusí umìt otevøít soubory s vloženými názvy souborù
Recreated HTTrack internal cached resources
Interní zdroje v mezipamìti HTTrack byly znovu vytvoøeny
Could not create internal cached resources
Interní zdroje v mezipamìti se nepodaøilo vytvoøit
Could not get the system external storage directory
Nepodaøilo se najít systémové externí úložištì
Could not write to:
Nelze zapisovat do:
Read-only media (SDCARD)
Médium jen pro ètení (SDCARD)
No storage media (SDCARD)
Žádné úložné médium (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Dokud nebude tento problém vyøešen, nemusí HTTrack stahovat weby
HTTrack: mirror '%s' stopped!
HTTrack: zrcadlení „%s“ bylo zastaveno!
Click on this notification to restart the interrupted mirror
Klepnutím na toto oznámení obnovíte pøerušené zrcadlení
HTTrack: could not save profile for '%s'!
HTTrack: profil pro „%s“ se nepodaøilo uložit!
Proxy type:
Typ proxy:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Nejvìtší velikost segmentu WARC:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Zaèít nový segment WARC, jakmile aktuální pøekroèí tento poèet bajtù; ponechte prázdné nebo 0 pro jeden soubor.
Host aliases:
Aliasy hostitele:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Další názvy hostitelù poskytující tentýž web, slouèené do jednoho, jedno pravidlo na øádek (napø. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Zrcadlit %s a všechny hostitele pod ním
Ignore %s and every host below it
Ignorovat %s a všechny hostitele pod ním

View File

@@ -679,7 +679,7 @@ Mirror site
Mirror domain
鏡像網域名稱
Ignore all
全部
全部
Wizard query
精靈提問
NO
@@ -946,6 +946,36 @@ Server terminated
伺服器已終止
A fatal error has occurred during this mirror
這鏡像發生了不可回復的錯誤
View Documentation
檢視說明文件
Go To HTTrack Website
前往 HTTrack 網站
Go To HTTrack Forum
前往 HTTrack 論壇
View License
檢視授權條款
Beware: you local browser might be unable to browse files with embedded filenames
注意:本機瀏覽器可能無法開啟檔名內嵌的檔案
Recreated HTTrack internal cached resources
已重新建立 HTTrack 內部快取資源
Could not create internal cached resources
無法建立內部快取資源
Could not get the system external storage directory
無法取得系統外部儲存空間目錄
Could not write to:
無法寫入:
Read-only media (SDCARD)
唯讀媒體 (SDCARD)
No storage media (SDCARD)
沒有儲存媒體 (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
在此問題修正前HTTrack 可能無法下載網站
HTTrack: mirror '%s' stopped!
HTTrack鏡像「%s」已停止
Click on this notification to restart the interrupted mirror
點選此通知以重新啟動中斷的鏡像
HTTrack: could not save profile for '%s'!
HTTrack無法儲存「%s」的設定檔
Proxy type:
proxy 類型:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
WARC 分段大小上限:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
目前分段超過此位元組數時開始新的 WARC 分段;留空或填 0 則保持單一檔案。
Host aliases:
主機別名:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
提供同一網站的其他主機名稱,合併為一個,每行一條規則(例如 www2.example.com,m.example.com=example.com
Mirror %s and every host below it
鏡像 %s 及其下所有主機
Ignore %s and every host below it
忽略 %s 及其下所有主機

View File

@@ -946,6 +946,36 @@ Server terminated
A fatal error has occurred during this mirror
View Documentation
查看文档
Go To HTTrack Website
前往 HTTrack 网站
Go To HTTrack Forum
前往 HTTrack 论坛
View License
查看许可协议
Beware: you local browser might be unable to browse files with embedded filenames
注意:本地浏览器可能无法打开文件名内嵌的文件
Recreated HTTrack internal cached resources
已重新创建 HTTrack 内部缓存资源
Could not create internal cached resources
无法创建内部缓存资源
Could not get the system external storage directory
无法获取系统外部存储目录
Could not write to:
无法写入:
Read-only media (SDCARD)
只读介质 (SDCARD)
No storage media (SDCARD)
没有存储介质 (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
在此问题修复前HTTrack 可能无法下载网站
HTTrack: mirror '%s' stopped!
HTTrack镜像“%s”已停止
Click on this notification to restart the interrupted mirror
点击此通知以重新启动中断的镜像
HTTrack: could not save profile for '%s'!
HTTrack无法保存“%s”的配置文件
Proxy type:
代理类型:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
WARC 分段大小上限:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
当前分段超过该字节数时开始新的 WARC 分段;留空或填 0 则保持单个文件。
Host aliases:
主机别名:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
提供同一网站的其他主机名,合并为一个,每行一条规则(例如 www2.example.com,m.example.com=example.com
Mirror %s and every host below it
镜像 %s 及其下所有主机
Ignore %s and every host below it
忽略 %s 及其下所有主机

View File

@@ -946,6 +946,36 @@ Server terminated
Poslužitelj je razriješen
A fatal error has occurred during this mirror
Tijekom ovog zrcaljenja je nastala fatalna pogreška
View Documentation
Prikaži dokumentaciju
Go To HTTrack Website
Otvori mrežno mjesto HTTrack
Go To HTTrack Forum
Otvori forum HTTrack
View License
Prikaži licencu
Beware: you local browser might be unable to browse files with embedded filenames
Pozor: vaš lokalni preglednik možda neæe moæi otvoriti datoteke s ugraðenim nazivima datoteka
Recreated HTTrack internal cached resources
Interni predmemorirani resursi HTTracka ponovno su stvoreni
Could not create internal cached resources
Interne predmemorirane resurse nije moguæe stvoriti
Could not get the system external storage directory
Nije moguæe pronaæi vanjsku pohranu sustava
Could not write to:
Nije moguæe pisati u:
Read-only media (SDCARD)
Medij samo za èitanje (SDCARD)
No storage media (SDCARD)
Nema medija za pohranu (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Dok se taj problem ne riješi, HTTrack možda neæe moæi preuzimati mrežna mjesta
HTTrack: mirror '%s' stopped!
HTTrack: zrcaljenje „%s” je zaustavljeno!
Click on this notification to restart the interrupted mirror
Dodirnite tu obavijest za nastavak prekinutog zrcaljenja
HTTrack: could not save profile for '%s'!
HTTrack: profil za „%s” nije moguæe spremiti!
Proxy type:
Vrsta posrednika:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Najveæa velièina WARC segmenta:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Zapoèni novi WARC segment kada trenutni prijeðe ovaj broj bajtova; ostavite prazno ili 0 za jednu datoteku.
Host aliases:
Aliasi poslužitelja:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Ostala imena poslužitelja koja poslužuju isto mrežno mjesto, sažeta u jedno, jedno pravilo po retku (npr. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Zrcali %s i sve poslužitelje ispod njega
Ignore %s and every host below it
Zanemari %s i sve poslužitelje ispod njega

View File

@@ -1044,3 +1044,11 @@ WARC segment size limit:
Største størrelse på WARC-segment:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Begynd et nyt WARC-segment, når det aktuelle overstiger dette antal byte; lad feltet stå tomt eller skriv 0 for én fil.
Host aliases:
Værtsaliasser:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Andre værtsnavne der betjener det samme websted, samlet til ét, én regel pr. linje (f.eks. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Spejlkopiér %s og alle værter under den
Ignore %s and every host below it
Ignorer %s og alle værter under den

View File

@@ -946,6 +946,36 @@ Server terminated
Der Server wurde beendet
A fatal error has occurred during this mirror
Fataler Fehler während der Webseiten-Kopie
View Documentation
Dokumentation anzeigen
Go To HTTrack Website
Zur HTTrack-Website
Go To HTTrack Forum
Zum HTTrack-Forum
View License
Lizenz anzeigen
Beware: you local browser might be unable to browse files with embedded filenames
Achtung: Ihr lokaler Browser kann Dateien mit eingebetteten Dateinamen möglicherweise nicht öffnen
Recreated HTTrack internal cached resources
Interne zwischengespeicherte Ressourcen von HTTrack neu angelegt
Could not create internal cached resources
Interne zwischengespeicherte Ressourcen konnten nicht angelegt werden
Could not get the system external storage directory
Der externe Systemspeicher konnte nicht ermittelt werden
Could not write to:
Schreiben nicht möglich in:
Read-only media (SDCARD)
Schreibgeschützter Datenträger (SDCARD)
No storage media (SDCARD)
Kein Datenträger vorhanden (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Solange dieses Problem besteht, kann HTTrack möglicherweise keine Websites herunterladen
HTTrack: mirror '%s' stopped!
HTTrack: Kopie "%s" wurde angehalten!
Click on this notification to restart the interrupted mirror
Tippen Sie auf diese Benachrichtigung, um die unterbrochene Kopie fortzusetzen
HTTrack: could not save profile for '%s'!
HTTrack: Profil für "%s" konnte nicht gespeichert werden!
Proxy type:
Proxy-Typ:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Maximale Größe eines WARC-Segments:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Ein neues WARC-Segment beginnen, sobald das aktuelle diese Anzahl Bytes überschreitet; leer lassen oder 0 für eine einzige Datei.
Host aliases:
Host-Aliase:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Weitere Hostnamen derselben Website, auf einen zusammengefasst, eine Regel pro Zeile (z. B. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
%s und alle darunter liegenden Hosts kopieren
Ignore %s and every host below it
%s und alle darunter liegenden Hosts ignorieren

View File

@@ -946,6 +946,36 @@ Server terminated
A fatal error has occurred during this mirror
View Documentation
Vaata dokumentatsiooni
Go To HTTrack Website
Ava HTTracki veebisait
Go To HTTrack Forum
Ava HTTracki foorum
View License
Vaata litsentsi
Beware: you local browser might be unable to browse files with embedded filenames
Tähelepanu: kohalik brauser ei pruugi suuta avada faile, mille nimi on faili sisse põimitud
Recreated HTTrack internal cached resources
HTTracki sisemised puhverdatud ressursid loodi uuesti
Could not create internal cached resources
Sisemisi puhverdatud ressursse ei õnnestunud luua
Could not get the system external storage directory
Süsteemi välist salvestusruumi ei leitud
Could not write to:
Ei saa kirjutada asukohta:
Read-only media (SDCARD)
Kirjutuskaitstud andmekandja (SDCARD)
No storage media (SDCARD)
Andmekandjat pole (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Kuni see probleem püsib, ei pruugi HTTrack veebisaite alla laadida
HTTrack: mirror '%s' stopped!
HTTrack: koopia "%s" peatati!
Click on this notification to restart the interrupted mirror
Katkenud koopia jätkamiseks puuduta seda teadet
HTTrack: could not save profile for '%s'!
HTTrack: profiili "%s" ei õnnestunud salvestada!
Proxy type:
Proxy tüüp:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
WARC-segmendi suurim maht:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Alusta uut WARC-segmenti, kui praegune ületab selle baitide arvu; jäta tühjaks või 0, et hoida üks fail.
Host aliases:
Hosti aliased:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Muud sama saiti teenindavad hostinimed, koondatud üheks, üks reegel rea kohta (nt www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Kopeeri %s ja kõik selle all olevad hostid
Ignore %s and every host below it
Ignoreeri %s ja kõiki selle all olevaid hoste

View File

@@ -1044,3 +1044,11 @@ WARC segment size limit:
WARC segment size limit:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Host aliases:
Host aliases:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Mirror %s and every host below it
Ignore %s and every host below it
Ignore %s and every host below it

View File

@@ -946,6 +946,36 @@ Server terminated
Palvelin lopetettu
A fatal error has occurred during this mirror
Tällä peilillä tapahtui vakava virhe
View Documentation
Näytä ohjeet
Go To HTTrack Website
Siirry HTTrackin sivustolle
Go To HTTrack Forum
Siirry HTTrackin foorumille
View License
Näytä lisenssi
Beware: you local browser might be unable to browse files with embedded filenames
Huomio: paikallinen selaimesi ei ehkä pysty avaamaan tiedostoja, joiden nimi on upotettu
Recreated HTTrack internal cached resources
HTTrackin sisäiset välimuistiresurssit luotiin uudelleen
Could not create internal cached resources
Sisäisiä välimuistiresursseja ei voitu luoda
Could not get the system external storage directory
Järjestelmän ulkoista tallennushakemistoa ei löytynyt
Could not write to:
Ei voi kirjoittaa kohteeseen:
Read-only media (SDCARD)
Vain luku -media (SDCARD)
No storage media (SDCARD)
Ei tallennusmediaa (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
HTTrack ei ehkä pysty lataamaan sivustoja ennen kuin tämä ongelma on korjattu
HTTrack: mirror '%s' stopped!
HTTrack: peilaus "%s" pysäytettiin!
Click on this notification to restart the interrupted mirror
Jatka keskeytynyttä peilausta napauttamalla tätä ilmoitusta
HTTrack: could not save profile for '%s'!
HTTrack: profiilia "%s" ei voitu tallentaa!
Proxy type:
Välityspalvelimen tyyppi:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
WARC-osan enimmäiskoko:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Aloita uusi WARC-osa, kun nykyinen ylittää tämän tavumäärän; jätä tyhjäksi tai 0, jolloin tiedosto pysyy yhtenä.
Host aliases:
Palvelimen aliakset:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Muut samaa sivustoa palvelevat palvelinnimet yhdistettynä yhdeksi, yksi sääntö riviä kohden (esim. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Peilaa %s ja kaikki sen alla olevat palvelimet
Ignore %s and every host below it
Sivuuta %s ja kaikki sen alla olevat palvelimet

View File

@@ -1044,3 +1044,11 @@ WARC segment size limit:
Taille maximale d'un segment WARC :
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Commencer un nouveau segment WARC dès que le courant dépasse ce nombre d'octets ; laissez vide ou 0 pour un fichier unique.
Host aliases:
Alias d'hôtes :
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Autres noms d'hôtes servant ce même site, ramenés à un seul, une règle par ligne (ex. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Copier %s et tous les hôtes en dessous
Ignore %s and every host below it
Ignorer %s et tous les hôtes en dessous

View File

@@ -946,6 +946,36 @@ Server terminated
Ακυρώθηκε από τον εξυπηρετητή
A fatal error has occurred during this mirror
Ένα καταστροφικό σφάλμα προκλήθηκε κατά την αντιγραφή αυτού του τόπου
View Documentation
Προβολή τεκμηρίωσης
Go To HTTrack Website
Μετάβαση στον ιστότοπο του HTTrack
Go To HTTrack Forum
Μετάβαση στο φόρουμ του HTTrack
View License
Προβολή άδειας χρήσης
Beware: you local browser might be unable to browse files with embedded filenames
Προσοχή: ο τοπικός σας browser ίσως δεν μπορεί να ανοίξει αρχεία με ενσωματωμένα ονόματα αρχείων
Recreated HTTrack internal cached resources
Οι εσωτερικοί προσωρινοί πόροι του HTTrack δημιουργήθηκαν ξανά
Could not create internal cached resources
Δεν ήταν δυνατή η δημιουργία των εσωτερικών προσωρινών πόρων
Could not get the system external storage directory
Δεν ήταν δυνατή η εύρεση του εξωτερικού χώρου αποθήκευσης του συστήματος
Could not write to:
Δεν ήταν δυνατή η εγγραφή στο:
Read-only media (SDCARD)
Μέσο μόνο για ανάγνωση (SDCARD)
No storage media (SDCARD)
Δεν υπάρχει μέσο αποθήκευσης (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Μέχρι να διορθωθεί αυτό το πρόβλημα, το HTTrack ίσως δεν μπορεί να κατεβάσει ιστότοπους
HTTrack: mirror '%s' stopped!
HTTrack: η αντιγραφή «%s» σταμάτησε!
Click on this notification to restart the interrupted mirror
Πατήστε αυτή την ειδοποίηση για να συνεχίσετε τη διακοπείσα αντιγραφή
HTTrack: could not save profile for '%s'!
HTTrack: δεν ήταν δυνατή η αποθήκευση του προφίλ «%s»!
Proxy type:
Τύπος proxy:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Μέγιστο μέγεθος τμήματος WARC:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Έναρξη νέου τμήματος WARC μόλις το τρέχον ξεπεράσει αυτόν τον αριθμό byte. Αφήστε κενό ή 0 για ένα ενιαίο αρχείο.
Host aliases:
Ψευδώνυμα host:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Άλλα ονόματα host που εξυπηρετούν την ίδια τοποθεσία, ενοποιημένα σε ένα, ένας κανόνας ανά γραμμή (π.χ. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Αντιγραφή του %s και κάθε host κάτω από αυτό
Ignore %s and every host below it
Αγνόηση του %s και κάθε host κάτω από αυτό

View File

@@ -946,6 +946,36 @@ Server terminated
Server disconnesso
A fatal error has occurred during this mirror
Si è verificato un errore fatale durante la copia
View Documentation
Visualizza la documentazione
Go To HTTrack Website
Vai al sito di HTTrack
Go To HTTrack Forum
Vai al forum di HTTrack
View License
Visualizza la licenza
Beware: you local browser might be unable to browse files with embedded filenames
Attenzione: il browser locale potrebbe non riuscire ad aprire i file con nomi di file incorporati
Recreated HTTrack internal cached resources
Risorse interne nella cache di HTTrack ricreate
Could not create internal cached resources
Impossibile creare le risorse interne nella cache
Could not get the system external storage directory
Impossibile individuare la memoria esterna di sistema
Could not write to:
Impossibile scrivere in:
Read-only media (SDCARD)
Supporto di sola lettura (SDCARD)
No storage media (SDCARD)
Nessun supporto di memorizzazione (SDCARD)
HTTrack may not be able to download websites until this problem is fixed
Finché il problema non è risolto, HTTrack potrebbe non riuscire a scaricare siti web
HTTrack: mirror '%s' stopped!
HTTrack: mirror "%s" interrotto!
Click on this notification to restart the interrupted mirror
Tocca questa notifica per riprendere il mirror interrotto
HTTrack: could not save profile for '%s'!
HTTrack: impossibile salvare il profilo di "%s"!
Proxy type:
Tipo di proxy:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.
@@ -1014,3 +1044,11 @@ WARC segment size limit:
Dimensione massima del segmento WARC:
Start a new WARC segment once the current one grows past this many bytes; leave blank or 0 to keep a single file.
Inizia un nuovo segmento WARC quando quello corrente supera questo numero di byte; lascia vuoto o 0 per un solo file.
Host aliases:
Alias host:
Other hostnames serving this same site, folded onto one, one rule per line (e.g. www2.example.com,m.example.com=example.com).
Altri nomi host che servono questo stesso sito, ricondotti a uno solo, una regola per riga (es. www2.example.com,m.example.com=example.com).
Mirror %s and every host below it
Mirror di %s e di tutti gli host sottostanti
Ignore %s and every host below it
Ignora %s e tutti gli host sottostanti

Some files were not shown because too many files have changed in this diff Show More