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.
@@ -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);