file-type 16.5.3 → 17.1.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/browser.d.ts +22 -28
- package/browser.js +21 -19
- package/core.d.ts +364 -356
- package/core.js +1236 -1169
- package/index.d.ts +4 -17
- package/index.js +5 -24
- package/package.json +29 -16
- package/readme.md +114 -97
- package/supported.js +277 -278
- package/util.js +10 -12
package/core.js
CHANGED
|
@@ -1,43 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {Buffer} from 'node:buffer';
|
|
2
|
+
import * as Token from 'token-types';
|
|
3
|
+
import * as strtok3 from 'strtok3/core';
|
|
4
|
+
import {
|
|
5
5
|
stringToBytes,
|
|
6
6
|
tarHeaderChecksumMatches,
|
|
7
|
-
uint32SyncSafeToken
|
|
8
|
-
}
|
|
9
|
-
|
|
7
|
+
uint32SyncSafeToken,
|
|
8
|
+
} from './util.js';
|
|
9
|
+
import {extensions, mimeTypes} from './supported.js';
|
|
10
10
|
|
|
11
|
-
const minimumBytes = 4100; // A fair amount of file-types are detectable within this range
|
|
11
|
+
const minimumBytes = 4100; // A fair amount of file-types are detectable within this range.
|
|
12
12
|
|
|
13
|
-
async function
|
|
13
|
+
export async function fileTypeFromStream(stream) {
|
|
14
14
|
const tokenizer = await strtok3.fromStream(stream);
|
|
15
15
|
try {
|
|
16
|
-
return await
|
|
16
|
+
return await fileTypeFromTokenizer(tokenizer);
|
|
17
17
|
} finally {
|
|
18
18
|
await tokenizer.close();
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async function
|
|
23
|
-
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer
|
|
22
|
+
export async function fileTypeFromBuffer(input) {
|
|
23
|
+
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
|
|
24
24
|
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
const buffer = input instanceof
|
|
27
|
+
const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
28
28
|
|
|
29
29
|
if (!(buffer && buffer.length > 1)) {
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
return fromTokenizer(tokenizer);
|
|
33
|
+
return fileTypeFromTokenizer(strtok3.fromBuffer(buffer));
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
function _check(buffer, headers, options) {
|
|
38
37
|
options = {
|
|
39
38
|
offset: 0,
|
|
40
|
-
...options
|
|
39
|
+
...options,
|
|
41
40
|
};
|
|
42
41
|
|
|
43
42
|
for (const [index, header] of headers.entries()) {
|
|
@@ -55,9 +54,9 @@ function _check(buffer, headers, options) {
|
|
|
55
54
|
return true;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
async function
|
|
57
|
+
export async function fileTypeFromTokenizer(tokenizer) {
|
|
59
58
|
try {
|
|
60
|
-
return
|
|
59
|
+
return new FileTypeParser().parse(tokenizer);
|
|
61
60
|
} catch (error) {
|
|
62
61
|
if (!(error instanceof strtok3.EndOfStreamError)) {
|
|
63
62
|
throw error;
|
|
@@ -65,1401 +64,1469 @@ async function fromTokenizer(tokenizer) {
|
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const check = (header, options) => _check(buffer, header, options);
|
|
72
|
-
const checkString = (header, options) => check(stringToBytes(header), options);
|
|
73
|
-
|
|
74
|
-
// Keep reading until EOF if the file size is unknown.
|
|
75
|
-
if (!tokenizer.fileInfo.size) {
|
|
76
|
-
tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER;
|
|
67
|
+
class FileTypeParser {
|
|
68
|
+
check(header, options) {
|
|
69
|
+
return _check(this.buffer, header, options);
|
|
77
70
|
}
|
|
78
71
|
|
|
79
|
-
|
|
72
|
+
checkString(header, options) {
|
|
73
|
+
return this.check(stringToBytes(header), options);
|
|
74
|
+
}
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
async parse(tokenizer) {
|
|
77
|
+
this.buffer = Buffer.alloc(minimumBytes);
|
|
82
78
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
}
|
|
79
|
+
// Keep reading until EOF if the file size is unknown.
|
|
80
|
+
if (tokenizer.fileInfo.size === undefined) {
|
|
81
|
+
tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER;
|
|
82
|
+
}
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
95
|
-
}
|
|
84
|
+
// Keep reading until EOF if the file size is unknown.
|
|
85
|
+
if (tokenizer.fileInfo.size === undefined) {
|
|
86
|
+
tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER;
|
|
87
|
+
}
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
ext: 'dmg',
|
|
100
|
-
mime: 'application/x-apple-diskimage'
|
|
101
|
-
};
|
|
102
|
-
}
|
|
89
|
+
this.tokenizer = tokenizer;
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
ext: 'exe',
|
|
107
|
-
mime: 'application/x-msdownload'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
91
|
+
await tokenizer.peekBuffer(this.buffer, {length: 12, mayBeLess: true});
|
|
110
92
|
|
|
111
|
-
|
|
112
|
-
await tokenizer.peekBuffer(buffer, {length: 24, mayBeLess: true});
|
|
93
|
+
// -- 2-byte signatures --
|
|
113
94
|
|
|
114
|
-
if (
|
|
115
|
-
checkString(' EPSF-', {offset: 14})) {
|
|
95
|
+
if (this.check([0x42, 0x4D])) {
|
|
116
96
|
return {
|
|
117
|
-
ext: '
|
|
118
|
-
mime: '
|
|
97
|
+
ext: 'bmp',
|
|
98
|
+
mime: 'image/bmp',
|
|
119
99
|
};
|
|
120
100
|
}
|
|
121
101
|
|
|
122
|
-
|
|
123
|
-
ext: 'ps',
|
|
124
|
-
mime: 'application/postscript'
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
check([0x1F, 0xA0]) ||
|
|
130
|
-
check([0x1F, 0x9D])
|
|
131
|
-
) {
|
|
132
|
-
return {
|
|
133
|
-
ext: 'Z',
|
|
134
|
-
mime: 'application/x-compress'
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// -- 3-byte signatures --
|
|
139
|
-
|
|
140
|
-
if (check([0xFF, 0xD8, 0xFF])) {
|
|
141
|
-
return {
|
|
142
|
-
ext: 'jpg',
|
|
143
|
-
mime: 'image/jpeg'
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (check([0x49, 0x49, 0xBC])) {
|
|
148
|
-
return {
|
|
149
|
-
ext: 'jxr',
|
|
150
|
-
mime: 'image/vnd.ms-photo'
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (check([0x1F, 0x8B, 0x8])) {
|
|
155
|
-
return {
|
|
156
|
-
ext: 'gz',
|
|
157
|
-
mime: 'application/gzip'
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (check([0x42, 0x5A, 0x68])) {
|
|
162
|
-
return {
|
|
163
|
-
ext: 'bz2',
|
|
164
|
-
mime: 'application/x-bzip2'
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (checkString('ID3')) {
|
|
169
|
-
await tokenizer.ignore(6); // Skip ID3 header until the header size
|
|
170
|
-
const id3HeaderLen = await tokenizer.readToken(uint32SyncSafeToken);
|
|
171
|
-
if (tokenizer.position + id3HeaderLen > tokenizer.fileInfo.size) {
|
|
172
|
-
// Guess file type based on ID3 header for backward compatibility
|
|
102
|
+
if (this.check([0x0B, 0x77])) {
|
|
173
103
|
return {
|
|
174
|
-
ext: '
|
|
175
|
-
mime: 'audio/
|
|
104
|
+
ext: 'ac3',
|
|
105
|
+
mime: 'audio/vnd.dolby.dd-raw',
|
|
176
106
|
};
|
|
177
107
|
}
|
|
178
108
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
109
|
+
if (this.check([0x78, 0x01])) {
|
|
110
|
+
return {
|
|
111
|
+
ext: 'dmg',
|
|
112
|
+
mime: 'application/x-apple-diskimage',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
182
115
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
}
|
|
116
|
+
if (this.check([0x4D, 0x5A])) {
|
|
117
|
+
return {
|
|
118
|
+
ext: 'exe',
|
|
119
|
+
mime: 'application/x-msdownload',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
190
122
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
check([0x57, 0x53], {offset: 1})
|
|
194
|
-
) {
|
|
195
|
-
return {
|
|
196
|
-
ext: 'swf',
|
|
197
|
-
mime: 'application/x-shockwave-flash'
|
|
198
|
-
};
|
|
199
|
-
}
|
|
123
|
+
if (this.check([0x25, 0x21])) {
|
|
124
|
+
await tokenizer.peekBuffer(this.buffer, {length: 24, mayBeLess: true});
|
|
200
125
|
|
|
201
|
-
|
|
126
|
+
if (
|
|
127
|
+
this.checkString('PS-Adobe-', {offset: 2})
|
|
128
|
+
&& this.checkString(' EPSF-', {offset: 14})
|
|
129
|
+
) {
|
|
130
|
+
return {
|
|
131
|
+
ext: 'eps',
|
|
132
|
+
mime: 'application/eps',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
202
135
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
}
|
|
136
|
+
return {
|
|
137
|
+
ext: 'ps',
|
|
138
|
+
mime: 'application/postscript',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
209
141
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
142
|
+
if (
|
|
143
|
+
this.check([0x1F, 0xA0])
|
|
144
|
+
|| this.check([0x1F, 0x9D])
|
|
145
|
+
) {
|
|
146
|
+
return {
|
|
147
|
+
ext: 'Z',
|
|
148
|
+
mime: 'application/x-compress',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
216
151
|
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
ext: 'psd',
|
|
220
|
-
mime: 'image/vnd.adobe.photoshop'
|
|
221
|
-
};
|
|
222
|
-
}
|
|
152
|
+
// -- 3-byte signatures --
|
|
223
153
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
154
|
+
if (this.check([0xFF, 0xD8, 0xFF])) {
|
|
155
|
+
return {
|
|
156
|
+
ext: 'jpg',
|
|
157
|
+
mime: 'image/jpeg',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
230
160
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
}
|
|
161
|
+
if (this.check([0x49, 0x49, 0xBC])) {
|
|
162
|
+
return {
|
|
163
|
+
ext: 'jxr',
|
|
164
|
+
mime: 'image/vnd.ms-photo',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
238
167
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
168
|
+
if (this.check([0x1F, 0x8B, 0x8])) {
|
|
169
|
+
return {
|
|
170
|
+
ext: 'gz',
|
|
171
|
+
mime: 'application/gzip',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
245
174
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
175
|
+
if (this.check([0x42, 0x5A, 0x68])) {
|
|
176
|
+
return {
|
|
177
|
+
ext: 'bz2',
|
|
178
|
+
mime: 'application/x-bzip2',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
252
181
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const zipHeader = {
|
|
262
|
-
compressedSize: buffer.readUInt32LE(18),
|
|
263
|
-
uncompressedSize: buffer.readUInt32LE(22),
|
|
264
|
-
filenameLength: buffer.readUInt16LE(26),
|
|
265
|
-
extraFieldLength: buffer.readUInt16LE(28)
|
|
182
|
+
if (this.checkString('ID3')) {
|
|
183
|
+
await tokenizer.ignore(6); // Skip ID3 header until the header size
|
|
184
|
+
const id3HeaderLength = await tokenizer.readToken(uint32SyncSafeToken);
|
|
185
|
+
if (tokenizer.position + id3HeaderLength > tokenizer.fileInfo.size) {
|
|
186
|
+
// Guess file type based on ID3 header for backward compatibility
|
|
187
|
+
return {
|
|
188
|
+
ext: 'mp3',
|
|
189
|
+
mime: 'audio/mpeg',
|
|
266
190
|
};
|
|
191
|
+
}
|
|
267
192
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// Assumes signed `.xpi` from addons.mozilla.org
|
|
272
|
-
if (zipHeader.filename === 'META-INF/mozilla.rsa') {
|
|
273
|
-
return {
|
|
274
|
-
ext: 'xpi',
|
|
275
|
-
mime: 'application/x-xpinstall'
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (zipHeader.filename.endsWith('.rels') || zipHeader.filename.endsWith('.xml')) {
|
|
280
|
-
const type = zipHeader.filename.split('/')[0];
|
|
281
|
-
switch (type) {
|
|
282
|
-
case '_rels':
|
|
283
|
-
break;
|
|
284
|
-
case 'word':
|
|
285
|
-
return {
|
|
286
|
-
ext: 'docx',
|
|
287
|
-
mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
288
|
-
};
|
|
289
|
-
case 'ppt':
|
|
290
|
-
return {
|
|
291
|
-
ext: 'pptx',
|
|
292
|
-
mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
|
293
|
-
};
|
|
294
|
-
case 'xl':
|
|
295
|
-
return {
|
|
296
|
-
ext: 'xlsx',
|
|
297
|
-
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
298
|
-
};
|
|
299
|
-
default:
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (zipHeader.filename.startsWith('xl/')) {
|
|
305
|
-
return {
|
|
306
|
-
ext: 'xlsx',
|
|
307
|
-
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (zipHeader.filename.startsWith('3D/') && zipHeader.filename.endsWith('.model')) {
|
|
312
|
-
return {
|
|
313
|
-
ext: '3mf',
|
|
314
|
-
mime: 'model/3mf'
|
|
315
|
-
};
|
|
316
|
-
}
|
|
193
|
+
await tokenizer.ignore(id3HeaderLength);
|
|
194
|
+
return fileTypeFromTokenizer(tokenizer); // Skip ID3 header, recursion
|
|
195
|
+
}
|
|
317
196
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const mimeType = await tokenizer.readToken(new Token.StringType(zipHeader.compressedSize, 'utf-8'));
|
|
326
|
-
|
|
327
|
-
switch (mimeType) {
|
|
328
|
-
case 'application/epub+zip':
|
|
329
|
-
return {
|
|
330
|
-
ext: 'epub',
|
|
331
|
-
mime: 'application/epub+zip'
|
|
332
|
-
};
|
|
333
|
-
case 'application/vnd.oasis.opendocument.text':
|
|
334
|
-
return {
|
|
335
|
-
ext: 'odt',
|
|
336
|
-
mime: 'application/vnd.oasis.opendocument.text'
|
|
337
|
-
};
|
|
338
|
-
case 'application/vnd.oasis.opendocument.spreadsheet':
|
|
339
|
-
return {
|
|
340
|
-
ext: 'ods',
|
|
341
|
-
mime: 'application/vnd.oasis.opendocument.spreadsheet'
|
|
342
|
-
};
|
|
343
|
-
case 'application/vnd.oasis.opendocument.presentation':
|
|
344
|
-
return {
|
|
345
|
-
ext: 'odp',
|
|
346
|
-
mime: 'application/vnd.oasis.opendocument.presentation'
|
|
347
|
-
};
|
|
348
|
-
default:
|
|
349
|
-
}
|
|
350
|
-
}
|
|
197
|
+
// Musepack, SV7
|
|
198
|
+
if (this.checkString('MP+')) {
|
|
199
|
+
return {
|
|
200
|
+
ext: 'mpc',
|
|
201
|
+
mime: 'audio/x-musepack',
|
|
202
|
+
};
|
|
203
|
+
}
|
|
351
204
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
205
|
+
if (
|
|
206
|
+
(this.buffer[0] === 0x43 || this.buffer[0] === 0x46)
|
|
207
|
+
&& this.check([0x57, 0x53], {offset: 1})
|
|
208
|
+
) {
|
|
209
|
+
return {
|
|
210
|
+
ext: 'swf',
|
|
211
|
+
mime: 'application/x-shockwave-flash',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
355
214
|
|
|
356
|
-
|
|
357
|
-
await tokenizer.peekBuffer(buffer, {mayBeLess: true});
|
|
215
|
+
// -- 4-byte signatures --
|
|
358
216
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
await tokenizer.ignore(zipHeader.compressedSize);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
} catch (error) {
|
|
368
|
-
if (!(error instanceof strtok3.EndOfStreamError)) {
|
|
369
|
-
throw error;
|
|
370
|
-
}
|
|
217
|
+
if (this.check([0x7F, 0x45, 0x4C, 0x46])) {
|
|
218
|
+
return {
|
|
219
|
+
ext: 'elf',
|
|
220
|
+
mime: 'application/x-elf',
|
|
221
|
+
};
|
|
371
222
|
}
|
|
372
223
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (checkString('OggS')) {
|
|
380
|
-
// This is an OGG container
|
|
381
|
-
await tokenizer.ignore(28);
|
|
382
|
-
const type = Buffer.alloc(8);
|
|
383
|
-
await tokenizer.readBuffer(type);
|
|
224
|
+
if (this.check([0x47, 0x49, 0x46])) {
|
|
225
|
+
return {
|
|
226
|
+
ext: 'gif',
|
|
227
|
+
mime: 'image/gif',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
384
230
|
|
|
385
|
-
|
|
386
|
-
if (_check(type, [0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64])) {
|
|
231
|
+
if (this.checkString('FLIF')) {
|
|
387
232
|
return {
|
|
388
|
-
ext: '
|
|
389
|
-
mime: '
|
|
233
|
+
ext: 'flif',
|
|
234
|
+
mime: 'image/flif',
|
|
390
235
|
};
|
|
391
236
|
}
|
|
392
237
|
|
|
393
|
-
|
|
394
|
-
if (_check(type, [0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61])) {
|
|
238
|
+
if (this.checkString('8BPS')) {
|
|
395
239
|
return {
|
|
396
|
-
ext: '
|
|
397
|
-
mime: '
|
|
240
|
+
ext: 'psd',
|
|
241
|
+
mime: 'image/vnd.adobe.photoshop',
|
|
398
242
|
};
|
|
399
243
|
}
|
|
400
244
|
|
|
401
|
-
|
|
402
|
-
if (_check(type, [0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00])) {
|
|
245
|
+
if (this.checkString('WEBP', {offset: 8})) {
|
|
403
246
|
return {
|
|
404
|
-
ext: '
|
|
405
|
-
mime: '
|
|
247
|
+
ext: 'webp',
|
|
248
|
+
mime: 'image/webp',
|
|
406
249
|
};
|
|
407
250
|
}
|
|
408
251
|
|
|
409
|
-
//
|
|
410
|
-
if (
|
|
252
|
+
// Musepack, SV8
|
|
253
|
+
if (this.checkString('MPCK')) {
|
|
411
254
|
return {
|
|
412
|
-
ext: '
|
|
413
|
-
mime: 'audio/
|
|
255
|
+
ext: 'mpc',
|
|
256
|
+
mime: 'audio/x-musepack',
|
|
414
257
|
};
|
|
415
258
|
}
|
|
416
259
|
|
|
417
|
-
|
|
418
|
-
if (_check(type, [0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20])) {
|
|
260
|
+
if (this.checkString('FORM')) {
|
|
419
261
|
return {
|
|
420
|
-
ext: '
|
|
421
|
-
mime: 'audio/
|
|
262
|
+
ext: 'aif',
|
|
263
|
+
mime: 'audio/aiff',
|
|
422
264
|
};
|
|
423
265
|
}
|
|
424
266
|
|
|
425
|
-
|
|
426
|
-
if (_check(type, [0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73])) {
|
|
267
|
+
if (this.checkString('icns', {offset: 0})) {
|
|
427
268
|
return {
|
|
428
|
-
ext: '
|
|
429
|
-
mime: '
|
|
269
|
+
ext: 'icns',
|
|
270
|
+
mime: 'image/icns',
|
|
430
271
|
};
|
|
431
272
|
}
|
|
432
273
|
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
274
|
+
// Zip-based file formats
|
|
275
|
+
// Need to be before the `zip` check
|
|
276
|
+
if (this.check([0x50, 0x4B, 0x3, 0x4])) { // Local file header signature
|
|
277
|
+
try {
|
|
278
|
+
while (tokenizer.position + 30 < tokenizer.fileInfo.size) {
|
|
279
|
+
await tokenizer.readBuffer(this.buffer, {length: 30});
|
|
280
|
+
|
|
281
|
+
// https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
|
|
282
|
+
const zipHeader = {
|
|
283
|
+
compressedSize: this.buffer.readUInt32LE(18),
|
|
284
|
+
uncompressedSize: this.buffer.readUInt32LE(22),
|
|
285
|
+
filenameLength: this.buffer.readUInt16LE(26),
|
|
286
|
+
extraFieldLength: this.buffer.readUInt16LE(28),
|
|
287
|
+
};
|
|
439
288
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
(buffer[2] === 0x3 || buffer[2] === 0x5 || buffer[2] === 0x7) &&
|
|
443
|
-
(buffer[3] === 0x4 || buffer[3] === 0x6 || buffer[3] === 0x8)
|
|
444
|
-
) {
|
|
445
|
-
return {
|
|
446
|
-
ext: 'zip',
|
|
447
|
-
mime: 'application/zip'
|
|
448
|
-
};
|
|
449
|
-
}
|
|
289
|
+
zipHeader.filename = await tokenizer.readToken(new Token.StringType(zipHeader.filenameLength, 'utf-8'));
|
|
290
|
+
await tokenizer.ignore(zipHeader.extraFieldLength);
|
|
450
291
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
checkString('ftyp', {offset: 4}) &&
|
|
459
|
-
(buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII?
|
|
460
|
-
) {
|
|
461
|
-
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
|
|
462
|
-
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
|
|
463
|
-
const brandMajor = buffer.toString('binary', 8, 12).replace('\0', ' ').trim();
|
|
464
|
-
switch (brandMajor) {
|
|
465
|
-
case 'avif':
|
|
466
|
-
return {ext: 'avif', mime: 'image/avif'};
|
|
467
|
-
case 'mif1':
|
|
468
|
-
return {ext: 'heic', mime: 'image/heif'};
|
|
469
|
-
case 'msf1':
|
|
470
|
-
return {ext: 'heic', mime: 'image/heif-sequence'};
|
|
471
|
-
case 'heic':
|
|
472
|
-
case 'heix':
|
|
473
|
-
return {ext: 'heic', mime: 'image/heic'};
|
|
474
|
-
case 'hevc':
|
|
475
|
-
case 'hevx':
|
|
476
|
-
return {ext: 'heic', mime: 'image/heic-sequence'};
|
|
477
|
-
case 'qt':
|
|
478
|
-
return {ext: 'mov', mime: 'video/quicktime'};
|
|
479
|
-
case 'M4V':
|
|
480
|
-
case 'M4VH':
|
|
481
|
-
case 'M4VP':
|
|
482
|
-
return {ext: 'm4v', mime: 'video/x-m4v'};
|
|
483
|
-
case 'M4P':
|
|
484
|
-
return {ext: 'm4p', mime: 'video/mp4'};
|
|
485
|
-
case 'M4B':
|
|
486
|
-
return {ext: 'm4b', mime: 'audio/mp4'};
|
|
487
|
-
case 'M4A':
|
|
488
|
-
return {ext: 'm4a', mime: 'audio/x-m4a'};
|
|
489
|
-
case 'F4V':
|
|
490
|
-
return {ext: 'f4v', mime: 'video/mp4'};
|
|
491
|
-
case 'F4P':
|
|
492
|
-
return {ext: 'f4p', mime: 'video/mp4'};
|
|
493
|
-
case 'F4A':
|
|
494
|
-
return {ext: 'f4a', mime: 'audio/mp4'};
|
|
495
|
-
case 'F4B':
|
|
496
|
-
return {ext: 'f4b', mime: 'audio/mp4'};
|
|
497
|
-
case 'crx':
|
|
498
|
-
return {ext: 'cr3', mime: 'image/x-canon-cr3'};
|
|
499
|
-
default:
|
|
500
|
-
if (brandMajor.startsWith('3g')) {
|
|
501
|
-
if (brandMajor.startsWith('3g2')) {
|
|
502
|
-
return {ext: '3g2', mime: 'video/3gpp2'};
|
|
292
|
+
// Assumes signed `.xpi` from addons.mozilla.org
|
|
293
|
+
if (zipHeader.filename === 'META-INF/mozilla.rsa') {
|
|
294
|
+
return {
|
|
295
|
+
ext: 'xpi',
|
|
296
|
+
mime: 'application/x-xpinstall',
|
|
297
|
+
};
|
|
503
298
|
}
|
|
504
299
|
|
|
505
|
-
|
|
506
|
-
|
|
300
|
+
if (zipHeader.filename.endsWith('.rels') || zipHeader.filename.endsWith('.xml')) {
|
|
301
|
+
const type = zipHeader.filename.split('/')[0];
|
|
302
|
+
switch (type) {
|
|
303
|
+
case '_rels':
|
|
304
|
+
break;
|
|
305
|
+
case 'word':
|
|
306
|
+
return {
|
|
307
|
+
ext: 'docx',
|
|
308
|
+
mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
309
|
+
};
|
|
310
|
+
case 'ppt':
|
|
311
|
+
return {
|
|
312
|
+
ext: 'pptx',
|
|
313
|
+
mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
314
|
+
};
|
|
315
|
+
case 'xl':
|
|
316
|
+
return {
|
|
317
|
+
ext: 'xlsx',
|
|
318
|
+
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
319
|
+
};
|
|
320
|
+
default:
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
507
324
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
325
|
+
if (zipHeader.filename.startsWith('xl/')) {
|
|
326
|
+
return {
|
|
327
|
+
ext: 'xlsx',
|
|
328
|
+
mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
329
|
+
};
|
|
330
|
+
}
|
|
511
331
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
332
|
+
if (zipHeader.filename.startsWith('3D/') && zipHeader.filename.endsWith('.model')) {
|
|
333
|
+
return {
|
|
334
|
+
ext: '3mf',
|
|
335
|
+
mime: 'model/3mf',
|
|
336
|
+
};
|
|
337
|
+
}
|
|
518
338
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
339
|
+
// The docx, xlsx and pptx file types extend the Office Open XML file format:
|
|
340
|
+
// https://en.wikipedia.org/wiki/Office_Open_XML_file_formats
|
|
341
|
+
// We look for:
|
|
342
|
+
// - one entry named '[Content_Types].xml' or '_rels/.rels',
|
|
343
|
+
// - one entry indicating specific type of file.
|
|
344
|
+
// MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it.
|
|
345
|
+
if (zipHeader.filename === 'mimetype' && zipHeader.compressedSize === zipHeader.uncompressedSize) {
|
|
346
|
+
const mimeType = await tokenizer.readToken(new Token.StringType(zipHeader.compressedSize, 'utf-8'));
|
|
347
|
+
|
|
348
|
+
switch (mimeType) {
|
|
349
|
+
case 'application/epub+zip':
|
|
350
|
+
return {
|
|
351
|
+
ext: 'epub',
|
|
352
|
+
mime: 'application/epub+zip',
|
|
353
|
+
};
|
|
354
|
+
case 'application/vnd.oasis.opendocument.text':
|
|
355
|
+
return {
|
|
356
|
+
ext: 'odt',
|
|
357
|
+
mime: 'application/vnd.oasis.opendocument.text',
|
|
358
|
+
};
|
|
359
|
+
case 'application/vnd.oasis.opendocument.spreadsheet':
|
|
360
|
+
return {
|
|
361
|
+
ext: 'ods',
|
|
362
|
+
mime: 'application/vnd.oasis.opendocument.spreadsheet',
|
|
363
|
+
};
|
|
364
|
+
case 'application/vnd.oasis.opendocument.presentation':
|
|
365
|
+
return {
|
|
366
|
+
ext: 'odp',
|
|
367
|
+
mime: 'application/vnd.oasis.opendocument.presentation',
|
|
368
|
+
};
|
|
369
|
+
default:
|
|
370
|
+
}
|
|
371
|
+
}
|
|
531
372
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
|
|
536
|
-
checkString('OTTO', {offset: 4})
|
|
537
|
-
)
|
|
538
|
-
) {
|
|
539
|
-
return {
|
|
540
|
-
ext: 'woff2',
|
|
541
|
-
mime: 'font/woff2'
|
|
542
|
-
};
|
|
543
|
-
}
|
|
373
|
+
// Try to find next header manually when current one is corrupted
|
|
374
|
+
if (zipHeader.compressedSize === 0) {
|
|
375
|
+
let nextHeaderIndex = -1;
|
|
544
376
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
ext: 'pcap',
|
|
548
|
-
mime: 'application/vnd.tcpdump.pcap'
|
|
549
|
-
};
|
|
550
|
-
}
|
|
377
|
+
while (nextHeaderIndex < 0 && (tokenizer.position < tokenizer.fileInfo.size)) {
|
|
378
|
+
await tokenizer.peekBuffer(this.buffer, {mayBeLess: true});
|
|
551
379
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
380
|
+
nextHeaderIndex = this.buffer.indexOf('504B0304', 0, 'hex');
|
|
381
|
+
// Move position to the next header if found, skip the whole buffer otherwise
|
|
382
|
+
await tokenizer.ignore(nextHeaderIndex >= 0 ? nextHeaderIndex : this.buffer.length);
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
await tokenizer.ignore(zipHeader.compressedSize);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (!(error instanceof strtok3.EndOfStreamError)) {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
559
393
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
}
|
|
394
|
+
return {
|
|
395
|
+
ext: 'zip',
|
|
396
|
+
mime: 'application/zip',
|
|
397
|
+
};
|
|
398
|
+
}
|
|
566
399
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
400
|
+
if (this.checkString('OggS')) {
|
|
401
|
+
// This is an OGG container
|
|
402
|
+
await tokenizer.ignore(28);
|
|
403
|
+
const type = Buffer.alloc(8);
|
|
404
|
+
await tokenizer.readBuffer(type);
|
|
573
405
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
406
|
+
// Needs to be before `ogg` check
|
|
407
|
+
if (_check(type, [0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64])) {
|
|
408
|
+
return {
|
|
409
|
+
ext: 'opus',
|
|
410
|
+
mime: 'audio/opus',
|
|
411
|
+
};
|
|
412
|
+
}
|
|
580
413
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
414
|
+
// If ' theora' in header.
|
|
415
|
+
if (_check(type, [0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61])) {
|
|
416
|
+
return {
|
|
417
|
+
ext: 'ogv',
|
|
418
|
+
mime: 'video/ogg',
|
|
419
|
+
};
|
|
420
|
+
}
|
|
587
421
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
422
|
+
// If '\x01video' in header.
|
|
423
|
+
if (_check(type, [0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00])) {
|
|
424
|
+
return {
|
|
425
|
+
ext: 'ogm',
|
|
426
|
+
mime: 'video/ogg',
|
|
427
|
+
};
|
|
428
|
+
}
|
|
593
429
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
430
|
+
// If ' FLAC' in header https://xiph.org/flac/faq.html
|
|
431
|
+
if (_check(type, [0x7F, 0x46, 0x4C, 0x41, 0x43])) {
|
|
432
|
+
return {
|
|
433
|
+
ext: 'oga',
|
|
434
|
+
mime: 'audio/ogg',
|
|
435
|
+
};
|
|
436
|
+
}
|
|
601
437
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
438
|
+
// 'Speex ' in header https://en.wikipedia.org/wiki/Speex
|
|
439
|
+
if (_check(type, [0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20])) {
|
|
440
|
+
return {
|
|
441
|
+
ext: 'spx',
|
|
442
|
+
mime: 'audio/ogg',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
608
445
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
446
|
+
// If '\x01vorbis' in header
|
|
447
|
+
if (_check(type, [0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73])) {
|
|
448
|
+
return {
|
|
449
|
+
ext: 'ogg',
|
|
450
|
+
mime: 'audio/ogg',
|
|
451
|
+
};
|
|
452
|
+
}
|
|
615
453
|
|
|
616
|
-
|
|
617
|
-
if (check([0x49, 0x49, 0x2A, 0x0])) {
|
|
618
|
-
if (checkString('CR', {offset: 8})) {
|
|
454
|
+
// Default OGG container https://www.iana.org/assignments/media-types/application/ogg
|
|
619
455
|
return {
|
|
620
|
-
ext: '
|
|
621
|
-
mime: '
|
|
456
|
+
ext: 'ogx',
|
|
457
|
+
mime: 'application/ogg',
|
|
622
458
|
};
|
|
623
459
|
}
|
|
624
460
|
|
|
625
|
-
if (
|
|
461
|
+
if (
|
|
462
|
+
this.check([0x50, 0x4B])
|
|
463
|
+
&& (this.buffer[2] === 0x3 || this.buffer[2] === 0x5 || this.buffer[2] === 0x7)
|
|
464
|
+
&& (this.buffer[3] === 0x4 || this.buffer[3] === 0x6 || this.buffer[3] === 0x8)
|
|
465
|
+
) {
|
|
626
466
|
return {
|
|
627
|
-
ext: '
|
|
628
|
-
mime: '
|
|
467
|
+
ext: 'zip',
|
|
468
|
+
mime: 'application/zip',
|
|
629
469
|
};
|
|
630
470
|
}
|
|
631
471
|
|
|
472
|
+
//
|
|
473
|
+
|
|
474
|
+
// File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
|
|
475
|
+
// It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box.
|
|
476
|
+
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
|
|
477
|
+
// Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
|
|
632
478
|
if (
|
|
633
|
-
|
|
634
|
-
(
|
|
635
|
-
check([0x27, 0x00, 0xFE, 0x00], {offset: 8}))
|
|
479
|
+
this.checkString('ftyp', {offset: 4})
|
|
480
|
+
&& (this.buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII?
|
|
636
481
|
) {
|
|
482
|
+
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
|
|
483
|
+
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
|
|
484
|
+
const brandMajor = this.buffer.toString('binary', 8, 12).replace('\0', ' ').trim();
|
|
485
|
+
switch (brandMajor) {
|
|
486
|
+
case 'avif':
|
|
487
|
+
case 'avis':
|
|
488
|
+
return {ext: 'avif', mime: 'image/avif'};
|
|
489
|
+
case 'mif1':
|
|
490
|
+
return {ext: 'heic', mime: 'image/heif'};
|
|
491
|
+
case 'msf1':
|
|
492
|
+
return {ext: 'heic', mime: 'image/heif-sequence'};
|
|
493
|
+
case 'heic':
|
|
494
|
+
case 'heix':
|
|
495
|
+
return {ext: 'heic', mime: 'image/heic'};
|
|
496
|
+
case 'hevc':
|
|
497
|
+
case 'hevx':
|
|
498
|
+
return {ext: 'heic', mime: 'image/heic-sequence'};
|
|
499
|
+
case 'qt':
|
|
500
|
+
return {ext: 'mov', mime: 'video/quicktime'};
|
|
501
|
+
case 'M4V':
|
|
502
|
+
case 'M4VH':
|
|
503
|
+
case 'M4VP':
|
|
504
|
+
return {ext: 'm4v', mime: 'video/x-m4v'};
|
|
505
|
+
case 'M4P':
|
|
506
|
+
return {ext: 'm4p', mime: 'video/mp4'};
|
|
507
|
+
case 'M4B':
|
|
508
|
+
return {ext: 'm4b', mime: 'audio/mp4'};
|
|
509
|
+
case 'M4A':
|
|
510
|
+
return {ext: 'm4a', mime: 'audio/x-m4a'};
|
|
511
|
+
case 'F4V':
|
|
512
|
+
return {ext: 'f4v', mime: 'video/mp4'};
|
|
513
|
+
case 'F4P':
|
|
514
|
+
return {ext: 'f4p', mime: 'video/mp4'};
|
|
515
|
+
case 'F4A':
|
|
516
|
+
return {ext: 'f4a', mime: 'audio/mp4'};
|
|
517
|
+
case 'F4B':
|
|
518
|
+
return {ext: 'f4b', mime: 'audio/mp4'};
|
|
519
|
+
case 'crx':
|
|
520
|
+
return {ext: 'cr3', mime: 'image/x-canon-cr3'};
|
|
521
|
+
default:
|
|
522
|
+
if (brandMajor.startsWith('3g')) {
|
|
523
|
+
if (brandMajor.startsWith('3g2')) {
|
|
524
|
+
return {ext: '3g2', mime: 'video/3gpp2'};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {ext: '3gp', mime: 'video/3gpp'};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return {ext: 'mp4', mime: 'video/mp4'};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (this.checkString('MThd')) {
|
|
637
535
|
return {
|
|
638
|
-
ext: '
|
|
639
|
-
mime: '
|
|
536
|
+
ext: 'mid',
|
|
537
|
+
mime: 'audio/midi',
|
|
640
538
|
};
|
|
641
539
|
}
|
|
642
540
|
|
|
643
|
-
buffer = Buffer.alloc(24);
|
|
644
|
-
await tokenizer.peekBuffer(buffer);
|
|
645
541
|
if (
|
|
646
|
-
(
|
|
647
|
-
|
|
648
|
-
|
|
542
|
+
this.checkString('wOFF')
|
|
543
|
+
&& (
|
|
544
|
+
this.check([0x00, 0x01, 0x00, 0x00], {offset: 4})
|
|
545
|
+
|| this.checkString('OTTO', {offset: 4})
|
|
546
|
+
)
|
|
649
547
|
) {
|
|
650
548
|
return {
|
|
651
|
-
ext: '
|
|
652
|
-
mime: '
|
|
549
|
+
ext: 'woff',
|
|
550
|
+
mime: 'font/woff',
|
|
653
551
|
};
|
|
654
552
|
}
|
|
655
553
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (checkString('MAC ')) {
|
|
671
|
-
return {
|
|
672
|
-
ext: 'ape',
|
|
673
|
-
mime: 'audio/ape'
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
|
|
678
|
-
if (check([0x1A, 0x45, 0xDF, 0xA3])) { // Root element: EBML
|
|
679
|
-
async function readField() {
|
|
680
|
-
const msb = await tokenizer.peekNumber(Token.UINT8);
|
|
681
|
-
let mask = 0x80;
|
|
682
|
-
let ic = 0; // 0 = A, 1 = B, 2 = C, 3 = D
|
|
683
|
-
|
|
684
|
-
while ((msb & mask) === 0) {
|
|
685
|
-
++ic;
|
|
686
|
-
mask >>= 1;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const id = Buffer.alloc(ic + 1);
|
|
690
|
-
await tokenizer.readBuffer(id);
|
|
691
|
-
return id;
|
|
554
|
+
if (
|
|
555
|
+
this.checkString('wOF2')
|
|
556
|
+
&& (
|
|
557
|
+
this.check([0x00, 0x01, 0x00, 0x00], {offset: 4})
|
|
558
|
+
|| this.checkString('OTTO', {offset: 4})
|
|
559
|
+
)
|
|
560
|
+
) {
|
|
561
|
+
return {
|
|
562
|
+
ext: 'woff2',
|
|
563
|
+
mime: 'font/woff2',
|
|
564
|
+
};
|
|
692
565
|
}
|
|
693
566
|
|
|
694
|
-
|
|
695
|
-
const id = await readField();
|
|
696
|
-
const lenField = await readField();
|
|
697
|
-
lenField[0] ^= 0x80 >> (lenField.length - 1);
|
|
698
|
-
const nrLen = Math.min(6, lenField.length); // JavaScript can max read 6 bytes integer
|
|
567
|
+
if (this.check([0xD4, 0xC3, 0xB2, 0xA1]) || this.check([0xA1, 0xB2, 0xC3, 0xD4])) {
|
|
699
568
|
return {
|
|
700
|
-
|
|
701
|
-
|
|
569
|
+
ext: 'pcap',
|
|
570
|
+
mime: 'application/vnd.tcpdump.pcap',
|
|
702
571
|
};
|
|
703
572
|
}
|
|
704
573
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
await tokenizer.ignore(e.len); // ignore payload
|
|
713
|
-
--children;
|
|
714
|
-
}
|
|
574
|
+
// Sony DSD Stream File (DSF)
|
|
575
|
+
if (this.checkString('DSD ')) {
|
|
576
|
+
return {
|
|
577
|
+
ext: 'dsf',
|
|
578
|
+
mime: 'audio/x-dsf', // Non-standard
|
|
579
|
+
};
|
|
715
580
|
}
|
|
716
581
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
return {
|
|
723
|
-
ext: 'webm',
|
|
724
|
-
mime: 'video/webm'
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
case 'matroska':
|
|
728
|
-
return {
|
|
729
|
-
ext: 'mkv',
|
|
730
|
-
mime: 'video/x-matroska'
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
default:
|
|
734
|
-
return;
|
|
582
|
+
if (this.checkString('LZIP')) {
|
|
583
|
+
return {
|
|
584
|
+
ext: 'lz',
|
|
585
|
+
mime: 'application/x-lzip',
|
|
586
|
+
};
|
|
735
587
|
}
|
|
736
|
-
}
|
|
737
588
|
|
|
738
|
-
|
|
739
|
-
if (check([0x52, 0x49, 0x46, 0x46])) {
|
|
740
|
-
if (check([0x41, 0x56, 0x49], {offset: 8})) {
|
|
589
|
+
if (this.checkString('fLaC')) {
|
|
741
590
|
return {
|
|
742
|
-
ext: '
|
|
743
|
-
mime: '
|
|
591
|
+
ext: 'flac',
|
|
592
|
+
mime: 'audio/x-flac',
|
|
744
593
|
};
|
|
745
594
|
}
|
|
746
595
|
|
|
747
|
-
if (check([
|
|
596
|
+
if (this.check([0x42, 0x50, 0x47, 0xFB])) {
|
|
748
597
|
return {
|
|
749
|
-
ext: '
|
|
750
|
-
mime: '
|
|
598
|
+
ext: 'bpg',
|
|
599
|
+
mime: 'image/bpg',
|
|
751
600
|
};
|
|
752
601
|
}
|
|
753
602
|
|
|
754
|
-
|
|
755
|
-
if (check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
|
|
603
|
+
if (this.checkString('wvpk')) {
|
|
756
604
|
return {
|
|
757
|
-
ext: '
|
|
758
|
-
mime: 'audio/
|
|
605
|
+
ext: 'wv',
|
|
606
|
+
mime: 'audio/wavpack',
|
|
759
607
|
};
|
|
760
608
|
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
if (checkString('SQLi')) {
|
|
764
|
-
return {
|
|
765
|
-
ext: 'sqlite',
|
|
766
|
-
mime: 'application/x-sqlite3'
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (check([0x4E, 0x45, 0x53, 0x1A])) {
|
|
771
|
-
return {
|
|
772
|
-
ext: 'nes',
|
|
773
|
-
mime: 'application/x-nintendo-nes-rom'
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (checkString('Cr24')) {
|
|
778
|
-
return {
|
|
779
|
-
ext: 'crx',
|
|
780
|
-
mime: 'application/x-google-chrome-extension'
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
if (
|
|
785
|
-
checkString('MSCF') ||
|
|
786
|
-
checkString('ISc(')
|
|
787
|
-
) {
|
|
788
|
-
return {
|
|
789
|
-
ext: 'cab',
|
|
790
|
-
mime: 'application/vnd.ms-cab-compressed'
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (check([0xED, 0xAB, 0xEE, 0xDB])) {
|
|
795
|
-
return {
|
|
796
|
-
ext: 'rpm',
|
|
797
|
-
mime: 'application/x-rpm'
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
if (check([0xC5, 0xD0, 0xD3, 0xC6])) {
|
|
802
|
-
return {
|
|
803
|
-
ext: 'eps',
|
|
804
|
-
mime: 'application/eps'
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (check([0x28, 0xB5, 0x2F, 0xFD])) {
|
|
809
|
-
return {
|
|
810
|
-
ext: 'zst',
|
|
811
|
-
mime: 'application/zstd'
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// -- 5-byte signatures --
|
|
816
|
-
|
|
817
|
-
if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
|
|
818
|
-
return {
|
|
819
|
-
ext: 'otf',
|
|
820
|
-
mime: 'font/otf'
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
609
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
if (checkString('{\\rtf')) {
|
|
832
|
-
return {
|
|
833
|
-
ext: 'rtf',
|
|
834
|
-
mime: 'application/rtf'
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (check([0x46, 0x4C, 0x56, 0x01])) {
|
|
839
|
-
return {
|
|
840
|
-
ext: 'flv',
|
|
841
|
-
mime: 'video/x-flv'
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
if (checkString('IMPM')) {
|
|
846
|
-
return {
|
|
847
|
-
ext: 'it',
|
|
848
|
-
mime: 'audio/x-it'
|
|
849
|
-
};
|
|
850
|
-
}
|
|
610
|
+
if (this.checkString('%PDF')) {
|
|
611
|
+
await tokenizer.ignore(1350);
|
|
612
|
+
const maxBufferSize = 10 * 1024 * 1024;
|
|
613
|
+
const buffer = Buffer.alloc(Math.min(maxBufferSize, tokenizer.fileInfo.size));
|
|
614
|
+
await tokenizer.readBuffer(buffer, {mayBeLess: true});
|
|
851
615
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
checkString('-lh6-', {offset: 2}) ||
|
|
860
|
-
checkString('-lh7-', {offset: 2}) ||
|
|
861
|
-
checkString('-lzs-', {offset: 2}) ||
|
|
862
|
-
checkString('-lz4-', {offset: 2}) ||
|
|
863
|
-
checkString('-lz5-', {offset: 2}) ||
|
|
864
|
-
checkString('-lhd-', {offset: 2})
|
|
865
|
-
) {
|
|
866
|
-
return {
|
|
867
|
-
ext: 'lzh',
|
|
868
|
-
mime: 'application/x-lzh-compressed'
|
|
869
|
-
};
|
|
870
|
-
}
|
|
616
|
+
// Check if this is an Adobe Illustrator file
|
|
617
|
+
if (buffer.includes(Buffer.from('AIPrivateData'))) {
|
|
618
|
+
return {
|
|
619
|
+
ext: 'ai',
|
|
620
|
+
mime: 'application/postscript',
|
|
621
|
+
};
|
|
622
|
+
}
|
|
871
623
|
|
|
872
|
-
|
|
873
|
-
if (check([0x00, 0x00, 0x01, 0xBA])) {
|
|
874
|
-
// MPEG-PS, MPEG-1 Part 1
|
|
875
|
-
if (check([0x21], {offset: 4, mask: [0xF1]})) {
|
|
624
|
+
// Assume this is just a normal PDF
|
|
876
625
|
return {
|
|
877
|
-
ext: '
|
|
878
|
-
mime: '
|
|
626
|
+
ext: 'pdf',
|
|
627
|
+
mime: 'application/pdf',
|
|
879
628
|
};
|
|
880
629
|
}
|
|
881
630
|
|
|
882
|
-
|
|
883
|
-
if (check([0x44], {offset: 4, mask: [0xC4]})) {
|
|
631
|
+
if (this.check([0x00, 0x61, 0x73, 0x6D])) {
|
|
884
632
|
return {
|
|
885
|
-
ext: '
|
|
886
|
-
mime: '
|
|
633
|
+
ext: 'wasm',
|
|
634
|
+
mime: 'application/wasm',
|
|
887
635
|
};
|
|
888
636
|
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
if (checkString('ITSF')) {
|
|
892
|
-
return {
|
|
893
|
-
ext: 'chm',
|
|
894
|
-
mime: 'application/vnd.ms-htmlhelp'
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// -- 6-byte signatures --
|
|
899
|
-
|
|
900
|
-
if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
|
|
901
|
-
return {
|
|
902
|
-
ext: 'xz',
|
|
903
|
-
mime: 'application/x-xz'
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (checkString('<?xml ')) {
|
|
908
|
-
return {
|
|
909
|
-
ext: 'xml',
|
|
910
|
-
mime: 'application/xml'
|
|
911
|
-
};
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
|
|
915
|
-
return {
|
|
916
|
-
ext: '7z',
|
|
917
|
-
mime: 'application/x-7z-compressed'
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
if (
|
|
922
|
-
check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
|
|
923
|
-
(buffer[6] === 0x0 || buffer[6] === 0x1)
|
|
924
|
-
) {
|
|
925
|
-
return {
|
|
926
|
-
ext: 'rar',
|
|
927
|
-
mime: 'application/x-rar-compressed'
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (checkString('solid ')) {
|
|
932
|
-
return {
|
|
933
|
-
ext: 'stl',
|
|
934
|
-
mime: 'model/stl'
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
637
|
|
|
938
|
-
|
|
638
|
+
// TIFF, little-endian type
|
|
639
|
+
if (this.check([0x49, 0x49])) {
|
|
640
|
+
const fileType = await this.readTiffHeader(false);
|
|
641
|
+
if (fileType) {
|
|
642
|
+
return fileType;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
939
645
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
646
|
+
// TIFF, big-endian type
|
|
647
|
+
if (this.check([0x4D, 0x4D])) {
|
|
648
|
+
const fileType = await this.readTiffHeader(true);
|
|
649
|
+
if (fileType) {
|
|
650
|
+
return fileType;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
946
653
|
|
|
947
|
-
|
|
948
|
-
await tokenizer.ignore(8);
|
|
949
|
-
const str = await tokenizer.readToken(new Token.StringType(13, 'ascii'));
|
|
950
|
-
if (str === 'debian-binary') {
|
|
654
|
+
if (this.checkString('MAC ')) {
|
|
951
655
|
return {
|
|
952
|
-
ext: '
|
|
953
|
-
mime: '
|
|
656
|
+
ext: 'ape',
|
|
657
|
+
mime: 'audio/ape',
|
|
954
658
|
};
|
|
955
659
|
}
|
|
956
660
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
661
|
+
// https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
|
|
662
|
+
if (this.check([0x1A, 0x45, 0xDF, 0xA3])) { // Root element: EBML
|
|
663
|
+
async function readField() {
|
|
664
|
+
const msb = await tokenizer.peekNumber(Token.UINT8);
|
|
665
|
+
let mask = 0x80;
|
|
666
|
+
let ic = 0; // 0 = A, 1 = B, 2 = C, 3
|
|
667
|
+
// = D
|
|
668
|
+
|
|
669
|
+
while ((msb & mask) === 0) {
|
|
670
|
+
++ic;
|
|
671
|
+
mask >>= 1;
|
|
672
|
+
}
|
|
969
673
|
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
674
|
+
const id = Buffer.alloc(ic + 1);
|
|
675
|
+
await tokenizer.readBuffer(id);
|
|
676
|
+
return id;
|
|
677
|
+
}
|
|
973
678
|
|
|
974
|
-
|
|
679
|
+
async function readElement() {
|
|
680
|
+
const id = await readField();
|
|
681
|
+
const lengthField = await readField();
|
|
682
|
+
lengthField[0] ^= 0x80 >> (lengthField.length - 1);
|
|
683
|
+
const nrLength = Math.min(6, lengthField.length); // JavaScript can max read 6 bytes integer
|
|
684
|
+
return {
|
|
685
|
+
id: id.readUIntBE(0, id.length),
|
|
686
|
+
len: lengthField.readUIntBE(lengthField.length - nrLength, nrLength),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
975
689
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
690
|
+
async function readChildren(level, children) {
|
|
691
|
+
while (children > 0) {
|
|
692
|
+
const element = await readElement();
|
|
693
|
+
if (element.id === 0x42_82) {
|
|
694
|
+
const rawValue = await tokenizer.readToken(new Token.StringType(element.len, 'utf-8'));
|
|
695
|
+
return rawValue.replace(/\00.*$/g, ''); // Return DocType
|
|
696
|
+
}
|
|
982
697
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
return; // Invalid chunk length
|
|
698
|
+
await tokenizer.ignore(element.len); // ignore payload
|
|
699
|
+
--children;
|
|
700
|
+
}
|
|
987
701
|
}
|
|
988
702
|
|
|
989
|
-
|
|
990
|
-
|
|
703
|
+
const re = await readElement();
|
|
704
|
+
const docType = await readChildren(1, re.len);
|
|
705
|
+
|
|
706
|
+
switch (docType) {
|
|
707
|
+
case 'webm':
|
|
991
708
|
return {
|
|
992
|
-
ext: '
|
|
993
|
-
mime: '
|
|
709
|
+
ext: 'webm',
|
|
710
|
+
mime: 'video/webm',
|
|
994
711
|
};
|
|
995
|
-
|
|
712
|
+
|
|
713
|
+
case 'matroska':
|
|
996
714
|
return {
|
|
997
|
-
ext: '
|
|
998
|
-
mime: '
|
|
715
|
+
ext: 'mkv',
|
|
716
|
+
mime: 'video/x-matroska',
|
|
999
717
|
};
|
|
718
|
+
|
|
1000
719
|
default:
|
|
1001
|
-
|
|
720
|
+
return;
|
|
1002
721
|
}
|
|
1003
|
-
}
|
|
722
|
+
}
|
|
1004
723
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
ext: 'arrow',
|
|
1014
|
-
mime: 'application/x-apache-arrow'
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
if (check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) {
|
|
1019
|
-
return {
|
|
1020
|
-
ext: 'glb',
|
|
1021
|
-
mime: 'model/gltf-binary'
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// `mov` format variants
|
|
1026
|
-
if (
|
|
1027
|
-
check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // `free`
|
|
1028
|
-
check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // `mdat` MJPEG
|
|
1029
|
-
check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // `moov`
|
|
1030
|
-
check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
|
|
1031
|
-
) {
|
|
1032
|
-
return {
|
|
1033
|
-
ext: 'mov',
|
|
1034
|
-
mime: 'video/quicktime'
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
724
|
+
// RIFF file format which might be AVI, WAV, QCP, etc
|
|
725
|
+
if (this.check([0x52, 0x49, 0x46, 0x46])) {
|
|
726
|
+
if (this.check([0x41, 0x56, 0x49], {offset: 8})) {
|
|
727
|
+
return {
|
|
728
|
+
ext: 'avi',
|
|
729
|
+
mime: 'video/vnd.avi',
|
|
730
|
+
};
|
|
731
|
+
}
|
|
1037
732
|
|
|
1038
|
-
|
|
733
|
+
if (this.check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
|
|
734
|
+
return {
|
|
735
|
+
ext: 'wav',
|
|
736
|
+
mime: 'audio/vnd.wave',
|
|
737
|
+
};
|
|
738
|
+
}
|
|
1039
739
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
740
|
+
// QLCM, QCP file
|
|
741
|
+
if (this.check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
|
|
742
|
+
return {
|
|
743
|
+
ext: 'qcp',
|
|
744
|
+
mime: 'audio/qcelp',
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
1046
748
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
749
|
+
if (this.checkString('SQLi')) {
|
|
750
|
+
return {
|
|
751
|
+
ext: 'sqlite',
|
|
752
|
+
mime: 'application/x-sqlite3',
|
|
753
|
+
};
|
|
754
|
+
}
|
|
1053
755
|
|
|
1054
|
-
|
|
756
|
+
if (this.check([0x4E, 0x45, 0x53, 0x1A])) {
|
|
757
|
+
return {
|
|
758
|
+
ext: 'nes',
|
|
759
|
+
mime: 'application/x-nintendo-nes-rom',
|
|
760
|
+
};
|
|
761
|
+
}
|
|
1055
762
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
763
|
+
if (this.checkString('Cr24')) {
|
|
764
|
+
return {
|
|
765
|
+
ext: 'crx',
|
|
766
|
+
mime: 'application/x-google-chrome-extension',
|
|
767
|
+
};
|
|
768
|
+
}
|
|
1062
769
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
await tokenizer.readBuffer(guid);
|
|
770
|
+
if (
|
|
771
|
+
this.checkString('MSCF')
|
|
772
|
+
|| this.checkString('ISc(')
|
|
773
|
+
) {
|
|
1068
774
|
return {
|
|
1069
|
-
|
|
1070
|
-
|
|
775
|
+
ext: 'cab',
|
|
776
|
+
mime: 'application/vnd.ms-cab-compressed',
|
|
1071
777
|
};
|
|
1072
778
|
}
|
|
1073
779
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
// Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
|
|
1081
|
-
const typeId = Buffer.alloc(16);
|
|
1082
|
-
payload -= await tokenizer.readBuffer(typeId);
|
|
780
|
+
if (this.check([0xED, 0xAB, 0xEE, 0xDB])) {
|
|
781
|
+
return {
|
|
782
|
+
ext: 'rpm',
|
|
783
|
+
mime: 'application/x-rpm',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
1083
786
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
787
|
+
if (this.check([0xC5, 0xD0, 0xD3, 0xC6])) {
|
|
788
|
+
return {
|
|
789
|
+
ext: 'eps',
|
|
790
|
+
mime: 'application/eps',
|
|
791
|
+
};
|
|
792
|
+
}
|
|
1091
793
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
794
|
+
if (this.check([0x28, 0xB5, 0x2F, 0xFD])) {
|
|
795
|
+
return {
|
|
796
|
+
ext: 'zst',
|
|
797
|
+
mime: 'application/zstd',
|
|
798
|
+
};
|
|
799
|
+
}
|
|
1099
800
|
|
|
1100
|
-
|
|
1101
|
-
}
|
|
801
|
+
// -- 5-byte signatures --
|
|
1102
802
|
|
|
1103
|
-
|
|
803
|
+
if (this.check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
|
|
804
|
+
return {
|
|
805
|
+
ext: 'otf',
|
|
806
|
+
mime: 'font/otf',
|
|
807
|
+
};
|
|
1104
808
|
}
|
|
1105
809
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
810
|
+
if (this.checkString('#!AMR')) {
|
|
811
|
+
return {
|
|
812
|
+
ext: 'amr',
|
|
813
|
+
mime: 'audio/amr',
|
|
814
|
+
};
|
|
815
|
+
}
|
|
1112
816
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
817
|
+
if (this.checkString('{\\rtf')) {
|
|
818
|
+
return {
|
|
819
|
+
ext: 'rtf',
|
|
820
|
+
mime: 'application/rtf',
|
|
821
|
+
};
|
|
822
|
+
}
|
|
1119
823
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
824
|
+
if (this.check([0x46, 0x4C, 0x56, 0x01])) {
|
|
825
|
+
return {
|
|
826
|
+
ext: 'flv',
|
|
827
|
+
mime: 'video/x-flv',
|
|
828
|
+
};
|
|
829
|
+
}
|
|
1126
830
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
831
|
+
if (this.checkString('IMPM')) {
|
|
832
|
+
return {
|
|
833
|
+
ext: 'it',
|
|
834
|
+
mime: 'audio/x-it',
|
|
835
|
+
};
|
|
836
|
+
}
|
|
1133
837
|
|
|
1134
|
-
|
|
1135
|
-
|
|
838
|
+
if (
|
|
839
|
+
this.checkString('-lh0-', {offset: 2})
|
|
840
|
+
|| this.checkString('-lh1-', {offset: 2})
|
|
841
|
+
|| this.checkString('-lh2-', {offset: 2})
|
|
842
|
+
|| this.checkString('-lh3-', {offset: 2})
|
|
843
|
+
|| this.checkString('-lh4-', {offset: 2})
|
|
844
|
+
|| this.checkString('-lh5-', {offset: 2})
|
|
845
|
+
|| this.checkString('-lh6-', {offset: 2})
|
|
846
|
+
|| this.checkString('-lh7-', {offset: 2})
|
|
847
|
+
|| this.checkString('-lzs-', {offset: 2})
|
|
848
|
+
|| this.checkString('-lz4-', {offset: 2})
|
|
849
|
+
|| this.checkString('-lz5-', {offset: 2})
|
|
850
|
+
|| this.checkString('-lhd-', {offset: 2})
|
|
851
|
+
) {
|
|
852
|
+
return {
|
|
853
|
+
ext: 'lzh',
|
|
854
|
+
mime: 'application/x-lzh-compressed',
|
|
855
|
+
};
|
|
856
|
+
}
|
|
1136
857
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
858
|
+
// MPEG program stream (PS or MPEG-PS)
|
|
859
|
+
if (this.check([0x00, 0x00, 0x01, 0xBA])) {
|
|
860
|
+
// MPEG-PS, MPEG-1 Part 1
|
|
861
|
+
if (this.check([0x21], {offset: 4, mask: [0xF1]})) {
|
|
1141
862
|
return {
|
|
1142
|
-
ext: '
|
|
1143
|
-
mime: '
|
|
863
|
+
ext: 'mpg', // May also be .ps, .mpeg
|
|
864
|
+
mime: 'video/MP1S',
|
|
1144
865
|
};
|
|
1145
|
-
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// MPEG-PS, MPEG-2 Part 1
|
|
869
|
+
if (this.check([0x44], {offset: 4, mask: [0xC4]})) {
|
|
1146
870
|
return {
|
|
1147
|
-
ext: '
|
|
1148
|
-
mime: '
|
|
871
|
+
ext: 'mpg', // May also be .mpg, .m2p, .vob or .sub
|
|
872
|
+
mime: 'video/MP2P',
|
|
1149
873
|
};
|
|
1150
|
-
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (this.checkString('ITSF')) {
|
|
878
|
+
return {
|
|
879
|
+
ext: 'chm',
|
|
880
|
+
mime: 'application/vnd.ms-htmlhelp',
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// -- 6-byte signatures --
|
|
885
|
+
|
|
886
|
+
if (this.check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
|
|
887
|
+
return {
|
|
888
|
+
ext: 'xz',
|
|
889
|
+
mime: 'application/x-xz',
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
if (this.checkString('<?xml ')) {
|
|
894
|
+
return {
|
|
895
|
+
ext: 'xml',
|
|
896
|
+
mime: 'application/xml',
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (this.check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
|
|
901
|
+
return {
|
|
902
|
+
ext: '7z',
|
|
903
|
+
mime: 'application/x-7z-compressed',
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (
|
|
908
|
+
this.check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7])
|
|
909
|
+
&& (this.buffer[6] === 0x0 || this.buffer[6] === 0x1)
|
|
910
|
+
) {
|
|
911
|
+
return {
|
|
912
|
+
ext: 'rar',
|
|
913
|
+
mime: 'application/x-rar-compressed',
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (this.checkString('solid ')) {
|
|
918
|
+
return {
|
|
919
|
+
ext: 'stl',
|
|
920
|
+
mime: 'model/stl',
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// -- 7-byte signatures --
|
|
925
|
+
|
|
926
|
+
if (this.checkString('BLENDER')) {
|
|
927
|
+
return {
|
|
928
|
+
ext: 'blend',
|
|
929
|
+
mime: 'application/x-blender',
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
if (this.checkString('!<arch>')) {
|
|
934
|
+
await tokenizer.ignore(8);
|
|
935
|
+
const string = await tokenizer.readToken(new Token.StringType(13, 'ascii'));
|
|
936
|
+
if (string === 'debian-binary') {
|
|
1151
937
|
return {
|
|
1152
|
-
ext: '
|
|
1153
|
-
mime: '
|
|
938
|
+
ext: 'deb',
|
|
939
|
+
mime: 'application/x-deb',
|
|
1154
940
|
};
|
|
1155
|
-
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return {
|
|
944
|
+
ext: 'ar',
|
|
945
|
+
mime: 'application/x-unix-archive',
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// -- 8-byte signatures --
|
|
950
|
+
|
|
951
|
+
if (this.check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
|
|
952
|
+
// APNG format (https://wiki.mozilla.org/APNG_Specification)
|
|
953
|
+
// 1. Find the first IDAT (image data) chunk (49 44 41 54)
|
|
954
|
+
// 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C)
|
|
955
|
+
|
|
956
|
+
// Offset calculated as follows:
|
|
957
|
+
// - 8 bytes: PNG signature
|
|
958
|
+
// - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk
|
|
959
|
+
|
|
960
|
+
await tokenizer.ignore(8); // ignore PNG signature
|
|
961
|
+
|
|
962
|
+
async function readChunkHeader() {
|
|
1156
963
|
return {
|
|
1157
|
-
|
|
1158
|
-
|
|
964
|
+
length: await tokenizer.readToken(Token.INT32_BE),
|
|
965
|
+
type: await tokenizer.readToken(new Token.StringType(4, 'binary')),
|
|
1159
966
|
};
|
|
1160
|
-
|
|
1161
|
-
return;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
967
|
+
}
|
|
1164
968
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
ext: 'jxl',
|
|
1171
|
-
mime: 'image/jxl'
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
969
|
+
do {
|
|
970
|
+
const chunk = await readChunkHeader();
|
|
971
|
+
if (chunk.length < 0) {
|
|
972
|
+
return; // Invalid chunk length
|
|
973
|
+
}
|
|
1174
974
|
|
|
1175
|
-
|
|
975
|
+
switch (chunk.type) {
|
|
976
|
+
case 'IDAT':
|
|
977
|
+
return {
|
|
978
|
+
ext: 'png',
|
|
979
|
+
mime: 'image/png',
|
|
980
|
+
};
|
|
981
|
+
case 'acTL':
|
|
982
|
+
return {
|
|
983
|
+
ext: 'apng',
|
|
984
|
+
mime: 'image/apng',
|
|
985
|
+
};
|
|
986
|
+
default:
|
|
987
|
+
await tokenizer.ignore(chunk.length + 4); // Ignore chunk-data + CRC
|
|
988
|
+
}
|
|
989
|
+
} while (tokenizer.position + 8 < tokenizer.fileInfo.size);
|
|
1176
990
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
ext: 'mpg',
|
|
1183
|
-
mime: 'video/mpeg'
|
|
1184
|
-
};
|
|
1185
|
-
}
|
|
991
|
+
return {
|
|
992
|
+
ext: 'png',
|
|
993
|
+
mime: 'image/png',
|
|
994
|
+
};
|
|
995
|
+
}
|
|
1186
996
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
997
|
+
if (this.check([0x41, 0x52, 0x52, 0x4F, 0x57, 0x31, 0x00, 0x00])) {
|
|
998
|
+
return {
|
|
999
|
+
ext: 'arrow',
|
|
1000
|
+
mime: 'application/x-apache-arrow',
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1193
1003
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1004
|
+
if (this.check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) {
|
|
1005
|
+
return {
|
|
1006
|
+
ext: 'glb',
|
|
1007
|
+
mime: 'model/gltf-binary',
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1200
1010
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1011
|
+
// `mov` format variants
|
|
1012
|
+
if (
|
|
1013
|
+
this.check([0x66, 0x72, 0x65, 0x65], {offset: 4}) // `free`
|
|
1014
|
+
|| this.check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) // `mdat` MJPEG
|
|
1015
|
+
|| this.check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) // `moov`
|
|
1016
|
+
|| this.check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
|
|
1017
|
+
) {
|
|
1018
|
+
return {
|
|
1019
|
+
ext: 'mov',
|
|
1020
|
+
mime: 'video/quicktime',
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1207
1023
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1024
|
+
if (this.check([0xEF, 0xBB, 0xBF]) && this.checkString('<?xml', {offset: 3})) { // UTF-8-BOM
|
|
1025
|
+
return {
|
|
1026
|
+
ext: 'xml',
|
|
1027
|
+
mime: 'application/xml',
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1215
1030
|
|
|
1216
|
-
|
|
1217
|
-
await tokenizer.peekBuffer(buffer, {length: Math.min(256, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1031
|
+
// -- 9-byte signatures --
|
|
1218
1032
|
|
|
1219
|
-
|
|
1033
|
+
if (this.check([0x49, 0x49, 0x52, 0x4F, 0x08, 0x00, 0x00, 0x00, 0x18])) {
|
|
1034
|
+
return {
|
|
1035
|
+
ext: 'orf',
|
|
1036
|
+
mime: 'image/x-olympus-orf',
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1220
1039
|
|
|
1221
|
-
|
|
1222
|
-
if (checkString('VCARD', {offset: 6})) {
|
|
1040
|
+
if (this.checkString('gimp xcf ')) {
|
|
1223
1041
|
return {
|
|
1224
|
-
ext: '
|
|
1225
|
-
mime: '
|
|
1042
|
+
ext: 'xcf',
|
|
1043
|
+
mime: 'image/x-xcf',
|
|
1226
1044
|
};
|
|
1227
1045
|
}
|
|
1228
1046
|
|
|
1229
|
-
|
|
1047
|
+
// -- 12-byte signatures --
|
|
1048
|
+
|
|
1049
|
+
if (this.check([0x49, 0x49, 0x55, 0x00, 0x18, 0x00, 0x00, 0x00, 0x88, 0xE7, 0x74, 0xD8])) {
|
|
1230
1050
|
return {
|
|
1231
|
-
ext: '
|
|
1232
|
-
mime: '
|
|
1051
|
+
ext: 'rw2',
|
|
1052
|
+
mime: 'image/x-panasonic-rw2',
|
|
1233
1053
|
};
|
|
1234
1054
|
}
|
|
1235
|
-
}
|
|
1236
1055
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1056
|
+
// ASF_Header_Object first 80 bytes
|
|
1057
|
+
if (this.check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
|
|
1058
|
+
async function readHeader() {
|
|
1059
|
+
const guid = Buffer.alloc(16);
|
|
1060
|
+
await tokenizer.readBuffer(guid);
|
|
1061
|
+
return {
|
|
1062
|
+
id: guid,
|
|
1063
|
+
size: Number(await tokenizer.readToken(Token.UINT64_LE)),
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1244
1066
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1067
|
+
await tokenizer.ignore(30);
|
|
1068
|
+
// Search for header should be in first 1KB of file.
|
|
1069
|
+
while (tokenizer.position + 24 < tokenizer.fileInfo.size) {
|
|
1070
|
+
const header = await readHeader();
|
|
1071
|
+
let payload = header.size - 24;
|
|
1072
|
+
if (_check(header.id, [0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65])) {
|
|
1073
|
+
// Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
|
|
1074
|
+
const typeId = Buffer.alloc(16);
|
|
1075
|
+
payload -= await tokenizer.readBuffer(typeId);
|
|
1076
|
+
|
|
1077
|
+
if (_check(typeId, [0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
|
|
1078
|
+
// Found audio:
|
|
1079
|
+
return {
|
|
1080
|
+
ext: 'asf',
|
|
1081
|
+
mime: 'audio/x-ms-asf',
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1251
1084
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1085
|
+
if (_check(typeId, [0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
|
|
1086
|
+
// Found video:
|
|
1087
|
+
return {
|
|
1088
|
+
ext: 'asf',
|
|
1089
|
+
mime: 'video/x-ms-asf',
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1258
1092
|
|
|
1259
|
-
|
|
1260
|
-
const jsonSize = buffer.readUInt32LE(12);
|
|
1261
|
-
if (jsonSize > 12 && buffer.length >= jsonSize + 16) {
|
|
1262
|
-
try {
|
|
1263
|
-
const header = buffer.slice(16, jsonSize + 16).toString();
|
|
1264
|
-
const json = JSON.parse(header);
|
|
1265
|
-
// Check if Pickle is ASAR
|
|
1266
|
-
if (json.files) { // Final check, assuring Pickle/ASAR format
|
|
1267
|
-
return {
|
|
1268
|
-
ext: 'asar',
|
|
1269
|
-
mime: 'application/x-asar'
|
|
1270
|
-
};
|
|
1093
|
+
break;
|
|
1271
1094
|
}
|
|
1272
|
-
|
|
1095
|
+
|
|
1096
|
+
await tokenizer.ignore(payload);
|
|
1273
1097
|
}
|
|
1098
|
+
|
|
1099
|
+
// Default to ASF generic extension
|
|
1100
|
+
return {
|
|
1101
|
+
ext: 'asf',
|
|
1102
|
+
mime: 'application/vnd.ms-asf',
|
|
1103
|
+
};
|
|
1274
1104
|
}
|
|
1275
|
-
}
|
|
1276
1105
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1106
|
+
if (this.check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
|
|
1107
|
+
return {
|
|
1108
|
+
ext: 'ktx',
|
|
1109
|
+
mime: 'image/ktx',
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1283
1112
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1113
|
+
if ((this.check([0x7E, 0x10, 0x04]) || this.check([0x7E, 0x18, 0x04])) && this.check([0x30, 0x4D, 0x49, 0x45], {offset: 4})) {
|
|
1114
|
+
return {
|
|
1115
|
+
ext: 'mie',
|
|
1116
|
+
mime: 'application/x-mie',
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1290
1119
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1120
|
+
if (this.check([0x27, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], {offset: 2})) {
|
|
1121
|
+
return {
|
|
1122
|
+
ext: 'shp',
|
|
1123
|
+
mime: 'application/x-esri-shape',
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1297
1126
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
ext: 'mobi',
|
|
1301
|
-
mime: 'application/x-mobipocket-ebook'
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1127
|
+
if (this.check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) {
|
|
1128
|
+
// JPEG-2000 family
|
|
1304
1129
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1130
|
+
await tokenizer.ignore(20);
|
|
1131
|
+
const type = await tokenizer.readToken(new Token.StringType(4, 'ascii'));
|
|
1132
|
+
switch (type) {
|
|
1133
|
+
case 'jp2 ':
|
|
1134
|
+
return {
|
|
1135
|
+
ext: 'jp2',
|
|
1136
|
+
mime: 'image/jp2',
|
|
1137
|
+
};
|
|
1138
|
+
case 'jpx ':
|
|
1139
|
+
return {
|
|
1140
|
+
ext: 'jpx',
|
|
1141
|
+
mime: 'image/jpx',
|
|
1142
|
+
};
|
|
1143
|
+
case 'jpm ':
|
|
1144
|
+
return {
|
|
1145
|
+
ext: 'jpm',
|
|
1146
|
+
mime: 'image/jpm',
|
|
1147
|
+
};
|
|
1148
|
+
case 'mjp2':
|
|
1149
|
+
return {
|
|
1150
|
+
ext: 'mj2',
|
|
1151
|
+
mime: 'image/mj2',
|
|
1152
|
+
};
|
|
1153
|
+
default:
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1311
1157
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1158
|
+
if (
|
|
1159
|
+
this.check([0xFF, 0x0A])
|
|
1160
|
+
|| this.check([0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A])
|
|
1161
|
+
) {
|
|
1162
|
+
return {
|
|
1163
|
+
ext: 'jxl',
|
|
1164
|
+
mime: 'image/jxl',
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1318
1167
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1168
|
+
if (
|
|
1169
|
+
this.check([0xFE, 0xFF, 0, 60, 0, 63, 0, 120, 0, 109, 0, 108]) // UTF-16-BOM-LE
|
|
1170
|
+
|| this.check([0xFF, 0xFE, 60, 0, 63, 0, 120, 0, 109, 0, 108, 0]) // UTF-16-BOM-LE
|
|
1171
|
+
) {
|
|
1172
|
+
return {
|
|
1173
|
+
ext: 'xml',
|
|
1174
|
+
mime: 'application/xml',
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1325
1177
|
|
|
1326
|
-
|
|
1327
|
-
check([0x4C, 0x50], {offset: 34}) &&
|
|
1328
|
-
(
|
|
1329
|
-
check([0x00, 0x00, 0x01], {offset: 8}) ||
|
|
1330
|
-
check([0x01, 0x00, 0x02], {offset: 8}) ||
|
|
1331
|
-
check([0x02, 0x00, 0x02], {offset: 8})
|
|
1332
|
-
)
|
|
1333
|
-
) {
|
|
1334
|
-
return {
|
|
1335
|
-
ext: 'eot',
|
|
1336
|
-
mime: 'application/vnd.ms-fontobject'
|
|
1337
|
-
};
|
|
1338
|
-
}
|
|
1178
|
+
// -- Unsafe signatures --
|
|
1339
1179
|
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1180
|
+
if (
|
|
1181
|
+
this.check([0x0, 0x0, 0x1, 0xBA])
|
|
1182
|
+
|| this.check([0x0, 0x0, 0x1, 0xB3])
|
|
1183
|
+
) {
|
|
1184
|
+
return {
|
|
1185
|
+
ext: 'mpg',
|
|
1186
|
+
mime: 'video/mpeg',
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1346
1189
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1190
|
+
if (this.check([0x00, 0x01, 0x00, 0x00, 0x00])) {
|
|
1191
|
+
return {
|
|
1192
|
+
ext: 'ttf',
|
|
1193
|
+
mime: 'font/ttf',
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1349
1196
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1197
|
+
if (this.check([0x00, 0x00, 0x01, 0x00])) {
|
|
1198
|
+
return {
|
|
1199
|
+
ext: 'ico',
|
|
1200
|
+
mime: 'image/x-icon',
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1357
1203
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1204
|
+
if (this.check([0x00, 0x00, 0x02, 0x00])) {
|
|
1205
|
+
return {
|
|
1206
|
+
ext: 'cur',
|
|
1207
|
+
mime: 'image/x-icon',
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1364
1210
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1211
|
+
if (this.check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
|
|
1212
|
+
// Detected Microsoft Compound File Binary File (MS-CFB) Format.
|
|
1213
|
+
return {
|
|
1214
|
+
ext: 'cfb',
|
|
1215
|
+
mime: 'application/x-cfb',
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1371
1218
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1219
|
+
// Increase sample size from 12 to 256.
|
|
1220
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(256, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1221
|
+
|
|
1222
|
+
// -- 15-byte signatures --
|
|
1223
|
+
|
|
1224
|
+
if (this.checkString('BEGIN:')) {
|
|
1225
|
+
if (this.checkString('VCARD', {offset: 6})) {
|
|
1377
1226
|
return {
|
|
1378
|
-
ext: '
|
|
1379
|
-
mime: '
|
|
1227
|
+
ext: 'vcf',
|
|
1228
|
+
mime: 'text/vcard',
|
|
1380
1229
|
};
|
|
1381
1230
|
}
|
|
1382
1231
|
|
|
1383
|
-
|
|
1232
|
+
if (this.checkString('VCALENDAR', {offset: 6})) {
|
|
1233
|
+
return {
|
|
1234
|
+
ext: 'ics',
|
|
1235
|
+
mime: 'text/calendar',
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// `raf` is here just to keep all the raw image detectors together.
|
|
1241
|
+
if (this.checkString('FUJIFILMCCD-RAW')) {
|
|
1384
1242
|
return {
|
|
1385
|
-
ext: '
|
|
1386
|
-
mime: '
|
|
1243
|
+
ext: 'raf',
|
|
1244
|
+
mime: 'image/x-fujifilm-raf',
|
|
1387
1245
|
};
|
|
1388
1246
|
}
|
|
1389
1247
|
|
|
1390
|
-
|
|
1391
|
-
// Check for MPEG layer 3
|
|
1392
|
-
if (check([0x02], {offset: 1, mask: [0x06]})) {
|
|
1248
|
+
if (this.checkString('Extended Module:')) {
|
|
1393
1249
|
return {
|
|
1394
|
-
ext: '
|
|
1395
|
-
mime: 'audio/
|
|
1250
|
+
ext: 'xm',
|
|
1251
|
+
mime: 'audio/x-xm',
|
|
1396
1252
|
};
|
|
1397
1253
|
}
|
|
1398
1254
|
|
|
1399
|
-
|
|
1400
|
-
if (check([0x04], {offset: 1, mask: [0x06]})) {
|
|
1255
|
+
if (this.checkString('Creative Voice File')) {
|
|
1401
1256
|
return {
|
|
1402
|
-
ext: '
|
|
1403
|
-
mime: 'audio/
|
|
1257
|
+
ext: 'voc',
|
|
1258
|
+
mime: 'audio/x-voc',
|
|
1404
1259
|
};
|
|
1405
1260
|
}
|
|
1406
1261
|
|
|
1407
|
-
//
|
|
1408
|
-
|
|
1262
|
+
if (this.check([0x04, 0x00, 0x00, 0x00]) && this.buffer.length >= 16) { // Rough & quick check Pickle/ASAR
|
|
1263
|
+
const jsonSize = this.buffer.readUInt32LE(12);
|
|
1264
|
+
if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) {
|
|
1265
|
+
try {
|
|
1266
|
+
const header = this.buffer.slice(16, jsonSize + 16).toString();
|
|
1267
|
+
const json = JSON.parse(header);
|
|
1268
|
+
// Check if Pickle is ASAR
|
|
1269
|
+
if (json.files) { // Final check, assuring Pickle/ASAR format
|
|
1270
|
+
return {
|
|
1271
|
+
ext: 'asar',
|
|
1272
|
+
mime: 'application/x-asar',
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
} catch {}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (this.check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
|
|
1409
1280
|
return {
|
|
1410
|
-
ext: '
|
|
1411
|
-
mime: '
|
|
1281
|
+
ext: 'mxf',
|
|
1282
|
+
mime: 'application/mxf',
|
|
1412
1283
|
};
|
|
1413
1284
|
}
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
1285
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1286
|
+
if (this.checkString('SCRM', {offset: 44})) {
|
|
1287
|
+
return {
|
|
1288
|
+
ext: 's3m',
|
|
1289
|
+
mime: 'audio/x-s3m',
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1420
1292
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
outputStream = stream.pipeline(readableStream, pass, () => {
|
|
1428
|
-
});
|
|
1429
|
-
} else {
|
|
1430
|
-
outputStream = readableStream.pipe(pass);
|
|
1293
|
+
// Raw MPEG-2 transport stream (188-byte packets)
|
|
1294
|
+
if (this.check([0x47]) && this.check([0x47], {offset: 188})) {
|
|
1295
|
+
return {
|
|
1296
|
+
ext: 'mts',
|
|
1297
|
+
mime: 'video/mp2t',
|
|
1298
|
+
};
|
|
1431
1299
|
}
|
|
1432
1300
|
|
|
1433
|
-
//
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
reject(error);
|
|
1301
|
+
// Blu-ray Disc Audio-Video (BDAV) MPEG-2 transport stream has 4-byte TP_extra_header before each 188-byte packet
|
|
1302
|
+
if (this.check([0x47], {offset: 4}) && this.check([0x47], {offset: 196})) {
|
|
1303
|
+
return {
|
|
1304
|
+
ext: 'mts',
|
|
1305
|
+
mime: 'video/mp2t',
|
|
1306
|
+
};
|
|
1440
1307
|
}
|
|
1441
1308
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1309
|
+
if (this.check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) {
|
|
1310
|
+
return {
|
|
1311
|
+
ext: 'mobi',
|
|
1312
|
+
mime: 'application/x-mobipocket-ebook',
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
if (this.check([0x44, 0x49, 0x43, 0x4D], {offset: 128})) {
|
|
1317
|
+
return {
|
|
1318
|
+
ext: 'dcm',
|
|
1319
|
+
mime: 'application/dicom',
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (this.check([0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46])) {
|
|
1324
|
+
return {
|
|
1325
|
+
ext: 'lnk',
|
|
1326
|
+
mime: 'application/x.ms.shortcut', // Invented by us
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (this.check([0x62, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00])) {
|
|
1331
|
+
return {
|
|
1332
|
+
ext: 'alias',
|
|
1333
|
+
mime: 'application/x.apple.alias', // Invented by us
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (
|
|
1338
|
+
this.check([0x4C, 0x50], {offset: 34})
|
|
1339
|
+
&& (
|
|
1340
|
+
this.check([0x00, 0x00, 0x01], {offset: 8})
|
|
1341
|
+
|| this.check([0x01, 0x00, 0x02], {offset: 8})
|
|
1342
|
+
|| this.check([0x02, 0x00, 0x02], {offset: 8})
|
|
1343
|
+
)
|
|
1344
|
+
) {
|
|
1345
|
+
return {
|
|
1346
|
+
ext: 'eot',
|
|
1347
|
+
mime: 'application/vnd.ms-fontobject',
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (this.check([0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D])) {
|
|
1352
|
+
return {
|
|
1353
|
+
ext: 'indd',
|
|
1354
|
+
mime: 'application/x-indesign',
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Increase sample size from 256 to 512
|
|
1359
|
+
await tokenizer.peekBuffer(this.buffer, {length: Math.min(512, tokenizer.fileInfo.size), mayBeLess: true});
|
|
1360
|
+
|
|
1361
|
+
// Requires a buffer size of 512 bytes
|
|
1362
|
+
if (tarHeaderChecksumMatches(this.buffer)) {
|
|
1363
|
+
return {
|
|
1364
|
+
ext: 'tar',
|
|
1365
|
+
mime: 'application/x-tar',
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (this.check([0xFF, 0xFE, 0xFF, 0x0E, 0x53, 0x00, 0x6B, 0x00, 0x65, 0x00, 0x74, 0x00, 0x63, 0x00, 0x68, 0x00, 0x55, 0x00, 0x70, 0x00, 0x20, 0x00, 0x4D, 0x00, 0x6F, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6C, 0x00])) {
|
|
1370
|
+
return {
|
|
1371
|
+
ext: 'skp',
|
|
1372
|
+
mime: 'application/vnd.sketchup.skp',
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
if (this.checkString('-----BEGIN PGP MESSAGE-----')) {
|
|
1377
|
+
return {
|
|
1378
|
+
ext: 'pgp',
|
|
1379
|
+
mime: 'application/pgp-encrypted',
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE)
|
|
1384
|
+
if (this.buffer.length >= 2 && this.check([0xFF, 0xE0], {offset: 0, mask: [0xFF, 0xE0]})) {
|
|
1385
|
+
if (this.check([0x10], {offset: 1, mask: [0x16]})) {
|
|
1386
|
+
// Check for (ADTS) MPEG-2
|
|
1387
|
+
if (this.check([0x08], {offset: 1, mask: [0x08]})) {
|
|
1388
|
+
return {
|
|
1389
|
+
ext: 'aac',
|
|
1390
|
+
mime: 'audio/aac',
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// Must be (ADTS) MPEG-4
|
|
1395
|
+
return {
|
|
1396
|
+
ext: 'aac',
|
|
1397
|
+
mime: 'audio/aac',
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// MPEG 1 or 2 Layer 3 header
|
|
1402
|
+
// Check for MPEG layer 3
|
|
1403
|
+
if (this.check([0x02], {offset: 1, mask: [0x06]})) {
|
|
1404
|
+
return {
|
|
1405
|
+
ext: 'mp3',
|
|
1406
|
+
mime: 'audio/mpeg',
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// Check for MPEG layer 2
|
|
1411
|
+
if (this.check([0x04], {offset: 1, mask: [0x06]})) {
|
|
1412
|
+
return {
|
|
1413
|
+
ext: 'mp2',
|
|
1414
|
+
mime: 'audio/mpeg',
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Check for MPEG layer 1
|
|
1419
|
+
if (this.check([0x06], {offset: 1, mask: [0x06]})) {
|
|
1420
|
+
return {
|
|
1421
|
+
ext: 'mp1',
|
|
1422
|
+
mime: 'audio/mpeg',
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1456
1426
|
}
|
|
1457
|
-
});
|
|
1458
1427
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1428
|
+
async readTiffTag(bigEndian) {
|
|
1429
|
+
const tagId = await this.tokenizer.readToken(bigEndian ? Token.UINT16_BE : Token.UINT16_LE);
|
|
1430
|
+
this.tokenizer.ignore(10);
|
|
1431
|
+
switch (tagId) {
|
|
1432
|
+
case 50_341:
|
|
1433
|
+
return {
|
|
1434
|
+
ext: 'arw',
|
|
1435
|
+
mime: 'image/x-sony-arw',
|
|
1436
|
+
};
|
|
1437
|
+
case 50_706:
|
|
1438
|
+
return {
|
|
1439
|
+
ext: 'dng',
|
|
1440
|
+
mime: 'image/x-adobe-dng',
|
|
1441
|
+
};
|
|
1442
|
+
default:
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
async readTiffIFD(bigEndian) {
|
|
1447
|
+
const numberOfTags = await this.tokenizer.readToken(bigEndian ? Token.UINT16_BE : Token.UINT16_LE);
|
|
1448
|
+
for (let n = 0; n < numberOfTags; ++n) {
|
|
1449
|
+
const fileType = await this.readTiffTag(bigEndian);
|
|
1450
|
+
if (fileType) {
|
|
1451
|
+
return fileType;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
async readTiffHeader(bigEndian) {
|
|
1457
|
+
const version = (bigEndian ? Token.UINT16_BE : Token.UINT16_LE).get(this.buffer, 2);
|
|
1458
|
+
const ifdOffset = (bigEndian ? Token.UINT32_BE : Token.UINT32_LE).get(this.buffer, 4);
|
|
1459
|
+
|
|
1460
|
+
if (version === 42) {
|
|
1461
|
+
// TIFF file header
|
|
1462
|
+
if (ifdOffset >= 6) {
|
|
1463
|
+
if (this.checkString('CR', {offset: 8})) {
|
|
1464
|
+
return {
|
|
1465
|
+
ext: 'cr2',
|
|
1466
|
+
mime: 'image/x-canon-cr2',
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
if (ifdOffset >= 8 && (this.check([0x1C, 0x00, 0xFE, 0x00], {offset: 8}) || this.check([0x1F, 0x00, 0x0B, 0x00], {offset: 8}))) {
|
|
1471
|
+
return {
|
|
1472
|
+
ext: 'nef',
|
|
1473
|
+
mime: 'image/x-nikon-nef',
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
await this.tokenizer.ignore(ifdOffset);
|
|
1479
|
+
const fileType = await this.readTiffIFD(false);
|
|
1480
|
+
return fileType ? fileType : {
|
|
1481
|
+
ext: 'tif',
|
|
1482
|
+
mime: 'image/tiff',
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
if (version === 43) { // Big TIFF file header
|
|
1487
|
+
return {
|
|
1488
|
+
ext: 'tif',
|
|
1489
|
+
mime: 'image/tiff',
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1462
1492
|
}
|
|
1463
|
-
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
export async function fileTypeStream(readableStream, {sampleSize = minimumBytes} = {}) {
|
|
1496
|
+
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
|
1497
|
+
const {default: stream} = await import('node:stream');
|
|
1498
|
+
|
|
1499
|
+
return new Promise((resolve, reject) => {
|
|
1500
|
+
readableStream.on('error', reject);
|
|
1501
|
+
|
|
1502
|
+
readableStream.once('readable', () => {
|
|
1503
|
+
(async () => {
|
|
1504
|
+
try {
|
|
1505
|
+
// Set up output stream
|
|
1506
|
+
const pass = new stream.PassThrough();
|
|
1507
|
+
const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass);
|
|
1508
|
+
|
|
1509
|
+
// Read the input stream and detect the filetype
|
|
1510
|
+
const chunk = readableStream.read(sampleSize) || readableStream.read() || Buffer.alloc(0);
|
|
1511
|
+
try {
|
|
1512
|
+
const fileType = await fileTypeFromBuffer(chunk);
|
|
1513
|
+
pass.fileType = fileType;
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
if (error instanceof strtok3.EndOfStreamError) {
|
|
1516
|
+
pass.fileType = undefined;
|
|
1517
|
+
} else {
|
|
1518
|
+
reject(error);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
resolve(outputStream);
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
reject(error);
|
|
1525
|
+
}
|
|
1526
|
+
})();
|
|
1527
|
+
});
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1464
1530
|
|
|
1465
|
-
|
|
1531
|
+
export const supportedExtensions = new Set(extensions);
|
|
1532
|
+
export const supportedMimeTypes = new Set(mimeTypes);
|