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