llonebot-dist 6.6.4
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.
- dist/default_config.json +68 -0
- dist/llonebot.js +48348 -0
- dist/llonebot.js.map +1 -0
- dist/node_modules/@borewit/text-codec/LICENSE.txt +9 -0
- dist/node_modules/@borewit/text-codec/README.md +76 -0
- dist/node_modules/@borewit/text-codec/lib/index.d.ts +8 -0
- dist/node_modules/@borewit/text-codec/lib/index.js +161 -0
- dist/node_modules/@borewit/text-codec/package.json +68 -0
- dist/node_modules/@minatojs/sql.js/LICENSE +44 -0
- dist/node_modules/@minatojs/sql.js/README.md +357 -0
- dist/node_modules/@minatojs/sql.js/dist/sql-wasm.d.ts +316 -0
- dist/node_modules/@minatojs/sql.js/dist/sql-wasm.js +225 -0
- dist/node_modules/@minatojs/sql.js/dist/sql-wasm.wasm +0 -0
- dist/node_modules/@minatojs/sql.js/package.json +58 -0
- dist/node_modules/@tokenizer/inflate/LICENSE +15 -0
- dist/node_modules/@tokenizer/inflate/README.md +114 -0
- dist/node_modules/@tokenizer/inflate/lib/GzipHandler.d.ts +6 -0
- dist/node_modules/@tokenizer/inflate/lib/GzipHandler.js +19 -0
- dist/node_modules/@tokenizer/inflate/lib/ZipHandler.d.ts +26 -0
- dist/node_modules/@tokenizer/inflate/lib/ZipHandler.js +233 -0
- dist/node_modules/@tokenizer/inflate/lib/ZipToken.d.ts +94 -0
- dist/node_modules/@tokenizer/inflate/lib/ZipToken.js +117 -0
- dist/node_modules/@tokenizer/inflate/lib/index.d.ts +3 -0
- dist/node_modules/@tokenizer/inflate/lib/index.js +2 -0
- dist/node_modules/@tokenizer/inflate/package.json +76 -0
- dist/node_modules/@tokenizer/token/README.md +19 -0
- dist/node_modules/@tokenizer/token/index.d.ts +30 -0
- dist/node_modules/@tokenizer/token/package.json +33 -0
- dist/node_modules/debug/LICENSE +20 -0
- dist/node_modules/debug/README.md +481 -0
- dist/node_modules/debug/package.json +64 -0
- dist/node_modules/debug/src/browser.js +272 -0
- dist/node_modules/debug/src/common.js +292 -0
- dist/node_modules/debug/src/index.js +10 -0
- dist/node_modules/debug/src/node.js +263 -0
- dist/node_modules/file-type/core.d.ts +253 -0
- dist/node_modules/file-type/core.js +1899 -0
- dist/node_modules/file-type/index.d.ts +98 -0
- dist/node_modules/file-type/index.js +86 -0
- dist/node_modules/file-type/license +9 -0
- dist/node_modules/file-type/package.json +288 -0
- dist/node_modules/file-type/readme.md +674 -0
- dist/node_modules/file-type/supported.js +356 -0
- dist/node_modules/file-type/util.js +60 -0
- dist/node_modules/ieee754/LICENSE +11 -0
- dist/node_modules/ieee754/README.md +51 -0
- dist/node_modules/ieee754/index.d.ts +10 -0
- dist/node_modules/ieee754/index.js +85 -0
- dist/node_modules/ieee754/package.json +52 -0
- dist/node_modules/ms/index.js +162 -0
- dist/node_modules/ms/license.md +21 -0
- dist/node_modules/ms/package.json +38 -0
- dist/node_modules/ms/readme.md +59 -0
- dist/node_modules/silk-wasm/LICENSE +21 -0
- dist/node_modules/silk-wasm/README.md +85 -0
- dist/node_modules/silk-wasm/lib/index.cjs +16 -0
- dist/node_modules/silk-wasm/lib/index.d.ts +70 -0
- dist/node_modules/silk-wasm/lib/index.mjs +16 -0
- dist/node_modules/silk-wasm/lib/silk.wasm +0 -0
- dist/node_modules/silk-wasm/lib/utils.d.ts +4 -0
- dist/node_modules/silk-wasm/package.json +39 -0
- dist/node_modules/strtok3/LICENSE.txt +21 -0
- dist/node_modules/strtok3/README.md +399 -0
- dist/node_modules/strtok3/lib/AbstractTokenizer.d.ts +76 -0
- dist/node_modules/strtok3/lib/AbstractTokenizer.js +108 -0
- dist/node_modules/strtok3/lib/BlobTokenizer.d.ts +29 -0
- dist/node_modules/strtok3/lib/BlobTokenizer.js +53 -0
- dist/node_modules/strtok3/lib/BufferTokenizer.d.ts +29 -0
- dist/node_modules/strtok3/lib/BufferTokenizer.js +52 -0
- dist/node_modules/strtok3/lib/FileTokenizer.d.ts +37 -0
- dist/node_modules/strtok3/lib/FileTokenizer.js +61 -0
- dist/node_modules/strtok3/lib/ReadStreamTokenizer.d.ts +31 -0
- dist/node_modules/strtok3/lib/ReadStreamTokenizer.js +102 -0
- dist/node_modules/strtok3/lib/core.d.ts +40 -0
- dist/node_modules/strtok3/lib/core.js +62 -0
- dist/node_modules/strtok3/lib/index.d.ts +16 -0
- dist/node_modules/strtok3/lib/index.js +22 -0
- dist/node_modules/strtok3/lib/stream/AbstractStreamReader.d.ts +54 -0
- dist/node_modules/strtok3/lib/stream/AbstractStreamReader.js +71 -0
- dist/node_modules/strtok3/lib/stream/Deferred.d.ts +6 -0
- dist/node_modules/strtok3/lib/stream/Deferred.js +10 -0
- dist/node_modules/strtok3/lib/stream/Errors.d.ts +10 -0
- dist/node_modules/strtok3/lib/stream/Errors.js +16 -0
- dist/node_modules/strtok3/lib/stream/StreamReader.d.ts +29 -0
- dist/node_modules/strtok3/lib/stream/StreamReader.js +83 -0
- dist/node_modules/strtok3/lib/stream/WebStreamByobReader.d.ts +14 -0
- dist/node_modules/strtok3/lib/stream/WebStreamByobReader.js +27 -0
- dist/node_modules/strtok3/lib/stream/WebStreamDefaultReader.d.ts +19 -0
- dist/node_modules/strtok3/lib/stream/WebStreamDefaultReader.js +62 -0
- dist/node_modules/strtok3/lib/stream/WebStreamReader.d.ts +14 -0
- dist/node_modules/strtok3/lib/stream/WebStreamReader.js +13 -0
- dist/node_modules/strtok3/lib/stream/WebStreamReaderFactory.d.ts +5 -0
- dist/node_modules/strtok3/lib/stream/WebStreamReaderFactory.js +19 -0
- dist/node_modules/strtok3/lib/stream/index.d.ts +6 -0
- dist/node_modules/strtok3/lib/stream/index.js +5 -0
- dist/node_modules/strtok3/lib/types.d.ts +139 -0
- dist/node_modules/strtok3/lib/types.js +1 -0
- dist/node_modules/strtok3/package.json +94 -0
- dist/node_modules/token-types/LICENSE.txt +9 -0
- dist/node_modules/token-types/README.md +120 -0
- dist/node_modules/token-types/lib/index.d.ts +135 -0
- dist/node_modules/token-types/lib/index.js +401 -0
- dist/node_modules/token-types/package.json +81 -0
- dist/node_modules/uint8array-extras/index.d.ts +312 -0
- dist/node_modules/uint8array-extras/index.js +321 -0
- dist/node_modules/uint8array-extras/license +9 -0
- dist/node_modules/uint8array-extras/package.json +54 -0
- dist/node_modules/uint8array-extras/readme.md +301 -0
- dist/node_modules/ws/LICENSE +20 -0
- dist/node_modules/ws/README.md +548 -0
- dist/node_modules/ws/browser.js +8 -0
- dist/node_modules/ws/index.js +13 -0
- dist/node_modules/ws/lib/buffer-util.js +131 -0
- dist/node_modules/ws/lib/constants.js +18 -0
- dist/node_modules/ws/lib/event-target.js +292 -0
- dist/node_modules/ws/lib/extension.js +203 -0
- dist/node_modules/ws/lib/limiter.js +55 -0
- dist/node_modules/ws/lib/permessage-deflate.js +528 -0
- dist/node_modules/ws/lib/receiver.js +706 -0
- dist/node_modules/ws/lib/sender.js +602 -0
- dist/node_modules/ws/lib/stream.js +161 -0
- dist/node_modules/ws/lib/subprotocol.js +62 -0
- dist/node_modules/ws/lib/validation.js +152 -0
- dist/node_modules/ws/lib/websocket-server.js +550 -0
- dist/node_modules/ws/lib/websocket.js +1388 -0
- dist/node_modules/ws/package.json +69 -0
- dist/node_modules/ws/wrapper.mjs +8 -0
- dist/package.json +1 -0
- dist/webui/assets/index-B9vGhdCO.js +256 -0
- dist/webui/assets/index-DaqFU7JR.css +1 -0
- dist/webui/index.html +13 -0
- dist/webui/logo.jpg +0 -0
- dist//344/275/277/347/224/250/350/257/264/346/230/216.txt +11 -0
- dist//346/233/264/346/226/260/346/227/245/345/277/227.txt +399 -0
|
@@ -0,0 +1,1899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Primary entry point, Node.js specific entry point is index.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as Token from 'token-types';
|
|
6
|
+
import * as strtok3 from 'strtok3/core';
|
|
7
|
+
import {ZipHandler, GzipHandler} from '@tokenizer/inflate';
|
|
8
|
+
import {getUintBE} from 'uint8array-extras';
|
|
9
|
+
import {
|
|
10
|
+
stringToBytes,
|
|
11
|
+
tarHeaderChecksumMatches,
|
|
12
|
+
uint32SyncSafeToken,
|
|
13
|
+
} from './util.js';
|
|
14
|
+
import {extensions, mimeTypes} from './supported.js';
|
|
15
|
+
|
|
16
|
+
export const reasonableDetectionSizeInBytes = 4100; // A fair amount of file-types are detectable within this range.
|
|
17
|
+
|
|
18
|
+
export async function fileTypeFromStream(stream, options) {
|
|
19
|
+
return new FileTypeParser(options).fromStream(stream);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function fileTypeFromBuffer(input, options) {
|
|
23
|
+
return new FileTypeParser(options).fromBuffer(input);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function fileTypeFromBlob(blob, options) {
|
|
27
|
+
return new FileTypeParser(options).fromBlob(blob);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getFileTypeFromMimeType(mimeType) {
|
|
31
|
+
mimeType = mimeType.toLowerCase();
|
|
32
|
+
switch (mimeType) {
|
|
33
|
+
case 'application/epub+zip':
|
|
34
|
+
return {
|
|
35
|
+
ext: 'epub',
|
|
36
|
+
mime: mimeType,
|
|
37
|
+
};
|
|
38
|
+
case 'application/vnd.oasis.opendocument.text':
|
|
39
|
+
return {
|
|
40
|
+
ext: 'odt',
|
|
41
|
+
mime: mimeType,
|
|
42
|
+
};
|
|
43
|
+
case 'application/vnd.oasis.opendocument.text-template':
|
|
44
|
+
return {
|
|
45
|
+
ext: 'ott',
|
|
46
|
+
mime: mimeType,
|
|
47
|
+
};
|
|
48
|
+
case 'application/vnd.oasis.opendocument.spreadsheet':
|
|
49
|
+
return {
|
|
50
|
+
ext: 'ods',
|
|
51
|
+
mime: mimeType,
|
|
52
|
+
};
|
|
53
|
+
case 'application/vnd.oasis.opendocument.spreadsheet-template':
|
|
54
|
+
return {
|
|
55
|
+
ext: 'ots',
|
|
56
|
+
mime: mimeType,
|
|
57
|
+
};
|
|
58
|
+
case 'application/vnd.oasis.opendocument.presentation':
|
|
59
|
+
return {
|
|
60
|
+
ext: 'odp',
|
|
61
|
+
mime: mimeType,
|
|
62
|
+
};
|
|
63
|
+
case 'application/vnd.oasis.opendocument.presentation-template':
|
|
64
|
+
return {
|
|
65
|
+
ext: 'otp',
|
|
66
|
+
mime: mimeType,
|
|
67
|
+
};
|
|
68
|
+
case 'application/vnd.oasis.opendocument.graphics':
|
|
69
|
+
return {
|
|
70
|
+
ext: 'odg',
|
|
71
|
+
mime: mimeType,
|
|
72
|
+
};
|
|
73
|
+
case 'application/vnd.oasis.opendocument.graphics-template':
|
|
74
|
+
return {
|
|
75
|
+
ext: 'otg',
|
|
76
|
+
mime: mimeType,
|
|
77
|
+
};
|
|
78
|
+
case 'application/vnd.openxmlformats-officedocument.presentationml.slideshow':
|
|
79
|
+
return {
|
|
80
|
+
ext: 'ppsx',
|
|
81
|
+
mime: mimeType,
|
|
82
|
+
};
|
|
83
|
+
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
|
84
|
+
return {
|
|
85
|
+
ext: 'xlsx',
|
|
86
|
+
mime: mimeType,
|
|
87
|
+
};
|
|
88
|
+
case 'application/vnd.ms-excel.sheet.macroenabled':
|
|
89
|
+
return {
|
|
90
|
+
ext: 'xlsm',
|
|
91
|
+
mime: 'application/vnd.ms-excel.sheet.macroenabled.12',
|
|
92
|
+
};
|
|
93
|
+
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.template':
|
|
94
|
+
return {
|
|
95
|
+
ext: 'xltx',
|
|
96
|
+
mime: mimeType,
|
|
97
|
+
};
|
|
98
|
+
case 'application/vnd.ms-excel.template.macroenabled':
|
|
99
|
+
return {
|
|
100
|
+
ext: 'xltm',
|
|
101
|
+
mime: 'application/vnd.ms-excel.template.macroenabled.12',
|
|
102
|
+
};
|
|
103
|
+
case 'application/vnd.ms-powerpoint.slideshow.macroenabled':
|
|
104
|
+
return {
|
|
105
|
+
ext: 'ppsm',
|
|
106
|
+
mime: 'application/vnd.ms-powerpoint.slideshow.macroenabled.12',
|
|
107
|
+
};
|
|
108
|
+
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
109
|
+
return {
|
|
110
|
+
ext: 'docx',
|
|
111
|
+
mime: mimeType,
|
|
112
|
+
};
|
|
113
|
+
case 'application/vnd.ms-word.document.macroenabled':
|
|
114
|
+
return {
|
|
115
|
+
ext: 'docm',
|
|
116
|
+
mime: 'application/vnd.ms-word.document.macroenabled.12',
|
|
117
|
+
};
|
|
118
|
+
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.template':
|
|
119
|
+
return {
|
|
120
|
+
ext: 'dotx',
|
|
121
|
+
mime: mimeType,
|
|
122
|
+
};
|
|
123
|
+
case 'application/vnd.ms-word.template.macroenabledtemplate':
|
|
124
|
+
return {
|
|
125
|
+
ext: 'dotm',
|
|
126
|
+
mime: 'application/vnd.ms-word.template.macroenabled.12',
|
|
127
|
+
};
|
|
128
|
+
case 'application/vnd.openxmlformats-officedocument.presentationml.template':
|
|
129
|
+
return {
|
|
130
|
+
ext: 'potx',
|
|
131
|
+
mime: mimeType,
|
|
132
|
+
};
|
|
133
|
+
case 'application/vnd.ms-powerpoint.template.macroenabled':
|
|
134
|
+
return {
|
|
135
|
+
ext: 'potm',
|
|
136
|
+
mime: 'application/vnd.ms-powerpoint.template.macroenabled.12',
|
|
137
|
+
};
|
|
138
|
+
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
|
139
|
+
return {
|
|
140
|
+
ext: 'pptx',
|
|
141
|
+
mime: mimeType,
|
|
142
|
+
};
|
|
143
|
+
case 'application/vnd.ms-powerpoint.presentation.macroenabled':
|
|
144
|
+
return {
|
|
145
|
+
ext: 'pptm',
|
|
146
|
+
mime: 'application/vnd.ms-powerpoint.presentation.macroenabled.12',
|
|
147
|
+
};
|
|
148
|
+
case 'application/vnd.ms-visio.drawing':
|
|
149
|
+
return {
|
|
150
|
+
ext: 'vsdx',
|
|
151
|
+
mime: 'application/vnd.visio',
|
|
152
|
+
};
|
|
153
|
+
case 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml':
|
|
154
|
+
return {
|
|
155
|
+
ext: '3mf',
|
|
156
|
+
mime: 'model/3mf',
|
|
157
|
+
};
|
|
158
|
+
default:
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _check(buffer, headers, options) {
|
|
163
|
+
options = {
|
|
164
|
+
offset: 0,
|
|
165
|
+
...options,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
for (const [index, header] of headers.entries()) {
|
|
169
|
+
// If a bitmask is set
|
|
170
|
+
if (options.mask) {
|
|
171
|
+
// If header doesn't equal `buf` with bits masked off
|
|
172
|
+
if (header !== (options.mask[index] & buffer[index + options.offset])) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
} else if (header !== buffer[index + options.offset]) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function fileTypeFromTokenizer(tokenizer, options) {
|
|
184
|
+
return new FileTypeParser(options).fromTokenizer(tokenizer);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function fileTypeStream(webStream, options) {
|
|
188
|
+
return new FileTypeParser(options).toDetectionStream(webStream, options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export class FileTypeParser {
|
|
192
|
+
constructor(options) {
|
|
193
|
+
this.options = {
|
|
194
|
+
mpegOffsetTolerance: 0,
|
|
195
|
+
...options,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
this.detectors = [...(options?.customDetectors ?? []),
|
|
199
|
+
{id: 'core', detect: this.detectConfident},
|
|
200
|
+
{id: 'core.imprecise', detect: this.detectImprecise}];
|
|
201
|
+
this.tokenizerOptions = {
|
|
202
|
+
abortSignal: options?.signal,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async fromTokenizer(tokenizer) {
|
|
207
|
+
const initialPosition = tokenizer.position;
|
|
208
|
+
|
|
209
|
+
// Iterate through all file-type detectors
|
|
210
|
+
for (const detector of this.detectors) {
|
|
211
|
+
const fileType = await detector.detect(tokenizer);
|
|
212
|
+
if (fileType) {
|
|
213
|
+
return fileType;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (initialPosition !== tokenizer.position) {
|
|
217
|
+
return undefined; // Cannot proceed scanning of the tokenizer is at an arbitrary position
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async fromBuffer(input) {
|
|
223
|
+
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
|
|
224
|
+
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof input}\``);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
228
|
+
|
|
229
|
+
if (!(buffer?.length > 1)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return this.fromTokenizer(strtok3.fromBuffer(buffer, this.tokenizerOptions));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async fromBlob(blob) {
|
|
237
|
+
const tokenizer = strtok3.fromBlob(blob, this.tokenizerOptions);
|
|
238
|
+
try {
|
|
239
|
+
return await this.fromTokenizer(tokenizer);
|
|
240
|
+
} finally {
|
|
241
|
+
await tokenizer.close();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async fromStream(stream) {
|
|
246
|
+
const tokenizer = strtok3.fromWebStream(stream, this.tokenizerOptions);
|
|
247
|
+
try {
|
|
248
|
+
return await this.fromTokenizer(tokenizer);
|
|
249
|
+
} finally {
|
|
250
|
+
await tokenizer.close();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async toDetectionStream(stream, options) {
|
|
255
|
+
const {sampleSize = reasonableDetectionSizeInBytes} = options;
|
|
256
|
+
let detectedFileType;
|
|
257
|
+
let firstChunk;
|
|
258
|
+
|
|
259
|
+
const reader = stream.getReader({mode: 'byob'});
|
|
260
|
+
try {
|
|
261
|
+
// Read the first chunk from the stream
|
|
262
|
+
const {value: chunk, done} = await reader.read(new Uint8Array(sampleSize));
|
|
263
|
+
firstChunk = chunk;
|
|
264
|
+
if (!done && chunk) {
|
|
265
|
+
try {
|
|
266
|
+
// Attempt to detect the file type from the chunk
|
|
267
|
+
detectedFileType = await this.fromBuffer(chunk.subarray(0, sampleSize));
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (!(error instanceof strtok3.EndOfStreamError)) {
|
|
270
|
+
throw error; // Re-throw non-EndOfStreamError
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
detectedFileType = undefined;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
firstChunk = chunk;
|
|
278
|
+
} finally {
|
|
279
|
+
reader.releaseLock(); // Ensure the reader is released
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Create a new ReadableStream to manage locking issues
|
|
283
|
+
const transformStream = new TransformStream({
|
|
284
|
+
async start(controller) {
|
|
285
|
+
controller.enqueue(firstChunk); // Enqueue the initial chunk
|
|
286
|
+
},
|
|
287
|
+
transform(chunk, controller) {
|
|
288
|
+
// Pass through the chunks without modification
|
|
289
|
+
controller.enqueue(chunk);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const newStream = stream.pipeThrough(transformStream);
|
|
294
|
+
newStream.fileType = detectedFileType;
|
|
295
|
+
|
|
296
|
+
return newStream;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
check(header, options) {
|
|
300
|
+
return _check(this.buffer, header, options);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
checkString(header, options) {
|
|
304
|
+
return this.check(stringToBytes(header, options?.encoding), options);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Detections with a high degree of certainty in identifying the correct file type
|
|
308
|
+
detectConfident = async tokenizer => {
|
|
309
|
+
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes);
|
|
310
|
+
|
|
311
|
+
// Keep reading until EOF if the file size is unknown.
|
|
312
|
+
if (tokenizer.fileInfo.size === undefined) {
|
|
313
|
+
tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.tokenizer = tokenizer;
|
|
317
|
+
|
|
318
|
+
await tokenizer.peekBuffer(this.buffer, {length: 32, mayBeLess: true});
|
|
319
|
+
|
|
320
|
+
// -- 2-byte signatures --
|
|
321
|
+
|
|
322
|
+
if (this.check([0x42, 0x4D])) {
|
|
323
|
+
return {
|
|
324
|
+
ext: 'bmp',
|
|
325
|
+
mime: 'image/bmp',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (this.check([0x0B, 0x77])) {
|
|
330
|
+
return {
|
|
331
|
+
ext: 'ac3',
|
|
332
|
+
mime: 'audio/vnd.dolby.dd-raw',
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (this.check([0x78, 0x01])) {
|
|
337
|
+
return {
|
|
338
|
+
ext: 'dmg',
|
|
339
|
+
mime: 'application/x-apple-diskimage',
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (this.check([0x4D, 0x5A])) {
|
|
344
|
+
return {
|
|
345
|
+
ext: 'exe',
|
|
346
|
+
mime: 'application/x-msdownload',
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (this.check([0x25, 0x21])) {
|
|
351
|
+
await tokenizer.peekBuffer(this.buffer, {length: 24, mayBeLess: true});
|
|
352
|
+
|
|
353
|
+
if (
|
|
354
|
+
this.checkString('PS-Adobe-', {offset: 2})
|
|
355
|
+
&& this.checkString(' EPSF-', {offset: 14})
|
|
356
|
+
) {
|
|
357
|
+
return {
|
|
358
|
+
ext: 'eps',
|
|
359
|
+
mime: 'application/eps',
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
ext: 'ps',
|
|
365
|
+
mime: 'application/postscript',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (
|
|
370
|
+
this.check([0x1F, 0xA0])
|
|
371
|
+
|| this.check([0x1F, 0x9D])
|
|
372
|
+
) {
|
|
373
|
+
return {
|
|
374
|
+
ext: 'Z',
|
|
375
|
+
mime: 'application/x-compress',
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (this.check([0xC7, 0x71])) {
|
|
380
|
+
return {
|
|
381
|
+
ext: 'cpio',
|
|
382
|
+
mime: 'application/x-cpio',
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (this.check([0x60, 0xEA])) {
|
|
387
|
+
return {
|
|
388
|
+
ext: 'arj',
|
|
389
|
+
mime: 'application/x-arj',
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// -- 3-byte signatures --
|
|
394
|
+
|
|
395
|
+
if (this.check([0xEF, 0xBB, 0xBF])) { // UTF-8-BOM
|
|
396
|
+
// Strip off UTF-8-BOM
|
|
397
|
+
this.tokenizer.ignore(3);
|
|
398
|
+
return this.detectConfident(tokenizer);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (this.check([0x47, 0x49, 0x46])) {
|
|
402
|
+
return {
|
|
403
|
+
ext: 'gif',
|
|
404
|
+
mime: 'image/gif',
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (this.check([0x49, 0x49, 0xBC])) {
|
|
409
|
+
return {
|
|
410
|
+
ext: 'jxr',
|
|
411
|
+
mime: 'image/vnd.ms-photo',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (this.check([0x1F, 0x8B, 0x8])) {
|
|
416
|
+
const gzipHandler = new GzipHandler(tokenizer);
|
|
417
|
+
|
|
418
|
+
const stream = gzipHandler.inflate();
|
|
419
|
+
let shouldCancelStream = true;
|
|
420
|
+
try {
|
|
421
|
+
let compressedFileType;
|
|
422
|
+
try {
|
|
423
|
+
compressedFileType = await this.fromStream(stream);
|
|
424
|
+
} catch {
|
|
425
|
+
shouldCancelStream = false;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (compressedFileType && compressedFileType.ext === 'tar') {
|
|
429
|
+
return {
|
|
430
|
+
ext: 'tar.gz',
|
|
431
|
+
mime: 'application/gzip',
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
} finally {
|
|
435
|
+
if (shouldCancelStream) {
|
|
436
|
+
await stream.cancel();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
ext: 'gz',
|
|
442
|
+
mime: 'application/gzip',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (this.check([0x42, 0x5A, 0x68])) {
|
|
447
|
+
return {
|
|
448
|
+
ext: 'bz2',
|
|
449
|
+
mime: 'application/x-bzip2',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (this.checkString('ID3')) {
|
|
454
|
+
await tokenizer.ignore(6); // Skip ID3 header until the header size
|
|
455
|
+
const id3HeaderLength = await tokenizer.readToken(uint32SyncSafeToken);
|
|
456
|
+
if (tokenizer.position + id3HeaderLength > tokenizer.fileInfo.size) {
|
|
457
|
+
// Guess file type based on ID3 header for backward compatibility
|
|
458
|
+
return {
|
|
459
|
+
ext: 'mp3',
|
|
460
|
+
mime: 'audio/mpeg',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
await tokenizer.ignore(id3HeaderLength);
|
|
465
|
+
return this.fromTokenizer(tokenizer); // Skip ID3 header, recursion
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Musepack, SV7
|
|
469
|
+
if (this.checkString('MP+')) {
|
|
470
|
+
return {
|
|
471
|
+
ext: 'mpc',
|
|
472
|
+
mime: 'audio/x-musepack',
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (
|
|
477
|
+
(this.buffer[0] === 0x43 || this.buffer[0] === 0x46)
|
|
478
|
+
&& this.check([0x57, 0x53], {offset: 1})
|
|
479
|
+
) {
|
|
480
|
+
return {
|
|
481
|
+
ext: 'swf',
|
|
482
|
+
mime: 'application/x-shockwave-flash',
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// -- 4-byte signatures --
|
|
487
|
+
|
|
488
|
+
// Requires a sample size of 4 bytes
|
|
489
|
+
if (this.check([0xFF, 0xD8, 0xFF])) {
|
|
490
|
+
if (this.check([0xF7], {offset: 3})) { // JPG7/SOF55, indicating a ISO/IEC 14495 / JPEG-LS file
|
|
491
|
+
return {
|
|
492
|
+
ext: 'jls',
|
|
493
|
+
mime: 'image/jls',
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
ext: 'jpg',
|
|
499
|
+
mime: 'image/jpeg',
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (this.check([0x4F, 0x62, 0x6A, 0x01])) {
|
|
504
|
+
return {
|
|
505
|
+
ext: 'avro',
|
|
506
|
+
mime: 'application/avro',
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (this.checkString('FLIF')) {
|
|
511
|
+
return {
|
|
512
|
+
ext: 'flif',
|
|
513
|
+
mime: 'image/flif',
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (this.checkString('8BPS')) {
|
|
518
|
+
return {
|
|
519
|
+
ext: 'psd',
|
|
520
|
+
mime: 'image/vnd.adobe.photoshop',
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Musepack, SV8
|
|
525
|
+
if (this.checkString('MPCK')) {
|
|
526
|
+
return {
|
|
527
|
+
ext: 'mpc',
|
|
528
|
+
mime: 'audio/x-musepack',
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (this.checkString('FORM')) {
|
|
533
|
+
return {
|
|
534
|
+
ext: 'aif',
|
|
535
|
+
mime: 'audio/aiff',
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (this.checkString('icns', {offset: 0})) {
|
|
540
|
+
return {
|
|
541
|
+
ext: 'icns',
|
|
542
|
+
mime: 'image/icns',
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Zip-based file formats
|
|
547
|
+
// Need to be before the `zip` check
|
|
548
|
+
if (this.check([0x50, 0x4B, 0x3, 0x4])) { // Local file header signature
|
|
549
|
+
let fileType;
|
|
550
|
+
await new ZipHandler(tokenizer).unzip(zipHeader => {
|
|
551
|
+
switch (zipHeader.filename) {
|
|
552
|
+
case 'META-INF/mozilla.rsa':
|
|
553
|
+
fileType = {
|
|
554
|
+
ext: 'xpi',
|
|
555
|
+
mime: 'application/x-xpinstall',
|
|
556
|
+
};
|
|
557
|
+
return {
|
|
558
|
+
stop: true,
|
|
559
|
+
};
|
|
560
|
+
case 'META-INF/MANIFEST.MF':
|
|
561
|
+
fileType = {
|
|
562
|
+
ext: 'jar',
|
|
563
|
+
mime: 'application/java-archive',
|
|
564
|
+
};
|
|
565
|
+
return {
|
|
566
|
+
stop: true,
|
|
567
|
+
};
|
|
568
|
+
case 'mimetype':
|
|
569
|
+
return {
|
|
570
|
+
async handler(fileData) {
|
|
571
|
+
// Use TextDecoder to decode the UTF-8 encoded data
|
|
572
|
+
const mimeType = new TextDecoder('utf-8').decode(fileData).trim();
|
|
573
|
+
fileType = getFileTypeFromMimeType(mimeType);
|
|
574
|
+
},
|
|
575
|
+
stop: true,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
case '[Content_Types].xml':
|
|
579
|
+
return {
|
|
580
|
+
async handler(fileData) {
|
|
581
|
+
// Use TextDecoder to decode the UTF-8 encoded data
|
|
582
|
+
let xmlContent = new TextDecoder('utf-8').decode(fileData);
|
|
583
|
+
const endPos = xmlContent.indexOf('.main+xml"');
|
|
584
|
+
if (endPos === -1) {
|
|
585
|
+
const mimeType = 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml';
|
|
586
|
+
if (xmlContent.includes(`ContentType="${mimeType}"`)) {
|
|
587
|
+
fileType = getFileTypeFromMimeType(mimeType);
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
xmlContent = xmlContent.slice(0, Math.max(0, endPos));
|
|
591
|
+
const firstPos = xmlContent.lastIndexOf('"');
|
|
592
|
+
const mimeType = xmlContent.slice(Math.max(0, firstPos + 1));
|
|
593
|
+
fileType = getFileTypeFromMimeType(mimeType);
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
stop: true,
|
|
597
|
+
};
|
|
598
|
+
default:
|
|
599
|
+
if (/classes\d*\.dex/.test(zipHeader.filename)) {
|
|
600
|
+
fileType = {
|
|
601
|
+
ext: 'apk',
|
|
602
|
+
mime: 'application/vnd.android.package-archive',
|
|
603
|
+
};
|
|
604
|
+
return {stop: true};
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return {};
|
|
608
|
+
}
|
|
609
|
+
}).catch(error => {
|
|
610
|
+
if (!(error instanceof strtok3.EndOfStreamError)) {
|
|
611
|
+
throw error; // Re-throw non-EndOfStreamError
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
return fileType ?? {
|
|
616
|
+
ext: 'zip',
|
|
617
|
+
mime: 'application/zip',
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (this.checkString('OggS')) {
|
|
622
|
+
// This is an OGG container
|
|
623
|
+
await tokenizer.ignore(28);
|
|
624
|
+
const type = new Uint8Array(8);
|
|
625
|
+
await tokenizer.readBuffer(type);
|
|
626
|
+
|
|
627
|
+
// Needs to be before `ogg` check
|
|
628
|
+
if (_check(type, [0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64])) {
|
|
629
|
+
return {
|
|
630
|
+
ext: 'opus',
|
|
631
|
+
mime: 'audio/ogg; codecs=opus',
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// If ' theora' in header.
|
|
636
|
+
if (_check(type, [0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61])) {
|
|
637
|
+
return {
|
|
638
|
+
ext: 'ogv',
|
|
639
|
+
mime: 'video/ogg',
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// If '\x01video' in header.
|
|
644
|
+
if (_check(type, [0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00])) {
|
|
645
|
+
return {
|
|
646
|
+
ext: 'ogm',
|
|
647
|
+
mime: 'video/ogg',
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// If ' FLAC' in header https://xiph.org/flac/faq.html
|
|
652
|
+
if (_check(type, [0x7F, 0x46, 0x4C, 0x41, 0x43])) {
|
|
653
|
+
return {
|
|
654
|
+
ext: 'oga',
|
|
655
|
+
mime: 'audio/ogg',
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 'Speex ' in header https://en.wikipedia.org/wiki/Speex
|
|
660
|
+
if (_check(type, [0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20])) {
|
|
661
|
+
return {
|
|
662
|
+
ext: 'spx',
|
|
663
|
+
mime: 'audio/ogg',
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// If '\x01vorbis' in header
|
|
668
|
+
if (_check(type, [0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73])) {
|
|
669
|
+
return {
|
|
670
|
+
ext: 'ogg',
|
|
671
|
+
mime: 'audio/ogg',
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Default OGG container https://www.iana.org/assignments/media-types/application/ogg
|
|
676
|
+
return {
|
|
677
|
+
ext: 'ogx',
|
|
678
|
+
mime: 'application/ogg',
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (
|
|
683
|
+
this.check([0x50, 0x4B])
|
|
684
|
+
&& (this.buffer[2] === 0x3 || this.buffer[2] === 0x5 || this.buffer[2] === 0x7)
|
|
685
|
+
&& (this.buffer[3] === 0x4 || this.buffer[3] === 0x6 || this.buffer[3] === 0x8)
|
|
686
|
+
) {
|
|
687
|
+
return {
|
|
688
|
+
ext: 'zip',
|
|
689
|
+
mime: 'application/zip',
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (this.checkString('MThd')) {
|
|
694
|
+
return {
|
|
695
|
+
ext: 'mid',
|
|
696
|
+
mime: 'audio/midi',
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (
|
|
701
|
+
this.checkString('wOFF')
|
|
702
|
+
&& (
|
|
703
|
+
this.check([0x00, 0x01, 0x00, 0x00], {offset: 4})
|
|
704
|
+
|| this.checkString('OTTO', {offset: 4})
|
|
705
|
+
)
|
|
706
|
+
) {
|
|
707
|
+
return {
|
|
708
|
+
ext: 'woff',
|
|
709
|
+
mime: 'font/woff',
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (
|
|
714
|
+
this.checkString('wOF2')
|
|
715
|
+
&& (
|
|
716
|
+
this.check([0x00, 0x01, 0x00, 0x00], {offset: 4})
|
|
717
|
+
|| this.checkString('OTTO', {offset: 4})
|
|
718
|
+
)
|
|
719
|
+
) {
|
|
720
|
+
return {
|
|
721
|
+
ext: 'woff2',
|
|
722
|
+
mime: 'font/woff2',
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (this.check([0xD4, 0xC3, 0xB2, 0xA1]) || this.check([0xA1, 0xB2, 0xC3, 0xD4])) {
|
|
727
|
+
return {
|
|
728
|
+
ext: 'pcap',
|
|
729
|
+
mime: 'application/vnd.tcpdump.pcap',
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Sony DSD Stream File (DSF)
|
|
734
|
+
if (this.checkString('DSD ')) {
|
|
735
|
+
return {
|
|
736
|
+
ext: 'dsf',
|
|
737
|
+
mime: 'audio/x-dsf', // Non-standard
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (this.checkString('LZIP')) {
|
|
742
|
+
return {
|
|
743
|
+
ext: 'lz',
|
|
744
|
+
mime: 'application/x-lzip',
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (this.checkString('fLaC')) {
|
|
749
|
+
return {
|
|
750
|
+
ext: 'flac',
|
|
751
|
+
mime: 'audio/flac',
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (this.check([0x42, 0x50, 0x47, 0xFB])) {
|
|
756
|
+
return {
|
|
757
|
+
ext: 'bpg',
|
|
758
|
+
mime: 'image/bpg',
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (this.checkString('wvpk')) {
|
|
763
|
+
return {
|
|
764
|
+
ext: 'wv',
|
|
765
|
+
mime: 'audio/wavpack',
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (this.checkString('%PDF')) {
|
|
770
|
+
// Assume this is just a normal PDF
|
|
771
|
+
return {
|
|
772
|
+
ext: 'pdf',
|
|
773
|
+
mime: 'application/pdf',
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (this.check([0x00, 0x61, 0x73, 0x6D])) {
|
|
778
|
+
return {
|
|
779
|
+
ext: 'wasm',
|
|
780
|
+
mime: 'application/wasm',
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// TIFF, little-endian type
|
|
785
|
+
if (this.check([0x49, 0x49])) {
|
|
786
|
+
const fileType = await this.readTiffHeader(false);
|
|
787
|
+
if (fileType) {
|
|
788
|
+
return fileType;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// TIFF, big-endian type
|
|
793
|
+
if (this.check([0x4D, 0x4D])) {
|
|
794
|
+
const fileType = await this.readTiffHeader(true);
|
|
795
|
+
if (fileType) {
|
|
796
|
+
return fileType;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (this.checkString('MAC ')) {
|
|
801
|
+
return {
|
|
802
|
+
ext: 'ape',
|
|
803
|
+
mime: 'audio/ape',
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// https://github.com/file/file/blob/master/magic/Magdir/matroska
|
|
808
|
+
if (this.check([0x1A, 0x45, 0xDF, 0xA3])) { // Root element: EBML
|
|
809
|
+
async function readField() {
|
|
810
|
+
const msb = await tokenizer.peekNumber(Token.UINT8);
|
|
811
|
+
let mask = 0x80;
|
|
812
|
+
let ic = 0; // 0 = A, 1 = B, 2 = C, 3 = D
|
|
813
|
+
|
|
814
|
+
while ((msb & mask) === 0 && mask !== 0) {
|
|
815
|
+
++ic;
|
|
816
|
+
mask >>= 1;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const id = new Uint8Array(ic + 1);
|
|
820
|
+
await tokenizer.readBuffer(id);
|
|
821
|
+
return id;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function readElement() {
|
|
825
|
+
const idField = await readField();
|
|
826
|
+
const lengthField = await readField();
|
|
827
|
+
|
|
828
|
+
lengthField[0] ^= 0x80 >> (lengthField.length - 1);
|
|
829
|
+
const nrLength = Math.min(6, lengthField.length); // JavaScript can max read 6 bytes integer
|
|
830
|
+
|
|
831
|
+
const idView = new DataView(idField.buffer);
|
|
832
|
+
const lengthView = new DataView(lengthField.buffer, lengthField.length - nrLength, nrLength);
|
|
833
|
+
|
|
834
|
+
return {
|
|
835
|
+
id: getUintBE(idView),
|
|
836
|
+
len: getUintBE(lengthView),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function readChildren(children) {
|
|
841
|
+
while (children > 0) {
|
|
842
|
+
const element = await readElement();
|
|
843
|
+
if (element.id === 0x42_82) {
|
|
844
|
+
const rawValue = await tokenizer.readToken(new Token.StringType(element.len));
|
|
845
|
+
return rawValue.replaceAll(/\00.*$/g, ''); // Return DocType
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
await tokenizer.ignore(element.len); // ignore payload
|
|
849
|
+
--children;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const re = await readElement();
|
|
854
|
+
const documentType = await readChildren(re.len);
|
|
855
|
+
|
|
856
|
+
switch (documentType) {
|
|
857
|
+
case 'webm':
|
|
858
|
+
return {
|
|
859
|
+
ext: 'webm',
|
|
860
|
+
mime: 'video/webm',
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
case 'matroska':
|
|
864
|
+
return {
|
|
865
|
+
ext: 'mkv',
|
|
866
|
+
mime: 'video/matroska',
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
default:
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (this.checkString('SQLi')) {
|
|
875
|
+
return {
|
|
876
|
+
ext: 'sqlite',
|
|
877
|
+
mime: 'application/x-sqlite3',
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (this.check([0x4E, 0x45, 0x53, 0x1A])) {
|
|
882
|
+
return {
|
|
883
|
+
ext: 'nes',
|
|
884
|
+
mime: 'application/x-nintendo-nes-rom',
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (this.checkString('Cr24')) {
|
|
889
|
+
return {
|
|
890
|
+
ext: 'crx',
|
|
891
|
+
mime: 'application/x-google-chrome-extension',
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (
|
|
896
|
+
this.checkString('MSCF')
|
|
897
|
+
|| this.checkString('ISc(')
|
|
898
|
+
) {
|
|
899
|
+
return {
|
|
900
|
+
ext: 'cab',
|
|
901
|
+
mime: 'application/vnd.ms-cab-compressed',
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (this.check([0xED, 0xAB, 0xEE, 0xDB])) {
|
|
906
|
+
return {
|
|
907
|
+
ext: 'rpm',
|
|
908
|
+
mime: 'application/x-rpm',
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (this.check([0xC5, 0xD0, 0xD3, 0xC6])) {
|
|
913
|
+
return {
|
|
914
|
+
ext: 'eps',
|
|
915
|
+
mime: 'application/eps',
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if (this.check([0x28, 0xB5, 0x2F, 0xFD])) {
|
|
920
|
+
return {
|
|
921
|
+
ext: 'zst',
|
|
922
|
+
mime: 'application/zstd',
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (this.check([0x7F, 0x45, 0x4C, 0x46])) {
|
|
927
|
+
return {
|
|
928
|
+
ext: 'elf',
|
|
929
|
+
mime: 'application/x-elf',
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (this.check([0x21, 0x42, 0x44, 0x4E])) {
|
|
934
|
+
return {
|
|
935
|
+
ext: 'pst',
|
|
936
|
+
mime: 'application/vnd.ms-outlook',
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (this.checkString('PAR1') || this.checkString('PARE')) {
|
|
941
|
+
return {
|
|
942
|
+
ext: 'parquet',
|
|
943
|
+
mime: 'application/vnd.apache.parquet',
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (this.checkString('ttcf')) {
|
|
948
|
+
return {
|
|
949
|
+
ext: 'ttc',
|
|
950
|
+
mime: 'font/collection',
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (this.check([0xCF, 0xFA, 0xED, 0xFE])) {
|
|
955
|
+
return {
|
|
956
|
+
ext: 'macho',
|
|
957
|
+
mime: 'application/x-mach-binary',
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (this.check([0x04, 0x22, 0x4D, 0x18])) {
|
|
962
|
+
return {
|
|
963
|
+
ext: 'lz4',
|
|
964
|
+
mime: 'application/x-lz4', // Invented by us
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (this.checkString('regf')) {
|
|
969
|
+
return {
|
|
970
|
+
ext: 'dat',
|
|
971
|
+
mime: 'application/x-ft-windows-registry-hive',
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// -- 5-byte signatures --
|
|
976
|
+
|
|
977
|
+
if (this.check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
|
|
978
|
+
return {
|
|
979
|
+
ext: 'otf',
|
|
980
|
+
mime: 'font/otf',
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (this.checkString('#!AMR')) {
|
|
985
|
+
return {
|
|
986
|
+
ext: 'amr',
|
|
987
|
+
mime: 'audio/amr',
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (this.checkString('{\\rtf')) {
|
|
992
|
+
return {
|
|
993
|
+
ext: 'rtf',
|
|
994
|
+
mime: 'application/rtf',
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (this.check([0x46, 0x4C, 0x56, 0x01])) {
|
|
999
|
+
return {
|
|
1000
|
+
ext: 'flv',
|
|
1001
|
+
mime: 'video/x-flv',
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (this.checkString('IMPM')) {
|
|
1006
|
+
return {
|
|
1007
|
+
ext: 'it',
|
|
1008
|
+
mime: 'audio/x-it',
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (
|
|
1013
|
+
this.checkString('-lh0-', {offset: 2})
|
|
1014
|
+
|| this.checkString('-lh1-', {offset: 2})
|
|
1015
|
+
|| this.checkString('-lh2-', {offset: 2})
|
|
1016
|
+
|| this.checkString('-lh3-', {offset: 2})
|
|
1017
|
+
|| this.checkString('-lh4-', {offset: 2})
|
|
1018
|
+
|| this.checkString('-lh5-', {offset: 2})
|
|
1019
|
+
|| this.checkString('-lh6-', {offset: 2})
|
|
1020
|
+
|| this.checkString('-lh7-', {offset: 2})
|
|
1021
|
+
|| this.checkString('-lzs-', {offset: 2})
|
|
1022
|
+
|| this.checkString('-lz4-', {offset: 2})
|
|
1023
|
+
|| this.checkString('-lz5-', {offset: 2})
|
|
1024
|
+
|| this.checkString('-lhd-', {offset: 2})
|
|
1025
|
+
) {
|
|
1026
|
+
return {
|
|
1027
|
+
ext: 'lzh',
|
|
1028
|
+
mime: 'application/x-lzh-compressed',
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// MPEG program stream (PS or MPEG-PS)
|
|
1033
|
+
if (this.check([0x00, 0x00, 0x01, 0xBA])) {
|
|
1034
|
+
// MPEG-PS, MPEG-1 Part 1
|
|
1035
|
+
if (this.check([0x21], {offset: 4, mask: [0xF1]})) {
|
|
1036
|
+
return {
|
|
1037
|
+
ext: 'mpg', // May also be .ps, .mpeg
|
|
1038
|
+
mime: 'video/MP1S',
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// MPEG-PS, MPEG-2 Part 1
|
|
1043
|
+
if (this.check([0x44], {offset: 4, mask: [0xC4]})) {
|
|
1044
|
+
return {
|
|
1045
|
+
ext: 'mpg', // May also be .mpg, .m2p, .vob or .sub
|
|
1046
|
+
mime: 'video/MP2P',
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (this.checkString('ITSF')) {
|
|
1052
|
+
return {
|
|
1053
|
+
ext: 'chm',
|
|
1054
|
+
mime: 'application/vnd.ms-htmlhelp',
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (this.check([0xCA, 0xFE, 0xBA, 0xBE])) {
|
|
1059
|
+
return {
|
|
1060
|
+
ext: 'class',
|
|
1061
|
+
mime: 'application/java-vm',
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
if (this.checkString('.RMF')) {
|
|
1066
|
+
return {
|
|
1067
|
+
ext: 'rm',
|
|
1068
|
+
mime: 'application/vnd.rn-realmedia',
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// -- 5-byte signatures --
|
|
1073
|
+
|
|
1074
|
+
if (this.checkString('DRACO')) {
|
|
1075
|
+
return {
|
|
1076
|
+
ext: 'drc',
|
|
1077
|
+
mime: 'application/vnd.google.draco', // Invented by us
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// -- 6-byte signatures --
|
|
1082
|
+
|
|
1083
|
+
if (this.check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
|
|
1084
|
+
return {
|
|
1085
|
+
ext: 'xz',
|
|
1086
|
+
mime: 'application/x-xz',
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (this.checkString('<?xml ')) {
|
|
1091
|
+
return {
|
|
1092
|
+
ext: 'xml',
|
|
1093
|
+
mime: 'application/xml',
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (this.check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
|
|
1098
|
+
return {
|
|
1099
|
+
ext: '7z',
|
|
1100
|
+
mime: 'application/x-7z-compressed',
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (
|
|
1105
|
+
this.check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7])
|
|
1106
|
+
&& (this.buffer[6] === 0x0 || this.buffer[6] === 0x1)
|
|
1107
|
+
) {
|
|
1108
|
+
return {
|
|
1109
|
+
ext: 'rar',
|
|
1110
|
+
mime: 'application/x-rar-compressed',
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (this.checkString('solid ')) {
|
|
1115
|
+
return {
|
|
1116
|
+
ext: 'stl',
|
|
1117
|
+
mime: 'model/stl',
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (this.checkString('AC')) {
|
|
1122
|
+
const version = new Token.StringType(4, 'latin1').get(this.buffer, 2);
|
|
1123
|
+
if (version.match('^d*') && version >= 1000 && version <= 1050) {
|
|
1124
|
+
return {
|
|
1125
|
+
ext: 'dwg',
|
|
1126
|
+
mime: 'image/vnd.dwg',
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
if (this.checkString('070707')) {
|
|
1132
|
+
return {
|
|
1133
|
+
ext: 'cpio',
|
|
1134
|
+
mime: 'application/x-cpio',
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// -- 7-byte signatures --
|
|
1139
|
+
|
|
1140
|
+
if (this.checkString('BLENDER')) {
|
|
1141
|
+
return {
|
|
1142
|
+
ext: 'blend',
|
|
1143
|
+
mime: 'application/x-blender',
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (this.checkString('!<arch>')) {
|
|
1148
|
+
await tokenizer.ignore(8);
|
|
1149
|
+
const string = await tokenizer.readToken(new Token.StringType(13, 'ascii'));
|
|
1150
|
+
if (string === 'debian-binary') {
|
|
1151
|
+
return {
|
|
1152
|
+
ext: 'deb',
|
|
1153
|
+
mime: 'application/x-deb',
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return {
|
|
1158
|
+
ext: 'ar',
|
|
1159
|
+
mime: 'application/x-unix-archive',
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (
|
|
1164
|
+
this.checkString('WEBVTT')
|
|
1165
|
+
&& (
|
|
1166
|
+
// One of LF, CR, tab, space, or end of file must follow "WEBVTT" per the spec (see `fixture/fixture-vtt-*.vtt` for examples). Note that `\0` is technically the null character (there is no such thing as an EOF character). However, checking for `\0` gives us the same result as checking for the end of the stream.
|
|
1167
|
+
(['\n', '\r', '\t', ' ', '\0'].some(char7 => this.checkString(char7, {offset: 6}))))
|
|
1168
|
+
) {
|
|
1169
|
+
return {
|
|
1170
|
+
ext: 'vtt',
|
|
1171
|
+
mime: 'text/vtt',
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// -- 8-byte signatures --
|
|
1176
|
+
|
|
1177
|
+
if (this.check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
|
|
1178
|
+
// APNG format (https://wiki.mozilla.org/APNG_Specification)
|
|
1179
|
+
// 1. Find the first IDAT (image data) chunk (49 44 41 54)
|
|
1180
|
+
// 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C)
|
|
1181
|
+
|
|
1182
|
+
// Offset calculated as follows:
|
|
1183
|
+
// - 8 bytes: PNG signature
|
|
1184
|
+
// - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk
|
|
1185
|
+
|
|
1186
|
+
await tokenizer.ignore(8); // ignore PNG signature
|
|
1187
|
+
|
|
1188
|
+
async function readChunkHeader() {
|
|
1189
|
+
return {
|
|
1190
|
+
length: await tokenizer.readToken(Token.INT32_BE),
|
|
1191
|
+
type: await tokenizer.readToken(new Token.StringType(4, 'latin1')),
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
do {
|
|
1196
|
+
const chunk = await readChunkHeader();
|
|
1197
|
+
if (chunk.length < 0) {
|
|
1198
|
+
return; // Invalid chunk length
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
switch (chunk.type) {
|
|
1202
|
+
case 'IDAT':
|
|
1203
|
+
return {
|
|
1204
|
+
ext: 'png',
|
|
1205
|
+
mime: 'image/png',
|
|
1206
|
+
};
|
|
1207
|
+
case 'acTL':
|
|
1208
|
+
return {
|
|
1209
|
+
ext: 'apng',
|
|
1210
|
+
mime: 'image/apng',
|
|
1211
|
+
};
|
|
1212
|
+
default:
|
|
1213
|
+
await tokenizer.ignore(chunk.length + 4); // Ignore chunk-data + CRC
|
|
1214
|
+
}
|
|
1215
|
+
} while (tokenizer.position + 8 < tokenizer.fileInfo.size);
|
|
1216
|
+
|
|
1217
|
+
return {
|
|
1218
|
+
ext: 'png',
|
|
1219
|
+
mime: 'image/png',
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (this.check([0x41, 0x52, 0x52, 0x4F, 0x57, 0x31, 0x00, 0x00])) {
|
|
1224
|
+
return {
|
|
1225
|
+
ext: 'arrow',
|
|
1226
|
+
mime: 'application/vnd.apache.arrow.file',
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (this.check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) {
|
|
1231
|
+
return {
|
|
1232
|
+
ext: 'glb',
|
|
1233
|
+
mime: 'model/gltf-binary',
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// `mov` format variants
|
|
1238
|
+
if (
|
|
1239
|
+
this.check([0x66, 0x72, 0x65, 0x65], {offset: 4}) // `free`
|
|
1240
|
+
|| this.check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) // `mdat` MJPEG
|
|
1241
|
+
|| this.check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) // `moov`
|
|
1242
|
+
|| this.check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
|
|
1243
|
+
) {
|
|
1244
|
+
return {
|
|
1245
|
+
ext: 'mov',
|
|
1246
|
+
mime: 'video/quicktime',
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// -- 9-byte signatures --
|
|
1251
|
+
|
|
1252
|
+
if (this.check([0x49, 0x49, 0x52, 0x4F, 0x08, 0x00, 0x00, 0x00, 0x18])) {
|
|
1253
|
+
return {
|
|
1254
|
+
ext: 'orf',
|
|
1255
|
+
mime: 'image/x-olympus-orf',
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (this.checkString('gimp xcf ')) {
|
|
1260
|
+
return {
|
|
1261
|
+
ext: 'xcf',
|
|
1262
|
+
mime: 'image/x-xcf',
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
|
|
1267
|
+
// It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box.
|
|
1268
|
+
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
|
|
1269
|
+
// Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
|
|
1270
|
+
if (
|
|
1271
|
+
this.checkString('ftyp', {offset: 4})
|
|
1272
|
+
&& (this.buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII?
|
|
1273
|
+
) {
|
|
1274
|
+
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
|
|
1275
|
+
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
|
|
1276
|
+
const brandMajor = new Token.StringType(4, 'latin1').get(this.buffer, 8).replace('\0', ' ').trim();
|
|
1277
|
+
switch (brandMajor) {
|
|
1278
|
+
case 'avif':
|
|
1279
|
+
case 'avis':
|
|
1280
|
+
return {ext: 'avif', mime: 'image/avif'};
|
|
1281
|
+
case 'mif1':
|
|
1282
|
+
return {ext: 'heic', mime: 'image/heif'};
|
|
1283
|
+
case 'msf1':
|
|
1284
|
+
return {ext: 'heic', mime: 'image/heif-sequence'};
|
|
1285
|
+
case 'heic':
|
|
1286
|
+
case 'heix':
|
|
1287
|
+
return {ext: 'heic', mime: 'image/heic'};
|
|
1288
|
+
case 'hevc':
|
|
1289
|
+
case 'hevx':
|
|
1290
|
+
return {ext: 'heic', mime: 'image/heic-sequence'};
|
|
1291
|
+
case 'qt':
|
|
1292
|
+
return {ext: 'mov', mime: 'video/quicktime'};
|
|
1293
|
+
case 'M4V':
|
|
1294
|
+
case 'M4VH':
|
|
1295
|
+
case 'M4VP':
|
|
1296
|
+
return {ext: 'm4v', mime: 'video/x-m4v'};
|
|
1297
|
+
case 'M4P':
|
|
1298
|
+
return {ext: 'm4p', mime: 'video/mp4'};
|
|
1299
|
+
case 'M4B':
|
|
1300
|
+
return {ext: 'm4b', mime: 'audio/mp4'};
|
|
1301
|
+
case 'M4A':
|
|
1302
|
+
return {ext: 'm4a', mime: 'audio/x-m4a'};
|
|
1303
|
+
case 'F4V':
|
|
1304
|
+
return {ext: 'f4v', mime: 'video/mp4'};
|
|
1305
|
+
case 'F4P':
|
|
1306
|
+
return {ext: 'f4p', mime: 'video/mp4'};
|
|
1307
|
+
case 'F4A':
|
|
1308
|
+
return {ext: 'f4a', mime: 'audio/mp4'};
|
|
1309
|
+
case 'F4B':
|
|
1310
|
+
return {ext: 'f4b', mime: 'audio/mp4'};
|
|
1311
|
+
case 'crx':
|
|
1312
|
+
return {ext: 'cr3', mime: 'image/x-canon-cr3'};
|
|
1313
|
+
default:
|
|
1314
|
+
if (brandMajor.startsWith('3g')) {
|
|
1315
|
+
if (brandMajor.startsWith('3g2')) {
|
|
1316
|
+
return {ext: '3g2', mime: 'video/3gpp2'};
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
return {ext: '3gp', mime: 'video/3gpp'};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
return {ext: 'mp4', mime: 'video/mp4'};
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// -- 10-byte signatures --
|
|
1327
|
+
|
|
1328
|
+
if (this.checkString('REGEDIT4\r\n')) {
|
|
1329
|
+
return {
|
|
1330
|
+
ext: 'reg',
|
|
1331
|
+
mime: 'application/x-ms-regedit',
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// -- 12-byte signatures --
|
|
1336
|
+
|
|
1337
|
+
// RIFF file format which might be AVI, WAV, QCP, etc
|
|
1338
|
+
if (this.check([0x52, 0x49, 0x46, 0x46])) {
|
|
1339
|
+
if (this.checkString('WEBP', {offset: 8})) {
|
|
1340
|
+
return {
|
|
1341
|
+
ext: 'webp',
|
|
1342
|
+
mime: 'image/webp',
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
if (this.check([0x41, 0x56, 0x49], {offset: 8})) {
|
|
1347
|
+
return {
|
|
1348
|
+
ext: 'avi',
|
|
1349
|
+
mime: 'video/vnd.avi',
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (this.check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
|
|
1354
|
+
return {
|
|
1355
|
+
ext: 'wav',
|
|
1356
|
+
mime: 'audio/wav',
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// QLCM, QCP file
|
|
1361
|
+
if (this.check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
|
|
1362
|
+
return {
|
|
1363
|
+
ext: 'qcp',
|
|
1364
|
+
mime: 'audio/qcelp',
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (this.check([0x49, 0x49, 0x55, 0x00, 0x18, 0x00, 0x00, 0x00, 0x88, 0xE7, 0x74, 0xD8])) {
|
|
1370
|
+
return {
|
|
1371
|
+
ext: 'rw2',
|
|
1372
|
+
mime: 'image/x-panasonic-rw2',
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// ASF_Header_Object first 80 bytes
|
|
1377
|
+
if (this.check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
|
|
1378
|
+
async function readHeader() {
|
|
1379
|
+
const guid = new Uint8Array(16);
|
|
1380
|
+
await tokenizer.readBuffer(guid);
|
|
1381
|
+
return {
|
|
1382
|
+
id: guid,
|
|
1383
|
+
size: Number(await tokenizer.readToken(Token.UINT64_LE)),
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
await tokenizer.ignore(30);
|
|
1388
|
+
// Search for header should be in first 1KB of file.
|
|
1389
|
+
while (tokenizer.position + 24 < tokenizer.fileInfo.size) {
|
|
1390
|
+
const header = await readHeader();
|
|
1391
|
+
let payload = header.size - 24;
|
|
1392
|
+
if (_check(header.id, [0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65])) {
|
|
1393
|
+
// Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
|
|
1394
|
+
const typeId = new Uint8Array(16);
|
|
1395
|
+
payload -= await tokenizer.readBuffer(typeId);
|
|
1396
|
+
|
|
1397
|
+
if (_check(typeId, [0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
|
|
1398
|
+
// Found audio:
|
|
1399
|
+
return {
|
|
1400
|
+
ext: 'asf',
|
|
1401
|
+
mime: 'audio/x-ms-asf',
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (_check(typeId, [0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
|
|
1406
|
+
// Found video:
|
|
1407
|
+
return {
|
|
1408
|
+
ext: 'asf',
|
|
1409
|
+
mime: 'video/x-ms-asf',
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
break;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
await tokenizer.ignore(payload);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Default to ASF generic extension
|
|
1420
|
+
return {
|
|
1421
|
+
ext: 'asf',
|
|
1422
|
+
mime: 'application/vnd.ms-asf',
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (this.check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
|
|
1427
|
+
return {
|
|
1428
|
+
ext: 'ktx',
|
|
1429
|
+
mime: 'image/ktx',
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
if ((this.check([0x7E, 0x10, 0x04]) || this.check([0x7E, 0x18, 0x04])) && this.check([0x30, 0x4D, 0x49, 0x45], {offset: 4})) {
|
|
1434
|
+
return {
|
|
1435
|
+
ext: 'mie',
|
|
1436
|
+
mime: 'application/x-mie',
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (this.check([0x27, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], {offset: 2})) {
|
|
1441
|
+
return {
|
|
1442
|
+
ext: 'shp',
|
|
1443
|
+
mime: 'application/x-esri-shape',
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (this.check([0xFF, 0x4F, 0xFF, 0x51])) {
|
|
1448
|
+
return {
|
|
1449
|
+
ext: 'j2c',
|
|
1450
|
+
mime: 'image/j2c',
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (this.check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) {
|
|
1455
|
+
// JPEG-2000 family
|
|
1456
|
+
|
|
1457
|
+
await tokenizer.ignore(20);
|
|
1458
|
+
const type = await tokenizer.readToken(new Token.StringType(4, 'ascii'));
|
|
1459
|
+
switch (type) {
|
|
1460
|
+
case 'jp2 ':
|
|
1461
|
+
return {
|
|
1462
|
+
ext: 'jp2',
|
|
1463
|
+
mime: 'image/jp2',
|
|
1464
|
+
};
|
|
1465
|
+
case 'jpx ':
|
|
1466
|
+
return {
|
|
1467
|
+
ext: 'jpx',
|
|
1468
|
+
mime: 'image/jpx',
|
|
1469
|
+
};
|
|
1470
|
+
case 'jpm ':
|
|
1471
|
+
return {
|
|
1472
|
+
ext: 'jpm',
|
|
1473
|
+
mime: 'image/jpm',
|
|
1474
|
+
};
|
|
1475
|
+
case 'mjp2':
|
|
1476
|
+
return {
|
|
1477
|
+
ext: 'mj2',
|
|
1478
|
+
mime: 'image/mj2',
|
|
1479
|
+
};
|
|
1480
|
+
default:
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (
|
|
1486
|
+
this.check([0xFF, 0x0A])
|
|
1487
|
+
|| this.check([0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A])
|
|
1488
|
+
) {
|
|
1489
|
+
return {
|
|
1490
|
+
ext: 'jxl',
|
|
1491
|
+
mime: 'image/jxl',
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (this.check([0xFE, 0xFF])) { // UTF-16-BOM-BE
|
|
1496
|
+
if (this.checkString('<?xml ', {offset: 2, encoding: 'utf-16be'})) {
|
|
1497
|
+
return {
|
|
1498
|
+
ext: 'xml',
|
|
1499
|
+
mime: 'application/xml',
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
return undefined; // Some unknown text based format
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (this.check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
|
|
1507
|
+
// Detected Microsoft Compound File Binary File (MS-CFB) Format.
|
|
1508
|
+
return {
|
|
1509
|
+
ext: 'cfb',
|
|
1510
|
+
mime: 'application/x-cfb',
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// Increase sample size from 32 to 256.
|
|
1515
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(256, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1516
|
+
|
|
1517
|
+
if (this.check([0x61, 0x63, 0x73, 0x70], {offset: 36})) {
|
|
1518
|
+
return {
|
|
1519
|
+
ext: 'icc',
|
|
1520
|
+
mime: 'application/vnd.iccprofile',
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// ACE: requires 14 bytes in the buffer
|
|
1525
|
+
if (this.checkString('**ACE', {offset: 7}) && this.checkString('**', {offset: 12})) {
|
|
1526
|
+
return {
|
|
1527
|
+
ext: 'ace',
|
|
1528
|
+
mime: 'application/x-ace-compressed',
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// -- 15-byte signatures --
|
|
1533
|
+
|
|
1534
|
+
if (this.checkString('BEGIN:')) {
|
|
1535
|
+
if (this.checkString('VCARD', {offset: 6})) {
|
|
1536
|
+
return {
|
|
1537
|
+
ext: 'vcf',
|
|
1538
|
+
mime: 'text/vcard',
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
if (this.checkString('VCALENDAR', {offset: 6})) {
|
|
1543
|
+
return {
|
|
1544
|
+
ext: 'ics',
|
|
1545
|
+
mime: 'text/calendar',
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// `raf` is here just to keep all the raw image detectors together.
|
|
1551
|
+
if (this.checkString('FUJIFILMCCD-RAW')) {
|
|
1552
|
+
return {
|
|
1553
|
+
ext: 'raf',
|
|
1554
|
+
mime: 'image/x-fujifilm-raf',
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (this.checkString('Extended Module:')) {
|
|
1559
|
+
return {
|
|
1560
|
+
ext: 'xm',
|
|
1561
|
+
mime: 'audio/x-xm',
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (this.checkString('Creative Voice File')) {
|
|
1566
|
+
return {
|
|
1567
|
+
ext: 'voc',
|
|
1568
|
+
mime: 'audio/x-voc',
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (this.check([0x04, 0x00, 0x00, 0x00]) && this.buffer.length >= 16) { // Rough & quick check Pickle/ASAR
|
|
1573
|
+
const jsonSize = new DataView(this.buffer.buffer).getUint32(12, true);
|
|
1574
|
+
|
|
1575
|
+
if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) {
|
|
1576
|
+
try {
|
|
1577
|
+
const header = new TextDecoder().decode(this.buffer.subarray(16, jsonSize + 16));
|
|
1578
|
+
const json = JSON.parse(header);
|
|
1579
|
+
// Check if Pickle is ASAR
|
|
1580
|
+
if (json.files) { // Final check, assuring Pickle/ASAR format
|
|
1581
|
+
return {
|
|
1582
|
+
ext: 'asar',
|
|
1583
|
+
mime: 'application/x-asar',
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
} catch {}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (this.check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
|
|
1591
|
+
return {
|
|
1592
|
+
ext: 'mxf',
|
|
1593
|
+
mime: 'application/mxf',
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (this.checkString('SCRM', {offset: 44})) {
|
|
1598
|
+
return {
|
|
1599
|
+
ext: 's3m',
|
|
1600
|
+
mime: 'audio/x-s3m',
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// Raw MPEG-2 transport stream (188-byte packets)
|
|
1605
|
+
if (this.check([0x47]) && this.check([0x47], {offset: 188})) {
|
|
1606
|
+
return {
|
|
1607
|
+
ext: 'mts',
|
|
1608
|
+
mime: 'video/mp2t',
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Blu-ray Disc Audio-Video (BDAV) MPEG-2 transport stream has 4-byte TP_extra_header before each 188-byte packet
|
|
1613
|
+
if (this.check([0x47], {offset: 4}) && this.check([0x47], {offset: 196})) {
|
|
1614
|
+
return {
|
|
1615
|
+
ext: 'mts',
|
|
1616
|
+
mime: 'video/mp2t',
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
if (this.check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) {
|
|
1621
|
+
return {
|
|
1622
|
+
ext: 'mobi',
|
|
1623
|
+
mime: 'application/x-mobipocket-ebook',
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
if (this.check([0x44, 0x49, 0x43, 0x4D], {offset: 128})) {
|
|
1628
|
+
return {
|
|
1629
|
+
ext: 'dcm',
|
|
1630
|
+
mime: 'application/dicom',
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
if (this.check([0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46])) {
|
|
1635
|
+
return {
|
|
1636
|
+
ext: 'lnk',
|
|
1637
|
+
mime: 'application/x.ms.shortcut', // Invented by us
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
if (this.check([0x62, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00])) {
|
|
1642
|
+
return {
|
|
1643
|
+
ext: 'alias',
|
|
1644
|
+
mime: 'application/x.apple.alias', // Invented by us
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
if (this.checkString('Kaydara FBX Binary \u0000')) {
|
|
1649
|
+
return {
|
|
1650
|
+
ext: 'fbx',
|
|
1651
|
+
mime: 'application/x.autodesk.fbx', // Invented by us
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
if (
|
|
1656
|
+
this.check([0x4C, 0x50], {offset: 34})
|
|
1657
|
+
&& (
|
|
1658
|
+
this.check([0x00, 0x00, 0x01], {offset: 8})
|
|
1659
|
+
|| this.check([0x01, 0x00, 0x02], {offset: 8})
|
|
1660
|
+
|| this.check([0x02, 0x00, 0x02], {offset: 8})
|
|
1661
|
+
)
|
|
1662
|
+
) {
|
|
1663
|
+
return {
|
|
1664
|
+
ext: 'eot',
|
|
1665
|
+
mime: 'application/vnd.ms-fontobject',
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
if (this.check([0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D])) {
|
|
1670
|
+
return {
|
|
1671
|
+
ext: 'indd',
|
|
1672
|
+
mime: 'application/x-indesign',
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
// Increase sample size from 256 to 512
|
|
1677
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(512, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1678
|
+
|
|
1679
|
+
// Requires a buffer size of 512 bytes
|
|
1680
|
+
if ((this.checkString('ustar', {offset: 257}) && (this.checkString('\0', {offset: 262}) || this.checkString(' ', {offset: 262})))
|
|
1681
|
+
|| (this.check([0, 0, 0, 0, 0, 0], {offset: 257}) && tarHeaderChecksumMatches(this.buffer))) {
|
|
1682
|
+
return {
|
|
1683
|
+
ext: 'tar',
|
|
1684
|
+
mime: 'application/x-tar',
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
if (this.check([0xFF, 0xFE])) { // UTF-16-BOM-LE
|
|
1689
|
+
const encoding = 'utf-16le';
|
|
1690
|
+
if (this.checkString('<?xml ', {offset: 2, encoding})) {
|
|
1691
|
+
return {
|
|
1692
|
+
ext: 'xml',
|
|
1693
|
+
mime: 'application/xml',
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (this.check([0xFF, 0x0E], {offset: 2}) && this.checkString('SketchUp Model', {offset: 4, encoding})) {
|
|
1698
|
+
return {
|
|
1699
|
+
ext: 'skp',
|
|
1700
|
+
mime: 'application/vnd.sketchup.skp',
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (this.checkString('Windows Registry Editor Version 5.00\r\n', {offset: 2, encoding})) {
|
|
1705
|
+
return {
|
|
1706
|
+
ext: 'reg',
|
|
1707
|
+
mime: 'application/x-ms-regedit',
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
return undefined; // Some text based format
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
if (this.checkString('-----BEGIN PGP MESSAGE-----')) {
|
|
1715
|
+
return {
|
|
1716
|
+
ext: 'pgp',
|
|
1717
|
+
mime: 'application/pgp-encrypted',
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
};
|
|
1721
|
+
// Detections with limited supporting data, resulting in a higher likelihood of false positives
|
|
1722
|
+
detectImprecise = async tokenizer => {
|
|
1723
|
+
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes);
|
|
1724
|
+
|
|
1725
|
+
// Read initial sample size of 8 bytes
|
|
1726
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(8, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1727
|
+
|
|
1728
|
+
if (
|
|
1729
|
+
this.check([0x0, 0x0, 0x1, 0xBA])
|
|
1730
|
+
|| this.check([0x0, 0x0, 0x1, 0xB3])
|
|
1731
|
+
) {
|
|
1732
|
+
return {
|
|
1733
|
+
ext: 'mpg',
|
|
1734
|
+
mime: 'video/mpeg',
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
if (this.check([0x00, 0x01, 0x00, 0x00, 0x00])) {
|
|
1739
|
+
return {
|
|
1740
|
+
ext: 'ttf',
|
|
1741
|
+
mime: 'font/ttf',
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (this.check([0x00, 0x00, 0x01, 0x00])) {
|
|
1746
|
+
return {
|
|
1747
|
+
ext: 'ico',
|
|
1748
|
+
mime: 'image/x-icon',
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
if (this.check([0x00, 0x00, 0x02, 0x00])) {
|
|
1753
|
+
return {
|
|
1754
|
+
ext: 'cur',
|
|
1755
|
+
mime: 'image/x-icon',
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
// Adjust buffer to `mpegOffsetTolerance`
|
|
1760
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(2 + this.options.mpegOffsetTolerance, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1761
|
+
|
|
1762
|
+
// Check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE)
|
|
1763
|
+
if (this.buffer.length >= (2 + this.options.mpegOffsetTolerance)) {
|
|
1764
|
+
for (let depth = 0; depth <= this.options.mpegOffsetTolerance; ++depth) {
|
|
1765
|
+
const type = this.scanMpeg(depth);
|
|
1766
|
+
if (type) {
|
|
1767
|
+
return type;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
async readTiffTag(bigEndian) {
|
|
1774
|
+
const tagId = await this.tokenizer.readToken(bigEndian ? Token.UINT16_BE : Token.UINT16_LE);
|
|
1775
|
+
this.tokenizer.ignore(10);
|
|
1776
|
+
switch (tagId) {
|
|
1777
|
+
case 50_341:
|
|
1778
|
+
return {
|
|
1779
|
+
ext: 'arw',
|
|
1780
|
+
mime: 'image/x-sony-arw',
|
|
1781
|
+
};
|
|
1782
|
+
case 50_706:
|
|
1783
|
+
return {
|
|
1784
|
+
ext: 'dng',
|
|
1785
|
+
mime: 'image/x-adobe-dng',
|
|
1786
|
+
};
|
|
1787
|
+
default:
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
async readTiffIFD(bigEndian) {
|
|
1792
|
+
const numberOfTags = await this.tokenizer.readToken(bigEndian ? Token.UINT16_BE : Token.UINT16_LE);
|
|
1793
|
+
for (let n = 0; n < numberOfTags; ++n) {
|
|
1794
|
+
const fileType = await this.readTiffTag(bigEndian);
|
|
1795
|
+
if (fileType) {
|
|
1796
|
+
return fileType;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
async readTiffHeader(bigEndian) {
|
|
1802
|
+
const version = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 2);
|
|
1803
|
+
const ifdOffset = (bigEndian ? Token.UINT32_BE : Token.UINT32_LE).get(this.buffer, 4);
|
|
1804
|
+
|
|
1805
|
+
if (version === 42) {
|
|
1806
|
+
// TIFF file header
|
|
1807
|
+
if (ifdOffset >= 6) {
|
|
1808
|
+
if (this.checkString('CR', {offset: 8})) {
|
|
1809
|
+
return {
|
|
1810
|
+
ext: 'cr2',
|
|
1811
|
+
mime: 'image/x-canon-cr2',
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
if (ifdOffset >= 8) {
|
|
1816
|
+
const someId1 = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 8);
|
|
1817
|
+
const someId2 = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 10);
|
|
1818
|
+
|
|
1819
|
+
if (
|
|
1820
|
+
(someId1 === 0x1C && someId2 === 0xFE)
|
|
1821
|
+
|| (someId1 === 0x1F && someId2 === 0x0B)) {
|
|
1822
|
+
return {
|
|
1823
|
+
ext: 'nef',
|
|
1824
|
+
mime: 'image/x-nikon-nef',
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
await this.tokenizer.ignore(ifdOffset);
|
|
1831
|
+
const fileType = await this.readTiffIFD(bigEndian);
|
|
1832
|
+
return fileType ?? {
|
|
1833
|
+
ext: 'tif',
|
|
1834
|
+
mime: 'image/tiff',
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
if (version === 43) { // Big TIFF file header
|
|
1839
|
+
return {
|
|
1840
|
+
ext: 'tif',
|
|
1841
|
+
mime: 'image/tiff',
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
Scan check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE).
|
|
1848
|
+
|
|
1849
|
+
@param offset - Offset to scan for sync-preamble.
|
|
1850
|
+
@returns {{ext: string, mime: string}}
|
|
1851
|
+
*/
|
|
1852
|
+
scanMpeg(offset) {
|
|
1853
|
+
if (this.check([0xFF, 0xE0], {offset, mask: [0xFF, 0xE0]})) {
|
|
1854
|
+
if (this.check([0x10], {offset: offset + 1, mask: [0x16]})) {
|
|
1855
|
+
// Check for (ADTS) MPEG-2
|
|
1856
|
+
if (this.check([0x08], {offset: offset + 1, mask: [0x08]})) {
|
|
1857
|
+
return {
|
|
1858
|
+
ext: 'aac',
|
|
1859
|
+
mime: 'audio/aac',
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// Must be (ADTS) MPEG-4
|
|
1864
|
+
return {
|
|
1865
|
+
ext: 'aac',
|
|
1866
|
+
mime: 'audio/aac',
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// MPEG 1 or 2 Layer 3 header
|
|
1871
|
+
// Check for MPEG layer 3
|
|
1872
|
+
if (this.check([0x02], {offset: offset + 1, mask: [0x06]})) {
|
|
1873
|
+
return {
|
|
1874
|
+
ext: 'mp3',
|
|
1875
|
+
mime: 'audio/mpeg',
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// Check for MPEG layer 2
|
|
1880
|
+
if (this.check([0x04], {offset: offset + 1, mask: [0x06]})) {
|
|
1881
|
+
return {
|
|
1882
|
+
ext: 'mp2',
|
|
1883
|
+
mime: 'audio/mpeg',
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// Check for MPEG layer 1
|
|
1888
|
+
if (this.check([0x06], {offset: offset + 1, mask: [0x06]})) {
|
|
1889
|
+
return {
|
|
1890
|
+
ext: 'mp1',
|
|
1891
|
+
mime: 'audio/mpeg',
|
|
1892
|
+
};
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
export const supportedExtensions = new Set(extensions);
|
|
1899
|
+
export const supportedMimeTypes = new Set(mimeTypes);
|