Compare commits

...

7 Commits

Author SHA1 Message Date
Xavier Roche
739ce41821 Keep the MSVC aliases out of the installed headers
htsglobal.h is installed and reached from httrack-library.h, so defining fseeko,
ftello and timegm there rewrote those identifiers in every consumer's own code on
MSVC: a caller's struct member, C++ method or compat declaration named timegm
silently became _mkgmtime. Move the block to htslib.h, which is not installed and
already holds the strcasecmp/snprintf aliases; both call sites reach it through
htscore.h.

Drop the (off_t) cast on the truncate as well. It is 32-bit on MSVC, so the same
resume past 2GB it would have wrapped on the seek it also wraps here, and
_chsize_s then destroys the partial file before the seek is reached.

Restore NDEBUG/_DEBUG, which the legacy .vcproj set and the port had dropped,
leaving assert() live in the release DLL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 23:50:58 +02:00
Xavier Roche
937fa4a54b Tag the env-override resolver backend too
Same C2440 on x86 as the libc backend: override_getaddrinfo and
override_freeaddrinfo feed hts_resolver_backend, so they need HTS_RESOLVER_CALL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 23:23:49 +02:00
Xavier Roche
8ca091be6e Give the resolver backend Winsock's calling convention
getaddrinfo and freeaddrinfo are __stdcall on Windows, but hts_resolver_backend
declared its pointers plain. That compiles on x64, which has a single calling
convention, and fails on x86 with a C2440 on the libc backend initializer.

Tag the pointers, and the self-test's mock backend, with HTS_RESOLVER_CALL: WSAAPI
on Windows, empty elsewhere. htsnet.h is not an installed header, so no ABI change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 23:13:44 +02:00
Xavier Roche
0f371aa13d Map fseeko/ftello/timegm to their MSVC names
MSVC has no POSIX fseeko/ftello/timegm, so libhttrack.dll failed to link with
two unresolved externals. It does ship the same functions under _fseeki64,
_ftelli64 and _mkgmtime; alias them in htsglobal.h next to the other platform
switches.

The resume offset in back_wait was cast to off_t, which is 32-bit on MSVC and
would have silently truncated a resume past 2GB. Drop the cast and pass the
LLint: on POSIX it converts to off_t as before, on MSVC _fseeki64 takes it whole.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 23:01:57 +02:00
Xavier Roche
7e6cda44f4 ci: find MSBuild with vswhere, not a third-party action
The repo restricts Actions to GitHub-owned ones, so microsoft/setup-msbuild
never starts: the run dies with a startup failure before any job. vswhere ships
on the Windows image and locates the same MSBuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 22:47:36 +02:00
Xavier Roche
64a1bc8dff Sync the libhttrack.vcxproj source list with Makefile.am
The list came over from the VS2008 .vcproj, which predates htssniff.c and the
selftest files and carried httrack.c, the CLI main(). Take libhttrack_la_SOURCES
as the reference instead: the DLL now builds the four missing files (htssniff.c
would have left htsparse/htsname with unresolved externals, htsselftest.c
htscoremain) and drops the CLI entry point.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 22:44:26 +02:00
Xavier Roche
f3001be45f Build libhttrack with VS2022 and OpenSSL 3.x from vcpkg
The MSVC build had no CI and had drifted badly: libhttrack.vcproj is a VS2008
file that v143 cannot open, and it pins OpenSSL 1.0.1j and zlib from hardcoded
C:\Dev paths. The C already builds against OpenSSL 3.x on unix, so this is
only the Windows build.

Adds src/libhttrack.vcxproj (v143, x86+x64) alongside the legacy .vcproj, which
stays until the remaining projects are ported. It keeps what the old file
already had right, a /MD dynamic-CRT DLL exporting through HTSEXT_API, and
takes OpenSSL and zlib from vcpkg instead of the hardcoded paths. Output lands
in src/$(Platform)/$(Configuration), which is where WinHTTrack looks for
libhttrack.lib.

Also adds a Windows CI job, which the repo did not have. It builds the library
and asserts the DLL and import lib appear under the expected name, so the GUI
link cannot silently break again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-12 22:16:49 +02:00
8 changed files with 261 additions and 14 deletions

73
.github/workflows/windows-build.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
# Windows build of libhttrack (VS2022 v143 + vcpkg).
#
# The autotools CI covers the unix builds; this covers the MSVC one, which had
# no coverage at all and had drifted (the old .vcproj pinned OpenSSL 1.0.1j).
# libhttrack.dll is what the WinHTTrack GUI links against.
name: windows-build
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
libhttrack:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
platform: [x64, Win32]
configuration: [Release]
steps:
- uses: actions/checkout@v4
with:
submodules: recursive # coucal lives in src/coucal
# Located through vswhere rather than microsoft/setup-msbuild: the repo
# only allows GitHub-owned actions.
- name: Find MSBuild
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$msbuild = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild `
-find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1
if (-not $msbuild) { throw "MSBuild not found" }
Write-Host "MSBuild: $msbuild"
"MSBUILD=$msbuild" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Enable vcpkg MSBuild integration
shell: pwsh
run: vcpkg integrate install
- name: Build libhttrack
shell: pwsh
run: |
& $env:MSBUILD src\libhttrack.vcxproj `
/m `
/p:Configuration=${{ matrix.configuration }} `
/p:Platform=${{ matrix.platform }} `
/p:VcpkgEnableManifest=true `
/flp:LogFile=msbuild.log`;Verbosity=normal
# WinHTTrack links src\$(Platform)\$(Configuration)\libhttrack.lib, so the
# import lib and the DLL both have to land there under that exact name.
- name: Check the DLL and import lib landed where WinHTTrack expects
shell: pwsh
run: |
$dir = "src\${{ matrix.platform }}\${{ matrix.configuration }}"
foreach ($f in @("libhttrack.dll", "libhttrack.lib")) {
if (-not (Test-Path "$dir\$f")) { throw "missing $dir\$f" }
Write-Host "found $dir\$f"
}
- name: Upload MSBuild log
if: always()
uses: actions/upload-artifact@v4
with:
name: msbuild-${{ matrix.platform }}-${{ matrix.configuration }}
path: msbuild.log
if-no-files-found: ignore

View File

@@ -3868,8 +3868,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
// drop bytes past the resume point; a silent
// failure could leave a stale tail, so on error
// drop the partial and refetch the whole file
if (HTS_FTRUNCATE(back[i].r.out,
(off_t) resume) != 0) {
/* not (off_t): 32-bit on MSVC, wrapping a resume
past 2GB */
if (HTS_FTRUNCATE(back[i].r.out, resume) != 0) {
fclose(back[i].r.out);
back[i].r.out = NULL;
url_savename_refname_remove(
@@ -3881,7 +3882,9 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
"Can not truncate partial file, "
"restarting");
} else {
fseeko(back[i].r.out, (off_t) resume, SEEK_SET);
/* not (off_t): 32-bit on MSVC, truncating a
resume past 2GB */
fseeko(back[i].r.out, resume, SEEK_SET);
/* create a temporary reference file in case of
* broken mirror */
if (back_serialize_ref(opt, &back[i]) != 0) {

View File

@@ -133,9 +133,10 @@ static struct addrinfo *mock_mkai(const mock_addr *a) {
return ai;
}
static int mock_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
static int HTS_RESOLVER_CALL mock_getaddrinfo(const char *node,
const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
mock_host *const h = mock_find(node);
const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
struct addrinfo *head = NULL, *tail = NULL;
@@ -164,7 +165,7 @@ static int mock_getaddrinfo(const char *node, const char *service,
return 0;
}
static void mock_freeaddrinfo(struct addrinfo *res) {
static void HTS_RESOLVER_CALL mock_freeaddrinfo(struct addrinfo *res) {
while (res != NULL) {
struct addrinfo *const next = res->ai_next;

View File

@@ -5018,7 +5018,7 @@ static struct addrinfo *resolver_make_ai(const char *ip, int want_family) {
return ai;
}
static void override_freeaddrinfo(struct addrinfo *res) {
static void HTS_RESOLVER_CALL override_freeaddrinfo(struct addrinfo *res) {
while (res != NULL) {
struct addrinfo *const next = res->ai_next;
@@ -5028,9 +5028,10 @@ static void override_freeaddrinfo(struct addrinfo *res) {
}
}
static int override_getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
static int HTS_RESOLVER_CALL override_getaddrinfo(const char *node,
const char *service,
const struct addrinfo *hints,
struct addrinfo **res) {
const char *const spec = getenv("HTTRACK_DEBUG_RESOLVE");
const int want = (hints != NULL) ? hints->ai_family : PF_UNSPEC;
const char *colon;

View File

@@ -493,6 +493,14 @@ HTS_STATIC int strcmpnocase(const char *a, const char *b) {
#define snprintf _snprintf
#endif
/* MSVC ships these POSIX functions under other names. Kept out of the installed
headers: they would rewrite the same identifiers in a consumer's own code. */
#ifdef _MSC_VER
#define fseeko _fseeki64
#define ftello _ftelli64
#define timegm _mkgmtime
#endif
#define strfield2(f,s) ( (strlen(f)!=strlen(s)) ? 0 : (strfield(f,s)) )
// is this MIME an hypertext MIME (text/html), html/js-style or other script/text type?

View File

@@ -309,10 +309,19 @@ typedef socklen_t SOClen;
self-test can script DNS answers (families, multiplicity, errors)
in-process. The free function must match its getaddrinfo (a fake allocates
its own chain), hence the pair. */
/* Winsock's resolver is __stdcall; a plain pointer only compiles on x64, where
there is one convention. Backend implementations must carry this too. */
#ifdef _WIN32
#define HTS_RESOLVER_CALL WSAAPI
#else
#define HTS_RESOLVER_CALL
#endif
typedef struct hts_resolver_backend {
int (*getaddrinfo)(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
void (*freeaddrinfo)(struct addrinfo *res);
int(HTS_RESOLVER_CALL *getaddrinfo)(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
void(HTS_RESOLVER_CALL *freeaddrinfo)(struct addrinfo *res);
} hts_resolver_backend;
/** Install a resolver backend for the process; NULL restores the libc default.

143
src/libhttrack.vcxproj Normal file
View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{E76AD871-54C1-45E8-A657-6117ADEFFB46}</ProjectGuid>
<RootNamespace>libhttrack</RootNamespace>
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)'==''">10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<!-- OpenSSL 3.x and zlib come from vcpkg. Dynamic triplets: the DLLs ship with
the app, so an OpenSSL CVE is a DLL swap rather than a rebuild. -->
<PropertyGroup>
<VcpkgEnableManifest>true</VcpkgEnableManifest>
<VcpkgTriplet Condition="'$(Platform)'=='Win32'">x86-windows</VcpkgTriplet>
<VcpkgTriplet Condition="'$(Platform)'=='x64'">x64-windows</VcpkgTriplet>
<!-- WinHTTrack links ..\httrack\src\$(Platform)\$(Configuration)\libhttrack.lib -->
<OutDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(MSBuildThisFileDirectory)$(Platform)\$(Configuration)\obj\</IntDir>
<TargetName>libhttrack</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- LIBHTTRACK_EXPORTS turns HTSEXT_API into __declspec(dllexport); ZLIB_DLL
imports zlib from its DLL. Windows 7 floor, matching WinHTTrack. -->
<PreprocessorDefinitions>WIN32;_WINDOWS;_MBCS;_USRDLL;LIBHTTRACK_EXPORTS;ZLIB_DLL;WINVER=0x0601;_WIN32_WINNT=0x0601;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(MSBuildThisFileDirectory);$(MSBuildThisFileDirectory)coucal;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<!-- vcpkg auto-links libssl/libcrypto/zlib; only the OS import lib is explicit. -->
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>Disabled</Optimization>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Optimization>MaxSpeed</Optimization>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<StringPooling>true</StringPooling>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="coucal\coucal.c" />
<ClCompile Include="htsalias.c" />
<ClCompile Include="htsback.c" />
<ClCompile Include="htsbauth.c" />
<ClCompile Include="htscache.c" />
<ClCompile Include="htscache_selftest.c" />
<ClCompile Include="htscatchurl.c" />
<ClCompile Include="htscharset.c" />
<ClCompile Include="htsconcat.c" />
<ClCompile Include="htscore.c" />
<ClCompile Include="htscoremain.c" />
<ClCompile Include="htsdns_selftest.c" />
<ClCompile Include="htsencoding.c" />
<ClCompile Include="htsfilters.c" />
<ClCompile Include="htsftp.c" />
<ClCompile Include="htshash.c" />
<ClCompile Include="htshelp.c" />
<ClCompile Include="htsindex.c" />
<ClCompile Include="htslib.c" />
<ClCompile Include="htsmd5.c" />
<ClCompile Include="htsmodules.c" />
<ClCompile Include="htsname.c" />
<ClCompile Include="htsparse.c" />
<ClCompile Include="htsrobots.c" />
<ClCompile Include="htsselftest.c" />
<ClCompile Include="htssniff.c" />
<ClCompile Include="htsthread.c" />
<ClCompile Include="htstools.c" />
<ClCompile Include="htswizard.c" />
<ClCompile Include="htswrap.c" />
<ClCompile Include="htszlib.c" />
<ClCompile Include="md5.c" />
<ClCompile Include="minizip\ioapi.c" />
<ClCompile Include="minizip\iowin32.c" />
<ClCompile Include="minizip\mztools.c" />
<ClCompile Include="minizip\unzip.c" />
<ClCompile Include="minizip\zip.c" />
<ClCompile Include="punycode.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

9
src/vcpkg.json Normal file
View File

@@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"name": "libhttrack",
"version-string": "3.49",
"dependencies": [
"openssl",
"zlib"
]
}