single-file-core 1.0.0
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/.eslintrc.js +43 -0
- package/LICENSE +661 -0
- package/index.js +86 -0
- package/modules/css-fonts-alt-minifier.js +340 -0
- package/modules/css-fonts-minifier.js +371 -0
- package/modules/css-matched-rules.js +350 -0
- package/modules/css-medias-alt-minifier.js +91 -0
- package/modules/css-rules-minifier.js +146 -0
- package/modules/html-images-alt-minifier.js +109 -0
- package/modules/html-minifier.js +262 -0
- package/modules/html-serializer.js +180 -0
- package/modules/index.js +42 -0
- package/package.json +18 -0
- package/processors/frame-tree/content/content-frame-tree.js +418 -0
- package/processors/hooks/content/content-hooks-frames-web.js +404 -0
- package/processors/hooks/content/content-hooks-frames.js +214 -0
- package/processors/hooks/content/content-hooks-web.js +48 -0
- package/processors/hooks/content/content-hooks.js +39 -0
- package/processors/index.js +34 -0
- package/processors/lazy/content/content-lazy-loader.js +229 -0
- package/single-file-bootstrap.js +33 -0
- package/single-file-core.js +2471 -0
- package/single-file-frames.js +1 -0
- package/single-file-helper.js +557 -0
- package/single-file-util.js +412 -0
- package/vendor/css-font-property-parser.js +343 -0
- package/vendor/css-media-query-parser.js +474 -0
- package/vendor/css-minifier.js +789 -0
- package/vendor/css-tree.js +35 -0
- package/vendor/css-unescape.js +72 -0
- package/vendor/html-srcset-parser.js +339 -0
- package/vendor/index.js +40 -0
- package/vendor/mime-type-parser.js +446 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2010-2020 Gildas Lormeau
|
|
3
|
+
* contact : gildas.lormeau <at> gmail.com
|
|
4
|
+
*
|
|
5
|
+
* This file is part of SingleFile.
|
|
6
|
+
*
|
|
7
|
+
* The code in this file is free software: you can redistribute it and/or
|
|
8
|
+
* modify it under the terms of the GNU Affero General Public License
|
|
9
|
+
* (GNU AGPL) as published by the Free Software Foundation, either version 3
|
|
10
|
+
* of the License, or (at your option) any later version.
|
|
11
|
+
*
|
|
12
|
+
* The code in this file is distributed in the hope that it will be useful,
|
|
13
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
|
|
15
|
+
* General Public License for more details.
|
|
16
|
+
*
|
|
17
|
+
* As additional permission under GNU AGPL version 3 section 7, you may
|
|
18
|
+
* distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
|
|
19
|
+
* AGPL normally required by section 4, provided you include this license
|
|
20
|
+
* notice and a URL through which recipients can access the Corresponding
|
|
21
|
+
* Source.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/* global globalThis, URLSearchParams */
|
|
25
|
+
|
|
26
|
+
import * as vendor from "./vendor/index.js";
|
|
27
|
+
import * as modules from "./modules/index.js";
|
|
28
|
+
import * as helper from "./single-file-helper.js";
|
|
29
|
+
|
|
30
|
+
const DEBUG = false;
|
|
31
|
+
const ONE_MB = 1024 * 1024;
|
|
32
|
+
const PREFIX_CONTENT_TYPE_TEXT = "text/";
|
|
33
|
+
const DEFAULT_REPLACED_CHARACTERS = ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"];
|
|
34
|
+
const DEFAULT_REPLACEMENT_CHARACTER = "_";
|
|
35
|
+
|
|
36
|
+
const URL = globalThis.URL;
|
|
37
|
+
const DOMParser = globalThis.DOMParser;
|
|
38
|
+
const Blob = globalThis.Blob;
|
|
39
|
+
const FileReader = globalThis.FileReader;
|
|
40
|
+
const fetch = (url, options) => globalThis.fetch(url, options);
|
|
41
|
+
const crypto = globalThis.crypto;
|
|
42
|
+
const TextDecoder = globalThis.TextDecoder;
|
|
43
|
+
const TextEncoder = globalThis.TextEncoder;
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
getInstance
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function getInstance(utilOptions) {
|
|
50
|
+
utilOptions = utilOptions || {};
|
|
51
|
+
utilOptions.fetch = utilOptions.fetch || fetch;
|
|
52
|
+
utilOptions.frameFetch = utilOptions.frameFetch || utilOptions.fetch || fetch;
|
|
53
|
+
return {
|
|
54
|
+
getContent,
|
|
55
|
+
parseURL(resourceURL, baseURI) {
|
|
56
|
+
if (baseURI === undefined) {
|
|
57
|
+
return new URL(resourceURL);
|
|
58
|
+
} else {
|
|
59
|
+
return new URL(resourceURL, baseURI);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
resolveURL(resourceURL, baseURI) {
|
|
63
|
+
return this.parseURL(resourceURL, baseURI).href;
|
|
64
|
+
},
|
|
65
|
+
getSearchParams(searchParams) {
|
|
66
|
+
return Array.from(new URLSearchParams(searchParams));
|
|
67
|
+
},
|
|
68
|
+
getValidFilename(filename, replacedCharacters = DEFAULT_REPLACED_CHARACTERS, replacementCharacter = DEFAULT_REPLACEMENT_CHARACTER) {
|
|
69
|
+
replacedCharacters.forEach(replacedCharacter => filename = filename.replace(new RegExp("[" + replacedCharacter + "]+", "g"), replacementCharacter));
|
|
70
|
+
filename = filename
|
|
71
|
+
.replace(/\.\.\//g, "")
|
|
72
|
+
.replace(/^\/+/, "")
|
|
73
|
+
.replace(/\/+/g, "/")
|
|
74
|
+
.replace(/\/$/, "")
|
|
75
|
+
.replace(/\.$/, "")
|
|
76
|
+
.replace(/\.\//g, "." + replacementCharacter)
|
|
77
|
+
.replace(/\/\./g, "/" + replacementCharacter);
|
|
78
|
+
return filename;
|
|
79
|
+
},
|
|
80
|
+
parseDocContent(content, baseURI) {
|
|
81
|
+
const doc = (new DOMParser()).parseFromString(content, "text/html");
|
|
82
|
+
if (!doc.head) {
|
|
83
|
+
doc.documentElement.insertBefore(doc.createElement("HEAD"), doc.body);
|
|
84
|
+
}
|
|
85
|
+
let baseElement = doc.querySelector("base");
|
|
86
|
+
if (!baseElement || !baseElement.getAttribute("href")) {
|
|
87
|
+
if (baseElement) {
|
|
88
|
+
baseElement.remove();
|
|
89
|
+
}
|
|
90
|
+
baseElement = doc.createElement("base");
|
|
91
|
+
baseElement.setAttribute("href", baseURI);
|
|
92
|
+
doc.head.insertBefore(baseElement, doc.head.firstChild);
|
|
93
|
+
}
|
|
94
|
+
return doc;
|
|
95
|
+
},
|
|
96
|
+
parseXMLContent(content) {
|
|
97
|
+
return (new DOMParser()).parseFromString(content, "text/xml");
|
|
98
|
+
},
|
|
99
|
+
parseSVGContent(content) {
|
|
100
|
+
const doc = (new DOMParser()).parseFromString(content, "image/svg+xml");
|
|
101
|
+
if (doc.querySelector("parsererror")) {
|
|
102
|
+
return (new DOMParser()).parseFromString(content, "text/html");
|
|
103
|
+
} else {
|
|
104
|
+
return doc;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async digest(algo, text) {
|
|
108
|
+
try {
|
|
109
|
+
const hash = await crypto.subtle.digest(algo, new TextEncoder("utf-8").encode(text));
|
|
110
|
+
return hex(hash);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return "";
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
getContentSize(content) {
|
|
116
|
+
return new Blob([content]).size;
|
|
117
|
+
},
|
|
118
|
+
truncateText(content, maxSize) {
|
|
119
|
+
const blob = new Blob([content]);
|
|
120
|
+
const reader = new FileReader();
|
|
121
|
+
reader.readAsText(blob.slice(0, maxSize));
|
|
122
|
+
return new Promise((resolve, reject) => {
|
|
123
|
+
reader.addEventListener("load", () => {
|
|
124
|
+
if (content.startsWith(reader.result)) {
|
|
125
|
+
resolve(reader.result);
|
|
126
|
+
} else {
|
|
127
|
+
this.truncateText(content, maxSize - 1).then(resolve).catch(reject);
|
|
128
|
+
}
|
|
129
|
+
}, false);
|
|
130
|
+
reader.addEventListener("error", reject, false);
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
minifyHTML(doc, options) {
|
|
134
|
+
return modules.htmlMinifier.process(doc, options);
|
|
135
|
+
},
|
|
136
|
+
minifyCSSRules(stylesheets, styles, mediaAllInfo) {
|
|
137
|
+
return modules.cssRulesMinifier.process(stylesheets, styles, mediaAllInfo);
|
|
138
|
+
},
|
|
139
|
+
removeUnusedFonts(doc, stylesheets, styles, options) {
|
|
140
|
+
return modules.fontsMinifier.process(doc, stylesheets, styles, options);
|
|
141
|
+
},
|
|
142
|
+
removeAlternativeFonts(doc, stylesheets, fontDeclarations, fontTests) {
|
|
143
|
+
return modules.fontsAltMinifier.process(doc, stylesheets, fontDeclarations, fontTests);
|
|
144
|
+
},
|
|
145
|
+
getMediaAllInfo(doc, stylesheets, styles) {
|
|
146
|
+
return modules.matchedRules.getMediaAllInfo(doc, stylesheets, styles);
|
|
147
|
+
},
|
|
148
|
+
compressCSS(content, options) {
|
|
149
|
+
return vendor.cssMinifier.processString(content, options);
|
|
150
|
+
},
|
|
151
|
+
minifyMedias(stylesheets) {
|
|
152
|
+
return modules.mediasAltMinifier.process(stylesheets);
|
|
153
|
+
},
|
|
154
|
+
removeAlternativeImages(doc) {
|
|
155
|
+
return modules.imagesAltMinifier.process(doc);
|
|
156
|
+
},
|
|
157
|
+
parseSrcset(srcset) {
|
|
158
|
+
return vendor.srcsetParser.process(srcset);
|
|
159
|
+
},
|
|
160
|
+
preProcessDoc(doc, win, options) {
|
|
161
|
+
return helper.preProcessDoc(doc, win, options);
|
|
162
|
+
},
|
|
163
|
+
postProcessDoc(doc, markedElements, invalidElements) {
|
|
164
|
+
helper.postProcessDoc(doc, markedElements, invalidElements);
|
|
165
|
+
},
|
|
166
|
+
serialize(doc, compressHTML) {
|
|
167
|
+
return modules.serializer.process(doc, compressHTML);
|
|
168
|
+
},
|
|
169
|
+
removeQuotes(string) {
|
|
170
|
+
return helper.removeQuotes(string);
|
|
171
|
+
},
|
|
172
|
+
ON_BEFORE_CAPTURE_EVENT_NAME: helper.ON_BEFORE_CAPTURE_EVENT_NAME,
|
|
173
|
+
ON_AFTER_CAPTURE_EVENT_NAME: helper.ON_AFTER_CAPTURE_EVENT_NAME,
|
|
174
|
+
WIN_ID_ATTRIBUTE_NAME: helper.WIN_ID_ATTRIBUTE_NAME,
|
|
175
|
+
REMOVED_CONTENT_ATTRIBUTE_NAME: helper.REMOVED_CONTENT_ATTRIBUTE_NAME,
|
|
176
|
+
HIDDEN_CONTENT_ATTRIBUTE_NAME: helper.HIDDEN_CONTENT_ATTRIBUTE_NAME,
|
|
177
|
+
HIDDEN_FRAME_ATTRIBUTE_NAME: helper.HIDDEN_FRAME_ATTRIBUTE_NAME,
|
|
178
|
+
IMAGE_ATTRIBUTE_NAME: helper.IMAGE_ATTRIBUTE_NAME,
|
|
179
|
+
POSTER_ATTRIBUTE_NAME: helper.POSTER_ATTRIBUTE_NAME,
|
|
180
|
+
VIDEO_ATTRIBUTE_NAME: helper.VIDEO_ATTRIBUTE_NAME,
|
|
181
|
+
CANVAS_ATTRIBUTE_NAME: helper.CANVAS_ATTRIBUTE_NAME,
|
|
182
|
+
HTML_IMPORT_ATTRIBUTE_NAME: helper.HTML_IMPORT_ATTRIBUTE_NAME,
|
|
183
|
+
STYLE_ATTRIBUTE_NAME: helper.STYLE_ATTRIBUTE_NAME,
|
|
184
|
+
INPUT_VALUE_ATTRIBUTE_NAME: helper.INPUT_VALUE_ATTRIBUTE_NAME,
|
|
185
|
+
SHADOW_ROOT_ATTRIBUTE_NAME: helper.SHADOW_ROOT_ATTRIBUTE_NAME,
|
|
186
|
+
PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME: helper.PRESERVED_SPACE_ELEMENT_ATTRIBUTE_NAME,
|
|
187
|
+
STYLESHEET_ATTRIBUTE_NAME: helper.STYLESHEET_ATTRIBUTE_NAME,
|
|
188
|
+
SELECTED_CONTENT_ATTRIBUTE_NAME: helper.SELECTED_CONTENT_ATTRIBUTE_NAME,
|
|
189
|
+
COMMENT_HEADER: helper.COMMENT_HEADER,
|
|
190
|
+
COMMENT_HEADER_LEGACY: helper.COMMENT_HEADER_LEGACY,
|
|
191
|
+
SINGLE_FILE_UI_ELEMENT_CLASS: helper.SINGLE_FILE_UI_ELEMENT_CLASS,
|
|
192
|
+
EMPTY_RESOURCE: helper.EMPTY_RESOURCE
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
async function getContent(resourceURL, options) {
|
|
196
|
+
let response, startTime, networkTimeoutId, networkTimeoutPromise, resolveNetworkTimeoutPromise;
|
|
197
|
+
const fetchResource = utilOptions.fetch;
|
|
198
|
+
const fetchFrameResource = utilOptions.frameFetch;
|
|
199
|
+
if (DEBUG) {
|
|
200
|
+
startTime = Date.now();
|
|
201
|
+
log(" // STARTED download url =", resourceURL, "asBinary =", options.asBinary);
|
|
202
|
+
}
|
|
203
|
+
if (options.blockMixedContent && /^https:/i.test(options.baseURI) && !/^https:/i.test(resourceURL)) {
|
|
204
|
+
return getFetchResponse(resourceURL, options);
|
|
205
|
+
}
|
|
206
|
+
if (options.networkTimeout) {
|
|
207
|
+
networkTimeoutPromise = new Promise((resolve, reject) => {
|
|
208
|
+
resolveNetworkTimeoutPromise = resolve;
|
|
209
|
+
networkTimeoutId = globalThis.setTimeout(() => reject(new Error("network timeout")), options.networkTimeout);
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
networkTimeoutPromise = new Promise(resolve => {
|
|
213
|
+
resolveNetworkTimeoutPromise = resolve;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const accept = options.acceptHeaders ? options.acceptHeaders[options.expectedType] : "*/*";
|
|
218
|
+
if (options.frameId) {
|
|
219
|
+
try {
|
|
220
|
+
response = await Promise.race([
|
|
221
|
+
fetchFrameResource(resourceURL, { frameId: options.frameId, referrer: options.resourceReferrer, headers: { accept } }),
|
|
222
|
+
networkTimeoutPromise
|
|
223
|
+
]);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
response = await Promise.race([
|
|
226
|
+
fetchResource(resourceURL, { headers: { accept } }),
|
|
227
|
+
networkTimeoutPromise
|
|
228
|
+
]);
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
response = await Promise.race([
|
|
232
|
+
fetchResource(resourceURL, { referrer: options.resourceReferrer, headers: { accept } }),
|
|
233
|
+
networkTimeoutPromise
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return getFetchResponse(resourceURL, options);
|
|
238
|
+
} finally {
|
|
239
|
+
resolveNetworkTimeoutPromise();
|
|
240
|
+
if (options.networkTimeout) {
|
|
241
|
+
globalThis.clearTimeout(networkTimeoutId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let buffer;
|
|
245
|
+
try {
|
|
246
|
+
buffer = await response.arrayBuffer();
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return { data: options.asBinary ? helper.EMPTY_RESOURCE : "", resourceURL };
|
|
249
|
+
}
|
|
250
|
+
resourceURL = response.url || resourceURL;
|
|
251
|
+
let contentType = "", charset;
|
|
252
|
+
try {
|
|
253
|
+
const mimeType = new vendor.MIMEType(response.headers.get("content-type"));
|
|
254
|
+
contentType = mimeType.type + "/" + mimeType.subtype;
|
|
255
|
+
charset = mimeType.parameters.get("charset");
|
|
256
|
+
} catch (error) {
|
|
257
|
+
// ignored
|
|
258
|
+
}
|
|
259
|
+
if (!contentType) {
|
|
260
|
+
contentType = guessMIMEType(options.expectedType, buffer);
|
|
261
|
+
}
|
|
262
|
+
if (!charset && options.charset) {
|
|
263
|
+
charset = options.charset;
|
|
264
|
+
}
|
|
265
|
+
if (options.asBinary) {
|
|
266
|
+
if (response.status >= 400) {
|
|
267
|
+
return getFetchResponse(resourceURL, options);
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
if (DEBUG) {
|
|
271
|
+
log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
|
|
272
|
+
}
|
|
273
|
+
if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
|
|
274
|
+
return getFetchResponse(resourceURL, options);
|
|
275
|
+
} else {
|
|
276
|
+
return getFetchResponse(resourceURL, options, buffer, null, contentType);
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
return getFetchResponse(resourceURL, options);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
if (response.status >= 400 || (options.validateTextContentType && contentType && !contentType.startsWith(PREFIX_CONTENT_TYPE_TEXT))) {
|
|
283
|
+
return getFetchResponse(resourceURL, options);
|
|
284
|
+
}
|
|
285
|
+
if (!charset) {
|
|
286
|
+
charset = "utf-8";
|
|
287
|
+
}
|
|
288
|
+
if (DEBUG) {
|
|
289
|
+
log(" // ENDED download url =", resourceURL, "delay =", Date.now() - startTime);
|
|
290
|
+
}
|
|
291
|
+
if (options.maxResourceSizeEnabled && buffer.byteLength > options.maxResourceSize * ONE_MB) {
|
|
292
|
+
return getFetchResponse(resourceURL, options, null, charset);
|
|
293
|
+
} else {
|
|
294
|
+
try {
|
|
295
|
+
return getFetchResponse(resourceURL, options, buffer, charset, contentType);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return getFetchResponse(resourceURL, options, null, charset);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function getFetchResponse(resourceURL, options, data, charset, contentType) {
|
|
305
|
+
if (data) {
|
|
306
|
+
if (options.asBinary) {
|
|
307
|
+
const reader = new FileReader();
|
|
308
|
+
reader.readAsDataURL(new Blob([data], { type: contentType + (options.charset ? ";charset=" + options.charset : "") }));
|
|
309
|
+
data = await new Promise((resolve, reject) => {
|
|
310
|
+
reader.addEventListener("load", () => resolve(reader.result), false);
|
|
311
|
+
reader.addEventListener("error", reject, false);
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
const firstBytes = new Uint8Array(data.slice(0, 4));
|
|
315
|
+
if (firstBytes[0] == 132 && firstBytes[1] == 49 && firstBytes[2] == 149 && firstBytes[3] == 51) {
|
|
316
|
+
charset = "gb18030";
|
|
317
|
+
} else if (firstBytes[0] == 255 && firstBytes[1] == 254) {
|
|
318
|
+
charset = "utf-16le";
|
|
319
|
+
} else if (firstBytes[0] == 254 && firstBytes[1] == 255) {
|
|
320
|
+
charset = "utf-16be";
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
data = new TextDecoder(charset).decode(data);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
charset = "utf-8";
|
|
326
|
+
data = new TextDecoder(charset).decode(data);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
data = options.asBinary ? helper.EMPTY_RESOURCE : "";
|
|
331
|
+
}
|
|
332
|
+
return { data, resourceURL, charset };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function guessMIMEType(expectedType, buffer) {
|
|
336
|
+
if (expectedType == "image") {
|
|
337
|
+
if (compareBytes([255, 255, 255, 255], [0, 0, 1, 0])) {
|
|
338
|
+
return "image/x-icon";
|
|
339
|
+
}
|
|
340
|
+
if (compareBytes([255, 255, 255, 255], [0, 0, 2, 0])) {
|
|
341
|
+
return "image/x-icon";
|
|
342
|
+
}
|
|
343
|
+
if (compareBytes([255, 255], [78, 77])) {
|
|
344
|
+
return "image/bmp";
|
|
345
|
+
}
|
|
346
|
+
if (compareBytes([255, 255, 255, 255, 255, 255], [71, 73, 70, 56, 57, 97])) {
|
|
347
|
+
return "image/gif";
|
|
348
|
+
}
|
|
349
|
+
if (compareBytes([255, 255, 255, 255, 255, 255], [71, 73, 70, 56, 59, 97])) {
|
|
350
|
+
return "image/gif";
|
|
351
|
+
}
|
|
352
|
+
if (compareBytes([255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255], [82, 73, 70, 70, 0, 0, 0, 0, 87, 69, 66, 80, 86, 80])) {
|
|
353
|
+
return "image/webp";
|
|
354
|
+
}
|
|
355
|
+
if (compareBytes([255, 255, 255, 255, 255, 255, 255, 255], [137, 80, 78, 71, 13, 10, 26, 10])) {
|
|
356
|
+
return "image/png";
|
|
357
|
+
}
|
|
358
|
+
if (compareBytes([255, 255, 255], [255, 216, 255])) {
|
|
359
|
+
return "image/jpeg";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (expectedType == "font") {
|
|
363
|
+
if (compareBytes([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255],
|
|
364
|
+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 80])) {
|
|
365
|
+
return "application/vnd.ms-fontobject";
|
|
366
|
+
}
|
|
367
|
+
if (compareBytes([255, 255, 255, 255], [0, 1, 0, 0])) {
|
|
368
|
+
return "font/ttf";
|
|
369
|
+
}
|
|
370
|
+
if (compareBytes([255, 255, 255, 255], [79, 84, 84, 79])) {
|
|
371
|
+
return "font/otf";
|
|
372
|
+
}
|
|
373
|
+
if (compareBytes([255, 255, 255, 255], [116, 116, 99, 102])) {
|
|
374
|
+
return "font/collection";
|
|
375
|
+
}
|
|
376
|
+
if (compareBytes([255, 255, 255, 255], [119, 79, 70, 70])) {
|
|
377
|
+
return "font/woff";
|
|
378
|
+
}
|
|
379
|
+
if (compareBytes([255, 255, 255, 255], [119, 79, 70, 50])) {
|
|
380
|
+
return "font/woff2";
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function compareBytes(mask, pattern) {
|
|
385
|
+
let patternMatch = true, index = 0;
|
|
386
|
+
if (buffer.byteLength >= pattern.length) {
|
|
387
|
+
const value = new Uint8Array(buffer, 0, mask.length);
|
|
388
|
+
for (index = 0; index < mask.length && patternMatch; index++) {
|
|
389
|
+
patternMatch = patternMatch && ((value[index] & mask[index]) == pattern[index]);
|
|
390
|
+
}
|
|
391
|
+
return patternMatch;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
|
397
|
+
function hex(buffer) {
|
|
398
|
+
const hexCodes = [];
|
|
399
|
+
const view = new DataView(buffer);
|
|
400
|
+
for (let i = 0; i < view.byteLength; i += 4) {
|
|
401
|
+
const value = view.getUint32(i);
|
|
402
|
+
const stringValue = value.toString(16);
|
|
403
|
+
const padding = "00000000";
|
|
404
|
+
const paddedValue = (padding + stringValue).slice(-padding.length);
|
|
405
|
+
hexCodes.push(paddedValue);
|
|
406
|
+
}
|
|
407
|
+
return hexCodes.join("");
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function log(...args) {
|
|
411
|
+
console.log("S-File <browser>", ...args); // eslint-disable-line no-console
|
|
412
|
+
}
|