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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.5.113",
3
+ "version": "1.5.115",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -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 archive was written without a face, its data names every wrapper tag:";
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
- const comment = pageData.comment && !options.embeddedImage ? "<!--" + pageData.comment + "-->" : "";
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
- if (options.insertCanonicalLink) {
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, name); // eslint-disable-line no-console
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);