single-file-core 1.5.113 → 1.5.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -113,7 +113,9 @@ const MAX_APPENDED_DATA_LENGTH = 65535;
|
|
|
113
113
|
const PDF_ENTRY_FILENAME = "page.pdf";
|
|
114
114
|
const PDF_HEADER_MAX_OFFSET = 1024;
|
|
115
115
|
const MINIMAL_DOCTYPE = "<!DOCTYPE html>";
|
|
116
|
-
const UNHIDDEN_FACE_WARNING_MESSAGE = "SingleFile: the
|
|
116
|
+
const UNHIDDEN_FACE_WARNING_MESSAGE = "SingleFile: the page data contains every HTML tag that could hide an embedded file, the archive was written without its";
|
|
117
|
+
const EMBEDDED_IMAGE_LABEL = "PNG image";
|
|
118
|
+
const EMBEDDED_PDF_LABEL = "PDF document";
|
|
117
119
|
const LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
|
|
118
120
|
const CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
|
|
119
121
|
const END_OF_CENTRAL_DIR_SIGNATURE = 0x06054b50;
|
|
@@ -181,7 +183,7 @@ async function createArchive(pageData, options, script, writeEntries, lastModDat
|
|
|
181
183
|
if (options.embeddedImage && options.selfExtractingArchive) {
|
|
182
184
|
imageChunk = getImageHTMLChunk(pageData, options, lastModDate);
|
|
183
185
|
if (!imageChunk) {
|
|
184
|
-
dropUnhiddenFace(options, "embeddedImage");
|
|
186
|
+
dropUnhiddenFace(options, "embeddedImage", EMBEDDED_IMAGE_LABEL);
|
|
185
187
|
}
|
|
186
188
|
}
|
|
187
189
|
if (options.embeddedImage) {
|
|
@@ -571,8 +573,11 @@ function getStartHTMLArray(pageData, options, lastModDate, startTag = "") {
|
|
|
571
573
|
const html = bom + doctype + documentStart;
|
|
572
574
|
// the comment carries the page URL, whose length is unbounded: it is emitted after the
|
|
573
575
|
// declaration of the character encoding, and after the embedded PDF when there is one, so
|
|
574
|
-
// that neither the encoding declaration nor the PDF header leaves the first 1024 bytes
|
|
575
|
-
|
|
576
|
+
// that neither the encoding declaration nor the PDF header leaves the first 1024 bytes.
|
|
577
|
+
// it is left out of a password-protected archive, like the title below: the same URL is
|
|
578
|
+
// in manifest.json, which is encrypted, so emitting it here would publish what the
|
|
579
|
+
// password is meant to cover
|
|
580
|
+
const comment = pageData.comment && !options.embeddedImage && !options.password ? "<!--" + pageData.comment + "-->" : "";
|
|
576
581
|
const htmlHeadData = getHTMLHeadData(pageData, options);
|
|
577
582
|
let htmlArray, pdfEntry;
|
|
578
583
|
if (options.embeddedPdf) {
|
|
@@ -582,7 +587,7 @@ function getStartHTMLArray(pageData, options, lastModDate, startTag = "") {
|
|
|
582
587
|
const embeddedPdfText = TEXT_DECODER.decode(localHeader) + TEXT_DECODER.decode(embeddedPdf);
|
|
583
588
|
const pdfTagIndex = findEmbeddedDataTagIndex(embeddedPdfText);
|
|
584
589
|
if (pdfTagIndex == -1) {
|
|
585
|
-
dropUnhiddenFace(options, "embeddedPdf");
|
|
590
|
+
dropUnhiddenFace(options, "embeddedPdf", EMBEDDED_PDF_LABEL);
|
|
586
591
|
pdfEntry = undefined;
|
|
587
592
|
} else {
|
|
588
593
|
const [pdfStartTag, pdfEndTag] = EMBEDDED_DATA_TAGS[pdfTagIndex];
|
|
@@ -616,7 +621,9 @@ function getHTMLHeadData(pageData, options) {
|
|
|
616
621
|
// is encrypted, so emitting it here would publish what the password is meant to cover
|
|
617
622
|
const title = options.password ? "" : getPageTitle(pageData);
|
|
618
623
|
pageContent += "<title>" + title + "</title>";
|
|
619
|
-
|
|
624
|
+
// the canonical link publishes the URL the archive was saved from, for the same reason
|
|
625
|
+
// the title above is left out of a password-protected archive
|
|
626
|
+
if (options.insertCanonicalLink && !options.password) {
|
|
620
627
|
pageContent += "<link rel=canonical href=\"" + options.url + "\">";
|
|
621
628
|
}
|
|
622
629
|
if (options.insertMetaNoIndex) {
|
|
@@ -704,9 +711,9 @@ function getEmbeddedImageData(embeddedImage) {
|
|
|
704
711
|
return embeddedImage.slice(PNG_SIGNATURE_LENGTH + PNG_IHDR_LENGTH, embeddedImage.length - PNG_IEND_LENGTH);
|
|
705
712
|
}
|
|
706
713
|
|
|
707
|
-
function dropUnhiddenFace(options, name) {
|
|
714
|
+
function dropUnhiddenFace(options, name, label) {
|
|
708
715
|
delete options[name];
|
|
709
|
-
console.warn(UNHIDDEN_FACE_WARNING_MESSAGE,
|
|
716
|
+
console.warn(UNHIDDEN_FACE_WARNING_MESSAGE, label); // eslint-disable-line no-console
|
|
710
717
|
}
|
|
711
718
|
|
|
712
719
|
async function writeData(writable, array) {
|
|
@@ -37,20 +37,36 @@ function imageResource(url) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
{
|
|
40
|
-
const options = makeOptions({ password: "secret" });
|
|
40
|
+
const options = makeOptions({ password: "secret", insertCanonicalLink: true, url: "https://example.com/secret-page" });
|
|
41
41
|
const pageData = makePageData(2, 4 * 1024);
|
|
42
42
|
pageData.title = TITLE;
|
|
43
|
+
pageData.comment = "\n url: https://example.com/secret-page \n";
|
|
43
44
|
pageData.resources.images.push(imageResource("https://example.com/secret-image.png"));
|
|
44
45
|
const { bytes } = await runProcess(pageData, options);
|
|
45
46
|
const text = decodeText(bytes);
|
|
46
47
|
check("title withheld from a protected archive", text.includes("<title></title>"), true);
|
|
47
48
|
check("resource url absent from a protected archive", text.includes("secret-image.png"), false);
|
|
49
|
+
// the wrapper ladder hides the zip data in a comment of its own, so only the text
|
|
50
|
+
// of the SingleFile comment tells whether it was written
|
|
51
|
+
check("comment withheld from a protected archive", text.includes("<!--\n url:"), false);
|
|
52
|
+
check("canonical link withheld from a protected archive", text.includes("<link rel=canonical"), false);
|
|
53
|
+
check("page url absent from a protected archive", text.includes("secret-page"), false);
|
|
48
54
|
const zipReader = new ZipReader(new BlobReader(new Blob([bytes])));
|
|
49
55
|
const entries = await zipReader.getEntries();
|
|
50
56
|
await zipReader.close();
|
|
51
57
|
check("entry comments empty in a protected archive", entries.every(entry => !entry.comment), true);
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
{
|
|
61
|
+
const options = makeOptions({ insertCanonicalLink: true, url: "https://example.com/page" });
|
|
62
|
+
const pageData = makePageData(9, 4 * 1024);
|
|
63
|
+
pageData.comment = "\n url: https://example.com/page \n";
|
|
64
|
+
const { bytes } = await runProcess(pageData, options);
|
|
65
|
+
const text = decodeText(bytes);
|
|
66
|
+
check("comment written without a password", text.includes("<!--\n url: https://example.com/page \n-->"), true);
|
|
67
|
+
check("canonical link written without a password", text.includes("<link rel=canonical href=\"https://example.com/page\">"), true);
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
{
|
|
55
71
|
const options = makeOptions({ insertTextBody: true });
|
|
56
72
|
const pageData = makePageData(7, 4 * 1024);
|