mirror of
https://github.com/xroche/httrack.git
synced 2026-07-26 18:49:39 +03:00
Compare commits
9 Commits
ci/dependa
...
warnfix-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8188b55c84 | ||
|
|
c169db5ab9 | ||
|
|
dfdf10e7fd | ||
|
|
7fe073e9fb | ||
|
|
f47359247d | ||
|
|
dfc72fdbc6 | ||
|
|
8ed607b078 | ||
|
|
0708bf9f98 | ||
|
|
301d7717be |
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -6,3 +6,9 @@ updates:
|
||||
directory: /src
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
# Keep the workflow action pins current (they only rot manually otherwise).
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
|
||||
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
name: build (no python3, Debian buildd)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
name: build (macOS arm64, clang)
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -166,7 +166,7 @@ jobs:
|
||||
name: webhttrack smoke (macOS arm64)
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
name: build (linux i386, gcc -m32)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
name: sanitize (ASan+UBSan, gcc)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -296,7 +296,7 @@ jobs:
|
||||
name: msan (MemorySanitizer, clang)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -344,7 +344,7 @@ jobs:
|
||||
name: fuzz (libFuzzer corpus replay, clang)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -383,7 +383,7 @@ jobs:
|
||||
name: build (no openssl, --disable-https)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -437,7 +437,7 @@ jobs:
|
||||
zlib1g-dev libssl-dev libbrotli-dev libzstd-dev \
|
||||
debhelper devscripts lintian fakeroot
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -468,7 +468,7 @@ jobs:
|
||||
name: distcheck (release tarball)
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
@@ -493,7 +493,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -537,7 +537,7 @@ jobs:
|
||||
tests/*.test
|
||||
tools/mkdeb.sh
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install linters
|
||||
run: |
|
||||
@@ -572,7 +572,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -624,7 +624,7 @@ jobs:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/codeql.yml
vendored
2
.github/workflows/codeql.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
# Upload findings to the repo's code-scanning dashboard.
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
29
.github/workflows/windows-build.yml
vendored
29
.github/workflows/windows-build.yml
vendored
@@ -23,8 +23,13 @@ jobs:
|
||||
matrix:
|
||||
platform: [x64, Win32]
|
||||
configuration: [Release]
|
||||
# Redirect vcpkg's default `files` binary cache into the workspace so
|
||||
# actions/cache can persist it. vcpkg builds openssl/brotli/zlib/zstd from
|
||||
# source otherwise, several minutes every run.
|
||||
env:
|
||||
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\vcpkg_cache
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: recursive # coucal lives in src/coucal
|
||||
|
||||
@@ -44,6 +49,24 @@ jobs:
|
||||
shell: pwsh
|
||||
run: vcpkg integrate install
|
||||
|
||||
# vcpkg errors if VCPKG_DEFAULT_BINARY_CACHE points at a missing dir, and
|
||||
# actions/cache does not create it on a miss.
|
||||
- name: Create the vcpkg binary cache directory
|
||||
shell: pwsh
|
||||
run: New-Item -ItemType Directory -Force -Path $env:VCPKG_DEFAULT_BINARY_CACHE | Out-Null
|
||||
|
||||
# x-gha is gone (vcpkg-tool #1662 dropped it after GitHub changed the cache
|
||||
# API), so cache the binary archives directly. Keyed on the manifest, which
|
||||
# carries the builtin-baseline, so a Dependabot bump busts it; restore-keys
|
||||
# still seeds the unchanged ports' archives, so only the bumped one rebuilds.
|
||||
- name: Cache vcpkg binary archives
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}\vcpkg_cache
|
||||
key: vcpkg-${{ matrix.platform }}-${{ hashFiles('src/vcpkg.json') }}
|
||||
restore-keys: |
|
||||
vcpkg-${{ matrix.platform }}-
|
||||
|
||||
# The runner image's vcpkg checkout is pinned to some commit; our manifest's
|
||||
# builtin-baseline is usually newer, so `git show <baseline>:versions/...`
|
||||
# fails until that commit is local. Fetch exactly the pinned baseline (read
|
||||
@@ -210,7 +233,7 @@ jobs:
|
||||
|
||||
- name: Upload the test logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: engine-tests-${{ matrix.platform }}-${{ matrix.configuration }}
|
||||
path: tests/*.log
|
||||
@@ -218,7 +241,7 @@ jobs:
|
||||
|
||||
- name: Upload MSBuild logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: msbuild-${{ matrix.platform }}-${{ matrix.configuration }}
|
||||
path: msbuild-*.log
|
||||
|
||||
45
configure.ac
45
configure.ac
@@ -66,10 +66,11 @@ AC_SUBST(LT_CV_OBJDIR,$lt_cv_objdir)
|
||||
AC_SUBST(VERSION_INFO)
|
||||
|
||||
### Default CFLAGS
|
||||
# No -Wdeclaration-after-statement: nothing sets -std=, so this builds as gnu17.
|
||||
DEFAULT_CFLAGS="-Wall -Wformat -Wformat-security \
|
||||
-Wmultichar -Wwrite-strings -Wcast-qual -Wcast-align \
|
||||
-Wstrict-prototypes -Wmissing-prototypes \
|
||||
-Wmissing-declarations -Wdeclaration-after-statement \
|
||||
-Wmissing-declarations \
|
||||
-Wpointer-arith -Wsequence-point -Wnested-externs \
|
||||
-D_REENTRANT"
|
||||
AC_SUBST(DEFAULT_CFLAGS)
|
||||
@@ -77,26 +78,28 @@ DEFAULT_LDFLAGS=""
|
||||
AC_SUBST(DEFAULT_LDFLAGS)
|
||||
|
||||
### Additional flags (if supported)
|
||||
AX_CHECK_COMPILE_FLAG([-Wparentheses], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wparentheses"])
|
||||
AX_CHECK_COMPILE_FLAG([-Winit-self], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Winit-self"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wunused-but-set-parameter], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wunused-but-set-parameter"])
|
||||
AX_CHECK_COMPILE_FLAG([-Waddress], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Waddress"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wuninitialized], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wuninitialized"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wformat=2], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wformat=2"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wformat-nonliteral], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wformat-nonliteral"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wmissing-parameter-type], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wmissing-parameter-type"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wold-style-definition], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wold-style-definition"])
|
||||
AX_CHECK_COMPILE_FLAG([-Wignored-qualifiers], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wignored-qualifiers"])
|
||||
# -Werror on probes: exit-status-only checks let clang's warn-on-unknown-flag through.
|
||||
AX_CHECK_COMPILE_FLAG([-Wparentheses], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wparentheses"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Winit-self], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Winit-self"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Wunused-but-set-parameter], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wunused-but-set-parameter"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Waddress], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Waddress"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Wuninitialized], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wuninitialized"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Wformat=2], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wformat=2"], [], [-Werror])
|
||||
# -Wformat-nonliteral needs -Wformat in the probe or gcc rejects it as ignored.
|
||||
AX_CHECK_COMPILE_FLAG([-Wformat-nonliteral], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wformat-nonliteral"], [], [-Werror -Wformat])
|
||||
AX_CHECK_COMPILE_FLAG([-Wmissing-parameter-type], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wmissing-parameter-type"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Wold-style-definition], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wold-style-definition"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Wignored-qualifiers], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Wignored-qualifiers"], [], [-Werror])
|
||||
# Make htssafe.h's pointer-dest 'warning' attribute a hard error in our build
|
||||
# (migration is at zero; a new char* dest is a regression). gcc/clang each take
|
||||
# only their own spelling; downstream keeps the plain warning, not a build break.
|
||||
AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"])
|
||||
AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"])
|
||||
AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"])
|
||||
AX_CHECK_COMPILE_FLAG([-Werror=attribute-warning], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=attribute-warning"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-Werror=user-defined-warnings], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -Werror=user-defined-warnings"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-fstrict-aliasing -Wstrict-aliasing], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstrict-aliasing -Wstrict-aliasing"], [], [-Werror])
|
||||
AX_CHECK_COMPILE_FLAG([-fstack-protector-strong], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector-strong"],
|
||||
[AX_CHECK_COMPILE_FLAG([-fstack-protector], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-protector"])])
|
||||
AX_CHECK_COMPILE_FLAG([-fstack-clash-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fstack-clash-protection"])
|
||||
AX_CHECK_COMPILE_FLAG([-fcf-protection], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -fcf-protection"])
|
||||
[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])
|
||||
AX_CHECK_LINK_FLAG([-Wl,--discard-all], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--discard-all"])
|
||||
AX_CHECK_LINK_FLAG([-Wl,--no-undefined], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,--no-undefined"])
|
||||
AX_CHECK_LINK_FLAG([-Wl,-z,relro,-z,now], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -Wl,-z,relro,-z,now"])
|
||||
@@ -119,13 +122,13 @@ AC_SUBST([LIBC_FORCE_LINK])
|
||||
### PIE
|
||||
CFLAGS_PIE=""
|
||||
LDFLAGS_PIE=""
|
||||
AX_CHECK_COMPILE_FLAG([-fpie -pie], [CFLAGS_PIE="-fpie -pie"])
|
||||
AX_CHECK_COMPILE_FLAG([-fpie], [CFLAGS_PIE="-fpie"], [], [-Werror])
|
||||
AX_CHECK_LINK_FLAG([-pie], [LDFLAGS_PIE="-pie"])
|
||||
AC_SUBST([CFLAGS_PIE])
|
||||
AC_SUBST([LDFLAGS_PIE])
|
||||
|
||||
## Export all symbols for backtraces
|
||||
AX_CHECK_COMPILE_FLAG([-rdynamic], [DEFAULT_CFLAGS="$DEFAULT_CFLAGS -rdynamic"])
|
||||
## -rdynamic must be a link flag; DEFAULT_CFLAGS (AM_CPPFLAGS) never reaches the linker.
|
||||
AX_CHECK_LINK_FLAG([-rdynamic], [DEFAULT_LDFLAGS="$DEFAULT_LDFLAGS -rdynamic"])
|
||||
|
||||
### Check for -fvisibility=hidden support
|
||||
gl_VISIBILITY
|
||||
@@ -169,7 +172,7 @@ AC_CHECK_TYPE(sa_family_t, [], [AC_DEFINE([sa_family_t], [uint16_t], [sa_family_
|
||||
AX_CHECK_ALIGNED_ACCESS_REQUIRED
|
||||
|
||||
# check for various headers
|
||||
AC_CHECK_HEADERS([execinfo.h])
|
||||
AC_CHECK_HEADERS([execinfo.h sys/ioctl.h])
|
||||
|
||||
### zlib
|
||||
CHECK_ZLIB()
|
||||
|
||||
@@ -390,6 +390,10 @@ HTSEXT_API char *hts_convertStringSystemToUTF8(const char *s, size_t size) {
|
||||
return hts_convertStringCPToUTF8(s, size, GetACP());
|
||||
}
|
||||
|
||||
HTSEXT_API char *hts_convertStringUTF8ToSystem(const char *s, size_t size) {
|
||||
return hts_convertStringCPFromUTF8(s, size, GetACP());
|
||||
}
|
||||
|
||||
HTSEXT_API void hts_argv_utf8(int *pargc, char ***pargv) {
|
||||
int wargc = 0;
|
||||
LPWSTR *const wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
|
||||
|
||||
@@ -184,6 +184,12 @@ extern LPWSTR hts_pathToUCS2(const char *path);
|
||||
**/
|
||||
HTSEXT_API char *hts_convertStringSystemToUTF8(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Convert UTF-8 to the current system codepage. Caller frees; NULL upon error.
|
||||
* This function is WIN32 specific.
|
||||
**/
|
||||
HTSEXT_API char *hts_convertStringUTF8ToSystem(const char *s, size_t size);
|
||||
|
||||
/**
|
||||
* Replace the CRT's ANSI argv by a UTF-8 one decoded from the real UTF-16
|
||||
* command line: every char* is UTF-8 on Windows (FOPEN, STAT, ... convert at
|
||||
|
||||
@@ -887,6 +887,66 @@ static int st_charset(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Oracle is the raw Win32 two-step this replaces, so a mis-wired direction or
|
||||
a plain copy fails whatever the machine's ACP. */
|
||||
static int st_syscharset(httrackp *opt, int argc, char **argv) {
|
||||
#ifdef _WIN32
|
||||
static const char *const utf8 = "caf\xC3\xA9 \xE2\x82\xAC"; /* "café €" */
|
||||
const UINT cp = GetACP();
|
||||
const int len = (int) strlen(utf8);
|
||||
WCHAR wide[64], round[64];
|
||||
char want[64];
|
||||
char *sys, *back, *part;
|
||||
int wn, n, lossless;
|
||||
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
wn = MultiByteToWideChar(CP_UTF8, 0, utf8, len, wide,
|
||||
(int) (sizeof(wide) / sizeof(wide[0])));
|
||||
assertf(wn > 0);
|
||||
n = WideCharToMultiByte(cp, 0, wide, wn, want, (int) sizeof(want) - 1, NULL,
|
||||
NULL);
|
||||
assertf(n > 0);
|
||||
want[n] = '\0';
|
||||
/* the ACP holds the string only if its bytes decode back to the same UTF-16;
|
||||
lpUsedDefaultChar would miss a best-fit mapping (é to a bare e) */
|
||||
lossless =
|
||||
MultiByteToWideChar(cp, 0, want, n, round,
|
||||
(int) (sizeof(round) / sizeof(round[0]))) == wn &&
|
||||
memcmp(round, wide, (size_t) wn * sizeof(WCHAR)) == 0;
|
||||
|
||||
sys = hts_convertStringUTF8ToSystem(utf8, (size_t) len);
|
||||
assertf(sys != NULL);
|
||||
assertf(strcmp(sys, want) == 0);
|
||||
assertf(strlen(sys) == (size_t) n); /* NUL-terminated, nothing past it */
|
||||
/* the copy the ASCII fast path returns is a pass on a UTF-8 ACP only */
|
||||
assertf(cp == CP_UTF8 || strcmp(sys, utf8) != 0);
|
||||
/* size bounds the read: a caller-given length, not strlen() */
|
||||
part = hts_convertStringUTF8ToSystem(utf8, 3);
|
||||
assertf(part != NULL && strcmp(part, "caf") == 0);
|
||||
freet(part);
|
||||
part = hts_convertStringUTF8ToSystem(utf8, 0);
|
||||
assertf(part != NULL && *part == '\0');
|
||||
freet(part);
|
||||
if (lossless) {
|
||||
back = hts_convertStringSystemToUTF8(sys, strlen(sys));
|
||||
assertf(back != NULL);
|
||||
assertf(strcmp(back, utf8) == 0);
|
||||
freet(back);
|
||||
}
|
||||
freet(sys);
|
||||
printf("syscharset: acp=%u %s: OK\n", (unsigned) cp,
|
||||
lossless ? "round-trip" : "one-way");
|
||||
return 0;
|
||||
#else
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
return 77; /* WIN32-only entry point */
|
||||
#endif
|
||||
}
|
||||
|
||||
static int st_metacharset(httrackp *opt, int argc, char **argv) {
|
||||
char *s;
|
||||
|
||||
@@ -4732,6 +4792,8 @@ static const struct selftest_entry {
|
||||
{"mime", "<filename>", "MIME type for a filename", st_mime},
|
||||
{"charset", "<charset> <hex:..|string>",
|
||||
"convert a string to UTF-8 from a charset", st_charset},
|
||||
{"syscharset", "", "UTF-8 <-> system codepage conversion (WIN32 only)",
|
||||
st_syscharset},
|
||||
{"metacharset", "<html>", "extract the <meta> charset from an HTML page",
|
||||
st_metacharset},
|
||||
{"isutf8", "<hex:..|string>", "is the string valid UTF-8 (1/0)", st_isutf8},
|
||||
|
||||
@@ -68,6 +68,9 @@ static int linput(FILE * fp, char *s, int max);
|
||||
#ifdef HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_IOCTL_H
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
#include <ctype.h>
|
||||
#if (defined(__linux) && defined(HAVE_EXECINFO_H))
|
||||
#include <execinfo.h>
|
||||
@@ -185,6 +188,43 @@ static void vt_home(void) {
|
||||
printf("%s%s", VT_RESET, VT_GOTOXY("1", "0"));
|
||||
}
|
||||
|
||||
/* Last known terminal geometry; the defaults are the classic VT100 size. */
|
||||
static int term_cols = 80;
|
||||
static int term_rows = 24;
|
||||
|
||||
/* Returns HTS_TRUE if the terminal size changed since the last call. */
|
||||
static hts_boolean vt_size_refresh(void) {
|
||||
int cols = 0;
|
||||
int rows = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
const HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
if (console != INVALID_HANDLE_VALUE &&
|
||||
GetConsoleScreenBufferInfo(console, &info)) {
|
||||
cols = info.srWindow.Right - info.srWindow.Left + 1;
|
||||
rows = info.srWindow.Bottom - info.srWindow.Top + 1;
|
||||
}
|
||||
#elif defined(TIOCGWINSZ)
|
||||
struct winsize ws;
|
||||
|
||||
if (ioctl(fileno(stdout), TIOCGWINSZ, &ws) == 0) {
|
||||
cols = ws.ws_col;
|
||||
rows = ws.ws_row;
|
||||
}
|
||||
#endif
|
||||
if (cols <= 0)
|
||||
cols = 80;
|
||||
if (rows <= 0)
|
||||
rows = 24;
|
||||
if (cols == term_cols && rows == term_rows)
|
||||
return HTS_FALSE;
|
||||
term_cols = cols;
|
||||
term_rows = rows;
|
||||
return HTS_TRUE;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/*
|
||||
@@ -195,7 +235,8 @@ static void vt_home(void) {
|
||||
#define STYLE_STATTEXT VT_UNBOLD
|
||||
#define STYLE_STATRESET VT_UNBOLD
|
||||
#define NStatsBuffer 14
|
||||
#define MAX_LEN_INPROGRESS 40
|
||||
/* Rows the stats block and "Current job" take above the in-progress list. */
|
||||
#define NStatsHeaderRows 7
|
||||
|
||||
static int use_show;
|
||||
static httrackp *global_opt = NULL;
|
||||
@@ -291,6 +332,7 @@ static int __cdecl htsshow_start(t_hts_callbackarg * carg, httrackp * opt) {
|
||||
use_show = 0;
|
||||
if (opt->verbosedisplay == HTS_VERBOSE_FULL) {
|
||||
use_show = 1;
|
||||
(void) vt_size_refresh();
|
||||
vt_clear();
|
||||
}
|
||||
return 1;
|
||||
@@ -396,6 +438,10 @@ static int __cdecl htsshow_loop(t_hts_callbackarg * carg, httrackp * opt, lien_b
|
||||
|
||||
prev_mytime = mytime;
|
||||
|
||||
/* A resize leaves stale wrapped text on screen: repaint everything. */
|
||||
if (vt_size_refresh())
|
||||
vt_clear();
|
||||
|
||||
st[0] = '\0';
|
||||
qsec2str(st, stat_time);
|
||||
vt_home();
|
||||
@@ -435,6 +481,13 @@ static int __cdecl htsshow_loop(t_hts_callbackarg * carg, httrackp * opt, lien_b
|
||||
//
|
||||
t_StatsBuffer StatsBuffer[NStatsBuffer];
|
||||
|
||||
/* Keep the frame within the terminal, or it scrolls the stats away. */
|
||||
const int nstats =
|
||||
min(NStatsBuffer, max(0, term_rows - NStatsHeaderRows));
|
||||
/* URL budget: half the width, i.e. the historical 40 at 80 columns. */
|
||||
const int maxurl =
|
||||
min((int) sizeof(StatsBuffer[0].name) - 1, max(16, term_cols / 2));
|
||||
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -449,10 +502,11 @@ static int __cdecl htsshow_loop(t_hts_callbackarg * carg, httrackp * opt, lien_b
|
||||
}
|
||||
}
|
||||
for(k = 0; k < 2; k++) { // 0: lien en cours 1: autres liens
|
||||
for(j = 0; (j < 3) && (index < NStatsBuffer); j++) { // passe de priorité
|
||||
for (j = 0; (j < 3) && (index < nstats); j++) { // passe de priorité
|
||||
int _i;
|
||||
|
||||
for(_i = 0 + k; (_i < max(back_max * k, 1)) && (index < NStatsBuffer); _i++) { // no lien
|
||||
for (_i = 0 + k; (_i < max(back_max * k, 1)) && (index < nstats);
|
||||
_i++) { // no lien
|
||||
int i = (back_index + _i) % back_max; // commencer par le "premier" (l'actuel)
|
||||
|
||||
if (back[i].status >= 0) { // signifie "lien actif"
|
||||
@@ -527,16 +581,14 @@ static int __cdecl htsshow_loop(t_hts_callbackarg * carg, httrackp * opt, lien_b
|
||||
}
|
||||
}
|
||||
|
||||
if ((l = (int) strlen(s)) < MAX_LEN_INPROGRESS)
|
||||
if ((l = (int) strlen(s)) < maxurl)
|
||||
strcpybuff(StatsBuffer[index].name, s);
|
||||
else {
|
||||
// couper
|
||||
StatsBuffer[index].name[0] = '\0';
|
||||
strncatbuff(StatsBuffer[index].name, s,
|
||||
MAX_LEN_INPROGRESS / 2 - 2);
|
||||
strncatbuff(StatsBuffer[index].name, s, maxurl / 2 - 2);
|
||||
strcatbuff(StatsBuffer[index].name, "...");
|
||||
strcatbuff(StatsBuffer[index].name,
|
||||
s + l - MAX_LEN_INPROGRESS / 2 + 2);
|
||||
strcatbuff(StatsBuffer[index].name, s + l - maxurl / 2 + 2);
|
||||
}
|
||||
|
||||
if (back[i].r.totalsize >= 0) { // taille prédéfinie
|
||||
@@ -597,7 +649,7 @@ static int __cdecl htsshow_loop(t_hts_callbackarg * carg, httrackp * opt, lien_b
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < NStatsBuffer; i++) {
|
||||
for (i = 0; i < nstats; i++) {
|
||||
if (strnotempty(StatsBuffer[i].state)) {
|
||||
printf(VT_CLREOL " %s - \t%s%s \t%s / \t%s", StatsBuffer[i].state,
|
||||
StatsBuffer[i].name, StatsBuffer[i].file, int2bytes(&strc,
|
||||
|
||||
21
tests/01_engine-syscharset.test
Normal file
21
tests/01_engine-syscharset.test
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# UTF-8 <-> system codepage ('httrack -#test=syscharset'), the pair an MBCS GUI
|
||||
# uses at the Win32 ANSI entry points. WIN32 only, skipped elsewhere.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
rc=0
|
||||
out=$(httrack -O /dev/null -#test=syscharset) || rc=$?
|
||||
echo "$out"
|
||||
|
||||
[ "$rc" = 77 ] && exit 77
|
||||
[ "$rc" = 0 ] || exit "$rc"
|
||||
|
||||
case "$out" in
|
||||
"syscharset: acp="*": OK") ;;
|
||||
*)
|
||||
echo "FAIL: unexpected report"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
70
tests/76_cli-resize.test
Normal file
70
tests/76_cli-resize.test
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
# #97: a terminal resize must repaint the whole screen, and the frame must
|
||||
# follow the new geometry instead of the hardcoded 80x24 one.
|
||||
set -eu
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
testdir=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "${testdir}/testlib.sh"
|
||||
server=$(nativepath "${testdir}/local-server.py")
|
||||
root=$(nativepath "${testdir}/server-root")
|
||||
driver=$(nativepath "${testdir}/pty-resize.py")
|
||||
|
||||
if is_windows; then
|
||||
echo "no pty on Windows; skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
python=$(find_python) || ! echo "python3 not found; skipping" >&2 || exit 77
|
||||
|
||||
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/httrack_resize.XXXXXX") || exit 1
|
||||
serverpid=
|
||||
cleanup() {
|
||||
stop_server "$serverpid"
|
||||
rm -rf "$tmpdir"
|
||||
}
|
||||
trap cleanup EXIT HUP INT QUIT PIPE TERM
|
||||
|
||||
serverlog="${tmpdir}/server.log"
|
||||
: >"$serverlog"
|
||||
"$python" "$server" --root "$root" >"$serverlog" 2>&1 &
|
||||
serverpid=$!
|
||||
port=
|
||||
for _ in $(seq 1 50); do
|
||||
line=$(head -n1 "$serverlog" 2>/dev/null)
|
||||
if test "${line%% *}" == "PORT"; then
|
||||
port="${line#PORT }"
|
||||
break
|
||||
fi
|
||||
kill -0 "$serverpid" 2>/dev/null || {
|
||||
echo "server exited early: $(cat "$serverlog")"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
done
|
||||
test -n "$port" || {
|
||||
echo "could not discover server port"
|
||||
exit 1
|
||||
}
|
||||
|
||||
which httrack >/dev/null || {
|
||||
echo "could not find httrack"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Trickled pages under a long path: the display keeps animating while the pty is
|
||||
# resized under it, and the URL is truncated at 80 columns but not at 200.
|
||||
deep="127.0.0.1:${port}/trickle/deep/a-long-directory-segment/another-long-segment"
|
||||
out="${tmpdir}/crawl"
|
||||
mkdir "$out"
|
||||
rc=0
|
||||
# The in-progress rows show host:port, not the scheme.
|
||||
"$python" "$driver" "${tmpdir}/pty.raw" "${deep}/p" -- \
|
||||
httrack "http://${deep}/index.html" \
|
||||
-O "$out" -%v --robots=0 --disable-security-limits \
|
||||
--timeout=30 --retries=1 -c4 --max-time 60 || rc=$?
|
||||
test "$rc" -eq 0 || {
|
||||
echo "pty capture tail:"
|
||||
tail -c 600 "${tmpdir}/pty.raw" | cat -v
|
||||
exit 1
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
EXTRA_DIST = $(TESTS) 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 \
|
||||
local-crawl.sh local-server.py testlib.sh server.crt server.key \
|
||||
server-root/simple/basic.html server-root/simple/link.html \
|
||||
server-root/stripquery/index.html server-root/stripquery/a.html \
|
||||
@@ -77,6 +78,7 @@ TESTS = \
|
||||
01_engine-status.test \
|
||||
01_engine-stripquery.test \
|
||||
01_engine-strsafe.test \
|
||||
01_engine-syscharset.test \
|
||||
01_engine-topindex.test \
|
||||
01_engine-urlhack.test \
|
||||
01_engine-unescape-bounds.test \
|
||||
@@ -167,6 +169,7 @@ TESTS = \
|
||||
73_local-warc.test \
|
||||
74_local-warc-wacz.test \
|
||||
74_local-warc-verbatim.test \
|
||||
75_engine-longpath-posix.test
|
||||
75_engine-longpath-posix.test \
|
||||
76_cli-resize.test
|
||||
|
||||
CLEANFILES = check-network_sh.cache
|
||||
|
||||
@@ -457,6 +457,9 @@ def _big_sitemap(port):
|
||||
).encode()
|
||||
|
||||
|
||||
DEEPDIR = "/trickle/deep/a-long-directory-segment/another-long-segment"
|
||||
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
# Quieter logging; the launcher captures httrack's own log anyway.
|
||||
def log_message(self, fmt, *args):
|
||||
@@ -1461,6 +1464,11 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
def route_trickle_index(self):
|
||||
self.send_bin_index()
|
||||
|
||||
# #97: a path long enough that the CLI in-progress column truncates it at 80
|
||||
# columns but not at 200. Its own index: /trickle/ is asserted on elsewhere.
|
||||
def route_deeptrickle_index(self):
|
||||
self.send_html('\t<a href="p0.bin">p0</a>\n\t<a href="p1.bin">p1</a>\n')
|
||||
|
||||
def route_trickle_page(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/octet-stream")
|
||||
@@ -1646,6 +1654,9 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"/trickle/p5.bin": route_trickle_page,
|
||||
"/trickle/p6.bin": route_trickle_page,
|
||||
"/trickle/p7.bin": route_trickle_page,
|
||||
DEEPDIR + "/index.html": route_deeptrickle_index,
|
||||
DEEPDIR + "/p0.bin": route_trickle_page,
|
||||
DEEPDIR + "/p1.bin": route_trickle_page,
|
||||
"/dcancel/index.html": route_dcancel_index,
|
||||
"/dcancel/p0.bin": route_dcancel_page,
|
||||
"/dcancel/p1.bin": route_dcancel_page,
|
||||
|
||||
130
tests/pty-resize.py
Normal file
130
tests/pty-resize.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Drive httrack on a pty and check the animated display follows the terminal.
|
||||
|
||||
Usage: pty-resize.py <capture-file> <long-url-prefix> -- <cmd> [args...]
|
||||
"""
|
||||
import fcntl
|
||||
import os
|
||||
import pty
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
|
||||
CLEAR = b"\033[2J"
|
||||
FRAME = b"\033[1;1f" # every refresh homes here before painting the stats
|
||||
LIVE = b"Bytes saved"
|
||||
|
||||
|
||||
class Term:
|
||||
def __init__(self, argv, capture):
|
||||
self.capture = capture
|
||||
self.buf = bytearray()
|
||||
self.master, slave = pty.openpty()
|
||||
self.resize(24, 80)
|
||||
self.proc = subprocess.Popen(
|
||||
argv, stdin=slave, stdout=slave, stderr=slave, start_new_session=True
|
||||
)
|
||||
os.close(slave)
|
||||
|
||||
def resize(self, rows, cols):
|
||||
fcntl.ioctl(
|
||||
self.master, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0)
|
||||
)
|
||||
|
||||
def _read(self, timeout):
|
||||
ready, _, _ = select.select([self.master], [], [], timeout)
|
||||
if not ready:
|
||||
return b""
|
||||
try:
|
||||
return os.read(self.master, 65536)
|
||||
except OSError:
|
||||
return b""
|
||||
|
||||
def pump(self, seconds):
|
||||
"""Read for the given window; return only the bytes read during it."""
|
||||
mark = len(self.buf)
|
||||
end = time.monotonic() + seconds
|
||||
while time.monotonic() < end:
|
||||
self.buf += self._read(0.1)
|
||||
return bytes(self.buf[mark:])
|
||||
|
||||
def wait_for(self, pattern, timeout):
|
||||
mark = len(self.buf)
|
||||
end = time.monotonic() + timeout
|
||||
while time.monotonic() < end:
|
||||
self.buf += self._read(0.1)
|
||||
if pattern in bytes(self.buf[mark:]):
|
||||
return True
|
||||
if self.proc.poll() is not None:
|
||||
break
|
||||
return False
|
||||
|
||||
def alive(self):
|
||||
return self.proc.poll() is None
|
||||
|
||||
def close(self):
|
||||
if self.alive():
|
||||
os.killpg(self.proc.pid, signal.SIGKILL)
|
||||
self.proc.wait()
|
||||
os.close(self.master)
|
||||
with open(self.capture, "wb") as fp:
|
||||
fp.write(self.buf)
|
||||
|
||||
|
||||
def frame_heights(chunk):
|
||||
"""Newline count of each complete frame in the chunk."""
|
||||
return [f.count(b"\n") for f in chunk.split(FRAME)[1:-1]]
|
||||
|
||||
|
||||
def main():
|
||||
capture, long_url = sys.argv[1], sys.argv[2].encode()
|
||||
argv = sys.argv[sys.argv.index("--") + 1 :]
|
||||
term = Term(argv, capture)
|
||||
failures = []
|
||||
|
||||
def check(cond, msg):
|
||||
if not cond:
|
||||
failures.append(msg)
|
||||
return cond
|
||||
|
||||
try:
|
||||
if not check(term.wait_for(LIVE, 15), "the display never started"):
|
||||
return 1
|
||||
|
||||
# Steady state: a repaint here would mask any resize-driven one below.
|
||||
idle = term.pump(1.0)
|
||||
check(CLEAR not in idle, "repaint without a resize")
|
||||
check(long_url not in idle, "long URL not truncated at 80 columns")
|
||||
|
||||
term.resize(24, 120)
|
||||
check(term.wait_for(CLEAR, 3), "no repaint after a width-only resize")
|
||||
check(CLEAR not in term.pump(1.0), "repaint kept firing after the resize")
|
||||
|
||||
term.resize(10, 120)
|
||||
check(term.wait_for(CLEAR, 3), "no repaint after a height-only resize")
|
||||
heights = frame_heights(term.pump(1.0))
|
||||
check(heights, "no frame after the height change")
|
||||
check(
|
||||
all(h <= 9 for h in heights),
|
||||
"frame overflows a 10-row terminal: %s" % heights,
|
||||
)
|
||||
|
||||
term.resize(24, 200)
|
||||
check(term.wait_for(CLEAR, 3), "no repaint after growing the terminal")
|
||||
check(long_url in term.pump(1.0), "long URL still truncated at 200 columns")
|
||||
|
||||
check(term.alive(), "httrack died mid-run (exit %s)" % term.proc.poll())
|
||||
finally:
|
||||
term.close()
|
||||
|
||||
for msg in failures:
|
||||
print("FAIL: %s" % msg)
|
||||
return 1 if failures else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user