mirror of
https://github.com/xroche/httrack.git
synced 2026-07-28 03:22:12 +03:00
* Single-file output is MHT, which browsers no longer open Add --single-file (-%Z): after the mirror completes, rewrite every saved page in place with its stylesheets, scripts, images and fonts embedded as data: URIs, while links between pages stay relative. The mirror remains a browsable tree and each page also stands alone. This cannot reuse the -%M path: MHT streams a MIME part per file as it is saved, but a data: URI needs the asset's bytes when the page is written, and pages are normally saved before their assets are fetched. The new pass runs over the finished tree at the tail of httpmirror(), after the update purge. Audio, video, page-to-page links and anything over --single-file-max-size (10 MB default) keep an ordinary link. References carrying a scheme are skipped, which covers data: and makes a second --update run a no-op. Resolution is clamped to the mirror root: the HTML is hostile input. htsopt.h gains two tail-appended fields; VERSION_INFO is untouched. Closes #713 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * tests: renumber the single-file test to 87, run the self-test last Master took 82 through 85 while this branch was open. The engine self-test also moves to the end of the script: it and the crawl assertions cover different ground, and failing first hid the crawl half. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * Fix the parser and path bugs review found, and cover them sf_relative_from() indexed one byte past from_dir's terminator whenever the page's own directory was a prefix of the asset path, which is the ordinary mirror layout: an out-of-bounds read that also miscounted the ../ prefix and emitted a broken link. Two review agents hit the same ASan trace; the branch had no test because every nested asset in the fixture was under the cap. Also from review: an escaped quote no longer ends a CSS string early (which exposed its contents to the url()/@import scanner), @import url(...) now inlines like the quoted form, a raw-text element ends only on a real end tag rather than any prefix of one, an over-wide tag is copied through by the same quote-aware scan instead of a second quote-blind one, <!--> is an empty comment, whitespace before a tag's > survives, and a page cannot inline more than SINGLEFILE_MAX_PAGE_SIZE, which bounds the multiplicative @import fan-out. A failed encode no longer leaves a payload-less data: prefix behind, and base_dir is matched against the root on a component boundary. sf_readfile now wraps a new readfile2_utf8() rather than being a fifth copy of the readfile family. Docs place --single-file beside -%M instead of implying it supersedes it: MHT stays the better container, this wins on opening anywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * singlefile: tighten two comments Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * tests: cover the option plumbing review found untested copy_htsopt's two new fields, including the >0 guard that must not let an unset source clear the target's default; -%Z0; and a rejected --single-file-max-size argument falling back to the built-in cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * tests: stand the single-file GUI check down where htsserver is absent The Windows job builds httrack.exe only, and reaches this file through its *_local-*.test glob, so requiring htsserver failed the whole test there even though every crawl assertion had passed. Skip that half instead, and run the engine self-test before it so the skip cannot swallow it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * tests: check the sprintfbuff master just made warn_unused_result #722 gave slprintfbuff the attribute, so the over-wide-tag fixture's call became the one warning this branch adds over master's baseline. Handle it the way that PR's own self-test code does; the buffer cannot truncate here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <xroche@gmail.com> * single-file: translate the new GUI strings into the remaining 28 locales (#739) The feature PR added the four LANG_SINGLEFILE* entries to lang.def with English and Francais only; every other locale fell back to English in the WebHTTrack form. Each file is written in its own declared charset. Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * single-file: restore the mirror file mode, and give the guards real coverage The rewrite spools to a .sfnew temp and renames it over the page, which bypasses filecreate() and with it the HTS_ACCESS_FILE chmod the engine puts on every other mirrored file. Under a restrictive umask the pages came out 0600 while their assets stayed 0644, so a mirror served by a webserver or shared with a group lost read access on exactly the pages. chmod the spool before the rename. Two guards had no coverage: deleting the scheme/data: check in sf_resolve left both the self-test and the crawl test passing, because the fixtures resolved to paths that were absent either way, and the per-page inline budget was never exercised. The fixtures now plant a file where each guard's removal would land the walk, and a self-importing stylesheet measures the budget against a large-budget control. Charging that budget after the nested rewrite instead of before let an @import chain spend what its ancestors had already claimed and drove it negative; charge it up front and refund on failure. The attribute table missed the lazy-loading attributes hts_detect[] already downloads, so a modern page inlined almost nothing: add data-src, data-srcset, lowsrc, object@data and embed@src, and record why the rest stay links. The new web GUI checkbox had no hidden companion input, so it could be ticked but never cleared, which is what #725 fixed for every other box. Master's test 90 catches it once the branch merges. Adds a libFuzzer harness over the rewriter, since it re-serializes hostile HTML, and renames the crawl test to 91 now that master holds 87 and 90. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * single-file: document the inlined-stylesheet limitation in the CLI guide Recorded in htssinglefile.h already; the user-facing guide is where someone raising --single-file-max-size will look. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * tests: move the Windows note down to the gate it explains Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> * ci: register the single-file test's Windows skip Its GUI half needs htsserver, which the Windows job does not build, so the test now exits 77 there instead of reporting a pass for assertions it never ran. The skip list is pinned, so it has to be declared. Both Windows jobs reported fail=0; only the list check was red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Xavier Roche <roche@httrack.com> --------- Signed-off-by: Xavier Roche <xroche@gmail.com> Signed-off-by: Xavier Roche <roche@httrack.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
203 lines
9.9 KiB
HTML
203 lines
9.9 KiB
HTML
<html xmlns="http://www.w3.org/1999/xhtml" lang="${LANGUAGE_ISO}">
|
|
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=${LANGUAGE_CHARSET}" />
|
|
<meta name="description" content="${LANG_METADESC}" />
|
|
<meta name="keywords" content="${LANG_METAKEYW}" />
|
|
<title>'${projname}' - HTTrack Website Copier</title>
|
|
|
|
<link rel="stylesheet" type="text/css" media="screen" href="style.css">
|
|
<script type="text/javascript" src="ping.js" defer></script>
|
|
|
|
<script language="javascript">
|
|
<!--
|
|
function do_load() {
|
|
window.status=' ';
|
|
${do:if-not-empty:closeme}
|
|
window.close();
|
|
${do:end-if}
|
|
${do:set:closeme:}
|
|
}
|
|
function do_unload() {
|
|
}
|
|
function info(str) {
|
|
window.status = str;
|
|
}
|
|
// -->
|
|
</script>
|
|
|
|
</head>
|
|
<body onLoad="do_load();" onUnload="do_unload();">
|
|
|
|
<table width="76%" border="0" align="center" cellspacing="0" cellpadding="0" class="tableWidth">
|
|
<tr>
|
|
<td><img src="images/header_title_4.gif" width="400" height="34" alt="HTTrack Website Copier" title="" border="0" id="title" /></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<table width="76%" border="0" align="center" cellspacing="0" cellpadding="3" class="tableWidth">
|
|
<tr>
|
|
<td id="subTitle">${LANG_OSFWB}</td>
|
|
</tr>
|
|
</table>
|
|
<table width="76%" border="0" align="center" cellspacing="0" cellpadding="0" class="tableWidth">
|
|
<tr class="blak">
|
|
<td>
|
|
<table width="100%" border="0" align="center" cellspacing="1" cellpadding="0">
|
|
<tr>
|
|
<td colspan="6">
|
|
<table width="100%" border="0" align="center" cellspacing="0" cellpadding="10">
|
|
<tr>
|
|
<td id="pageContent">
|
|
<!-- ==================== End prologue ==================== -->
|
|
|
|
<table border="0" width="100%">
|
|
<tr><td width="90%">
|
|
<h2 align="center"><em>${LANG_O2} - ${LANG_IOPT2}</em></h2>
|
|
</td>
|
|
${/* show help only if available */}
|
|
${do:if-file-exists:html/index.html}
|
|
<td>
|
|
<a href="/step9_opt5.html" target="_blank"
|
|
onClick="window.open('/step9_opt5.html', 'help', 'toolbar=no, location=no, directories=no, status=yes, menubar=no, scrollbars=yes, resizable=yes, width=640, height=480'); return false"
|
|
title='${html:LANG_TIPHELP}' onMouseOver="info('${html:LANG_TIPHELP}'); return true" onMouseOut="info(' '); return true"
|
|
>${LANG_TIPHELP}</a>
|
|
</td>
|
|
${do:end-if}
|
|
</tr></table>
|
|
|
|
<br>
|
|
|
|
<table class="tableWidth" border="0" width="100%" cellspacing="0">
|
|
<tr>
|
|
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option1.html" onClick="form.redirect.value='option1.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT1}' onMouseOver="info('${html:LANG_IOPT1}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT1}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option2.html" onClick="form.redirect.value='option2.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT2}' onMouseOver="info('${html:LANG_IOPT2}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT2}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option3.html" onClick="form.redirect.value='option3.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT3}' onMouseOver="info('${html:LANG_IOPT3}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT3}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option4.html" onClick="form.redirect.value='option4.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT4}' onMouseOver="info('${html:LANG_IOPT4}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT4}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option5.html" onClick="form.redirect.value='option5.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT5}' onMouseOver="info('${html:LANG_IOPT5}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT5}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option11.html" onClick="form.redirect.value='option11.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT11}' onMouseOver="info('${html:LANG_IOPT11}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT11}</a></td>
|
|
</tr><tr>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option6.html" onClick="form.redirect.value='option6.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT6}' onMouseOver="info('${html:LANG_IOPT6}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT6}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option7.html" onClick="form.redirect.value='option7.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT7}' onMouseOver="info('${html:LANG_IOPT7}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT7}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option8.html" onClick="form.redirect.value='option8.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT8}' onMouseOver="info('${html:LANG_IOPT8}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT8}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option9.html" onClick="form.redirect.value='option9.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT9}' onMouseOver="info('${html:LANG_IOPT9}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT9}</a></td>
|
|
<td class="tabCtrl" align="center"><a style="background:black;color: white" href="option10.html" onClick="form.redirect.value='option10.html'; form.submit(); return false;" align="center" title='${html:LANG_IOPT10}' onMouseOver="info('${html:LANG_IOPT10}'); return true" onMouseOut="info(' '); return true">${LANG_IOPT10}</a></td>
|
|
<td class="tabCtrl" align="center"> </td>
|
|
|
|
</tr>
|
|
</table>
|
|
|
|
<br><br>
|
|
|
|
<table>
|
|
<tr><td>
|
|
|
|
<form method="POST" action="${thisfile}" name="form">
|
|
<input type="hidden" name="sid" value="${sid}">
|
|
<input type="hidden" name="redirect" value="">
|
|
<input type="hidden" name="closeme" value="">
|
|
|
|
<!-- clear if not checked -->
|
|
<input type="hidden" name="errpage" value="">
|
|
<input type="hidden" name="external" value="">
|
|
<input type="hidden" name="hidepwd" value="">
|
|
<input type="hidden" name="hidequery" value="">
|
|
<input type="hidden" name="nopurge" value="">
|
|
<input type="hidden" name="singlefile" value="">
|
|
|
|
${LANG_I33}
|
|
<br>
|
|
<select name="build"
|
|
title='${html:LANG_I3}' onMouseOver="info('${html:LANG_I3}'); return true" onMouseOut="info(' '); return true"
|
|
>
|
|
${listid:build:LISTDEF_3}
|
|
</select>
|
|
<input type="button" value="${LANG_O2}"
|
|
onClick="form.build.selectedIndex=14; window.open('option2b.html', 'option2b', 'toolbar=no, location=no, directories=no, status=yes, menubar=no, scrollbars=yes, resizable=yes, width=640, height=480').wparent=document; form.submit();"
|
|
title='${html:LANG_I4}' onMouseOver="info('${html:LANG_I4}'); return true" onMouseOut="info(' '); return true"
|
|
>
|
|
|
|
<!-- checkboxes -->
|
|
<table border="0" width="100%" cellspacing="0">
|
|
<tr><td>
|
|
<select name="dos"
|
|
title='${html:LANG_I8} ${html:LANG_I8b}' onMouseOver="info('${html:LANG_I8}\r\n${LANG_I8b}'); return true" onMouseOut="info(' '); return true"
|
|
>
|
|
<option value="0"${ztest:dos: selected::}> </option>
|
|
<option value="1"${ztest:dos:: selected:}>${LANG_I37}</option>
|
|
<option value="2"${ztest:dos::: selected}>${LANG_I37b}</option>
|
|
</select>
|
|
</td></tr>
|
|
<tr><td><input type="checkbox" name="errpage" ${checked:errpage}
|
|
title='${html:LANG_I9}' onMouseOver="info('${html:LANG_I9}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_I38}</td></tr>
|
|
<tr><td><input type="checkbox" name="external" ${checked:external}
|
|
title='${html:LANG_I29}' onMouseOver="info('${html:LANG_I29}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_I56}</td></tr>
|
|
<tr><td><input type="checkbox" name="hidepwd" ${checked:hidepwd}
|
|
title='${html:LANG_I30}' onMouseOver="info('${html:LANG_I30}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_I66}</td></tr>
|
|
<tr><td><input type="checkbox" name="hidequery" ${checked:hidequery}
|
|
title='${html:LANG_I30b}' onMouseOver="info('${html:LANG_I30b}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_I67}</td></tr>
|
|
<tr><td><input type="checkbox" name="nopurge" ${checked:nopurge}
|
|
title='${html:LANG_I1a}' onMouseOver="info('${html:LANG_I1a}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_I57}</td></tr>
|
|
<tr><td><input type="checkbox" name="singlefile" ${checked:singlefile}
|
|
title='${html:LANG_SINGLEFILETIP}' onMouseOver="info('${html:LANG_SINGLEFILETIP}'); return true" onMouseOut="info(' '); return true"
|
|
> ${LANG_SINGLEFILE}</td></tr>
|
|
<tr><td>${LANG_SINGLEFILEMAX}
|
|
<input name="singlefilemax" value="${singlefilemax}" size="12"
|
|
title='${html:LANG_SINGLEFILEMAXTIP}' onMouseOver="info('${html:LANG_SINGLEFILEMAXTIP}'); return true" onMouseOut="info(' '); return true"
|
|
></td></tr>
|
|
</table>
|
|
|
|
<tr><td>
|
|
<table width="100%">
|
|
<tr><td align="left">
|
|
<input type="submit" value="${LANG_OK}"
|
|
onClick="form.closeme.value=1; form.submit(); return false;"
|
|
title='${html:LANG_TIPOK}' onMouseOver="info('${html:LANG_TIPOK}'); return true" onMouseOut="info(' '); return true"
|
|
>
|
|
</td><td align="right">
|
|
<input type="button" value="${LANG_CANCEL}"
|
|
onClick="window.close();"
|
|
title='${html:LANG_TIPCANCEL}' onMouseOver="info('${html:LANG_TIPCANCEL}'); return true" onMouseOut="info(' '); return true"
|
|
>
|
|
</td></tr>
|
|
</table>
|
|
</td></tr>
|
|
|
|
</form>
|
|
|
|
</td></tr>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
<!-- ==================== Start epilogue ==================== -->
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<table width="76%" border="0" align="center" valign="bottom" cellspacing="0" cellpadding="0">
|
|
<tr>
|
|
<td id="footer"><small><small>© 1998-2026 Xavier Roche & other contributors - Web Design: Leto Kauler.</small></small></td>
|
|
</tr>
|
|
</table>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
|