mirror of
https://github.com/xroche/httrack.git
synced 2026-08-02 14:01:43 +03:00
Compare commits
16 Commits
3.49.15
...
fix-908-ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b270becb5 | ||
|
|
b4263417c3 | ||
|
|
2d041866ed | ||
|
|
eab2929637 | ||
|
|
5f52c3d942 | ||
|
|
b1218d8aa7 | ||
|
|
592f3dd154 | ||
|
|
620418a9c6 | ||
|
|
f3fa3a8b98 | ||
|
|
311b99afd4 | ||
|
|
ef81f5b488 | ||
|
|
22c506944d | ||
|
|
b127323a68 | ||
|
|
fac60dff4a | ||
|
|
264478c846 | ||
|
|
8f383f4caf |
96
.github/workflows/ci.yml
vendored
96
.github/workflows/ci.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
# so point configure at it; everything else is in the SDK or default paths.
|
||||
macos:
|
||||
name: build (macOS arm64, clang)
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
# temp prefix, then check webhttrack brings up htsserver and serves the UI.
|
||||
webhttrack-macos:
|
||||
name: webhttrack smoke (macOS arm64)
|
||||
runs-on: macos-14
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -217,6 +217,95 @@ jobs:
|
||||
- name: Smoke-test webhttrack
|
||||
run: bash tests/webhttrack-smoke.sh "$RUNNER_TEMP/inst"
|
||||
|
||||
# Same smoke on the platform that ships webhttrack as a package: the install layout
|
||||
# it asserts (a relative data symlink, #885) is what Debian consumes, and the macOS
|
||||
# job alone left that untested on Linux.
|
||||
webhttrack-linux:
|
||||
name: webhttrack smoke (Linux x86-64)
|
||||
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
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential autoconf automake libtool autoconf-archive \
|
||||
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev
|
||||
|
||||
- name: Build and install into a temp prefix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
./bootstrap
|
||||
./configure --prefix="$RUNNER_TEMP/inst"
|
||||
make -j"$(nproc)"
|
||||
make install
|
||||
|
||||
- name: Smoke-test webhttrack
|
||||
run: bash tests/webhttrack-smoke.sh "$RUNNER_TEMP/inst"
|
||||
|
||||
# The macOS app bundle (#886): assemble it, then prove it still works after being
|
||||
# moved, which is the only thing a .app has to survive that a prefix install does not.
|
||||
macos-app:
|
||||
name: macOS app bundle (arm64)
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
set -euo pipefail
|
||||
brew install autoconf automake libtool autoconf-archive brotli zstd
|
||||
|
||||
# --disable-shared keeps libhttrack inside the binaries, so the bundle carries no
|
||||
# absolute dylib path back to the staging prefix.
|
||||
- name: Build and install into a temp prefix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ssl="$(brew --prefix openssl@3)"
|
||||
brewp="$(brew --prefix)"
|
||||
./bootstrap
|
||||
./configure CPPFLAGS="-I${ssl}/include -I${brewp}/include" \
|
||||
LDFLAGS="-L${ssl}/lib -L${brewp}/lib" \
|
||||
--prefix="$RUNNER_TEMP/inst" --disable-shared
|
||||
make -j"$(sysctl -n hw.ncpu)"
|
||||
make install
|
||||
|
||||
- name: Assemble HTTrack.app
|
||||
run: |
|
||||
set -euo pipefail
|
||||
make macos-app APP_FLAGS="--prefix $RUNNER_TEMP/inst --out $RUNNER_TEMP"
|
||||
plutil -lint "$RUNNER_TEMP/HTTrack.app/Contents/Info.plist"
|
||||
|
||||
- name: Run it through the bundle stub
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app="$RUNNER_TEMP/HTTrack.app"
|
||||
bash tests/webhttrack-smoke.sh "$app/Contents/Resources" "$app/Contents/MacOS/HTTrack"
|
||||
|
||||
- name: Run it again after moving it
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/moved"
|
||||
mv "$RUNNER_TEMP/HTTrack.app" "$RUNNER_TEMP/moved/"
|
||||
rm -rf "$RUNNER_TEMP/inst"
|
||||
app="$RUNNER_TEMP/moved/HTTrack.app"
|
||||
bash tests/webhttrack-smoke.sh "$app/Contents/Resources" "$app/Contents/MacOS/HTTrack"
|
||||
|
||||
# No Developer ID here, so Gatekeeper cannot be exercised; an ad-hoc signature
|
||||
# still proves the bundle is well formed enough to sign.
|
||||
- name: Check the bundle is signable
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app="$RUNNER_TEMP/moved/HTTrack.app"
|
||||
codesign -s - --force --deep "$app"
|
||||
codesign --verify --strict --verbose=2 "$app"
|
||||
|
||||
# Portability/hardening: 32-bit (i386) build on the x86-64 runner via multilib
|
||||
# -- no extra hardware. Exercises the 32-bit size_t/pointer ABI, where size
|
||||
# and bounds math can truncate or wrap in ways 64-bit never reveals (the axis
|
||||
@@ -593,9 +682,10 @@ jobs:
|
||||
man/makeman.sh
|
||||
src/htsbasiccharsets.sh
|
||||
src/htsentities.sh
|
||||
src/webhttrack
|
||||
src/webhttrack.in
|
||||
tests/*.sh
|
||||
tests/*.test
|
||||
tools/macos-app.sh
|
||||
tools/mkdeb.sh
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
7
.github/workflows/windows-build.yml
vendored
7
.github/workflows/windows-build.yml
vendored
@@ -265,15 +265,18 @@ jobs:
|
||||
# cause a false mismatch either.
|
||||
# footer-overflow and purge-longpath skip on Windows (need a path past MAX_PATH);
|
||||
# crange pending #581;
|
||||
# webdav-default and webdav-mime need a reapable background listener, which MSYS cannot give them;
|
||||
# webdav-default, webdav-mime and webdav-overflow need a reapable background listener, which MSYS cannot give them;
|
||||
# badmtime needs a filesystem that stores an mtime past gmtime's range;
|
||||
# single-file ends on a GUI half needing htsserver, which this job does not build;
|
||||
# update-304-leak needs a LeakSanitizer build, which MSVC has no equivalent of;
|
||||
# crash-symbolize needs backtrace(), which Windows has no equivalent of.
|
||||
# crash-symbolize and backtrace-empty need backtrace(), which Windows has no
|
||||
# equivalent of.
|
||||
expected_skips="01_engine-footer-overflow.test
|
||||
100_local-purge-longpath.test
|
||||
114_local-update-304-leak.test
|
||||
120_local-proxytrack-webdav-default.test
|
||||
143_engine-backtrace-empty.test
|
||||
147_local-proxytrack-webdav-overflow.test
|
||||
48_local-crange-memresume.test
|
||||
71_local-crange-repaircache.test
|
||||
79_local-proxytrack-webdav-mime.test
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,6 +24,8 @@ Makefile.in
|
||||
/config.log
|
||||
/config.status
|
||||
/stamp-h1
|
||||
# src/webhttrack.in is the source; an in-tree build generates this one (#887).
|
||||
/src/webhttrack
|
||||
Makefile
|
||||
.deps/
|
||||
.libs/
|
||||
|
||||
12
Makefile.am
12
Makefile.am
@@ -6,13 +6,21 @@ ACLOCAL_AMFLAGS = -I m4
|
||||
EXTRA_DIST = INSTALL.Linux \
|
||||
gpl-fr.txt license.txt greetings.txt history.txt \
|
||||
httrack-doc.html lang.def README.md tools/mkdeb.sh \
|
||||
tools/macos-app.sh tools/Info.plist.in \
|
||||
bootstrap build.sh
|
||||
|
||||
# Build the signed Debian packages from a clean source export. Pass the signing
|
||||
# key and other options through DEB_FLAGS, e.g.:
|
||||
# make deb DEB_FLAGS="--key BB71C7E6CB1AD8FAF53FE42A60C3AA7180598EFB"
|
||||
# See tools/mkdeb.sh --help for all options.
|
||||
# See tools/mkdeb.sh --help for all options. Not $(SHELL): mkdeb.sh is bash, and dash chokes (#891).
|
||||
DEB_FLAGS =
|
||||
deb:
|
||||
$(SHELL) $(top_srcdir)/tools/mkdeb.sh $(DEB_FLAGS)
|
||||
$(BASH_SHELL) $(top_srcdir)/tools/mkdeb.sh $(DEB_FLAGS)
|
||||
.PHONY: deb
|
||||
|
||||
# Assemble the macOS application bundle from an installed prefix, e.g.
|
||||
# make macos-app APP_FLAGS="--prefix /tmp/inst --out /tmp"
|
||||
APP_FLAGS =
|
||||
macos-app:
|
||||
$(SHELL) $(top_srcdir)/tools/macos-app.sh --plist tools/Info.plist $(APP_FLAGS)
|
||||
.PHONY: macos-app
|
||||
|
||||
71
configure.ac
71
configure.ac
@@ -38,6 +38,68 @@ VERSION_INFO="3:7:0"
|
||||
AM_MAINTAINER_MODE
|
||||
AC_USE_SYSTEM_EXTENSIONS
|
||||
|
||||
# A real bash, for "make deb" and the test harness. Not searched into BASH: bash presets
|
||||
# that to its own path, and macOS /bin/sh is a bash, so the macro never searches (#895).
|
||||
# BASH_SHELL isn't preset the way BASH is, so AC_ARG_VAR needs no guard. Kept ahead of the
|
||||
# compiler probes so a bad override dies before them.
|
||||
AC_ARG_VAR([BASH_SHELL], [path to a real (non-POSIX-mode) bash])
|
||||
# AC_PATH_PROGS drops a relative override and searches instead, which loses the user's intent.
|
||||
# Nothing quotes $(BASH_SHELL) in the Makefiles, and quoting could not save it anyway: make
|
||||
# splits on whitespace, expands '$' and treats '#' as a comment before any shell sees it.
|
||||
case $BASH_SHELL in
|
||||
*[[[:space:]]]* | *'#'* | *'$'* | *'`'* | *'\'* | *'"'* | *"'"* | *';'* | *'&'* | *'|'* | \
|
||||
*'<'* | *'>'* | *'('* | *')'* | *'*'* | *'?'* | *'@<:@'* | *'@:>@'* | *'{'* | *'}'*)
|
||||
AC_MSG_ERROR([BASH_SHELL must not contain shell or make metacharacters, got: $BASH_SHELL]) ;;
|
||||
'' | [[\\/]]* | ?:[[\\/]]*) ;;
|
||||
*) AC_MSG_ERROR([BASH_SHELL must be an absolute path, got: $BASH_SHELL]) ;;
|
||||
esac
|
||||
hts_bash_override=$BASH_SHELL
|
||||
AC_PATH_PROGS([BASH_SHELL], [bash], [/bin/bash])
|
||||
|
||||
# An absolute override is taken verbatim, so BASH_SHELL=/bin/sh would put #895 back and only
|
||||
# surface at "make check" or "make deb" (#908). What we found ourselves is only a warning:
|
||||
# a box with no bash still builds, it just cannot run those two.
|
||||
AC_MSG_CHECKING([whether $BASH_SHELL is a bash outside POSIX mode])
|
||||
hts_bash_why=
|
||||
hts_bash_env=no
|
||||
if test ! -x "$BASH_SHELL"; then
|
||||
hts_bash_why="not an executable file"
|
||||
elif test -z "$("$BASH_SHELL" -c 'echo "${BASH_VERSINFO[[0]]}"' 2>/dev/null)"; then
|
||||
# Not BASH_VERSION: that is an ordinary variable, so any shell echoes back a spoofed one.
|
||||
hts_bash_why="not a bash: it reports no BASH_VERSINFO"
|
||||
else
|
||||
# sh-mode bash reports a version too, so only SHELLOPTS tells the two apart.
|
||||
case $("$BASH_SHELL" -c 'echo ":$SHELLOPTS:"' 2>/dev/null) in
|
||||
*:posix:*)
|
||||
hts_bash_why="a bash in POSIX sh-mode"
|
||||
# POSIXLY_CORRECT and an exported SHELLOPTS do that to every bash on the box, so no path
|
||||
# can pass and blaming this one would send the user hunting for another. Reading them
|
||||
# here would not do: configure puts its own shell in posix mode, which sets both.
|
||||
case $(env -u POSIXLY_CORRECT -u SHELLOPTS "$BASH_SHELL" -c 'echo ":$SHELLOPTS:"' 2>/dev/null) in
|
||||
'' | *:posix:*) ;; # no "env -u", or posix whatever the environment: blame the path
|
||||
*) hts_bash_env=yes ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if test -z "$hts_bash_why"; then
|
||||
AC_MSG_RESULT([yes])
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
hts_bash_msg="POSIXLY_CORRECT or SHELLOPTS forces every bash into POSIX sh-mode, $BASH_SHELL included. Clear them for configure and for make, which hands them to make check and make deb: env -u POSIXLY_CORRECT -u SHELLOPTS ..."
|
||||
if test "$hts_bash_env" = yes; then
|
||||
if test -n "$hts_bash_override"; then
|
||||
AC_MSG_ERROR([$hts_bash_msg])
|
||||
fi
|
||||
AC_MSG_WARN([$hts_bash_msg])
|
||||
else
|
||||
if test -n "$hts_bash_override"; then
|
||||
AC_MSG_ERROR([BASH_SHELL=$BASH_SHELL is $hts_bash_why])
|
||||
fi
|
||||
AC_MSG_WARN([no usable bash found: $BASH_SHELL is $hts_bash_why. "make check" and "make deb" need one; pass BASH_SHELL=/path/to/bash])
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_PROG_CC
|
||||
AM_PROG_CC_C_O
|
||||
m4_warn([obsolete],
|
||||
@@ -48,14 +110,13 @@ m4_warn([obsolete],
|
||||
# script's behavior did not change. They are probably safe to remove.
|
||||
AC_CHECK_INCLUDES_DEFAULT
|
||||
AC_PROG_EGREP
|
||||
# $(SED) substitutes $(datadir) into src/webhttrack
|
||||
AC_PROG_SED
|
||||
|
||||
LT_INIT
|
||||
AC_PROG_LN_S
|
||||
LT_INIT
|
||||
|
||||
# bash, used to run the test scripts (see tests/Makefile.am TEST_LOG_COMPILER)
|
||||
AC_PATH_PROGS([BASH], [bash], [/bin/bash])
|
||||
|
||||
# Export LD_LIBRARY_PATH name or equivalent.
|
||||
AC_SUBST(SHLIBPATH_VAR,$shlibpath_var)
|
||||
|
||||
@@ -100,6 +161,9 @@ AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [DEFAULT_CFLAGS="$DEFAULT_CFLA
|
||||
[AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"], [], [-Werror])], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"], [], [-Werror])
|
||||
# backtrace() unwinds through these; armhf, unlike amd64/arm64, defaults them off
|
||||
# and printed frameless crash reports.
|
||||
AX_CHECK_COMPILE_FLAG([-fasynchronous-unwind-tables], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fasynchronous-unwind-tables"], [], [-Werror])
|
||||
# No --discard-all: it drops the local symbols naming every static function, so
|
||||
# a trace misattributes them to the nearest surviving global. Costs 0.6% size.
|
||||
AX_CHECK_LINK_FLAG([-Wl,--no-undefined], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--no-undefined"])
|
||||
@@ -360,5 +424,6 @@ html/Makefile
|
||||
libtest/Makefile
|
||||
tests/Makefile
|
||||
fuzz/Makefile
|
||||
tools/Info.plist
|
||||
])
|
||||
AC_OUTPUT
|
||||
|
||||
@@ -128,7 +128,7 @@ This file lists all changes and fixes that have been made for HTTrack
|
||||
+ Fixed: report why a -%L URL list could not be loaded (#49)
|
||||
+ Changed: multiple internal hardening, build and CI improvements
|
||||
|
||||
.49-9
|
||||
3.49-9
|
||||
+ Fixed: file-type detection from the Content-Type header: trust a declared type over a binary URL extension, honor --assume under the delayed type check, and keep a known extension against a bogus or empty Content-Type (#267, #29, #56)
|
||||
+ Fixed: an uninitialized-buffer read when the Content-Type is empty (#411)
|
||||
+ Fixed: restored C++ source-compatibility of the installed headers so reverse dependencies (httraqt) build again (#413)
|
||||
|
||||
@@ -40,10 +40,3 @@ EXTRA_DIST = $(HelpHtml_DATA) $(HelpHtmlimg_DATA) $(HelpHtmlimages_DATA) \
|
||||
$(HelpHtmldiv_DATA) $(WebHtml_DATA) $(WebHtmlimages_DATA) \
|
||||
$(WebPixmap_DATA) $(WebIcon16x16_DATA) $(WebIcon32x32_DATA) $(WebIcon48x48_DATA) \
|
||||
$(VFolderEntry_DATA) $(MetaInfo_DATA)
|
||||
|
||||
install-data-hook:
|
||||
if test ! -L $(DESTDIR)$(prefix)/share/httrack/html ; then \
|
||||
( cd $(DESTDIR)$(prefix)/share/httrack \
|
||||
&& $(LN_S) $(htmldir) html \
|
||||
) \
|
||||
fi
|
||||
|
||||
@@ -72,14 +72,14 @@ body {
|
||||
background: var(--panel);
|
||||
border-bottom: 6px solid #000;
|
||||
padding: 1.5rem;
|
||||
/* The rings the 2007 pages carried. The image has the light panel colour baked
|
||||
into it, so it is dropped rather than inverted in dark mode. */
|
||||
background-image: url(images/bg_rings.gif);
|
||||
/* The rings the 2007 pages carried. Transparent, so it rides on --panel. */
|
||||
background-image: url(images/bg_rings.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* Rings dropped, not inverted: their lavender is a light-panel tone. */
|
||||
.wrap { background-image: none; }
|
||||
|
||||
/* The wordmark is black on transparent, and all but vanishes on the dark field. */
|
||||
|
||||
@@ -66,7 +66,7 @@ the screenshots and notes follow along.</p>
|
||||
|
||||
<div class="platforms" role="group" aria-label="Choose your version">
|
||||
<button type="button" data-platform="win" aria-pressed="false">WinHTTrack <small>Windows</small></button>
|
||||
<button type="button" data-platform="web" aria-pressed="false">WebHTTrack <small>Linux and Unix</small></button>
|
||||
<button type="button" data-platform="web" aria-pressed="false">WebHTTrack <small>Linux, macOS and Unix</small></button>
|
||||
<button type="button" data-platform="droid" aria-pressed="false">HTTrack <small>Android</small></button>
|
||||
</div>
|
||||
|
||||
@@ -125,6 +125,11 @@ data-for="droid">Tap <b>Next</b></span> to create a project, or open one you alr
|
||||
<p class="note" data-for="web">The language dropdown starts blank on purpose: its first entry
|
||||
means "leave the interface as it is". Pick a language only if you want to change it.</p>
|
||||
|
||||
<p class="note" data-for="web">On macOS, <code>brew install httrack</code> installs WebHTTrack
|
||||
alongside the command line tool. Open <b>HTTrack.app</b> if you have it, or run
|
||||
<code>webhttrack</code> in a terminal; either way the interface opens in your browser and
|
||||
behaves exactly as described here.</p>
|
||||
|
||||
<h2 id="step-project">2. Name the project</h2>
|
||||
|
||||
<p>A project is one mirror: its name becomes the folder your files land in, so give it something
|
||||
|
||||
@@ -37,7 +37,7 @@ a:active { text-decoration: underline; }
|
||||
border-bottom: 6px solid #000;
|
||||
padding: 10px; padding-top: 20px;
|
||||
line-height: 1.65em;
|
||||
background-image: url(images/bg_rings.gif);
|
||||
background-image: url(images/bg_rings.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
8
html/images/bg_rings.svg
Normal file
8
html/images/bg_rings.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="501" height="456" viewBox="0 0 501 456">
|
||||
<title>HTTrack rings</title>
|
||||
<!-- Transparent, unlike the GIF this replaces: the panel's own #ccd shows through. -->
|
||||
<g fill="#b9b9d0" fill-rule="evenodd">
|
||||
<path d="M614.59 346.3A323.36 197.55 21.16 1 0 11.46 112.89A323.36 197.55 21.16 1 0 614.59 346.3 ZM590.57 315.66A262.58 160.99 22.18 1 0 104.26 117.43A262.58 160.99 22.18 1 0 590.57 315.66 Z"/>
|
||||
<path d="M578.08 292.16A185.6 113.57 21.23 1 0 232.08 157.73A185.6 113.57 21.23 1 0 578.08 292.16 ZM564.4 274.56A150.77 92.55 22.26 1 0 285.34 160.32A150.77 92.55 22.26 1 0 564.4 274.56 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 641 B |
@@ -49,7 +49,72 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<!-- Newest first; tests/01_engine-version-macros.test enforces it. -->
|
||||
<releases>
|
||||
<release version="3.49.8" date="2026-06-07"/>
|
||||
<release version="3.49.15" date="2026-07-30">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Save a page and everything it needs as one self-contained file</li>
|
||||
<li>Read a site's sitemap, so pages nothing links to are still found</li>
|
||||
<li>See what a new pass added, updated or removed</li>
|
||||
<li>Updating a mirror no longer destroys a good local copy when a download fails or is interrupted</li>
|
||||
<li>In the web interface, options ticked by default can be un-ticked again, the size limit applies to the whole site, and the finished mirror opens from its link</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.14" date="2026-07-24">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Save a mirror as a web archive, indexed and packaged for replay</li>
|
||||
<li>Choose what the page footer says</li>
|
||||
<li>A mirror saved to a folder with accented characters in its name lands in the right place</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.13" date="2026-07-18">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Mirror through a SOCKS5 proxy</li>
|
||||
<li>Files of 2 GB and over are handled correctly on Windows and on 32-bit systems</li>
|
||||
<li>The web interface offers options that used to be command-line only, among them a cookies file and a pause between downloads</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.12" date="2026-07-10">
|
||||
<description>
|
||||
<p>Links carrying accented characters are followed again, a page wrongly labelled as compressed is no longer lost, and a new option explains why a given address is kept or skipped.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.11" date="2026-07-05">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Audio and video sources are mirrored along with the page</li>
|
||||
<li>A site's robots.txt is followed more closely, including its Allow rules and wildcards</li>
|
||||
<li>A time limit now stops a slow download instead of waiting for it to end</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.10" date="2026-06-28">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Start from a cookies file, and space downloads out with a random pause</li>
|
||||
<li>Ignore chosen query parameters when naming saved files</li>
|
||||
<li>Resuming a partial download no longer duplicates bytes</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.9" date="2026-06-21">
|
||||
<description>
|
||||
<p>Saved files get the right type when the server and the address disagree about it.</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="3.49.8" date="2026-06-20">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Secure downloads can go through a plain web proxy</li>
|
||||
<li>Every size of a responsive image is fetched</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB |
8
html/server/images/bg_rings.svg
Normal file
8
html/server/images/bg_rings.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="501" height="456" viewBox="0 0 501 456">
|
||||
<title>HTTrack rings</title>
|
||||
<!-- Transparent, unlike the GIF this replaces: the panel's own #ccd shows through. -->
|
||||
<g fill="#b9b9d0" fill-rule="evenodd">
|
||||
<path d="M614.59 346.3A323.36 197.55 21.16 1 0 11.46 112.89A323.36 197.55 21.16 1 0 614.59 346.3 ZM590.57 315.66A262.58 160.99 22.18 1 0 104.26 117.43A262.58 160.99 22.18 1 0 590.57 315.66 Z"/>
|
||||
<path d="M578.08 292.16A185.6 113.57 21.23 1 0 232.08 157.73A185.6 113.57 21.23 1 0 578.08 292.16 ZM564.4 274.56A150.77 92.55 22.26 1 0 285.34 160.32A150.77 92.55 22.26 1 0 564.4 274.56 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 641 B |
@@ -36,7 +36,7 @@ a:active { text-decoration: underline; }
|
||||
border-bottom: 6px solid #000;
|
||||
padding: 10px; padding-top: 20px;
|
||||
line-height: 1.65em;
|
||||
background-image: url(images/bg_rings.gif);
|
||||
background-image: url(images/bg_rings.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top right;
|
||||
}
|
||||
|
||||
@@ -8,4 +8,24 @@ langroot_DATA = ../lang.def ../lang.indexes
|
||||
|
||||
EXTRA_DIST = $(lang_DATA) $(langroot_DATA)
|
||||
|
||||
# webhttrack and htsserver reach the served UI through $(langrootdir)/html, so the
|
||||
# link is relative: an absolute $(htmldir) dangles under DESTDIR or relocation (#885).
|
||||
install-data-hook:
|
||||
@rel='$(htmldir)'; \
|
||||
case '$(htmldir)' in \
|
||||
'$(datadir)'/*) rel="../$${rel#$(datadir)/}" ;; \
|
||||
esac; \
|
||||
link='$(DESTDIR)$(langrootdir)/html'; \
|
||||
if test -L "$$link"; then rm -f "$$link" || exit 1; fi; \
|
||||
if test -e "$$link"; then \
|
||||
echo "$$link exists and is not a symlink, leaving it alone" >&2; \
|
||||
else \
|
||||
echo " $(LN_S) $$rel $$link"; \
|
||||
$(LN_S) "$$rel" "$$link"; \
|
||||
fi
|
||||
|
||||
uninstall-hook:
|
||||
@link='$(DESTDIR)$(langrootdir)/html'; \
|
||||
if test -L "$$link"; then rm -f "$$link"; fi
|
||||
|
||||
#dist-hook:
|
||||
|
||||
@@ -60,6 +60,13 @@ proxytrack_SOURCES = proxy/main.c \
|
||||
whttrackrundir = $(bindir)
|
||||
whttrackrun_SCRIPTS = webhttrack
|
||||
|
||||
# $(datadir) only expands at make time, so substitute here rather than through
|
||||
# AC_CONFIG_FILES (#887).
|
||||
webhttrack: webhttrack.in Makefile
|
||||
$(AM_V_GEN)$(SED) -e 's|@datadir[@]|$(datadir)|g' $(srcdir)/webhttrack.in >$@.tmp \
|
||||
&& chmod +x $@.tmp && mv -f $@.tmp $@
|
||||
CLEANFILES = webhttrack
|
||||
|
||||
libhttrack_la_SOURCES = htscore.c htsparse.c htsback.c htscache.c \
|
||||
htscache_selftest.c htsdns_selftest.c htsselftest.c \
|
||||
htscatchurl.c htsfilters.c htsftp.c htshash.c coucal/coucal.c \
|
||||
@@ -88,7 +95,7 @@ libhttrack_la_LIBADD = $(THREADS_LIBS) $(ZLIB_LIBS) $(BROTLI_LIBS) $(ZSTD_LIBS)
|
||||
libhttrack_la_CFLAGS = $(AM_CFLAGS) -DLIBHTTRACK_EXPORTS -DZLIB_CONST
|
||||
libhttrack_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(VERSION_INFO)
|
||||
|
||||
EXTRA_DIST = httrack.h htsstats.h webhttrack \
|
||||
EXTRA_DIST = httrack.h htsstats.h webhttrack.in \
|
||||
version.rc \
|
||||
libhttrack.rc \
|
||||
httrack.rc \
|
||||
|
||||
@@ -62,7 +62,30 @@ Please visit our Website: http://www.httrack.com
|
||||
|
||||
#define VT_CLREOL "\33[K"
|
||||
|
||||
/* Subdirectory holding a mirrored file's temporaries, beside it. url_savename()
|
||||
maps '~' to '_', so no URL can ever be mirrored inside it (#774, #842). */
|
||||
#define HTS_TMPDIR "~hts-tmp"
|
||||
|
||||
/* Slot operations */
|
||||
static hts_boolean back_tmpname(char *dest, size_t size, const char *save,
|
||||
const char *ext);
|
||||
|
||||
hts_boolean back_spoolname(httrackp *opt, const char *save, char *dest,
|
||||
size_t size) {
|
||||
/* -p0 keeps no save name to derive from, so it counts instead. No separator:
|
||||
path_html_utf8 brings its own, and is "" with no -O, where an added one
|
||||
would make this absolute and spool into the filesystem root. */
|
||||
if (opt->getmode == 0) {
|
||||
if (!slprintfbuff(dest, size, "%s" HTS_TMPDIR "/tmpfile%d.tmp",
|
||||
StringBuff(opt->path_html_utf8),
|
||||
opt->state.tmpnameid++)) {
|
||||
dest[0] = '\0';
|
||||
return HTS_FALSE;
|
||||
}
|
||||
return HTS_TRUE;
|
||||
}
|
||||
return back_tmpname(dest, size, save, "tmp");
|
||||
}
|
||||
static int slot_can_be_cached_on_disk(const lien_back * back);
|
||||
static int slot_can_be_cleaned(const lien_back * back);
|
||||
static int slot_can_be_finalized(httrackp * opt, const lien_back * back);
|
||||
@@ -217,6 +240,7 @@ void back_delete_all(httrackp * opt, cache_back * cache, struct_back * sback) {
|
||||
|
||||
if (filename != NULL) {
|
||||
(void) UNLINK(filename);
|
||||
back_tmpdir_drop(filename);
|
||||
}
|
||||
#else
|
||||
/* clear entry content (but not yet the entry) */
|
||||
@@ -311,6 +335,7 @@ static int back_index_ready(httrackp * opt, struct_back * sback, const char *adr
|
||||
adr, fil, sav);
|
||||
}
|
||||
(void) UNLINK(fileback);
|
||||
back_tmpdir_drop(fileback);
|
||||
#else
|
||||
itemback = (lien_back *) ptr;
|
||||
#endif
|
||||
@@ -483,22 +508,11 @@ int back_cleanup_background(httrackp * opt, cache_back * cache,
|
||||
#ifndef HTS_NO_BACK_ON_DISK
|
||||
/* temporarily serialize the entry on disk */
|
||||
{
|
||||
/* +16: room for the ".tmp" the url_sav form appends to a full-length
|
||||
save name, so one buffer holds both shapes */
|
||||
char BIGSTK tmpname[HTS_URLMAXSIZE * 2 + 16];
|
||||
/* +32: room for the directory and extension back_spoolname() inserts */
|
||||
char BIGSTK tmpname[HTS_URLMAXSIZE * 2 + 32];
|
||||
char *filename;
|
||||
hts_boolean named;
|
||||
|
||||
/* the -p0 name is not derived from url_sav, so it needs a buffer of
|
||||
its own size rather than the save name's */
|
||||
if (opt->getmode != 0) {
|
||||
named =
|
||||
slprintfbuff(tmpname, sizeof(tmpname), "%s.tmp", back[i].url_sav);
|
||||
} else {
|
||||
named = slprintfbuff(tmpname, sizeof(tmpname), "%stmpfile%d.tmp",
|
||||
StringBuff(opt->path_html_utf8),
|
||||
opt->state.tmpnameid++);
|
||||
}
|
||||
const hts_boolean named =
|
||||
back_spoolname(opt, back[i].url_sav, tmpname, sizeof(tmpname));
|
||||
filename = named ? strdupt(tmpname) : NULL;
|
||||
|
||||
if (filename != NULL) {
|
||||
@@ -656,10 +670,6 @@ int back_nsoc_overall(const struct_back * sback) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/* Subdirectory holding a mirrored file's temporaries, beside it. url_savename()
|
||||
maps '~' to '_', so no URL can ever be mirrored inside it (#774, #842). */
|
||||
#define HTS_TMPDIR "~hts-tmp"
|
||||
|
||||
/* Build save's temporary as <dir>/<HTS_TMPDIR>/<name>.<ext>. Appending the
|
||||
extension to save instead put it in the mirror namespace, so a site serving
|
||||
<path>.bak had its copy taken as the backup and then unlinked (#774).
|
||||
@@ -717,7 +727,7 @@ static int create_back_tmpfile(httrackp *opt, lien_back *const back,
|
||||
/* same directory as the named case, so back_tmpdir_drop() only removes one
|
||||
the engine made (#842) */
|
||||
/* truncation here would collide distinct tmpnameid's onto one name */
|
||||
if (!sprintfbuff(back->tmpfile_buffer, "%s/" HTS_TMPDIR "/tmp%d.%s",
|
||||
if (!sprintfbuff(back->tmpfile_buffer, "%s" HTS_TMPDIR "/tmp%d.%s",
|
||||
StringBuff(opt->path_html_utf8), opt->state.tmpnameid++,
|
||||
ext)) {
|
||||
hts_log_print(opt, LOG_WARNING, "temporary filename too long in %s",
|
||||
@@ -819,6 +829,14 @@ static hts_boolean back_chunked_unterminated(const lien_back *const back) {
|
||||
return back->is_chunk && back->chunk_blocksize != -1 ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
|
||||
/* Past the terminating chunk, the line still owed is the optional trailer
|
||||
section (RFC 9112 7.1.2), read and discarded like a header block. */
|
||||
static hts_boolean back_in_chunk_trailers(const lien_back *const back) {
|
||||
return back->status == STATUS_CHUNK_CR && back->chunk_blocksize == -1
|
||||
? HTS_TRUE
|
||||
: HTS_FALSE;
|
||||
}
|
||||
|
||||
// objet (lien) téléchargé ou transféré depuis le cache
|
||||
//
|
||||
// fermer les paramètres de transfert,
|
||||
@@ -3480,6 +3498,10 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
else if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) { // recevoir longueur chunk en hexa caractère par caractère
|
||||
// backuper pour lire dans le buffer chunk
|
||||
htsblk r;
|
||||
/* Block mode bounds the trailer section, which declares no length
|
||||
of its own, by HTS_LINE_BLOCK_SIZE. */
|
||||
const int chunk_read_mode =
|
||||
back_in_chunk_trailers(&back[i]) ? 0 : -1;
|
||||
|
||||
memcpy(&r, &(back[i].r), sizeof(htsblk));
|
||||
back[i].r.is_write = 0; // mémoire
|
||||
@@ -3490,7 +3512,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
back[i].r.is_file = 0;
|
||||
//
|
||||
// ligne par ligne
|
||||
retour_fread = http_xfread1(&(back[i].r), -1);
|
||||
retour_fread = http_xfread1(&(back[i].r), chunk_read_mode);
|
||||
// modifier et restaurer
|
||||
back[i].chunk_adr = back[i].r.adr; // adresse
|
||||
back[i].chunk_size = back[i].r.size; // taille taille chunk
|
||||
@@ -3628,12 +3650,22 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
}
|
||||
// Traitement des en têtes chunks ou en têtes
|
||||
if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) { // réception taille chunk en hexa ( après les en têtes, peut ne pas
|
||||
if (back[i].chunk_size > 0
|
||||
&& back[i].chunk_adr[back[i].chunk_size - 1] == 10) {
|
||||
const hts_boolean in_trailers = back_in_chunk_trailers(&back[i]);
|
||||
|
||||
/* A chunk-size or chunk-CRLF line closes on its first LF, the
|
||||
trailer section on the blank line ending it. Two LFs mean a
|
||||
blank line only because the reader drops every CR. */
|
||||
if (back[i].chunk_size > 0 &&
|
||||
back[i].chunk_adr[back[i].chunk_size - 1] == 10 &&
|
||||
(!in_trailers || back[i].chunk_size == 1 ||
|
||||
back[i].chunk_adr[back[i].chunk_size - 2] == 10)) {
|
||||
int chunk_size = -1;
|
||||
char chunk_data[64];
|
||||
|
||||
if (back[i].chunk_size < 32) { // pas trop gros
|
||||
if (in_trailers) {
|
||||
chunk_size =
|
||||
0; /* fields discarded, the blank line ends the body */
|
||||
} else if (back[i].chunk_size < 32) { // not too big
|
||||
char *chstrip = back[i].chunk_adr;
|
||||
|
||||
back[i].chunk_adr[back[i].chunk_size - 1] = '\0'; // octet nul
|
||||
@@ -3817,12 +3849,6 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Oops, trailers! */
|
||||
if (back[i].r.keep_alive_trailers) {
|
||||
/* fixme (not yet supported) */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3836,7 +3862,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
// NO! xxback[i].chunk_blocksize = 0;
|
||||
}
|
||||
|
||||
} // taille buffer chunk > 1 && LF
|
||||
} // chunk buffer holds a complete line
|
||||
//
|
||||
} else if (back[i].status == STATUS_WAIT_HEADERS) { // en têtes (avant le chunk si il est présent)
|
||||
//
|
||||
|
||||
@@ -155,6 +155,11 @@ hts_boolean back_finalize_backup(httrackp *opt, lien_back *const back,
|
||||
/* Remove the reserved directory a temporary sat in, once the last slot sharing
|
||||
it is done; a non-empty one just refuses. No-op outside that directory. */
|
||||
void back_tmpdir_drop(const char *tmp);
|
||||
/* Name the spool file of a frozen backlog slot, inside the reserved directory
|
||||
no save name can spell. HTS_FALSE (dest emptied) if it would not fit.
|
||||
Consumes an opt->state.tmpnameid under -p0. Note: utf-8. */
|
||||
hts_boolean back_spoolname(httrackp *opt, const char *save, char *dest,
|
||||
size_t size);
|
||||
/* -#test=backswap: slots eligible for the on-disk ready table. */
|
||||
int back_selftest_slot_swap(void);
|
||||
void back_info(struct_back * sback, int i, int j, FILE * fp);
|
||||
|
||||
@@ -211,6 +211,13 @@ void hts_backtrace_init(void) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Why the report has no frames: a silent gap reads as a handler that died. */
|
||||
static void print_no_trace(int fd, const char *msg, size_t len) {
|
||||
if (write(fd, msg, len) != len) { /* no ssize_t: this is built on MSVC too */
|
||||
/* sorry GCC */
|
||||
}
|
||||
}
|
||||
|
||||
void hts_print_backtrace(int fd) {
|
||||
#ifdef USES_BACKTRACE
|
||||
void *stack[256];
|
||||
@@ -227,12 +234,15 @@ void hts_print_backtrace(int fd) {
|
||||
symbolize_backtrace(stack, size, fd);
|
||||
entered = 0;
|
||||
}
|
||||
} else {
|
||||
/* An empty trace means the build carries no unwind tables. */
|
||||
const char msg[] = "No stack trace available: unwinding failed\n";
|
||||
|
||||
print_no_trace(fd, msg, sizeof(msg) - 1);
|
||||
}
|
||||
#else
|
||||
const char msg[] = "No stack trace available on this OS :(\n";
|
||||
|
||||
if (write(fd, msg, sizeof(msg) - 1) != sizeof(msg) - 1) {
|
||||
/* sorry GCC */
|
||||
}
|
||||
print_no_trace(fd, msg, sizeof(msg) - 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htsmd5.h"
|
||||
|
||||
#include <ctype.h>
|
||||
/* hts_self_path() */
|
||||
#ifdef __APPLE__
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if USE_BEGINTHREAD
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
@@ -71,6 +78,95 @@ Please visit our Website: http://www.httrack.com
|
||||
/* Resolver */
|
||||
extern int IPV6_resolver;
|
||||
|
||||
/* A data directory is one that carries the templates path_bin is read for. */
|
||||
static int datadir_has_templates(const char *dir) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
|
||||
return dir != NULL && *dir != '\0' &&
|
||||
fexist(fconcat(catbuff, sizeof(catbuff), dir,
|
||||
"templates/index-header.html"));
|
||||
}
|
||||
|
||||
const char *hts_self_path(char *dst, size_t dstsize) {
|
||||
#if defined(_WIN32)
|
||||
const DWORD n = GetModuleFileNameA(NULL, dst, (DWORD) dstsize);
|
||||
|
||||
/* Pre-Win8 returns nSize on truncation without terminating: a full buffer
|
||||
is a failure, not a path. */
|
||||
return (n > 0 && (size_t) n < dstsize) ? dst : NULL;
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t n = (uint32_t) dstsize;
|
||||
|
||||
return _NSGetExecutablePath(dst, &n) == 0 ? dst : NULL;
|
||||
#else
|
||||
/* Linux; anywhere else this is simply absent and argv[0] has to do. */
|
||||
const ssize_t n = readlink("/proc/self/exe", dst, dstsize - 1);
|
||||
|
||||
if (n <= 0 || (size_t) n >= dstsize - 1)
|
||||
return NULL;
|
||||
dst[n] = '\0';
|
||||
return dst;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Directory part of path, trailing '/' kept, or NULL when it carries none: a
|
||||
bare name came from a PATH lookup and locates nothing. */
|
||||
static const char *dirname_of(char *dst, size_t dstsize, const char *path) {
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
const char *slashed, *sep;
|
||||
size_t len;
|
||||
|
||||
if (path == NULL)
|
||||
return NULL;
|
||||
slashed = fslash(catbuff, sizeof(catbuff), path);
|
||||
if ((sep = strrchr(slashed, '/')) == NULL)
|
||||
return NULL;
|
||||
len = (size_t) (sep - slashed) + 1;
|
||||
if (len >= dstsize)
|
||||
return NULL;
|
||||
memcpy(dst, slashed, len);
|
||||
dst[len] = '\0';
|
||||
return dst;
|
||||
}
|
||||
|
||||
void hts_resolve_datadir(char *dst, size_t dstsize, const char *selfpath,
|
||||
const char *builtin) {
|
||||
/* An installed tree that was moved, then a flat one with templates/ beside
|
||||
the binary. */
|
||||
static const char *const layout[] = {"../share/httrack/", ""};
|
||||
char exedir[HTS_URLMAXSIZE * 2];
|
||||
const char *base = NULL;
|
||||
const char *fallback;
|
||||
|
||||
if (!datadir_has_templates(builtin)) {
|
||||
base = dirname_of(exedir, sizeof(exedir), selfpath);
|
||||
}
|
||||
if (base != NULL) {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(layout) / sizeof(layout[0]); i++) {
|
||||
char cand[HTS_URLMAXSIZE * 2];
|
||||
/* snprintf, not the strlncatbuff idiom: appending to a non-empty buffer
|
||||
aborts on overflow, and a long enough argv[0] reaches it. */
|
||||
const int n = snprintf(cand, sizeof(cand), "%s%s", base, layout[i]);
|
||||
|
||||
if (n < 0 || (size_t) n >= sizeof(cand)) {
|
||||
continue; /* truncated, so not the path we meant to probe */
|
||||
}
|
||||
if (datadir_has_templates(cand)) {
|
||||
snprintf(dst, dstsize, "%s", cand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Windows has no compiled-in data directory, so there the executable's own
|
||||
is all we have. */
|
||||
fallback = (builtin != NULL && *builtin != '\0') ? builtin
|
||||
: (base != NULL) ? base
|
||||
: "";
|
||||
snprintf(dst, dstsize, "%s", fallback);
|
||||
}
|
||||
|
||||
#define htsmain_free() do { \
|
||||
if (url != NULL) { \
|
||||
free(url); \
|
||||
@@ -174,21 +270,25 @@ static int hts_main_internal(int argc, char **argv, httrackp * opt) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Binary program path?
|
||||
#ifndef HTS_HTTRACKDIR
|
||||
// Data directory holding the HTML templates
|
||||
{
|
||||
char catbuff[CATBUFF_SIZE];
|
||||
char *path = fslash(catbuff, sizeof(catbuff), argv[0]);
|
||||
char *a;
|
||||
char datadir[HTS_URLMAXSIZE * 2];
|
||||
char selfbuff[HTS_URLMAXSIZE * 2];
|
||||
const char *self = hts_self_path(selfbuff, sizeof(selfbuff));
|
||||
|
||||
if ((a = strrchr(path, '/'))) {
|
||||
StringCopyN(opt->path_bin, argv[0], a - path);
|
||||
}
|
||||
}
|
||||
#ifdef HTS_HTTRACKDIR
|
||||
const char *const builtin = HTS_HTTRACKDIR;
|
||||
#else
|
||||
StringCopy(opt->path_bin, HTS_HTTRACKDIR);
|
||||
const char *const builtin = "";
|
||||
#endif
|
||||
|
||||
if (self == NULL && argc > 0) {
|
||||
self = argv[0];
|
||||
}
|
||||
hts_resolve_datadir(datadir, sizeof(datadir), self, builtin);
|
||||
StringCopy(opt->path_bin, datadir);
|
||||
}
|
||||
|
||||
/* filter CR, LF, TAB.. */
|
||||
{
|
||||
int na;
|
||||
|
||||
@@ -47,6 +47,16 @@ Please visit our Website: http://www.httrack.com
|
||||
int cmdl_opt(char *s);
|
||||
int check_path(String * s, char *defaultname);
|
||||
|
||||
/* Absolute path of the running executable, or NULL where the OS will not say
|
||||
and argv[0] is the only clue left. Fills dst (dstsize bytes). */
|
||||
const char *hts_self_path(char *dst, size_t dstsize);
|
||||
|
||||
/* Write the data directory holding the HTML templates into dst (dstsize bytes,
|
||||
NUL-terminated, trailing '/' included): builtin when it is there, else a
|
||||
layout under selfpath's directory. selfpath may be NULL, builtin empty. */
|
||||
void hts_resolve_datadir(char *dst, size_t dstsize, const char *selfpath,
|
||||
const char *builtin);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1954,7 +1954,7 @@ LLint http_xfread1(htsblk * r, int bufl) {
|
||||
|
||||
} else if (bufl == -2) { // force reserve
|
||||
if (r->adr == NULL) {
|
||||
r->adr = (char *) malloct(8192);
|
||||
r->adr = (char *) malloct(HTS_LINE_BLOCK_SIZE);
|
||||
r->size = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -1969,11 +1969,11 @@ LLint http_xfread1(htsblk * r, int bufl) {
|
||||
nl = READ_INTERNAL_ERROR;
|
||||
count--;
|
||||
if (r->adr == NULL) {
|
||||
r->adr = (char *) malloct(8192);
|
||||
r->adr = (char *) malloct(HTS_LINE_BLOCK_SIZE);
|
||||
r->size = 0;
|
||||
}
|
||||
if (r->adr != NULL) {
|
||||
if (r->size < 8190) {
|
||||
if (r->size < HTS_LINE_BLOCK_SIZE - 2) {
|
||||
// lecture
|
||||
nl = hts_read(r, r->adr + r->size, 1);
|
||||
if (nl > 0) {
|
||||
|
||||
@@ -248,6 +248,10 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
|
||||
void treatfirstline(htsblk * retour, const char *rcvd);
|
||||
|
||||
// sous-fonctions
|
||||
/* Buffer http_xfread1() fills in its line modes, and so the ceiling on any
|
||||
blank-line-terminated block it reads: a header section or a chunk trailer
|
||||
section. Overrunning it fails the transfer. */
|
||||
#define HTS_LINE_BLOCK_SIZE 8192
|
||||
LLint http_xfread1(htsblk * r, int bufl);
|
||||
/* Cached resolver: fill out[0..count-1] with up to max addresses for iadr (in
|
||||
resolver order), returning the count (0 = does not resolve, negative-cached).
|
||||
|
||||
@@ -54,6 +54,7 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htsdns_selftest.h"
|
||||
#include "htscharset.h"
|
||||
#include "htscmdline.h"
|
||||
#include "htscoremain.h"
|
||||
#include "htsencoding.h"
|
||||
#include "htsftp.h"
|
||||
#include "htsmd5.h"
|
||||
@@ -3314,6 +3315,104 @@ static int st_makeindex(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void datadir_expect(const char *selfpath, const char *builtin,
|
||||
const char *expect) {
|
||||
char got[HTS_URLMAXSIZE * 2];
|
||||
|
||||
hts_resolve_datadir(got, sizeof(got), selfpath, builtin);
|
||||
if (strcmp(got, expect) != 0) {
|
||||
fprintf(stderr,
|
||||
"datadir: self=%s builtin=\"%s\" gave \"%s\", expected \"%s\"\n",
|
||||
selfpath != NULL ? selfpath : "(null)", builtin, got, expect);
|
||||
}
|
||||
assertf(strcmp(got, expect) == 0);
|
||||
}
|
||||
|
||||
// -#test=datadir <dir>: a relocated tree must find its own templates instead of
|
||||
// silently falling back to the built-in ones (#894). argv[0] is writable.
|
||||
static int st_datadir(httrackp *opt, int argc, char **argv) {
|
||||
char path[HTS_URLMAXSIZE];
|
||||
char self[HTS_URLMAXSIZE];
|
||||
char expect[HTS_URLMAXSIZE * 2];
|
||||
char installed[HTS_URLMAXSIZE];
|
||||
char gone[HTS_URLMAXSIZE];
|
||||
/* Each holds a templates/index-header.html, the file path_bin is read for.
|
||||
nest/ keeps the flat case away from the installed share/httrack above. */
|
||||
static const char *const dirs[] = {"share/httrack", "bin", "nest/flat"};
|
||||
size_t i;
|
||||
|
||||
(void) opt;
|
||||
assertf(argc >= 1);
|
||||
|
||||
/* argv[0] is a fallback: what the engine actually resolves from is this. */
|
||||
assertf(hts_self_path(path, sizeof(path)) != NULL);
|
||||
assertf(fexist(path));
|
||||
/* Too small for any real path, so the truncation guard must refuse. */
|
||||
assertf(hts_self_path(path, 2) == NULL);
|
||||
|
||||
for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) {
|
||||
FILE *fp;
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s/templates/", argv[0], dirs[i]);
|
||||
assertf(structcheck(path) == 0);
|
||||
snprintf(path, sizeof(path), "%s/%s/templates/index-header.html", argv[0],
|
||||
dirs[i]);
|
||||
fp = fopen(path, "wb");
|
||||
assertf(fp != NULL);
|
||||
fclose(fp);
|
||||
}
|
||||
snprintf(installed, sizeof(installed), "%s/share/httrack/", argv[0]);
|
||||
snprintf(gone, sizeof(gone), "%s/gone/", argv[0]);
|
||||
|
||||
/* A moved install: bin/ carries templates too, so this pins the order. */
|
||||
snprintf(self, sizeof(self), "%s/bin/httrack", argv[0]);
|
||||
snprintf(expect, sizeof(expect), "%s/bin/../share/httrack/", argv[0]);
|
||||
datadir_expect(self, gone, expect);
|
||||
|
||||
/* The compiled-in path still wins when it exists. */
|
||||
datadir_expect(self, installed, installed);
|
||||
|
||||
/* Flat layout: templates/ sits beside the binary. */
|
||||
snprintf(self, sizeof(self), "%s/nest/flat/httrack", argv[0]);
|
||||
snprintf(expect, sizeof(expect), "%s/nest/flat/", argv[0]);
|
||||
datadir_expect(self, gone, expect);
|
||||
|
||||
/* Nothing to derive from, or nothing found: the compiled-in path stands. */
|
||||
datadir_expect("httrack", gone, gone);
|
||||
datadir_expect(NULL, gone, gone);
|
||||
snprintf(self, sizeof(self), "%s/nowhere/deep/httrack", argv[0]);
|
||||
datadir_expect(self, gone, gone);
|
||||
|
||||
/* No compiled-in path, as on Windows: the executable's own directory. */
|
||||
snprintf(self, sizeof(self), "%s/nowhere/deep/httrack", argv[0]);
|
||||
snprintf(expect, sizeof(expect), "%s/nowhere/deep/", argv[0]);
|
||||
datadir_expect(self, "", expect);
|
||||
datadir_expect("httrack", "", "");
|
||||
|
||||
/* A directory part too long for the layout suffix to be appended must clip,
|
||||
not abort: appending to a non-empty buffer is the *_safe_ abort path. */
|
||||
{
|
||||
/* Long enough that dirname + "../share/httrack/" overflows the candidate
|
||||
buffer, short enough that the dirname itself still fits. */
|
||||
const size_t dirlen = HTS_URLMAXSIZE * 2 - 8;
|
||||
char huge[HTS_URLMAXSIZE * 3];
|
||||
char got[HTS_URLMAXSIZE * 2];
|
||||
size_t n;
|
||||
|
||||
huge[0] = '/';
|
||||
for (n = 1; n < dirlen - 1; n++) {
|
||||
huge[n] = 'a';
|
||||
}
|
||||
huge[dirlen - 1] = '/';
|
||||
memcpy(huge + dirlen, "httrack", sizeof("httrack"));
|
||||
hts_resolve_datadir(got, sizeof(got), huge, gone);
|
||||
assertf(strcmp(got, gone) == 0);
|
||||
}
|
||||
|
||||
printf("datadir self-test OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// hts_buildtopindex() writes a system-charset name into a charset=utf-8 doc: on
|
||||
// Windows the gifs land in a mangled twin dir (#217) and a listed name renders
|
||||
// as mojibake (#216). Both must come out utf-8. argv[0] is writable.
|
||||
@@ -6468,6 +6567,88 @@ static int st_refetchbackup(httrackp *opt, int argc, char **argv) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// -#test=spoolname <dir>: a frozen backlog slot must spool inside ~hts-tmp, not
|
||||
// beside the mirrored file where a site serving <path>.tmp collides (#859).
|
||||
static int st_spoolname(httrackp *opt, int argc, char **argv) {
|
||||
char BIGSTK got[HTS_URLMAXSIZE * 2 + 32];
|
||||
char BIGSTK want[HTS_URLMAXSIZE * 2 + 32];
|
||||
char BIGSTK save[HTS_URLMAXSIZE * 2];
|
||||
int err = 0;
|
||||
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "spoolname: needs a writable base dir\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* named: the spool lands in the save name's own ~hts-tmp, which no URL can
|
||||
spell since url_savename() maps '~' to '_' */
|
||||
snprintf(save, sizeof(save), "%s/sub/page.html", argv[0]);
|
||||
snprintf(want, sizeof(want), "%s/sub/~hts-tmp/page.html.tmp", argv[0]);
|
||||
opt->getmode = 1;
|
||||
if (!back_spoolname(opt, save, got, sizeof(got))) {
|
||||
fprintf(stderr, "spoolname: naming failed for %s\n", save);
|
||||
err++;
|
||||
} else if (strcmp(got, want) != 0) {
|
||||
fprintf(stderr, "spoolname: got %s, want %s\n", got, want);
|
||||
err++;
|
||||
}
|
||||
|
||||
/* pin the pre-#859 name as forbidden too: a site serving sub/page.html.tmp
|
||||
was mirrored straight onto it */
|
||||
snprintf(want, sizeof(want), "%s.tmp", save);
|
||||
if (strcmp(got, want) == 0) {
|
||||
fprintf(stderr, "spoolname: still spooling into the mirror namespace\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
/* -p0 keeps no save name, so the spool counts inside path_html's ~hts-tmp */
|
||||
{
|
||||
char BIGSTK base[HTS_URLMAXSIZE * 2];
|
||||
|
||||
snprintf(base, sizeof(base), "%s/", argv[0]);
|
||||
StringCopy(opt->path_html_utf8, base);
|
||||
opt->getmode = 0;
|
||||
opt->state.tmpnameid = 7;
|
||||
snprintf(want, sizeof(want), "%s/~hts-tmp/tmpfile7.tmp", argv[0]);
|
||||
if (!back_spoolname(opt, "", got, sizeof(got))) {
|
||||
fprintf(stderr, "spoolname: naming failed under -p0\n");
|
||||
err++;
|
||||
} else if (strcmp(got, want) != 0) {
|
||||
fprintf(stderr, "spoolname: -p0 got %s, want %s\n", got, want);
|
||||
err++;
|
||||
}
|
||||
if (opt->state.tmpnameid != 8) {
|
||||
fprintf(stderr, "spoolname: -p0 did not consume a tmpnameid\n");
|
||||
err++;
|
||||
}
|
||||
}
|
||||
|
||||
/* with no -O, path_html_utf8 is empty and the spool must stay relative to
|
||||
the working directory; a separator of our own would put it in / */
|
||||
StringCopy(opt->path_html_utf8, "");
|
||||
opt->getmode = 0;
|
||||
opt->state.tmpnameid = 0;
|
||||
if (!back_spoolname(opt, "", got, sizeof(got))) {
|
||||
fprintf(stderr, "spoolname: naming failed with no output directory\n");
|
||||
err++;
|
||||
} else if (strcmp(got, "~hts-tmp/tmpfile0.tmp") != 0) {
|
||||
fprintf(stderr, "spoolname: no -O gave %s, want ~hts-tmp/tmpfile0.tmp\n",
|
||||
got);
|
||||
err++;
|
||||
}
|
||||
|
||||
/* too long must empty dest, not hand back a truncated name landing
|
||||
somewhere real */
|
||||
opt->getmode = 1;
|
||||
if (back_spoolname(opt, save, got, 8) || got[0] != '\0') {
|
||||
fprintf(stderr, "spoolname: an overlong name was not rejected\n");
|
||||
err++;
|
||||
}
|
||||
|
||||
printf("spoolname: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
// -#test=direnum <dir>: enumerate a long+non-ASCII directory via the
|
||||
// opendir/readdir wrappers; children must round-trip as UTF-8 (#133,#630).
|
||||
static int st_direnum(httrackp *opt, int argc, char **argv) {
|
||||
@@ -7450,6 +7631,96 @@ static int st_rtrim(httrackp *opt, int argc, char **argv) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Format LEN bytes of EXPECTED into S as two arguments, and check what came
|
||||
back. HEAD and TAIL are scratch buffers of at least LEN+1 bytes. */
|
||||
static int strsprintf_case(String *s, const char *expected, size_t len,
|
||||
char *head, char *tail) {
|
||||
const size_t half = len / 2;
|
||||
|
||||
memcpy(head, expected, half);
|
||||
head[half] = '\0';
|
||||
memcpy(tail, expected + half, len - half);
|
||||
tail[len - half] = '\0';
|
||||
StringSprintf(*s, "%s%s", head, tail);
|
||||
return StringLength(*s) == len &&
|
||||
memcmp(StringBuff(*s), expected, len) == 0 &&
|
||||
StringBuff(*s)[len] == '\0';
|
||||
}
|
||||
|
||||
/* StringSprintf_ stores the terminator at buffer[ret], so its `ret < capacity`
|
||||
guard is off by one byte at the exact fill: an output whose length equals the
|
||||
capacity writes past the allocation (#836). The lengths that reach it are the
|
||||
capacities themselves, floored at 256 and doubling from there. */
|
||||
static int st_strsprintf(httrackp *opt, int argc, char **argv) {
|
||||
static const size_t caps[] = {256, 512, 1024, 2048};
|
||||
|
||||
enum { maxLen = 2100 };
|
||||
|
||||
char *expected = malloct(maxLen + 1);
|
||||
char *head = malloct(maxLen + 1);
|
||||
char *tail = malloct(maxLen + 1);
|
||||
String reused = STRING_EMPTY;
|
||||
size_t i, len;
|
||||
int err = 0;
|
||||
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
if (expected == NULL || head == NULL || tail == NULL) {
|
||||
printf("strsprintf self-test: FAIL (out of memory)\n");
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < maxLen; i++)
|
||||
expected[i] = (char) ('a' + (i % 26));
|
||||
expected[maxLen] = '\0';
|
||||
|
||||
/* one call on a String whose capacity is pinned to the boundary, so len ==
|
||||
capacity is reached exactly once per boundary */
|
||||
for (i = 0; i < sizeof(caps) / sizeof(caps[0]); i++) {
|
||||
for (len = caps[i] - 3; len <= caps[i] + 3; len++) {
|
||||
String s = STRING_EMPTY;
|
||||
|
||||
StringRoomTotal(s, caps[i]);
|
||||
if (StringCapacity(s) != caps[i]) {
|
||||
printf(" FAIL: capacity %u pinned to %u\n", (unsigned) caps[i],
|
||||
(unsigned) StringCapacity(s));
|
||||
err = 1;
|
||||
} else if (!strsprintf_case(&s, expected, len, head, tail)) {
|
||||
printf(" FAIL: length %u at capacity %u\n", (unsigned) len,
|
||||
(unsigned) caps[i]);
|
||||
err = 1;
|
||||
}
|
||||
StringFree(s);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* the same String reused: its capacity grows under it between calls, and a
|
||||
shorter output must not leave the previous one behind */
|
||||
for (len = 0; !err && len <= maxLen; len++) {
|
||||
if (!strsprintf_case(&reused, expected, len, head, tail)) {
|
||||
printf(" FAIL: growing length %u\n", (unsigned) len);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
for (len = maxLen + 1; !err && len-- > 0;) {
|
||||
if (!strsprintf_case(&reused, expected, len, head, tail)) {
|
||||
printf(" FAIL: shrinking length %u\n", (unsigned) len);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
StringFree(reused);
|
||||
|
||||
freet(expected);
|
||||
freet(head);
|
||||
freet(tail);
|
||||
|
||||
printf("strsprintf self-test: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Registry: name -> handler, with a usage hint and a one-line description. */
|
||||
/* ------------------------------------------------------------ */
|
||||
@@ -7504,6 +7775,8 @@ static const struct selftest_entry {
|
||||
{"hashtable", "<count|file>", "coucal hashtable stress test", st_hashtable},
|
||||
{"strsafe", "[overflow|overflow-buff|overflow-src [str]]",
|
||||
"bounded string-op self-test", st_strsafe},
|
||||
{"strsprintf", "", "StringSprintf grows to fit at every capacity boundary",
|
||||
st_strsprintf},
|
||||
{"copyopt", "", "copy_htsopt option-copy self-test", st_copyopt},
|
||||
{"lastchar", "",
|
||||
"last-char helpers never index before the buffer (#770, #781, #821)",
|
||||
@@ -7585,6 +7858,9 @@ static const struct selftest_entry {
|
||||
{"topindex", "[dir]",
|
||||
"hts_buildtopindex charset handling of a non-ASCII project dir",
|
||||
st_topindex},
|
||||
{"datadir", "<dir>",
|
||||
"data directory resolution: compiled-in path, then the executable's tree",
|
||||
st_datadir},
|
||||
{"inplace-escape", "", "inplace_escape_* vs escape_* equivalence self-test",
|
||||
st_inplace_escape},
|
||||
{"escape-room", "", "HT_ADD_HTMLESCAPED* reservation-factor self-test",
|
||||
@@ -7631,6 +7907,8 @@ static const struct selftest_entry {
|
||||
{"refetchbackup", "<dir>",
|
||||
"the re-fetch backup always leaves a copy, and stays out of the mirror",
|
||||
st_refetchbackup},
|
||||
{"spoolname", "<dir>",
|
||||
"a frozen backlog slot spools outside the mirror namespace", st_spoolname},
|
||||
{"direnum", "<dir>",
|
||||
"enumerate a long+non-ASCII directory through opendir/readdir",
|
||||
st_direnum},
|
||||
|
||||
@@ -36,6 +36,8 @@ Please visit our Website: http://www.httrack.com
|
||||
#define HTS_STRINGS_DEFSTATIC
|
||||
|
||||
/* System definitions. */
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* GCC extension */
|
||||
@@ -165,6 +167,44 @@ HTS_STATIC char *StringBuffN_(String *blk, int size) {
|
||||
return StringBuffRW(*blk);
|
||||
}
|
||||
|
||||
/** Ceiling on the pre-C99 doubling search below; BLK is emptied past it. **/
|
||||
#define STRING_SPRINTF_MAX ((size_t) 16 * 1024 * 1024)
|
||||
|
||||
/** Replace BLK's contents with the formatted output, growing to fit, so no
|
||||
fixed reserve has to bound an argument carrying remote input. An argument
|
||||
may not point into BLK's own buffer, which is reallocated here. **/
|
||||
#define StringSprintf(BLK, ...) StringSprintf_(&(BLK), __VA_ARGS__)
|
||||
|
||||
HTS_STATIC HTS_PRINTF_FUN(2, 3) void StringSprintf_(String *blk,
|
||||
const char *fmt, ...) {
|
||||
size_t capacity = StringCapacity(*blk) > 256 ? StringCapacity(*blk) : 256;
|
||||
|
||||
for (;;) {
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
StringRoomTotal(*blk, capacity);
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(StringBuffRW(*blk), capacity, fmt, args);
|
||||
va_end(args);
|
||||
if (ret >= 0 && (size_t) ret < capacity) {
|
||||
StringBuffRW(*blk)[ret] = '\0';
|
||||
StringLength(*blk) = (size_t) ret;
|
||||
return;
|
||||
}
|
||||
if (ret >= 0) {
|
||||
capacity = (size_t) ret + 1; /* C99 said what it needs */
|
||||
} else if (capacity < STRING_SPRINTF_MAX) {
|
||||
capacity *= 2; /* pre-C99 msvcrt only says "too small" */
|
||||
} else {
|
||||
/* a conversion error returns -1 too, and no capacity ever fixes that */
|
||||
StringBuffRW(*blk)[0] = '\0';
|
||||
StringLength(*blk) = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Zero the fields (NULL buffer, no allocation). Use on an uninitialized
|
||||
String only; does NOT free an existing buffer (use StringFree to reset
|
||||
an owned one), so calling it on a live String leaks. **/
|
||||
|
||||
@@ -548,29 +548,37 @@ static void proxytrack_add_DAV_Item(String * item, String * buff,
|
||||
name = "Default Document for the Folder";
|
||||
}
|
||||
|
||||
StringRoom(*item, 1024);
|
||||
sprintf(StringBuffRW(*item),
|
||||
"<response xmlns=\"DAV:\">\r\n" "<href>/webdav%s%s</href>\r\n"
|
||||
"<propstat>\r\n" "<prop>\r\n" "<displayname>%s</displayname>\r\n"
|
||||
"<iscollection>%d</iscollection>\r\n"
|
||||
"<haschildren>%d</haschildren>\r\n" "<isfolder>%d</isfolder>\r\n"
|
||||
"<resourcetype>%s</resourcetype>\r\n"
|
||||
"<creationdate>%d-%02d-%02dT%02d:%02d:%02dZ</creationdate>\r\n"
|
||||
"<getlastmodified>%s</getlastmodified>\r\n"
|
||||
"<supportedlock></supportedlock>\r\n" "<lockdiscovery/>\r\n"
|
||||
"<getcontenttype>%s</getcontenttype>\r\n"
|
||||
"<getcontentlength>%d</getcontentlength>\r\n"
|
||||
"<isroot>%d</isroot>\r\n" "</prop>\r\n"
|
||||
"<status>HTTP/1.1 200 OK</status>\r\n" "</propstat>\r\n"
|
||||
"</response>\r\n",
|
||||
/* */
|
||||
(StringBuff(*buff)[0] == '/') ? "" : "/", StringBuff(*buff), name,
|
||||
isDir ? 1 : 0, isDir ? 1 : 0, isDir ? 1 : 0,
|
||||
isDir ? "<collection/>" : "", timetm->tm_year + 1900,
|
||||
timetm->tm_mon + 1, timetm->tm_mday, timetm->tm_hour,
|
||||
timetm->tm_min, timetm->tm_sec, tms,
|
||||
isDir ? "httpd/unix-directory" : mime, (int) size, isRoot ? 1 : 0);
|
||||
StringLength(*item) = (int) strlen(StringBuff(*item));
|
||||
/* The path lands here twice and escapexml() expands '&' fivefold, so no
|
||||
fixed reserve bounds it (#836). */
|
||||
StringSprintf(
|
||||
*item,
|
||||
"<response xmlns=\"DAV:\">\r\n"
|
||||
"<href>/webdav%s%s</href>\r\n"
|
||||
"<propstat>\r\n"
|
||||
"<prop>\r\n"
|
||||
"<displayname>%s</displayname>\r\n"
|
||||
"<iscollection>%d</iscollection>\r\n"
|
||||
"<haschildren>%d</haschildren>\r\n"
|
||||
"<isfolder>%d</isfolder>\r\n"
|
||||
"<resourcetype>%s</resourcetype>\r\n"
|
||||
"<creationdate>%d-%02d-%02dT%02d:%02d:%02dZ</creationdate>\r\n"
|
||||
"<getlastmodified>%s</getlastmodified>\r\n"
|
||||
"<supportedlock></supportedlock>\r\n"
|
||||
"<lockdiscovery/>\r\n"
|
||||
"<getcontenttype>%s</getcontenttype>\r\n"
|
||||
"<getcontentlength>%d</getcontentlength>\r\n"
|
||||
"<isroot>%d</isroot>\r\n"
|
||||
"</prop>\r\n"
|
||||
"<status>HTTP/1.1 200 OK</status>\r\n"
|
||||
"</propstat>\r\n"
|
||||
"</response>\r\n",
|
||||
/* */
|
||||
(StringBuff(*buff)[0] == '/') ? "" : "/", StringBuff(*buff), name,
|
||||
isDir ? 1 : 0, isDir ? 1 : 0, isDir ? 1 : 0,
|
||||
isDir ? "<collection/>" : "", timetm->tm_year + 1900,
|
||||
timetm->tm_mon + 1, timetm->tm_mday, timetm->tm_hour, timetm->tm_min,
|
||||
timetm->tm_sec, tms, isDir ? "httpd/unix-directory" : mime, (int) size,
|
||||
isRoot ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,11 +729,8 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
}
|
||||
|
||||
/* Form response */
|
||||
StringRoom(response, 1024);
|
||||
sprintf(StringBuffRW(response),
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
|
||||
"<multistatus xmlns=\"DAV:\">\r\n");
|
||||
StringLength(response) = (int) strlen(StringBuff(response));
|
||||
StringSprintf(response, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
|
||||
"<multistatus xmlns=\"DAV:\">\r\n");
|
||||
/* */
|
||||
|
||||
/* Root */
|
||||
@@ -739,7 +744,6 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
if (depth > 0) {
|
||||
time_t timestampRep = (time_t) - 1;
|
||||
const char *prefix = StringBuff(url);
|
||||
unsigned int prefixLen = (unsigned int) strlen(prefix);
|
||||
char **list = PT_Enumerate(indexes, prefix, 0);
|
||||
|
||||
if (list != NULL) {
|
||||
@@ -752,15 +756,10 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
int thisIsDir = (hts_lastchar(thisUrl) == '/') ? 1 : 0;
|
||||
|
||||
/* Item URL */
|
||||
StringRoom(itemUrl,
|
||||
thisUrlLen + prefixLen + sizeof("/webdav/") + 1);
|
||||
StringClear(itemUrl);
|
||||
sprintf(StringBuffRW(itemUrl), "/%s/%s", prefix, thisUrl);
|
||||
if (!thisIsDir)
|
||||
StringLength(itemUrl) = (int) strlen(StringBuff(itemUrl));
|
||||
else
|
||||
StringLength(itemUrl) = (int) strlen(StringBuff(itemUrl)) - 1;
|
||||
StringBuffRW(itemUrl)[StringLength(itemUrl)] = '\0';
|
||||
StringSprintf(itemUrl, "/%s/%s", prefix, thisUrl);
|
||||
if (thisIsDir) { /* drop the trailing '/' */
|
||||
StringPopRight(itemUrl);
|
||||
}
|
||||
|
||||
if (thisIsDir == isDir) {
|
||||
size_t size = 0;
|
||||
@@ -1037,12 +1036,13 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
const char *options = "GET, HEAD, OPTIONS, POST, PROPFIND, TRACE" ", MKCOL, DELETE, PUT"; /* Not supported */
|
||||
|
||||
msgCode = HTTP_OK;
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "DAV: 1, 2\r\n" "MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n" "Allow: %s\r\n", msgCode,
|
||||
GetHttpMessage(msgCode), options);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"DAV: 1, 2\r\n"
|
||||
"MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n"
|
||||
"Allow: %s\r\n",
|
||||
msgCode, GetHttpMessage(msgCode), options);
|
||||
} else if (strcasecmp(command, "propfind") == 0) {
|
||||
if (davDepth > 1) {
|
||||
msgCode = 403;
|
||||
@@ -1141,11 +1141,9 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
proxytrack_process_DAV_Request(indexes, StringBuff(url),
|
||||
davDepth)) != NULL) {
|
||||
msgCode = element->statuscode;
|
||||
StringRoom(davHeaders, 1024);
|
||||
sprintf(StringBuffRW(davHeaders),
|
||||
"DAV: 1, 2\r\n" "MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n");
|
||||
StringLength(davHeaders) = (int) strlen(StringBuff(davHeaders));
|
||||
StringSprintf(davHeaders, "DAV: 1, 2\r\n"
|
||||
"MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1164,45 +1162,46 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
}
|
||||
}
|
||||
if (element != NULL) {
|
||||
/* lifted out of the format: a directive inside a macro argument list
|
||||
is undefined, and MSVC rejects it */
|
||||
#ifndef NO_WEBDAV
|
||||
const char *const davPart = StringBuff(davHeaders);
|
||||
#else
|
||||
const char *const davPart = "";
|
||||
#endif
|
||||
|
||||
msgCode = element->statuscode;
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
#ifndef NO_WEBDAV
|
||||
"%s"
|
||||
#endif
|
||||
"Content-Type: %s%s%s%s\r\n"
|
||||
"%s%s%s"
|
||||
"%s%s%s"
|
||||
"%s%s%s",
|
||||
/* */
|
||||
msgCode, element->msg,
|
||||
#ifndef NO_WEBDAV
|
||||
/* DAV */
|
||||
StringBuff(davHeaders),
|
||||
#endif
|
||||
/* Content-type: foo; [ charset=bar ] */
|
||||
hts_effective_mime(element->contenttype),
|
||||
((element->charset[0]) ? "; charset=\"" : ""),
|
||||
element->charset, ((element->charset[0]) ? "\"" : ""),
|
||||
/* location */
|
||||
((element->location != NULL && element->location[0])
|
||||
? "Location: "
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0])
|
||||
? element->location
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0]) ? "\r\n"
|
||||
: ""),
|
||||
/* last-modified */
|
||||
((element->lastmodified[0]) ? "Last-Modified: " : ""),
|
||||
((element->lastmodified[0]) ? element->lastmodified : ""),
|
||||
((element->lastmodified[0]) ? "\r\n" : ""),
|
||||
/* etag */
|
||||
((element->etag[0]) ? "ETag: " : ""),
|
||||
((element->etag[0]) ? element->etag : ""),
|
||||
((element->etag[0]) ? "\r\n" : ""));
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
StringSprintf(
|
||||
headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"%s"
|
||||
"Content-Type: %s%s%s%s\r\n"
|
||||
"%s%s%s"
|
||||
"%s%s%s"
|
||||
"%s%s%s",
|
||||
/* */
|
||||
msgCode, element->msg, davPart,
|
||||
/* Content-type: foo; [ charset=bar ] */
|
||||
hts_effective_mime(element->contenttype),
|
||||
((element->charset[0]) ? "; charset=\"" : ""), element->charset,
|
||||
((element->charset[0]) ? "\"" : ""),
|
||||
/* location */
|
||||
((element->location != NULL && element->location[0])
|
||||
? "Location: "
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0])
|
||||
? element->location
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0]) ? "\r\n"
|
||||
: ""),
|
||||
/* last-modified */
|
||||
((element->lastmodified[0]) ? "Last-Modified: " : ""),
|
||||
((element->lastmodified[0]) ? element->lastmodified : ""),
|
||||
((element->lastmodified[0]) ? "\r\n" : ""),
|
||||
/* etag */
|
||||
((element->etag[0]) ? "ETag: " : ""),
|
||||
((element->etag[0]) ? element->etag : ""),
|
||||
((element->etag[0]) ? "\r\n" : ""));
|
||||
} else {
|
||||
/* No query string, no ending / : check the the <url>/ page */
|
||||
if (StringLength(url) > 0
|
||||
@@ -1212,29 +1211,32 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
StringCat(urlRedirect, "/");
|
||||
if (PT_LookupIndex(indexes, StringBuff(urlRedirect))) {
|
||||
msgCode = 301; /* Moved Permanently */
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "Content-Type: text/html\r\n"
|
||||
"Location: %s\r\n",
|
||||
/* */
|
||||
msgCode, GetHttpMessage(msgCode), StringBuff(urlRedirect)
|
||||
);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"Location: %s\r\n",
|
||||
/* */
|
||||
msgCode, GetHttpMessage(msgCode),
|
||||
StringBuff(urlRedirect));
|
||||
/* */
|
||||
StringRoom(output,
|
||||
1024 + sizeof(PROXYTRACK_COMMENT_HEADER) +
|
||||
sizeof(DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES));
|
||||
sprintf(StringBuffRW(output),
|
||||
"<html>" PROXYTRACK_COMMENT_HEADER
|
||||
DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES "<head>"
|
||||
"<title>ProxyTrack - Page has moved</title>" "</head>\r\n"
|
||||
"<body>" "<h3>The correct location is:</h3><br />"
|
||||
"<b><a href=\"%s\">%s</a></b><br />" "<br />" "<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>" "\r\n"
|
||||
"</body>" "</header>", StringBuff(urlRedirect),
|
||||
StringBuff(urlRedirect));
|
||||
StringLength(output) = (int) strlen(StringBuff(output));
|
||||
StringSprintf(
|
||||
output,
|
||||
"<html"
|
||||
">" PROXYTRACK_COMMENT_HEADER DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES
|
||||
"<head>"
|
||||
"<title>ProxyTrack - Page has moved</title>"
|
||||
"</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>The correct location is:</h3><br />"
|
||||
"<b><a href=\"%s\">%s</a></b><br />"
|
||||
"<br />"
|
||||
"<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>"
|
||||
"\r\n"
|
||||
"</body>"
|
||||
"</header>",
|
||||
StringBuff(urlRedirect), StringBuff(urlRedirect));
|
||||
}
|
||||
}
|
||||
if (msgCode == 0) {
|
||||
@@ -1255,25 +1257,29 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
} else if (msgError == NULL) {
|
||||
msgError = GetHttpMessage(msgCode);
|
||||
}
|
||||
StringRoom(headers, 256);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "Content-type: text/html\r\n", msgCode,
|
||||
msgError);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
StringRoom(output,
|
||||
1024 + sizeof(PROXYTRACK_COMMENT_HEADER) +
|
||||
sizeof(DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES));
|
||||
sprintf(StringBuffRW(output),
|
||||
"<html>" PROXYTRACK_COMMENT_HEADER
|
||||
DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES "<head>"
|
||||
"<title>ProxyTrack - HTTP Proxy Error %d</title>" "</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>A proxy error has occurred while processing the request.</h3><br />"
|
||||
"<b>Error HTTP %d: <i>%s</i></b><br />" "<br />" "<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>" "\r\n" "</body>"
|
||||
"</html>", msgCode, msgCode, msgError);
|
||||
StringLength(output) = (int) strlen(StringBuff(output));
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-type: text/html\r\n",
|
||||
msgCode, msgError);
|
||||
StringSprintf(
|
||||
output,
|
||||
"<html"
|
||||
">" PROXYTRACK_COMMENT_HEADER DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES
|
||||
"<head>"
|
||||
"<title>ProxyTrack - HTTP Proxy Error %d</title>"
|
||||
"</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>A proxy error has occurred while processing the "
|
||||
"request.</h3><br />"
|
||||
"<b>Error HTTP %d: <i>%s</i></b><br />"
|
||||
"<br />"
|
||||
"<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>"
|
||||
"\r\n"
|
||||
"</body>"
|
||||
"</html>",
|
||||
msgCode, msgCode, msgError);
|
||||
}
|
||||
{
|
||||
char tmp[20 + 1]; /* 2^64 = 18446744073709551616 */
|
||||
|
||||
@@ -23,7 +23,9 @@ for d in "${pathdirs[@]}"; do
|
||||
# drop empty PATH fields, matching the old echo|tr word-split
|
||||
test -n "$d" && SRCHPATH+=("$d")
|
||||
done
|
||||
SRCHDISTPATH=("$BINWD/../share" "$BINWD/.." /usr/share /usr/local /usr /local /usr/local/share "${HOME}/usr" "${HOME}/usr/share" /opt/local/share /sw "${HOME}/usr/local" "${HOME}/usr/share")
|
||||
# The substituted datadir goes after the relative entries so a moved tree wins, and
|
||||
# before the guesses so --datadir works (#887).
|
||||
SRCHDISTPATH=("$BINWD/../share" "$BINWD/.." "@datadir@" /usr/share /usr/local /usr /local /usr/local/share "${HOME}/usr" "${HOME}/usr/share" /opt/local/share /sw "${HOME}/usr/local" "${HOME}/usr/share")
|
||||
|
||||
###
|
||||
# And now some famous cuisine
|
||||
@@ -3,7 +3,6 @@
|
||||
# Regression guard for the unsigned-enum sentinel trap: copy_htsopt's
|
||||
# `if (from->X > -1)` guard is always false for unsigned hts_boolean fields, so
|
||||
# they silently stop being copied. Driven by the in-process 'httrack -#test=copyopt' test.
|
||||
# Keep POSIX-portable (harness runs it via $(BASH), a plain /bin/sh on macOS).
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms despite the
|
||||
# #!/bin/bash above.
|
||||
|
||||
# A -%F footer whose expansion overflows the on-page buffer must be dropped, not
|
||||
# crash the crawl. Before the fix the unchecked hts_footer_format return left the
|
||||
# buffer unterminated and the next strcatbuff aborted (SIGABRT).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# --pause (#185): the inter-file pause target must stay in [min,max] and spread
|
||||
# across it (a per-call rand() would collapse it toward min). Driven by the
|
||||
# in-process 'httrack -#test=pause' test. POSIX-portable ($(BASH) is /bin/sh on macOS).
|
||||
# in-process 'httrack -#test=pause' test.
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# version.rc repeats the version that htsglobal.h declares. Signing enforces that
|
||||
# every binary in a release reports the same one, so a drift fails the signing
|
||||
# request on release day rather than the build. Assert the two agree.
|
||||
# htsglobal.h declares the version; version.rc, configure.ac and the metainfo
|
||||
# repeat it. Miss one and a release ships a mismatched binary or a stale store entry.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
src="${top_srcdir:-..}/src"
|
||||
top="${top_srcdir:-..}"
|
||||
src="$top/src"
|
||||
h="$src/htsglobal.h"
|
||||
rc="$src/version.rc"
|
||||
for f in "$h" "$rc"; do
|
||||
ac="$top/configure.ac"
|
||||
metainfo="$top/html/server/div/com.httrack.WebHTTrack.metainfo.xml"
|
||||
for f in "$h" "$rc" "$ac" "$metainfo"; do
|
||||
[ -f "$f" ] || {
|
||||
echo "cannot find $f"
|
||||
exit 1
|
||||
@@ -31,23 +33,49 @@ rc_fileversion=$(sed -n 's/.*VALUE "FileVersion",[[:space:]]*"\([^"]*\)".*/\1/p'
|
||||
rc_productversion=$(sed -n 's/.*VALUE "ProductVersion",[[:space:]]*"\([^"]*\)".*/\1/p' "$rc")
|
||||
rc_productname=$(sed -n 's/.*VALUE "ProductName",[[:space:]]*"\([^"]*\)".*/\1/p' "$rc")
|
||||
|
||||
# And again in configure.ac and the AppStream metainfo.
|
||||
ac_version=$(sed -n 's/^AC_INIT(\[[^]]*\],[[:space:]]*\[\([^]]*\)\].*/\1/p' "$ac")
|
||||
# Comments dropped, one tag per line: a parked <release> or a version= on the
|
||||
# <releases> wrapper would otherwise pose as the newest entry.
|
||||
releases=$(sed 's/<!--.*-->//g' "$metainfo" | tr '<' '\n' |
|
||||
sed -n 's/^release[[:space:]][^>]*version="\([^"]*\)".*/\1/p')
|
||||
if [ -z "$ac_version" ]; then
|
||||
echo "could not read the version from $ac"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$releases" ]; then
|
||||
echo "could not read any release version from $metainfo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 3.49.12 -> 3,49,12,0
|
||||
expected_numeric="$(echo "$versionid" | tr '.' ','),0"
|
||||
|
||||
fail=0
|
||||
check() { # what expected actual
|
||||
if [ "$2" != "$3" ]; then
|
||||
echo "version.rc $1 is \"$3\", but htsglobal.h says it should be \"$2\""
|
||||
echo "$1 is \"$3\", but htsglobal.h says it should be \"$2\""
|
||||
fail=1
|
||||
fi
|
||||
}
|
||||
check FILEVERSION "$expected_numeric" "$fileversion"
|
||||
check PRODUCTVERSION "$expected_numeric" "$productversion"
|
||||
check FileVersion "$versionid" "$rc_fileversion"
|
||||
check ProductVersion "$version" "$rc_productversion"
|
||||
check "version.rc FILEVERSION" "$expected_numeric" "$fileversion"
|
||||
check "version.rc PRODUCTVERSION" "$expected_numeric" "$productversion"
|
||||
check "version.rc FileVersion" "$versionid" "$rc_fileversion"
|
||||
check "version.rc ProductVersion" "$version" "$rc_productversion"
|
||||
|
||||
# Signing pins the product name too.
|
||||
check ProductName "HTTrack Website Copier" "$rc_productname"
|
||||
check "version.rc ProductName" "HTTrack Website Copier" "$rc_productname"
|
||||
|
||||
check "the configure.ac AC_INIT version" "$versionid" "$ac_version"
|
||||
check "the newest metainfo release" "$versionid" "$(echo "$releases" | head -n 1)"
|
||||
|
||||
# The check above trusts head -1, so require the list to actually descend.
|
||||
echo "$releases" | awk -F'[.]' '
|
||||
!/^[0-9]+\.[0-9]+\.[0-9]+$/ { print "unparsable metainfo release version: " $0; exit 1 }
|
||||
{ n = $1 * 1000000000 + $2 * 1000000 + $3 }
|
||||
NR > 1 && n >= prev { print "metainfo releases are not newest-first: " $0; exit 1 }
|
||||
{ prev = n }
|
||||
' || fail=1
|
||||
|
||||
[ "$fail" -eq 0 ] || exit 1
|
||||
|
||||
@@ -61,4 +89,4 @@ case "$out" in
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "version resource agrees with htsglobal.h: $version ($expected_numeric)"
|
||||
echo "version.rc, configure.ac and the metainfo agree with htsglobal.h: $version ($expected_numeric)"
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms and GNU-only
|
||||
# tool flags despite the #!/bin/bash above.
|
||||
|
||||
# Golden cache-format regression test (driven by 'httrack -#test=cache-golden <dir>').
|
||||
#
|
||||
# 01_zlib-cache.test writes the cache with the same build it reads back (a
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms and GNU-only
|
||||
# tool flags despite the #!/bin/bash above.
|
||||
|
||||
# Cache create/read/update logic (driven by 'httrack -#test=cache <dir>').
|
||||
#
|
||||
# The in-process self-test stores several hand-crafted edge entries (normal
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
# The committed man/httrack.1 must match what man/makeman.sh produces from the
|
||||
# current "httrack --help" output. This catches a --help change that was not
|
||||
# followed by "make -C man regen-man".
|
||||
#
|
||||
# Keep this POSIX-portable: the harness runs it via $(BASH), which is a plain
|
||||
# POSIX /bin/sh on some platforms (e.g. macOS), so avoid bashisms (such as
|
||||
# process substitution) despite the #!/bin/bash above.
|
||||
|
||||
# pipefail is a bashism; keep to POSIX set flags ($(BASH) may be /bin/sh here).
|
||||
set -eu
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
@@ -38,16 +38,7 @@ mkdir -p "$work/root" "$work/proj"
|
||||
>"$work/server.out" 2>"$work/server.err" &
|
||||
spid=$!
|
||||
|
||||
port=
|
||||
for _ in $(seq 1 100); do
|
||||
port=$(sed -n 's/^PORT \([0-9]*\)$/\1/p' "$work/server.out" 2>/dev/null || true)
|
||||
test -n "$port" && break
|
||||
sleep 0.1
|
||||
done
|
||||
test -n "$port" || {
|
||||
echo "local-server.py did not announce a port" >&2
|
||||
exit 1
|
||||
}
|
||||
port=$(discover_server_port "$work/server.out" "$spid") || exit 1
|
||||
|
||||
# Save path is "127.0.0.1_<port>/" + the URL path; target 998 bytes total.
|
||||
prefix="127.0.0.1_${port}/"
|
||||
|
||||
@@ -24,7 +24,7 @@ python=$(find_python) || {
|
||||
|
||||
# The GUI assets asserted below have to exist, or every check goes vacuous.
|
||||
for asset in server/ping.js server/style.css images/screenshot_01.jpg \
|
||||
images/bg_rings.gif img/guide-droid-opt-spider.png \
|
||||
images/bg_rings.svg images/header_title_4.gif img/guide-droid-opt-spider.png \
|
||||
server/div/com.httrack.WebHTTrack.metainfo.xml; do
|
||||
test -f "${distdir}/html/${asset}" || fail "missing GUI asset ${asset}"
|
||||
done
|
||||
@@ -131,7 +131,8 @@ def check_html_headers(path):
|
||||
check_html_headers("/server/index.html")
|
||||
check_type("/server/ping.js", "text/javascript")
|
||||
check_type("/server/style.css", "text/css")
|
||||
check_type("/images/bg_rings.gif", "image/gif")
|
||||
check_type("/images/bg_rings.svg", "image/svg+xml")
|
||||
check_type("/images/header_title_4.gif", "image/gif")
|
||||
check_type("/img/guide-droid-opt-spider.png", "image/png")
|
||||
check_type("/images/screenshot_01.jpg", "image/jpeg")
|
||||
# Dots in the stem: the type comes from the last one, not the first.
|
||||
|
||||
70
tests/143_engine-backtrace-empty.test
Normal file
70
tests/143_engine-backtrace-empty.test
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# An empty backtrace() must still leave a report; a silent one reads as a
|
||||
# handler that died halfway.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ulimit -c 0 # a deliberate crash must not litter the box with cores
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
echo "LD_PRELOAD interposition is Linux-only here, skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
command -v httrack >/dev/null || {
|
||||
echo "could not find httrack" >&2
|
||||
exit 1
|
||||
}
|
||||
# A --disable-shared build has nothing to preload; anything else missing is a
|
||||
# build problem, not a skip, or the leg below would pass vacuously.
|
||||
if [ ! -r "${NOBACKTRACE_LA:-}" ]; then
|
||||
echo "${NOBACKTRACE_LA:-\$NOBACKTRACE_LA} was not built" >&2
|
||||
exit 1
|
||||
fi
|
||||
if grep -q "^dlname=''" "$NOBACKTRACE_LA"; then
|
||||
echo "static-only build, skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
if [ ! -r "${NOBACKTRACE_LIB:-}" ]; then
|
||||
echo "${NOBACKTRACE_LIB:-\$NOBACKTRACE_LIB} was not built" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# An LD_PRELOAD library loads ahead of the executable's own libasan, which ASan
|
||||
# refuses by default. The shim allocates nothing, so the ordering is harmless.
|
||||
export ASAN_OPTIONS="${ASAN_OPTIONS:+$ASAN_OPTIONS:}verify_asan_link_order=0"
|
||||
|
||||
# Control: a build that never traces would pass the assertion below for the
|
||||
# wrong reason.
|
||||
rc=0
|
||||
plain=$(HTTRACK_NO_SYMBOLIZE=1 httrack -#c=abort 2>&1) || rc=$? # no addr2line to wedge on
|
||||
test "$rc" -eq 134 || {
|
||||
echo "the plain crash exited $rc, not 134" >&2
|
||||
exit 1
|
||||
}
|
||||
if grep -q 'No stack trace available' <<<"$plain"; then
|
||||
echo "no working backtrace() in this build; skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
|
||||
rc=0
|
||||
out=$(LD_PRELOAD="$NOBACKTRACE_LIB" httrack -#c=abort 2>&1) || rc=$?
|
||||
test "$rc" -eq 134 || {
|
||||
echo "the interposed crash exited $rc, not 134" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -q "^Caught signal 6$" <<<"$out" || {
|
||||
echo "no 'Caught signal' line:" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -q 'No stack trace available: unwinding failed' <<<"$out" || {
|
||||
echo "an empty backtrace left no diagnostic:" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -q "Please report the problem" <<<"$out" || {
|
||||
echo "the handler stopped before its closing line:" >&2
|
||||
printf '%s\n' "$out" >&2
|
||||
exit 1
|
||||
}
|
||||
14
tests/144_engine-datadir.test
Normal file
14
tests/144_engine-datadir.test
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A relocated install must read its own templates rather than silently falling
|
||||
# back to the compiled-in defaults (#894).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/datadir.XXXXXX") || {
|
||||
echo "no tmpdir" >&2
|
||||
exit 1
|
||||
}
|
||||
trap 'set +e; rm -rf "${work}"' EXIT
|
||||
|
||||
httrack -#test=datadir "${work}"
|
||||
40
tests/145_webhttrack-datadir.test
Normal file
40
tests/145_webhttrack-datadir.test
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A build with a non-default --datadir installed a launcher that could not start
|
||||
# (#887): the configured datadir must be searched, and after the relative entries.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script="${abs_top_builddir:?not run under make check}/src/webhttrack"
|
||||
datadir="${CONFIGURED_DATADIR:?not run under make check}"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
test -r "${script}" || fail "no ${script}"
|
||||
|
||||
line=$(grep '^SRCHDISTPATH=' "${script}") || fail "no SRCHDISTPATH in ${script}"
|
||||
case "${line}" in
|
||||
*'@datadir@'*) fail "@datadir@ was never substituted: ${line}" ;;
|
||||
esac
|
||||
|
||||
# Read the list back as the shell does, so a quoting slip fails here.
|
||||
BINWD='@BINWD@'
|
||||
eval "${line}"
|
||||
test "${#SRCHDISTPATH[@]}" -gt 3 || fail "SRCHDISTPATH is too short: ${line}"
|
||||
|
||||
pos=-1
|
||||
for i in "${!SRCHDISTPATH[@]}"; do
|
||||
test "${SRCHDISTPATH[$i]}" = "${datadir}" && pos=$i && break
|
||||
done
|
||||
test "${pos}" -ge 0 || fail "configured datadir ${datadir} is not in SRCHDISTPATH"
|
||||
|
||||
# The relative entries must still come first, or a moved tree reads the build machine's.
|
||||
if [ "${SRCHDISTPATH[0]}" != "${BINWD}/../share" ] || [ "${SRCHDISTPATH[1]}" != "${BINWD}/.." ]; then
|
||||
fail "the relative entries moved: ${SRCHDISTPATH[0]} ${SRCHDISTPATH[1]}"
|
||||
fi
|
||||
test "${pos}" -eq 2 || fail "configured datadir is at index ${pos}, expected 2"
|
||||
|
||||
echo "webhttrack searches the configured datadir: ${datadir}"
|
||||
90
tests/146_bash-shell.test
Normal file
90
tests/146_bash-shell.test
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# configure must hand the Makefiles a real bash (BASH_SHELL): "make deb" and the test
|
||||
# driver both run bash scripts through it. Not searched into BASH, which bash presets to
|
||||
# its own path, so on macOS -- where /bin/sh is a bash -- it reported /bin/sh (#895, #891).
|
||||
#
|
||||
# shellcheck disable=SC2016 # the probes are for the shell under test to expand
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
sh=${BASH_SHELL:-}
|
||||
test -n "$sh" || {
|
||||
echo "BASH_SHELL is empty; tests/Makefile.am must export the configured value" >&2
|
||||
exit 1
|
||||
}
|
||||
test -x "$sh" || {
|
||||
echo "BASH_SHELL=$sh is not an executable file" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
version=$("$sh" -c 'echo "${BASH_VERSION:-}"')
|
||||
test -n "$version" || {
|
||||
echo "BASH_SHELL=$sh sets no BASH_VERSION, so it is not a bash" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# sh-mode bash sets BASH_VERSION too, so the version alone proves nothing.
|
||||
opts=$("$sh" -c 'echo ":${SHELLOPTS:-}:"')
|
||||
case "$opts" in
|
||||
*:posix:*)
|
||||
echo "BASH_SHELL=$sh is a bash in POSIX sh-mode" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# The test helpers use process substitution, which macOS's bash-3.2-as-sh rejects.
|
||||
out=$("$sh" -c 'cat <(echo procsub)' 2>&1) || {
|
||||
echo "BASH_SHELL=$sh cannot run a process substitution: $out" >&2
|
||||
exit 1
|
||||
}
|
||||
test "$out" = procsub || {
|
||||
echo "expected 'procsub' from BASH_SHELL=$sh, got: $out" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Whatever configure picks, an absolute BASH_SHELL from the user must win over the search.
|
||||
configure="${abs_top_srcdir:-}/configure"
|
||||
test -r "$configure" || {
|
||||
echo "no configure script at $configure; tests/Makefile.am must export abs_top_srcdir" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
help=$(bash "$configure" --help)
|
||||
grep -q '^ *BASH_SHELL ' <<<"$help" || {
|
||||
echo "configure --help does not advertise BASH_SHELL (AC_ARG_VAR missing)" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
mkdir "$tmp/src" "$tmp/bld"
|
||||
ln -s "$sh" "$tmp/mybash"
|
||||
|
||||
# Symlink farm, not the real srcdir: an in-tree config.status makes autoconf refuse it.
|
||||
for f in "$abs_top_srcdir"/*; do
|
||||
case "${f##*/}" in
|
||||
config.status | config.log | config.h | stamp-h1 | Makefile) continue ;;
|
||||
esac
|
||||
ln -s "$f" "$tmp/src/"
|
||||
done
|
||||
|
||||
# Prefer the Makefile: it proves the value that reaches $(BASH_SHELL), not just the macro's
|
||||
# decision. configure may die on a library check before writing one, so fall back to the
|
||||
# trace it printed earlier.
|
||||
(cd "$tmp/bld" && BASH_SHELL="$tmp/mybash" bash "$tmp/src/configure" --disable-https >conf.log 2>&1) || true
|
||||
if test -f "$tmp/bld/Makefile"; then
|
||||
got=$(sed -n 's/^BASH_SHELL = //p' "$tmp/bld/Makefile")
|
||||
from="Makefile"
|
||||
else
|
||||
got=$(sed -n 's/^checking for bash\.\.\. //p' "$tmp/bld/conf.log")
|
||||
got=${got#"(cached) "}
|
||||
from="trace"
|
||||
fi
|
||||
test "$got" = "$tmp/mybash" || {
|
||||
echo "configure discarded BASH_SHELL=$tmp/mybash, $from has: ${got:-<nothing>}" >&2
|
||||
tail -20 "$tmp/bld/conf.log" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "configured bash is $sh ($version)"
|
||||
162
tests/147_local-proxytrack-webdav-overflow.test
Executable file
162
tests/147_local-proxytrack-webdav-overflow.test
Executable file
@@ -0,0 +1,162 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# escapexml() expands '&' fivefold, so an unauthenticated PROPFIND path of
|
||||
# escaped ampersands used to overflow the fixed DAV item reserve (#836). The
|
||||
# Depth: 1 listing at the end covers the enumeration branch's own item URLs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "$top_srcdir/tests/testlib.sh"
|
||||
|
||||
python=$(find_python) || {
|
||||
echo "python3 missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
command -v curl >/dev/null 2>&1 || {
|
||||
echo "curl missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
# MSYS cannot reap a native listener, and the orphan wedges the suite (#595).
|
||||
if is_windows; then
|
||||
echo "windows: cannot reap a backgrounded proxytrack, skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
dir=$(mktemp -d)
|
||||
ptpid=
|
||||
cleanup() {
|
||||
stop_server "$ptpid"
|
||||
rm -rf "$dir"
|
||||
}
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap 'set +e; cleanup; exit 1' INT TERM HUP
|
||||
|
||||
printf 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\n' >"$dir/hdr"
|
||||
printf 'hello' >"$dir/body"
|
||||
alen=$(($(wc -c <"$dir/hdr") + $(wc -c <"$dir/body")))
|
||||
{
|
||||
printf 'filedesc://t.arc 0.0.0.0 20250101000000 text/plain 200 - - 0 t.arc 9\n'
|
||||
printf '2 0 test\n'
|
||||
printf '\n\n'
|
||||
printf 'http://example.com/page.html 0.0.0.0 20250101000000 text/html 200 - - 0 t.arc %d\n' "$alen"
|
||||
cat "$dir/hdr" "$dir/body"
|
||||
# a record begins on the newline following the previous one's data
|
||||
printf '\n'
|
||||
# gives the Depth: 1 listing below a child directory as well as a child file
|
||||
printf 'http://example.com/sub/deep.html 0.0.0.0 20250101000000 text/html 200 - - 0 t.arc %d\n' "$alen"
|
||||
cat "$dir/hdr" "$dir/body"
|
||||
} >"$dir/in.arc"
|
||||
|
||||
freeport() {
|
||||
"$python" -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()'
|
||||
}
|
||||
proxyport=$(freeport)
|
||||
icpport=$(freeport)
|
||||
|
||||
proxytrack "127.0.0.1:$proxyport" "127.0.0.1:$icpport" "$dir/in.arc" >"$dir/pt.log" 2>&1 &
|
||||
ptpid=$!
|
||||
waited=0
|
||||
until grep -qE "HTTP Proxy installed on|Unable to (initialize a temporary server|create the server)" "$dir/pt.log"; do
|
||||
kill -0 "$ptpid" 2>/dev/null || {
|
||||
echo "FAIL: proxytrack exited before listening"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
test "$waited" -lt 50 || {
|
||||
echo "FAIL: proxytrack never announced its listen port"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
grep -q "HTTP Proxy installed on" "$dir/pt.log" || {
|
||||
echo "FAIL: proxytrack failed to bind"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
|
||||
propfind() {
|
||||
curl -s -i --max-time 30 -X PROPFIND -H "Depth: 0" \
|
||||
"http://127.0.0.1:$proxyport/webdav/x/$1"
|
||||
}
|
||||
|
||||
# Amplification, not raw length, clears the old 1024-byte reserve: 900 '&'
|
||||
# become 4500 bytes, written twice, all under the 1024-byte request-line cap.
|
||||
amps=$("$python" -c "print('&' * 900)")
|
||||
resp=$(propfind "$amps") || {
|
||||
echo "FAIL: proxytrack died on an escapexml-amplified PROPFIND"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status for the amplified path"
|
||||
head -c 512 <<<"$resp"
|
||||
exit 1
|
||||
}
|
||||
# Count what came back: a clipped path answers 207 too. The run appears in
|
||||
# the href and again in the displayname.
|
||||
got=$({ grep -o '&' <<<"$resp" || true; } | wc -l)
|
||||
test "$got" -eq 1800 || {
|
||||
echo "FAIL: 900 ampersands went in, $got came back escaped, wanted 1800"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Unescaped, so the bound is shown to be on the written length, not on '&'.
|
||||
plain=$("$python" -c "print('a' * 900)")
|
||||
resp=$(propfind "$plain") || {
|
||||
echo "FAIL: proxytrack died on a 900-byte plain PROPFIND path"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q "<href>/webdav/x/$plain</href>" <<<"$resp" || {
|
||||
echo "FAIL: a 900-byte path did not come back whole"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Depth: 1 is the only route into the enumeration branch, which builds each
|
||||
# item URL from the prefix and the child name. A child directory is enumerated
|
||||
# with a trailing '/' that has to come back off, or its name is lost and the
|
||||
# entry lists as the folder's default document.
|
||||
resp=$(curl -s -i --max-time 30 -X PROPFIND -H "Depth: 1" \
|
||||
"http://127.0.0.1:$proxyport/webdav/example.com/") || {
|
||||
echo "FAIL: proxytrack died on a Depth: 1 listing"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status for the Depth: 1 listing"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
for want in '<href>/webdav/example\.com</href>' \
|
||||
'<href>/webdav/example\.com/page\.html</href>' \
|
||||
'<href>/webdav/example\.com/sub</href>' \
|
||||
'<displayname>sub</displayname>'; do
|
||||
grep -q "$want" <<<"$resp" || {
|
||||
echo "FAIL: the Depth: 1 listing is missing $want"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
responses=$(grep -c '<response ' <<<"$resp" || true)
|
||||
test "$responses" -eq 3 || {
|
||||
echo "FAIL: expected the root, the file and the directory, got $responses responses"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The overflow killed the listener, so a later request is the liveness proof.
|
||||
resp=$(propfind "") || {
|
||||
echo "FAIL: proxytrack died before the follow-up listing"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status on the follow-up listing"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "OK: an escapexml-amplified PROPFIND path is served whole and does not overflow"
|
||||
14
tests/148_engine-spoolname.test
Executable file
14
tests/148_engine-spoolname.test
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A frozen backlog slot must spool outside the mirror namespace, or a site
|
||||
# serving <path>.tmp has its own copy truncated and then unlinked (#859).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/spoolname.XXXXXX") || {
|
||||
echo "no tmpdir" >&2
|
||||
exit 1
|
||||
}
|
||||
trap 'set +e; rm -rf "${work}"' EXIT
|
||||
|
||||
httrack -#test=spoolname "${work}"
|
||||
30
tests/149_local-chunked-trailer.test
Executable file
30
tests/149_local-chunked-trailer.test
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A trailer section after the terminating chunk (RFC 9112 7.1.2, #855) is
|
||||
# discarded, bounded by HTS_LINE_BLOCK_SIZE (huge.html), and junk where a data
|
||||
# chunk's own CRLF belongs stays a framing error (bogus.html). eof.html pins the
|
||||
# deliberate leniency: past a complete body, an unterminated section still lands.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" \
|
||||
--log-not-found 'Invalid chunk.*chunktrail/(one|many|none|file|eof)' \
|
||||
--log-not-found 'illegal chunk CRLF.*chunktrail/(one|many|none|file|huge|eof)' \
|
||||
--file-matches 'chunktrail/one.html' 'CHUNKTRAIL-ONE' \
|
||||
--file-matches 'chunktrail/many.html' 'CHUNKTRAIL-MANY' \
|
||||
--file-matches 'chunktrail/none.html' 'CHUNKTRAIL-NONE' \
|
||||
--file-matches 'chunktrail/file.bin' 'CHUNKTRAIL-BIN' \
|
||||
--file-min-bytes 'chunktrail/file.bin' 16384 \
|
||||
--file-matches 'chunktrail/eof.html' 'CHUNKTRAIL-EOF' \
|
||||
--cache-found '/chunktrail/one.html' \
|
||||
--cache-found '/chunktrail/many.html' \
|
||||
--cache-found '/chunktrail/file.bin' \
|
||||
--log-found 'Interrupted transfer.*chunktrail/huge\.html' \
|
||||
--not-found 'chunktrail/huge.html' \
|
||||
--cache-not-found '/chunktrail/huge.html' \
|
||||
--log-found 'illegal chunk CRLF.*chunktrail/bogus\.html' \
|
||||
--not-found 'chunktrail/bogus.html' \
|
||||
--cache-not-found '/chunktrail/bogus.html' \
|
||||
httrack 'BASEURL/chunktrail/index.html'
|
||||
25
tests/150_engine-strsprintf.test
Executable file
25
tests/150_engine-strsprintf.test
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# StringSprintf writes the terminator at buffer[ret], so a `ret <= capacity`
|
||||
# guard overflows by one byte whenever the output exactly fills the capacity
|
||||
# (#836). The proxytrack crawl tests never land on a capacity boundary.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
# Resolve httrack before any cd: PATH may carry a build-relative entry.
|
||||
bin=$(command -v httrack) || {
|
||||
echo "FAIL: httrack not found on PATH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
out=$("$bin" -#test=strsprintf) || fail "httrack -#test=strsprintf exited non-zero: $out"
|
||||
test "$out" = "strsprintf self-test: OK" || fail "expected 'strsprintf self-test: OK', got: $out"
|
||||
|
||||
exit 0
|
||||
141
tests/151_bash-shell-validate.test
Normal file
141
tests/151_bash-shell-validate.test
Normal file
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# An absolute BASH_SHELL is taken verbatim by AC_PATH_PROGS and a relative one is dropped
|
||||
# for the search result, so configure has to check what it ends up with: BASH_SHELL=/bin/sh
|
||||
# otherwise puts #895 back and only shows up at "make check" or "make deb" (#908).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
sh=${BASH_SHELL:-}
|
||||
test -n "$sh" || {
|
||||
echo "BASH_SHELL is empty; tests/Makefile.am must export the configured value" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
configure="${abs_top_srcdir:-}/configure"
|
||||
test -r "$configure" || {
|
||||
echo "no configure script at $configure; tests/Makefile.am must export abs_top_srcdir" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'set +e; rm -rf "$tmp"' EXIT
|
||||
trap 'set +e; rm -rf "$tmp"; exit 1' HUP INT TERM
|
||||
|
||||
# Symlink farm, not the real srcdir: an in-tree config.status makes autoconf refuse it.
|
||||
mkdir "$tmp/src"
|
||||
for f in "$abs_top_srcdir"/*; do
|
||||
case "${f##*/}" in
|
||||
config.status | config.log | config.h | stamp-h1 | Makefile) continue ;;
|
||||
esac
|
||||
ln -s "$f" "$tmp/src/"
|
||||
done
|
||||
|
||||
mkdir "$tmp/notexec" "$tmp/with space" "$tmp/fakebin"
|
||||
: >"$tmp/notexec/bash"
|
||||
chmod 644 "$tmp/notexec/bash"
|
||||
ln -s "$sh" "$tmp/mybash"
|
||||
ln -s "$sh" "$tmp/with space/bash"
|
||||
ln -s "$sh" "$tmp/sh" # bash invoked as "sh" enters POSIX mode
|
||||
# A "bash" the PATH search will find first, and that answers -c like any other shell.
|
||||
printf '#!/bin/sh\nexec /bin/sh "$@"\n' >"$tmp/fakebin/bash"
|
||||
chmod 755 "$tmp/fakebin/bash"
|
||||
|
||||
n=0
|
||||
status=0
|
||||
log=
|
||||
rundir=
|
||||
run() { # run <label> <env argument>...
|
||||
local label=$1
|
||||
shift
|
||||
n=$((n + 1))
|
||||
rundir="$tmp/run$n"
|
||||
mkdir "$rundir"
|
||||
status=0
|
||||
(cd "$rundir" && env "$@" bash "$tmp/src/configure" --disable-https) \
|
||||
>"$rundir/log" 2>&1 || status=$?
|
||||
log=$(cat "$rundir/log")
|
||||
echo "run $n ($label): exit $status"
|
||||
}
|
||||
|
||||
reject() { # reject <label> <expected message> <env argument>...
|
||||
local label=$1 want=$2
|
||||
shift 2
|
||||
run "$label" "$@"
|
||||
test "$status" -ne 0 || {
|
||||
echo "configure accepted $label" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -q "$want" <<<"$log" || {
|
||||
echo "$label rejected without '$want':" >&2
|
||||
tail -5 <<<"$log" >&2
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# accept <label> <expected $(BASH_SHELL), "" for any> <expected message, "" for none> <env argument>...
|
||||
accept() {
|
||||
local label=$1 path=$2 want=$3
|
||||
shift 3
|
||||
run "$label" "$@"
|
||||
test "$status" -eq 0 || {
|
||||
echo "configure rejected $label (exit $status):" >&2
|
||||
tail -10 <<<"$log" >&2
|
||||
exit 1
|
||||
}
|
||||
got=$(sed -n 's/^BASH_SHELL = //p' "$rundir/Makefile")
|
||||
test -n "$got" || {
|
||||
echo "$label configured, but the Makefile carries no BASH_SHELL" >&2
|
||||
exit 1
|
||||
}
|
||||
if test -n "$path" && test "$got" != "$path"; then
|
||||
echo "$label reached the Makefile as $got" >&2
|
||||
exit 1
|
||||
fi
|
||||
if test -n "$want"; then
|
||||
grep -q "$want" <<<"$log" || {
|
||||
echo "$label configured without '$want':" >&2
|
||||
tail -5 <<<"$log" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
reject relative 'BASH_SHELL must be an absolute path' BASH_SHELL=relbash
|
||||
reject missing 'is not an executable file' "BASH_SHELL=$tmp/missing/bash"
|
||||
reject non-executable 'is not an executable file' "BASH_SHELL=$tmp/notexec/bash"
|
||||
reject sh-mode 'is a bash in POSIX sh-mode' "BASH_SHELL=$tmp/sh"
|
||||
# The #908 case: dash on Linux, bash in sh-mode on macOS, unusable either way. The spoofed
|
||||
# BASH_VERSION is what an ordinary shell echoes straight back, so it cannot be the probe.
|
||||
reject /bin/sh 'BASH_SHELL=/bin/sh is ' BASH_SHELL=/bin/sh BASH_VERSION=9.9
|
||||
|
||||
# make splits the value on whitespace, cuts it at a '#' and expands a '$', and no Makefile
|
||||
# quotes $(BASH_SHELL).
|
||||
meta='must not contain shell or make metacharacters'
|
||||
reject space "$meta" "BASH_SHELL=$tmp/with space/bash"
|
||||
reject semicolon "$meta" 'BASH_SHELL=/opt/a;b/bash'
|
||||
# shellcheck disable=SC2016 # the '$' has to reach configure unexpanded
|
||||
reject dollar "$meta" 'BASH_SHELL=/opt/a$b/bash'
|
||||
reject hash "$meta" 'BASH_SHELL=/opt/a#b/bash'
|
||||
|
||||
# An environment in POSIX mode leaves no path that could pass, so the message must blame it
|
||||
# and not the shell.
|
||||
reject posix-env 'POSIXLY_CORRECT or SHELLOPTS' "BASH_SHELL=$sh" POSIXLY_CORRECT=1
|
||||
|
||||
# Both at once: clearing the variable would still leave a bash invoked as sh, so the second
|
||||
# probe has to decide which one to blame instead of reading the environment.
|
||||
reject sh-mode-in-posix-env "BASH_SHELL=$tmp/sh is a bash in POSIX sh-mode" \
|
||||
"BASH_SHELL=$tmp/sh" POSIXLY_CORRECT=1
|
||||
if grep -q 'POSIXLY_CORRECT or SHELLOPTS' <<<"$log"; then
|
||||
echo "a bash invoked as sh was blamed on the environment:" >&2
|
||||
tail -5 <<<"$log" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
accept override "$tmp/mybash" '' "BASH_SHELL=$tmp/mybash"
|
||||
accept empty '' '' BASH_SHELL=
|
||||
# No override: an unusable bash is a warning, so a box that has none still builds.
|
||||
accept searched "$tmp/fakebin/bash" 'no usable bash found' -u BASH_SHELL "PATH=$tmp/fakebin:$PATH"
|
||||
accept searched-posix-env '' 'POSIXLY_CORRECT or SHELLOPTS' -u BASH_SHELL POSIXLY_CORRECT=1
|
||||
|
||||
echo "configure validated $n BASH_SHELL values"
|
||||
@@ -7,7 +7,8 @@ set -euo pipefail
|
||||
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
distdir=$(cd "${testdir}/.." && pwd)
|
||||
script="${distdir}/src/webhttrack"
|
||||
# The .in, not the generated script: only SRCHDISTPATH differs, and this stays in srcdir.
|
||||
script="${distdir}/src/webhttrack.in"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
|
||||
@@ -43,6 +43,16 @@ if grep -q 'No stack trace available on this OS' "$out"; then
|
||||
echo "no backtrace() on this platform; skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
# 32-bit ARM is the one target whose toolchain may still refuse to unwind;
|
||||
# anywhere else an empty trace is the regression this test exists to catch.
|
||||
case "$(uname -m)" in
|
||||
arm | armv*)
|
||||
if grep -q 'No stack trace available' "$out"; then
|
||||
echo "no unwind tables in this ARM build; skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
# Unconditional and first: symbolizing must never cost the raw trace.
|
||||
grep -q "$rawframe" "$out" || {
|
||||
echo "raw module+offset frames are gone:" >&2
|
||||
|
||||
@@ -8,6 +8,8 @@ set -euo pipefail
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
distdir=${top_srcdir:-$(cd "${testdir}/.." && pwd)}
|
||||
distdir=$(cd "${distdir}" && pwd)
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "${testdir}/testlib.sh"
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $*" >&2
|
||||
@@ -148,12 +150,8 @@ grep -qi '^Content-type: text/html' "${hdr}" ||
|
||||
clog="${work}/content.log"
|
||||
python3 "${testdir}/local-server.py" --root "${work}" >"${clog}" 2>&1 &
|
||||
csrv=$!
|
||||
for _ in $(seq 1 40); do
|
||||
cport=$(sed -n 's/^PORT //p' "${clog}") && test -n "${cport}" && break
|
||||
kill -0 "${csrv}" 2>/dev/null || break
|
||||
sleep 0.25
|
||||
done
|
||||
test -n "${cport:-}" || fail "content server did not come up: $(cat "${clog}")"
|
||||
cport=$(discover_server_port "${clog}" "${csrv}") ||
|
||||
fail "content server did not come up"
|
||||
|
||||
post "${port}" "sid=${sid}" "path=${work}" projname=crawl winprofile=x \
|
||||
command_do=start \
|
||||
|
||||
@@ -4,7 +4,7 @@ include $(srcdir)/tests-list.mk
|
||||
# Committed binary fixture read by 01_zlib-cache-golden.test. List it
|
||||
# explicitly: automake does not expand wildcards in EXTRA_DIST, so a glob would
|
||||
# silently drop it from the dist tarball and break "make distcheck".
|
||||
EXTRA_DIST = $(TESTS) renamefail.c threadattrfail.c crawl-test.sh run-all-tests.sh check-network.sh \
|
||||
EXTRA_DIST = $(TESTS) renamefail.c threadattrfail.c nobacktrace.c crawl-test.sh run-all-tests.sh check-network.sh \
|
||||
proxy-https-server.py socks5-server.py proxy-connect-server.py \
|
||||
proxytestlib.py tls-stall-server.py warc-validate.py wacz-validate.py \
|
||||
pty-resize.py test-timeout.sh \
|
||||
@@ -24,15 +24,22 @@ TESTS_ENVIRONMENT += HTTPS_SUPPORT=$(HTTPS_SUPPORT)
|
||||
TESTS_ENVIRONMENT += BROTLI_ENABLED=$(BROTLI_ENABLED)
|
||||
TESTS_ENVIRONMENT += ZSTD_ENABLED=$(ZSTD_ENABLED)
|
||||
TESTS_ENVIRONMENT += V6_SUPPORT=$(V6_SUPPORT)
|
||||
# Asserted by 146_bash-shell.test.
|
||||
TESTS_ENVIRONMENT += BASH_SHELL=$(BASH_SHELL)
|
||||
TESTS_ENVIRONMENT += top_srcdir=$(top_srcdir)
|
||||
TESTS_ENVIRONMENT += abs_top_srcdir=$(abs_top_srcdir)
|
||||
TESTS_ENVIRONMENT += abs_top_builddir=$(abs_top_builddir)
|
||||
TESTS_ENVIRONMENT += CONFIGURED_DATADIR=$(datadir)
|
||||
TESTS_ENVIRONMENT += RENAMEFAIL_LA=$(abs_builddir)/librenamefail.la
|
||||
TESTS_ENVIRONMENT += RENAMEFAIL_LIB=$(abs_builddir)/$(LT_CV_OBJDIR)/librenamefail.so
|
||||
TESTS_ENVIRONMENT += THREADATTRFAIL_LA=$(abs_builddir)/libthreadattrfail.la
|
||||
TESTS_ENVIRONMENT += THREADATTRFAIL_LIB=$(abs_builddir)/$(LT_CV_OBJDIR)/libthreadattrfail.so
|
||||
TESTS_ENVIRONMENT += NOBACKTRACE_LA=$(abs_builddir)/libnobacktrace.la
|
||||
TESTS_ENVIRONMENT += NOBACKTRACE_LIB=$(abs_builddir)/$(LT_CV_OBJDIR)/libnobacktrace.so
|
||||
|
||||
# rename() interposer for 01_engine-renameover.test; -rpath is what makes
|
||||
# libtool build a shared module rather than a static-only convenience library.
|
||||
check_LTLIBRARIES = librenamefail.la libthreadattrfail.la
|
||||
check_LTLIBRARIES = librenamefail.la libthreadattrfail.la libnobacktrace.la
|
||||
librenamefail_la_SOURCES = renamefail.c
|
||||
librenamefail_la_LDFLAGS = -module -avoid-version -rpath $(abs_builddir)
|
||||
librenamefail_la_LIBADD = $(DL_LIBS)
|
||||
@@ -42,6 +49,10 @@ libthreadattrfail_la_SOURCES = threadattrfail.c
|
||||
libthreadattrfail_la_LDFLAGS = -module -avoid-version -rpath $(abs_builddir)
|
||||
libthreadattrfail_la_LIBADD = $(DL_LIBS)
|
||||
|
||||
# backtrace() interposer for 143_engine-backtrace-empty.test
|
||||
libnobacktrace_la_SOURCES = nobacktrace.c
|
||||
libnobacktrace_la_LDFLAGS = -module -avoid-version -rpath $(abs_builddir)
|
||||
|
||||
TEST_EXTENSIONS = .test
|
||||
# Run each .test through bash instead of execve()ing it. This lets "make check"
|
||||
# work when the source tree sits on a noexec filesystem (the driver would
|
||||
@@ -52,5 +63,5 @@ TEST_EXTENSIONS = .test
|
||||
# named, with diagnostics, rather than hang the job until CI cancels it -- a
|
||||
# cancelled step keeps neither its log nor its artifacts, so today a hang tells
|
||||
# us nothing at all. HTTRACK_TEST_TIMEOUT overrides the budget; 0 disables it.
|
||||
TEST_LOG_COMPILER = $(BASH) $(srcdir)/test-timeout.sh
|
||||
TEST_LOG_COMPILER = $(BASH_SHELL) $(srcdir)/test-timeout.sh
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -20,6 +20,7 @@ import hashlib
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import socketserver
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
@@ -1691,6 +1692,112 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# #855: a trailer section after the terminating zero-length chunk.
|
||||
def send_chunked_trailed(self, body, trailers, ctype="text/html; charset=utf-8"):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", ctype)
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
half = len(body) // 2
|
||||
for piece in (body[:half], body[half:]):
|
||||
self.wfile.write(b"%X\r\n" % len(piece) + piece + b"\r\n")
|
||||
self.wfile.write(
|
||||
b"0\r\n" + b"".join(t + b"\r\n" for t in trailers) + b"\r\n"
|
||||
)
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
def route_chunktrail_index(self):
|
||||
self.send_html(
|
||||
'\t<a href="one.html">one</a>\n'
|
||||
'\t<a href="many.html">many</a>\n'
|
||||
'\t<a href="none.html">none</a>\n'
|
||||
'\t<a href="huge.html">huge</a>\n'
|
||||
'\t<a href="bogus.html">bogus</a>\n'
|
||||
'\t<a href="eof.html">eof</a>\n'
|
||||
'\t<a href="file.bin">file</a>\n'
|
||||
)
|
||||
|
||||
def route_chunktrail_one(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-ONE</p></body></html>", [b"X-Foo: bar"]
|
||||
)
|
||||
|
||||
# Spans several reads: longer than the reader's 256 bytes per call.
|
||||
def route_chunktrail_many(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-MANY</p></body></html>",
|
||||
[b"X-Field-%02d: %s" % (n, b"v" * 40) for n in range(20)],
|
||||
)
|
||||
|
||||
# Control: same body, no trailer section.
|
||||
def route_chunktrail_none(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-NONE</p></body></html>", []
|
||||
)
|
||||
|
||||
# Past HTS_LINE_BLOCK_SIZE: the discard stays bounded, so the transfer drops.
|
||||
def route_chunktrail_huge(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-HUGE</p></body></html>",
|
||||
[b"X-Bloat-%04d: %s" % (n, b"w" * 100) for n in range(160)],
|
||||
)
|
||||
|
||||
# Only the terminating chunk opens the section, so junk in a data chunk's
|
||||
# own CRLF stays a framing error.
|
||||
def route_chunktrail_bogus(self):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
body = b"<html><body><p>CHUNKTRAIL-BOGUS</p></body></html>"
|
||||
self.wfile.write(b"%X\r\n" % len(body) + body + b"X-Foo: bar\r\n")
|
||||
self.wfile.write(b"0\r\n\r\n")
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# Body complete, then EOF before the blank line closing the section. The
|
||||
# terminating chunk already arrived, so the payload stands.
|
||||
def route_chunktrail_eof(self):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
body = b"<html><body><p>CHUNKTRAIL-EOF</p></body></html>"
|
||||
self.wfile.write(b"%X\r\n" % len(body) + body + b"\r\n")
|
||||
self.wfile.write(b"0\r\nX-Checksum: deadbeef\r\n")
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# The direct-to-disk path, where the body never sits in memory.
|
||||
def route_chunktrail_file(self):
|
||||
self.send_chunked_trailed(
|
||||
b"CHUNKTRAIL-BIN\n" + b"\x41\x42\x43\xfd" * 4096,
|
||||
[b"X-Checksum: 0badc0de", b"X-Done: 1"],
|
||||
"application/octet-stream",
|
||||
)
|
||||
|
||||
# Aborts the chunked body with an RST, so the read fails rather than seeing a
|
||||
# clean EOF and the transfer is already in error before the framing check.
|
||||
def route_chunktrunc_reset(self):
|
||||
@@ -2337,6 +2444,14 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"/chunktrunc/stay.html": route_chunktrunc_stay,
|
||||
"/chunktrunc/hostile.html": route_chunktrunc_hostile,
|
||||
"/chunktrunc/reset.bin": route_chunktrunc_reset,
|
||||
"/chunktrail/index.html": route_chunktrail_index,
|
||||
"/chunktrail/one.html": route_chunktrail_one,
|
||||
"/chunktrail/many.html": route_chunktrail_many,
|
||||
"/chunktrail/none.html": route_chunktrail_none,
|
||||
"/chunktrail/huge.html": route_chunktrail_huge,
|
||||
"/chunktrail/bogus.html": route_chunktrail_bogus,
|
||||
"/chunktrail/eof.html": route_chunktrail_eof,
|
||||
"/chunktrail/file.bin": route_chunktrail_file,
|
||||
"/errpage/index.html": route_errpage_index,
|
||||
"/errpage/good.html": route_errpage_good,
|
||||
"/errpage/missing.html": route_errpage_missing,
|
||||
@@ -2665,6 +2780,12 @@ def main():
|
||||
class BacklogHTTPServer(ThreadingHTTPServer):
|
||||
request_queue_size = 128
|
||||
|
||||
# Skip the getfqdn() reverse lookup stock server_bind() does for the
|
||||
# unread server_name: it stalls 35s on macOS (#870).
|
||||
def server_bind(self):
|
||||
socketserver.TCPServer.server_bind(self)
|
||||
self.server_name, self.server_port = self.server_address[:2]
|
||||
|
||||
httpd = BacklogHTTPServer((args.bind, 0), factory)
|
||||
|
||||
if args.tls:
|
||||
|
||||
15
tests/nobacktrace.c
Normal file
15
tests/nobacktrace.c
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Forces the empty backtrace() of an unwind-tableless build, for
|
||||
143_engine-backtrace-empty.test. */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
/* The tree builds with -fvisibility=hidden, which would hide the interposer. */
|
||||
#define SHIM_EXPORT __attribute__((visibility("default")))
|
||||
|
||||
SHIM_EXPORT int backtrace(void **buffer, int size);
|
||||
|
||||
SHIM_EXPORT int backtrace(void **buffer, int size) {
|
||||
(void) buffer;
|
||||
(void) size;
|
||||
return 0;
|
||||
}
|
||||
@@ -214,3 +214,12 @@ TESTS += 139_local-query-charref.test
|
||||
TESTS += 140_crash-handler.test
|
||||
TESTS += 141_webhttrack-warc-options.test
|
||||
TESTS += 142_webhttrack-content-type.test
|
||||
TESTS += 143_engine-backtrace-empty.test
|
||||
TESTS += 144_engine-datadir.test
|
||||
TESTS += 145_webhttrack-datadir.test
|
||||
TESTS += 146_bash-shell.test
|
||||
TESTS += 147_local-proxytrack-webdav-overflow.test
|
||||
TESTS += 149_local-chunked-trailer.test
|
||||
TESTS += 148_engine-spoolname.test
|
||||
TESTS += 151_bash-shell-validate.test
|
||||
TESTS += 150_engine-strsprintf.test
|
||||
|
||||
@@ -1,23 +1,81 @@
|
||||
#!/bin/bash
|
||||
# Smoke-test an installed webhttrack: launch it with a stub browser and assert
|
||||
# htsserver comes up and serves the web UI. Arg: the install prefix.
|
||||
# htsserver comes up and serves the web UI. Args: the install prefix, and optionally
|
||||
# the launcher to run instead of $prefix/bin/webhttrack (the macOS .app stub).
|
||||
set -euo pipefail
|
||||
|
||||
prefix="${1:?usage: webhttrack-smoke.sh <install-prefix>}"
|
||||
wht="$prefix/bin/webhttrack"
|
||||
prefix="${1:?usage: webhttrack-smoke.sh <install-prefix> [launcher]}"
|
||||
wht="${2:-$prefix/bin/webhttrack}"
|
||||
test -x "$wht" || {
|
||||
echo "no webhttrack at $wht" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
browserstub="$prefix/bin/x-www-browser"
|
||||
work="$(mktemp -d)"
|
||||
# webhttrack backgrounds htsserver, which outlives it; reap any stray one (scoped
|
||||
# to this prefix) so a lingering server can never hold the CI step open.
|
||||
trap 'set +e; pkill -f "$prefix/bin/htsserver" 2>/dev/null || true; rm -rf "$work"' EXIT
|
||||
trap 'set +e; pkill -f "$prefix/bin/htsserver" 2>/dev/null || true; rm -rf "$work" "$browserstub"' EXIT
|
||||
export HOME="$work/home"
|
||||
mkdir -p "$HOME/websites"
|
||||
marker="$work/marker"
|
||||
|
||||
# No installed symlink may be absolute, or the tree only works at the prefix it was
|
||||
# built for (#885). BSD find has no -lname, so read each link back by hand.
|
||||
abs=""
|
||||
nlink=0
|
||||
while IFS= read -r l; do
|
||||
nlink=$((nlink + 1))
|
||||
case "$(readlink "$l")" in
|
||||
/*) abs="$abs $l" ;;
|
||||
esac
|
||||
done < <(find "$prefix" -type l)
|
||||
test -z "$abs" || {
|
||||
echo "absolute symlink(s) in install tree:$abs" >&2
|
||||
exit 1
|
||||
}
|
||||
# find's status is lost through the pipe, so an empty scan would pass vacuously.
|
||||
test "$nlink" -gt 0 || {
|
||||
echo "scanned no symlinks at all under $prefix" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Locate the data dir the way webhttrack does, by the file it keys on, rather than
|
||||
# assuming $prefix/share: --datadir moves it, and the link has to follow the data.
|
||||
langdef=$(find "$prefix" -name lang.def -type f | head -1)
|
||||
test -n "$langdef" || {
|
||||
echo "no lang.def under $prefix" >&2
|
||||
exit 1
|
||||
}
|
||||
htmllink="$(dirname "$langdef")/html"
|
||||
test -L "$htmllink" || {
|
||||
echo "no data symlink beside $langdef" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Relocation: resolve the link inside a copy at another path and require it to stay
|
||||
# inside that copy. An absolute link resolves back to $prefix and fails here.
|
||||
reloc="$work/reloc"
|
||||
cp -R "$prefix" "$reloc"
|
||||
relocreal=$(cd "$reloc" && pwd -P)
|
||||
htmlreal=$(cd "$reloc${htmllink#"$prefix"}" 2>/dev/null && pwd -P) || {
|
||||
echo "relocated tree: the data link does not resolve" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$htmlreal" in
|
||||
"$relocreal"/*) ;;
|
||||
*)
|
||||
echo "relocated tree: link escapes to $htmlreal" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# Resolving is not enough: it must land on the served UI, not just any directory.
|
||||
test -d "$htmlreal/server" || {
|
||||
echo "relocated tree: $htmlreal has no server/" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "install tree is relocatable"
|
||||
|
||||
stubdir="$work/bin"
|
||||
mkdir -p "$stubdir"
|
||||
|
||||
@@ -34,14 +92,15 @@ exec /usr/bin/uname "$@"
|
||||
EOF
|
||||
chmod +x "$stubdir/uname"
|
||||
|
||||
# Stub browser: webhttrack tries its browser-name list in order and runs the
|
||||
# first it finds, so shadow the first entry, "x-www-browser". It fetches the
|
||||
# server URL and records PASS only for the working UI: the brand string, the
|
||||
# step-2 form action, and an option-page tooltip, which a truncated/degraded
|
||||
# template page would lack. htsserver only lives until webhttrack exits, so the
|
||||
# check has to happen here.
|
||||
# Stub browser, named after the first entry of webhttrack's browser list. It goes in
|
||||
# $prefix/bin because webhttrack searches its own SRCHPATH before $PATH, so a real
|
||||
# /usr/bin/x-www-browser (Edge, on the GitHub Linux runners) would beat a PATH shadow.
|
||||
# It fetches the server URL and records PASS only for the working UI: the brand
|
||||
# string, the step-2 form action, and an option-page tooltip, which a
|
||||
# truncated/degraded template page would lack. htsserver only lives until webhttrack
|
||||
# exits, so the check has to happen here.
|
||||
# -a: the UI is served ISO-8859-1, so grep must not treat it as binary.
|
||||
cat >"$stubdir/x-www-browser" <<EOF
|
||||
cat >"$browserstub" <<EOF
|
||||
#!/bin/bash
|
||||
echo "stub browser invoked with: \$1" >&2
|
||||
# Also fetch an option page and require a rendered title='' tooltip: proves the
|
||||
@@ -65,8 +124,10 @@ else
|
||||
echo "FAIL: unexpected response from \$1" >"$marker"
|
||||
fi
|
||||
EOF
|
||||
chmod +x "$stubdir/x-www-browser"
|
||||
export PATH="$stubdir:$prefix/bin:$PATH"
|
||||
chmod +x "$browserstub"
|
||||
# Deliberately NOT $prefix/bin: the launcher must find its payload from $0, and
|
||||
# the browser stub is picked up through webhttrack's SRCHPATH, not $PATH.
|
||||
export PATH="$stubdir:$PATH"
|
||||
|
||||
echo "launching webhttrack"
|
||||
"$wht" </dev/null >"$work/webhttrack.log" 2>&1 &
|
||||
|
||||
33
tools/Info.plist.in
Normal file
33
tools/Info.plist.in
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>HTTrack</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.httrack.WebHTTrack</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>WebHTTrack</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>WebHTTrack Website Copier</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@PACKAGE_VERSION@</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@PACKAGE_VERSION@</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<!-- Not background-only: the UI opens in the user's default browser. -->
|
||||
<key>LSBackgroundOnly</key>
|
||||
<false/>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>GNU General Public License v3</string>
|
||||
</dict>
|
||||
</plist>
|
||||
123
tools/macos-app.sh
Executable file
123
tools/macos-app.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/sh
|
||||
# Assemble HTTrack.app from an installed prefix, then verify it. POSIX sh on purpose: a
|
||||
# bashism then fails shellcheck, not CI. Payload in Contents/Resources, where webhttrack
|
||||
# resolves htsserver and its data relative to its own path.
|
||||
set -eu
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 --prefix DIR --plist FILE [--out DIR]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
prefix=""
|
||||
plist=""
|
||||
out="."
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--prefix)
|
||||
prefix="${2:?}"
|
||||
shift 2
|
||||
;;
|
||||
--plist)
|
||||
plist="${2:?}"
|
||||
shift 2
|
||||
;;
|
||||
--out)
|
||||
out="${2:?}"
|
||||
shift 2
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
test -n "$prefix" || usage
|
||||
test -n "$plist" || usage
|
||||
test -r "$prefix/bin/webhttrack" || {
|
||||
echo "no webhttrack under $prefix -- run make install first" >&2
|
||||
exit 1
|
||||
}
|
||||
test -r "$plist" || {
|
||||
echo "no Info.plist at $plist -- it is generated by configure" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
prefix=$(cd "$prefix" && pwd -P)
|
||||
list=$(mktemp)
|
||||
trap 'rm -f "$list"' EXIT
|
||||
app="$out/HTTrack.app"
|
||||
rm -rf "$app"
|
||||
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
|
||||
cp "$plist" "$app/Contents/Info.plist"
|
||||
cp -R "$prefix"/. "$app/Contents/Resources/"
|
||||
rm -rf "$app/Contents/Resources/include"
|
||||
find "$app/Contents/Resources" \( -name '*.la' -o -name '*.a' \) -delete
|
||||
|
||||
# webhttrack carries the configure-time datadir as a --datadir fallback (#887);
|
||||
# in a bundle that is only a build-machine path, and the relative entries ahead
|
||||
# of it already find the payload.
|
||||
wht="$app/Contents/Resources/bin/webhttrack"
|
||||
sed -e "s| \"$prefix/[^\"]*\"||g" "$wht" >"$wht.tmp"
|
||||
mv -f "$wht.tmp" "$wht"
|
||||
chmod +x "$wht"
|
||||
|
||||
cat >"$app/Contents/MacOS/HTTrack" <<'EOF'
|
||||
#!/bin/sh
|
||||
exec "$(dirname "$0")/../Resources/bin/webhttrack" "$@"
|
||||
EOF
|
||||
chmod +x "$app/Contents/MacOS/HTTrack"
|
||||
|
||||
fail() {
|
||||
echo "bundle check: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
appreal=$(cd "$app" && pwd -P)
|
||||
|
||||
find "$app" -type l >"$list"
|
||||
nlink=0
|
||||
while IFS= read -r l; do
|
||||
nlink=$((nlink + 1))
|
||||
case "$(readlink "$l")" in
|
||||
/*) fail "absolute symlink $l" ;;
|
||||
esac
|
||||
done <"$list"
|
||||
test "$nlink" -gt 0 || fail "scanned no symlinks at all, the check proved nothing"
|
||||
|
||||
test -d "$app/Contents/Resources/share/httrack/html/server" ||
|
||||
fail "Contents/Resources/share/httrack/html/server missing"
|
||||
|
||||
# Only our own library must be absent: system and Homebrew dylibs resolve from the
|
||||
# user's own install.
|
||||
if command -v otool >/dev/null 2>&1; then
|
||||
find "$app/Contents" -type f -perm -u+x >"$list"
|
||||
while IFS= read -r bin; do
|
||||
file "$bin" | grep -q Mach-O || continue
|
||||
if otool -L "$bin" | tail -n +2 | grep -q "$prefix"; then
|
||||
otool -L "$bin" >&2
|
||||
fail "$bin still links against the staging prefix (build with --disable-shared)"
|
||||
fi
|
||||
done <"$list"
|
||||
fi
|
||||
|
||||
# otool sees load commands only, so scan the text payload separately. The binaries
|
||||
# themselves still carry $(datadir) compiled in (#894), so they are not in scope here.
|
||||
find "$app/Contents" -type f >"$list"
|
||||
while IFS= read -r f; do
|
||||
file "$f" | grep -q text || continue
|
||||
grep -q -- "$prefix" "$f" 2>/dev/null && fail "$f embeds the staging prefix"
|
||||
done <"$list"
|
||||
|
||||
# The plist version is a separate spot from the engine's, so drift is silent unless
|
||||
# something compares them (#884 is what that looks like when nobody does).
|
||||
plistver=$(awk '/<key>CFBundleShortVersionString<\/key>/ {
|
||||
getline; gsub(/^[^>]*>|<[^<]*$/, ""); print; exit }' "$app/Contents/Info.plist")
|
||||
binver=$("$app/Contents/Resources/bin/httrack" --version 2>/dev/null |
|
||||
sed -n 's/.*[Vv]ersion \([0-9][0-9.]*[-.][0-9][0-9]*\).*/\1/p' | head -1 | tr '-' '.')
|
||||
test -n "$binver" || fail "could not read a version out of the installed httrack"
|
||||
test "$plistver" = "$binver" ||
|
||||
fail "CFBundleShortVersionString says $plistver, httrack says $binver"
|
||||
bundlever=$(awk '/<key>CFBundleVersion<\/key>/ {
|
||||
getline; gsub(/^[^>]*>|<[^<]*$/, ""); print; exit }' "$app/Contents/Info.plist")
|
||||
test "$bundlever" = "$binver" ||
|
||||
fail "CFBundleVersion says $bundlever, httrack says $binver"
|
||||
|
||||
echo "built $appreal (version $plistver)"
|
||||
Reference in New Issue
Block a user