single-file-core 1.3.21 → 1.3.23

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.
@@ -394,7 +394,7 @@ function getProcessorHelperClass(utilInstance) {
394
394
  }
395
395
 
396
396
  setMetaCSP(metaElement) {
397
- metaElement.content = "default-src 'none'; connect-src 'self'; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:;";
397
+ metaElement.content = "default-src 'none'; connect-src 'self' data: blob:; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:;";
398
398
  }
399
399
 
400
400
  removeUnusedStylesheets() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "single-file-core",
3
- "version": "1.3.21",
3
+ "version": "1.3.23",
4
4
  "description": "SingleFile Core",
5
5
  "author": "Gildas Lormeau",
6
6
  "license": "AGPL-3.0-or-later",
@@ -70,6 +70,7 @@ async function extract(content, { password, prompt = () => { }, shadowRootScript
70
70
  const REGEXP_MATCH_ROOT_INDEX = /^([0-9_]+\/)?index\.html$/;
71
71
  const REGEXP_MATCH_INDEX = /index\.html$/;
72
72
  const REGEXP_MATCH_FRAMES = /frames\//;
73
+ const REGEXP_MATCH_MANIFEST = /manifest\.json$/;
73
74
  const CHARSET_UTF8 = ";charset=utf-8";
74
75
  const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
75
76
 
@@ -105,11 +106,6 @@ async function extract(content, { password, prompt = () => { }, shadowRootScript
105
106
  } else if (filename.match(REGEXP_MATCH_SCRIPT)) {
106
107
  mimeType = "text/javascript" + CHARSET_UTF8;
107
108
  }
108
- if (textContent !== undefined) {
109
- content = noBlobURL ? await getDataURI(textContent, mimeType) : URL.createObjectURL(new Blob([textContent], { type: mimeType }));
110
- } else {
111
- content = "data:text/plain,";
112
- }
113
109
  }
114
110
  } else {
115
111
  resources.push(resourceInfo);
@@ -127,7 +123,13 @@ async function extract(content, { password, prompt = () => { }, shadowRootScript
127
123
  }
128
124
  }
129
125
  const name = entry.filename.match(/^([0-9_]+\/)?(.*)$/)[2];
126
+ let prefixPath = "";
127
+ const prefixPathMatch = filename.match(/(.*\/)[^/]+$/);
128
+ if (prefixPathMatch && prefixPathMatch[1]) {
129
+ prefixPath = prefixPathMatch[1];
130
+ }
130
131
  Object.assign(resourceInfo, {
132
+ prefixPath,
131
133
  filename: entry.filename,
132
134
  name,
133
135
  url: entry.comment,
@@ -142,43 +144,57 @@ async function extract(content, { password, prompt = () => { }, shadowRootScript
142
144
  textResources.sort(sortByFilenameLengthInc);
143
145
  resources = resources.sort(sortByFilenameLengthDec).concat(...textResources).concat(...indexPages);
144
146
  for (const resource of resources) {
145
- let { textContent, mimeType, filename } = resource;
147
+ const { filename, prefixPath } = resource;
148
+ let { textContent } = resource;
146
149
  if (textContent !== undefined) {
147
- let prefixPath = "";
148
- const prefixPathMatch = filename.match(/(.*\/)[^/]+$/);
149
- if (prefixPathMatch && prefixPathMatch[1]) {
150
- prefixPath = prefixPathMatch[1];
151
- }
152
150
  if (filename.match(REGEXP_MATCH_ROOT_INDEX)) {
153
151
  origDocContent = textContent;
154
152
  }
155
153
  if (!filename.match(REGEXP_MATCH_SCRIPT)) {
156
- const resourceFilename = filename;
157
154
  resources.forEach(innerResource => {
158
155
  const { filename, parentResources, content } = innerResource;
156
+ if (filename.startsWith(prefixPath) && filename != resource.filename) {
157
+ const relativeFilename = filename.substring(prefixPath.length);
158
+ if (!relativeFilename.match(REGEXP_MATCH_MANIFEST)) {
159
+ if (textContent.includes(relativeFilename)) {
160
+ parentResources.push(resource.filename);
161
+ if (innerResource.textContent === undefined) {
162
+ textContent = replaceAll(textContent, relativeFilename, content);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ });
168
+ resource.textContent = textContent;
169
+ }
170
+ }
171
+ }
172
+ for (const resource of resources) {
173
+ let { textContent, prefixPath, filename } = resource;
174
+ if (textContent !== undefined) {
175
+ if (!filename.match(REGEXP_MATCH_SCRIPT)) {
176
+ const resourceFilename = filename;
177
+ for (const innerResource of resources) {
178
+ const { filename } = innerResource;
159
179
  if (filename.startsWith(prefixPath) && filename != resourceFilename) {
160
180
  const relativeFilename = filename.substring(prefixPath.length);
161
- if (!relativeFilename.match(/manifest\.json$/)) {
181
+ if (!relativeFilename.match(REGEXP_MATCH_MANIFEST)) {
162
182
  const position = textContent.indexOf(relativeFilename);
163
183
  if (position != -1) {
164
- parentResources.push(resourceFilename);
165
- textContent = replaceAll(textContent, relativeFilename, content);
184
+ innerResource.content = await getContent(innerResource);
185
+ textContent = replaceAll(textContent, relativeFilename, innerResource.content);
166
186
  }
167
187
  }
168
188
  }
169
- });
189
+ }
170
190
  resource.textContent = textContent;
191
+ resource.content = await getContent(resource);
171
192
  }
172
193
  if (filename.match(REGEXP_MATCH_INDEX)) {
173
194
  if (shadowRootScriptURL) {
174
195
  resource.textContent = textContent.replace(/<script data-template-shadow-root.*<\/script>/g, "<script data-template-shadow-root src=" + shadowRootScriptURL + "></" + "script>");
175
196
  }
176
197
  }
177
- if (filename.match(REGEXP_MATCH_INDEX) || filename.match(REGEXP_MATCH_FRAMES) || noBlobURL) {
178
- resource.content = await getDataURI(textContent, mimeType);
179
- } else {
180
- resource.content = URL.createObjectURL(new Blob([textContent], { type: mimeType }));
181
- }
182
198
  if (filename.match(REGEXP_MATCH_ROOT_INDEX)) {
183
199
  docContent = textContent;
184
200
  url = resource.url;
@@ -187,6 +203,10 @@ async function extract(content, { password, prompt = () => { }, shadowRootScript
187
203
  }
188
204
  return { docContent, origDocContent, resources, url };
189
205
 
206
+ async function getContent(resource) {
207
+ return resource.filename.match(REGEXP_MATCH_FRAMES) || noBlobURL ? await getDataURI(resource.textContent, resource.mimeType) : URL.createObjectURL(new Blob([resource.textContent], { type: resource.mimeType }));
208
+ }
209
+
190
210
  async function getDataURI(textContent, mimeType) {
191
211
  const reader = new FileReader();
192
212
  reader.readAsDataURL(new Blob([textContent], { type: mimeType }));
@@ -52,13 +52,13 @@ const EMBEDDED_IMAGE_DATA_TAGS = [
52
52
  ...EXTRA_DATA_TAGS,
53
53
  ];
54
54
  const EXTRA_DATA_REGEXPS = [
55
- [/<\/noscript>/i],
56
- [/<\/script>/i],
57
- [/<\/xmp>/i],
58
- [/<\/plaintext>/i]
55
+ [/<noscript/i, /<\/noscript>/i],
56
+ [/<script/i, /<\/script>/i],
57
+ [/<xmp/i, /<\/xmp>/i],
58
+ [/<plaintext/i, /<\/plaintext>/i]
59
59
  ];
60
60
  const EMBEDDED_IMAGE_DATA_REGEXPS = [
61
- /-->/i,
61
+ [/<!--/i, /-->/i],
62
62
  ...EXTRA_DATA_REGEXPS,
63
63
  ];
64
64
  const CRC32_TABLE = new Uint32Array(256).map((_, indexTable) => {
@@ -96,7 +96,7 @@ async function process(pageData, options, lastModDate = new Date()) {
96
96
  await writeData(zipDataWriter.writable, options.embeddedImage.slice(0, PNG_SIGNATURE_LENGTH + PNG_IHDR_LENGTH));
97
97
  if (options.selfExtractingArchive) {
98
98
  const embeddedImageText = embeddedImageData.reduce((text, charCode) => text + String.fromCharCode(charCode), "");
99
- const tagIndex = EMBEDDED_IMAGE_DATA_REGEXPS.findIndex(test => !embeddedImageText.match(test));
99
+ const tagIndex = EMBEDDED_IMAGE_DATA_REGEXPS.findIndex(tests => !embeddedImageText.match(tests[1]));
100
100
  let startTag;
101
101
  [startTag, endTag] = tagIndex == -1 ? ["", ""] : EMBEDDED_IMAGE_DATA_TAGS[tagIndex];
102
102
  const html = getHTMLStartData(pageData, options) + startTag;
@@ -128,8 +128,8 @@ async function process(pageData, options, lastModDate = new Date()) {
128
128
  if (!options.extractDataFromPageTags) {
129
129
  let textContent = "";
130
130
  data.slice(startOffset).forEach(charCode => textContent += String.fromCharCode(charCode));
131
- const matchEndTagComment = textContent.match(/-->/i);
132
- if (matchEndTagComment) {
131
+ const matchCommentTags = textContent.match(/<!--/i) || textContent.match(/-->/i);
132
+ if (matchCommentTags) {
133
133
  return findExtraDataTags(textContent, pageData, options, lastModDate);
134
134
  }
135
135
  }
@@ -296,7 +296,7 @@ function getHTMLStartData(pageData, options) {
296
296
  pageContent += "<meta name=viewport content=" + JSON.stringify(pageData.viewport) + ">";
297
297
  }
298
298
  if (options.insertMetaCSP) {
299
- pageContent += "<meta http-equiv=content-security-policy content=\"default-src 'none'; connect-src 'self'; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:\">";
299
+ pageContent += "<meta http-equiv=content-security-policy content=\"default-src 'none'; connect-src 'self' data: blob:; font-src 'self' data: blob:; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline' data: blob:; frame-src 'self' data: blob:; media-src 'self' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; object-src 'self' data: blob:\">";
300
300
  }
301
301
  pageContent += "<style>@keyframes display-wait-message{0%{opacity:0}100%{opacity:1}};body{color:transparent};div{color:initial}</style>";
302
302
  pageContent += "<body hidden>";
@@ -308,8 +308,9 @@ function getPageTitle(pageData) {
308
308
  }
309
309
 
310
310
  function findExtraDataTags(textContent, pageData, options, lastModDate, indexExtractDataFromPageTags = 0) {
311
- const matchEndTag = textContent.match(EXTRA_DATA_REGEXPS[indexExtractDataFromPageTags]);
312
- if (matchEndTag) {
311
+ const regExpsTag = EXTRA_DATA_REGEXPS[indexExtractDataFromPageTags];
312
+ const matchTag = textContent.match(regExpsTag[0]) || textContent.match(regExpsTag[1]);
313
+ if (matchTag) {
313
314
  if (indexExtractDataFromPageTags < EXTRA_DATA_TAGS.length - 1) {
314
315
  return findExtraDataTags(textContent, pageData, options, lastModDate, indexExtractDataFromPageTags + 1);
315
316
  } else {