file-type 18.7.0 → 19.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core.d.ts +16 -51
- package/core.js +43 -71
- package/index.d.ts +58 -2
- package/index.js +61 -2
- package/package.json +14 -13
- package/readme.md +9 -7
- package/supported.js +1 -1
- package/util.js +7 -5
- package/browser.d.ts +0 -29
- package/browser.js +0 -15
package/core.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
Typings for primary entry point, Node.js specific typings can be found in index.d.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {ReadableStream as WebReadableStream} from 'node:stream/web';
|
|
2
6
|
import type {ITokenizer} from 'strtok3';
|
|
3
7
|
|
|
8
|
+
/**
|
|
9
|
+
Either the Node.js ReadableStream or the `lib.dom.d.ts` ReadableStream.
|
|
10
|
+
Related issue: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60377
|
|
11
|
+
*/
|
|
12
|
+
export type AnyWebReadableStream<G> = WebReadableStream<G> | ReadableStream<G>;
|
|
13
|
+
|
|
4
14
|
export type FileExtension =
|
|
5
15
|
| 'jpg'
|
|
6
16
|
| 'png'
|
|
@@ -192,7 +202,7 @@ export type MimeType =
|
|
|
192
202
|
| 'video/webm'
|
|
193
203
|
| 'video/quicktime'
|
|
194
204
|
| 'video/vnd.avi'
|
|
195
|
-
| 'audio/
|
|
205
|
+
| 'audio/wav'
|
|
196
206
|
| 'audio/qcelp'
|
|
197
207
|
| 'audio/x-ms-asf'
|
|
198
208
|
| 'video/x-ms-asf'
|
|
@@ -318,18 +328,14 @@ export type FileTypeResult = {
|
|
|
318
328
|
readonly mime: MimeType;
|
|
319
329
|
};
|
|
320
330
|
|
|
321
|
-
export type ReadableStreamWithFileType = ReadableStream & {
|
|
322
|
-
readonly fileType?: FileTypeResult;
|
|
323
|
-
};
|
|
324
|
-
|
|
325
331
|
/**
|
|
326
|
-
Detect the file type of a `
|
|
332
|
+
Detect the file type of a `Uint8Array`, or `ArrayBuffer`.
|
|
327
333
|
|
|
328
334
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
329
335
|
|
|
330
336
|
If file access is available, it is recommended to use `.fromFile()` instead.
|
|
331
337
|
|
|
332
|
-
@param buffer - An Uint8Array or
|
|
338
|
+
@param buffer - An Uint8Array or ArrayBuffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well.
|
|
333
339
|
@returns The detected file type, or `undefined` when there is no match.
|
|
334
340
|
*/
|
|
335
341
|
export function fileTypeFromBuffer(buffer: Uint8Array | ArrayBuffer): Promise<FileTypeResult | undefined>;
|
|
@@ -339,10 +345,10 @@ Detect the file type of a Node.js [readable stream](https://nodejs.org/api/strea
|
|
|
339
345
|
|
|
340
346
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
341
347
|
|
|
342
|
-
@param stream - A
|
|
348
|
+
@param stream - A Node.js Readable stream or Web API Readable Stream representing file data. The Web Readable stream **must be a byte stream**.
|
|
343
349
|
@returns The detected file type, or `undefined` when there is no match.
|
|
344
350
|
*/
|
|
345
|
-
export function fileTypeFromStream(stream:
|
|
351
|
+
export function fileTypeFromStream(stream: AnyWebReadableStream<Uint8Array>): Promise<FileTypeResult | undefined>;
|
|
346
352
|
|
|
347
353
|
/**
|
|
348
354
|
Detect the file type from an [`ITokenizer`](https://github.com/Borewit/strtok3#tokenizer) source.
|
|
@@ -391,37 +397,6 @@ export type StreamOptions = {
|
|
|
391
397
|
readonly sampleSize?: number;
|
|
392
398
|
};
|
|
393
399
|
|
|
394
|
-
/**
|
|
395
|
-
Returns a `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileTypeFromFile()`.
|
|
396
|
-
|
|
397
|
-
This method can be handy to put in between a stream, but it comes with a price.
|
|
398
|
-
Internally `stream()` builds up a buffer of `sampleSize` bytes, used as a sample, to determine the file type.
|
|
399
|
-
The sample size impacts the file detection resolution.
|
|
400
|
-
A smaller sample size will result in lower probability of the best file type detection.
|
|
401
|
-
|
|
402
|
-
**Note:** This method is only available when using Node.js.
|
|
403
|
-
**Note:** Requires Node.js 14 or later.
|
|
404
|
-
|
|
405
|
-
@param readableStream - A [readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) containing a file to examine.
|
|
406
|
-
@returns A `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileTypeFromFile()`.
|
|
407
|
-
|
|
408
|
-
@example
|
|
409
|
-
```
|
|
410
|
-
import got from 'got';
|
|
411
|
-
import {fileTypeStream} from 'file-type';
|
|
412
|
-
|
|
413
|
-
const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';
|
|
414
|
-
|
|
415
|
-
const stream1 = got.stream(url);
|
|
416
|
-
const stream2 = await fileTypeStream(stream1, {sampleSize: 1024});
|
|
417
|
-
|
|
418
|
-
if (stream2.fileType?.mime === 'image/jpeg') {
|
|
419
|
-
// stream2 can be used to stream the JPEG image (from the very beginning of the stream)
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
*/
|
|
423
|
-
export function fileTypeStream(readableStream: ReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
|
|
424
|
-
|
|
425
400
|
/**
|
|
426
401
|
Detect the file type of a [`Blob`](https://nodejs.org/api/buffer.html#class-blob) or [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File).
|
|
427
402
|
|
|
@@ -508,11 +483,6 @@ export declare class FileTypeParser {
|
|
|
508
483
|
*/
|
|
509
484
|
fromBuffer(buffer: Uint8Array | ArrayBuffer): Promise<FileTypeResult | undefined>;
|
|
510
485
|
|
|
511
|
-
/**
|
|
512
|
-
Works the same way as {@link fileTypeFromStream}, additionally taking into account custom detectors (if any were provided to the constructor).
|
|
513
|
-
*/
|
|
514
|
-
fromStream(stream: ReadableStream): Promise<FileTypeResult | undefined>;
|
|
515
|
-
|
|
516
486
|
/**
|
|
517
487
|
Works the same way as {@link fileTypeFromTokenizer}, additionally taking into account custom detectors (if any were provided to the constructor).
|
|
518
488
|
*/
|
|
@@ -522,9 +492,4 @@ export declare class FileTypeParser {
|
|
|
522
492
|
Works the same way as {@link fileTypeFromBlob}, additionally taking into account custom detectors (if any were provided to the constructor).
|
|
523
493
|
*/
|
|
524
494
|
fromBlob(blob: Blob): Promise<FileTypeResult | undefined>;
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor).
|
|
528
|
-
*/
|
|
529
|
-
toDetectionStream(readableStream: ReadableStream, options?: StreamOptions): Promise<FileTypeResult | undefined>;
|
|
530
495
|
}
|
package/core.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
Primary entry point, Node.js specific entry point is index.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
2
5
|
import * as Token from 'token-types';
|
|
3
|
-
import * as strtok3 from 'strtok3/core';
|
|
6
|
+
import * as strtok3 from 'strtok3/core';
|
|
7
|
+
import {includes, indexOf, getUintBE} from 'uint8array-extras';
|
|
4
8
|
import {
|
|
5
9
|
stringToBytes,
|
|
6
10
|
tarHeaderChecksumMatches,
|
|
@@ -8,7 +12,7 @@ import {
|
|
|
8
12
|
} from './util.js';
|
|
9
13
|
import {extensions, mimeTypes} from './supported.js';
|
|
10
14
|
|
|
11
|
-
const
|
|
15
|
+
export const reasonableDetectionSizeInBytes = 4100; // A fair amount of file-types are detectable within this range.
|
|
12
16
|
|
|
13
17
|
export async function fileTypeFromStream(stream) {
|
|
14
18
|
return new FileTypeParser().fromStream(stream);
|
|
@@ -75,7 +79,7 @@ export class FileTypeParser {
|
|
|
75
79
|
|
|
76
80
|
async fromBuffer(input) {
|
|
77
81
|
if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) {
|
|
78
|
-
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`
|
|
82
|
+
throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof input}\``);
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
@@ -88,12 +92,11 @@ export class FileTypeParser {
|
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
async fromBlob(blob) {
|
|
91
|
-
|
|
92
|
-
return this.fromBuffer(new Uint8Array(buffer));
|
|
95
|
+
return this.fromStream(blob.stream());
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
async fromStream(stream) {
|
|
96
|
-
const tokenizer = await strtok3.
|
|
99
|
+
const tokenizer = await strtok3.fromWebStream(stream);
|
|
97
100
|
try {
|
|
98
101
|
return await this.fromTokenizer(tokenizer);
|
|
99
102
|
} finally {
|
|
@@ -101,41 +104,6 @@ export class FileTypeParser {
|
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
async toDetectionStream(readableStream, options = {}) {
|
|
105
|
-
const {default: stream} = await import('node:stream');
|
|
106
|
-
const {sampleSize = minimumBytes} = options;
|
|
107
|
-
|
|
108
|
-
return new Promise((resolve, reject) => {
|
|
109
|
-
readableStream.on('error', reject);
|
|
110
|
-
|
|
111
|
-
readableStream.once('readable', () => {
|
|
112
|
-
(async () => {
|
|
113
|
-
try {
|
|
114
|
-
// Set up output stream
|
|
115
|
-
const pass = new stream.PassThrough();
|
|
116
|
-
const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass);
|
|
117
|
-
|
|
118
|
-
// Read the input stream and detect the filetype
|
|
119
|
-
const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? Buffer.alloc(0);
|
|
120
|
-
try {
|
|
121
|
-
pass.fileType = await this.fromBuffer(chunk);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (error instanceof strtok3.EndOfStreamError) {
|
|
124
|
-
pass.fileType = undefined;
|
|
125
|
-
} else {
|
|
126
|
-
reject(error);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
resolve(outputStream);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
reject(error);
|
|
133
|
-
}
|
|
134
|
-
})();
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
107
|
check(header, options) {
|
|
140
108
|
return _check(this.buffer, header, options);
|
|
141
109
|
}
|
|
@@ -145,7 +113,7 @@ export class FileTypeParser {
|
|
|
145
113
|
}
|
|
146
114
|
|
|
147
115
|
async parse(tokenizer) {
|
|
148
|
-
this.buffer =
|
|
116
|
+
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes);
|
|
149
117
|
|
|
150
118
|
// Keep reading until EOF if the file size is unknown.
|
|
151
119
|
if (tokenizer.fileInfo.size === undefined) {
|
|
@@ -372,12 +340,14 @@ export class FileTypeParser {
|
|
|
372
340
|
while (tokenizer.position + 30 < tokenizer.fileInfo.size) {
|
|
373
341
|
await tokenizer.readBuffer(this.buffer, {length: 30});
|
|
374
342
|
|
|
343
|
+
const view = new DataView(this.buffer.buffer);
|
|
344
|
+
|
|
375
345
|
// https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
|
|
376
346
|
const zipHeader = {
|
|
377
|
-
compressedSize:
|
|
378
|
-
uncompressedSize:
|
|
379
|
-
filenameLength:
|
|
380
|
-
extraFieldLength:
|
|
347
|
+
compressedSize: view.getUint32(18, true),
|
|
348
|
+
uncompressedSize: view.getUint32(22, true),
|
|
349
|
+
filenameLength: view.getUint16(26, true),
|
|
350
|
+
extraFieldLength: view.getUint16(28, true),
|
|
381
351
|
};
|
|
382
352
|
|
|
383
353
|
zipHeader.filename = await tokenizer.readToken(new Token.StringType(zipHeader.filenameLength, 'utf-8'));
|
|
@@ -472,7 +442,8 @@ export class FileTypeParser {
|
|
|
472
442
|
while (nextHeaderIndex < 0 && (tokenizer.position < tokenizer.fileInfo.size)) {
|
|
473
443
|
await tokenizer.peekBuffer(this.buffer, {mayBeLess: true});
|
|
474
444
|
|
|
475
|
-
nextHeaderIndex = this.buffer
|
|
445
|
+
nextHeaderIndex = indexOf(this.buffer, new Uint8Array([0x50, 0x4B, 0x03, 0x04]));
|
|
446
|
+
|
|
476
447
|
// Move position to the next header if found, skip the whole buffer otherwise
|
|
477
448
|
await tokenizer.ignore(nextHeaderIndex >= 0 ? nextHeaderIndex : this.buffer.length);
|
|
478
449
|
}
|
|
@@ -495,7 +466,7 @@ export class FileTypeParser {
|
|
|
495
466
|
if (this.checkString('OggS')) {
|
|
496
467
|
// This is an OGG container
|
|
497
468
|
await tokenizer.ignore(28);
|
|
498
|
-
const type =
|
|
469
|
+
const type = new Uint8Array(8);
|
|
499
470
|
await tokenizer.readBuffer(type);
|
|
500
471
|
|
|
501
472
|
// Needs to be before `ogg` check
|
|
@@ -576,7 +547,7 @@ export class FileTypeParser {
|
|
|
576
547
|
) {
|
|
577
548
|
// They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
|
|
578
549
|
// For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
|
|
579
|
-
const brandMajor =
|
|
550
|
+
const brandMajor = new Token.StringType(4, 'latin1').get(this.buffer, 8).replace('\0', ' ').trim();
|
|
580
551
|
switch (brandMajor) {
|
|
581
552
|
case 'avif':
|
|
582
553
|
case 'avis':
|
|
@@ -706,11 +677,11 @@ export class FileTypeParser {
|
|
|
706
677
|
try {
|
|
707
678
|
await tokenizer.ignore(1350);
|
|
708
679
|
const maxBufferSize = 10 * 1024 * 1024;
|
|
709
|
-
const buffer =
|
|
680
|
+
const buffer = new Uint8Array(Math.min(maxBufferSize, tokenizer.fileInfo.size));
|
|
710
681
|
await tokenizer.readBuffer(buffer, {mayBeLess: true});
|
|
711
682
|
|
|
712
683
|
// Check if this is an Adobe Illustrator file
|
|
713
|
-
if (
|
|
684
|
+
if (includes(buffer, new TextEncoder().encode('AIPrivateData'))) {
|
|
714
685
|
return {
|
|
715
686
|
ext: 'ai',
|
|
716
687
|
mime: 'application/postscript',
|
|
@@ -765,27 +736,31 @@ export class FileTypeParser {
|
|
|
765
736
|
async function readField() {
|
|
766
737
|
const msb = await tokenizer.peekNumber(Token.UINT8);
|
|
767
738
|
let mask = 0x80;
|
|
768
|
-
let ic = 0; // 0 = A, 1 = B, 2 = C, 3
|
|
769
|
-
// = D
|
|
739
|
+
let ic = 0; // 0 = A, 1 = B, 2 = C, 3 = D
|
|
770
740
|
|
|
771
741
|
while ((msb & mask) === 0 && mask !== 0) {
|
|
772
742
|
++ic;
|
|
773
743
|
mask >>= 1;
|
|
774
744
|
}
|
|
775
745
|
|
|
776
|
-
const id =
|
|
746
|
+
const id = new Uint8Array(ic + 1);
|
|
777
747
|
await tokenizer.readBuffer(id);
|
|
778
748
|
return id;
|
|
779
749
|
}
|
|
780
750
|
|
|
781
751
|
async function readElement() {
|
|
782
|
-
const
|
|
752
|
+
const idField = await readField();
|
|
783
753
|
const lengthField = await readField();
|
|
754
|
+
|
|
784
755
|
lengthField[0] ^= 0x80 >> (lengthField.length - 1);
|
|
785
756
|
const nrLength = Math.min(6, lengthField.length); // JavaScript can max read 6 bytes integer
|
|
757
|
+
|
|
758
|
+
const idView = new DataView(idField.buffer);
|
|
759
|
+
const lengthView = new DataView(lengthField.buffer, lengthField.length - nrLength, nrLength);
|
|
760
|
+
|
|
786
761
|
return {
|
|
787
|
-
id:
|
|
788
|
-
len:
|
|
762
|
+
id: getUintBE(idView),
|
|
763
|
+
len: getUintBE(lengthView),
|
|
789
764
|
};
|
|
790
765
|
}
|
|
791
766
|
|
|
@@ -793,8 +768,8 @@ export class FileTypeParser {
|
|
|
793
768
|
while (children > 0) {
|
|
794
769
|
const element = await readElement();
|
|
795
770
|
if (element.id === 0x42_82) {
|
|
796
|
-
const rawValue = await tokenizer.readToken(new Token.StringType(element.len
|
|
797
|
-
return rawValue.
|
|
771
|
+
const rawValue = await tokenizer.readToken(new Token.StringType(element.len));
|
|
772
|
+
return rawValue.replaceAll(/\00.*$/g, ''); // Return DocType
|
|
798
773
|
}
|
|
799
774
|
|
|
800
775
|
await tokenizer.ignore(element.len); // ignore payload
|
|
@@ -835,7 +810,7 @@ export class FileTypeParser {
|
|
|
835
810
|
if (this.check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
|
|
836
811
|
return {
|
|
837
812
|
ext: 'wav',
|
|
838
|
-
mime: 'audio/
|
|
813
|
+
mime: 'audio/wav',
|
|
839
814
|
};
|
|
840
815
|
}
|
|
841
816
|
|
|
@@ -1059,7 +1034,7 @@ export class FileTypeParser {
|
|
|
1059
1034
|
}
|
|
1060
1035
|
|
|
1061
1036
|
if (this.checkString('AC')) {
|
|
1062
|
-
const version =
|
|
1037
|
+
const version = new Token.StringType(4, 'latin1').get(this.buffer, 2);
|
|
1063
1038
|
if (version.match('^d*') && version >= 1000 && version <= 1050) {
|
|
1064
1039
|
return {
|
|
1065
1040
|
ext: 'dwg',
|
|
@@ -1126,7 +1101,7 @@ export class FileTypeParser {
|
|
|
1126
1101
|
async function readChunkHeader() {
|
|
1127
1102
|
return {
|
|
1128
1103
|
length: await tokenizer.readToken(Token.INT32_BE),
|
|
1129
|
-
type: await tokenizer.readToken(new Token.StringType(4, '
|
|
1104
|
+
type: await tokenizer.readToken(new Token.StringType(4, 'latin1')),
|
|
1130
1105
|
};
|
|
1131
1106
|
}
|
|
1132
1107
|
|
|
@@ -1213,7 +1188,7 @@ export class FileTypeParser {
|
|
|
1213
1188
|
// ASF_Header_Object first 80 bytes
|
|
1214
1189
|
if (this.check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
|
|
1215
1190
|
async function readHeader() {
|
|
1216
|
-
const guid =
|
|
1191
|
+
const guid = new Uint8Array(16);
|
|
1217
1192
|
await tokenizer.readBuffer(guid);
|
|
1218
1193
|
return {
|
|
1219
1194
|
id: guid,
|
|
@@ -1228,7 +1203,7 @@ export class FileTypeParser {
|
|
|
1228
1203
|
let payload = header.size - 24;
|
|
1229
1204
|
if (_check(header.id, [0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65])) {
|
|
1230
1205
|
// Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
|
|
1231
|
-
const typeId =
|
|
1206
|
+
const typeId = new Uint8Array(16);
|
|
1232
1207
|
payload -= await tokenizer.readBuffer(typeId);
|
|
1233
1208
|
|
|
1234
1209
|
if (_check(typeId, [0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) {
|
|
@@ -1432,10 +1407,11 @@ export class FileTypeParser {
|
|
|
1432
1407
|
}
|
|
1433
1408
|
|
|
1434
1409
|
if (this.check([0x04, 0x00, 0x00, 0x00]) && this.buffer.length >= 16) { // Rough & quick check Pickle/ASAR
|
|
1435
|
-
const jsonSize = this.buffer.
|
|
1410
|
+
const jsonSize = new DataView(this.buffer.buffer).getUint32(12, true);
|
|
1411
|
+
|
|
1436
1412
|
if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) {
|
|
1437
1413
|
try {
|
|
1438
|
-
const header = this.buffer.slice(16, jsonSize + 16)
|
|
1414
|
+
const header = new TextDecoder().decode(this.buffer.slice(16, jsonSize + 16));
|
|
1439
1415
|
const json = JSON.parse(header);
|
|
1440
1416
|
// Check if Pickle is ASAR
|
|
1441
1417
|
if (json.files) { // Final check, assuring Pickle/ASAR format
|
|
@@ -1682,9 +1658,5 @@ export class FileTypeParser {
|
|
|
1682
1658
|
}
|
|
1683
1659
|
}
|
|
1684
1660
|
|
|
1685
|
-
export async function fileTypeStream(readableStream, options = {}) {
|
|
1686
|
-
return new FileTypeParser().toDetectionStream(readableStream, options);
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
1661
|
export const supportedExtensions = new Set(extensions);
|
|
1690
1662
|
export const supportedMimeTypes = new Set(mimeTypes);
|
package/index.d.ts
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
Typings for Node.js specific entry point.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {Readable as NodeReadableStream} from 'node:stream';
|
|
6
|
+
import type {FileTypeResult, StreamOptions, AnyWebReadableStream} from './core.js';
|
|
7
|
+
import {FileTypeParser} from './core.js';
|
|
8
|
+
|
|
9
|
+
export type ReadableStreamWithFileType = NodeReadableStream & {
|
|
10
|
+
readonly fileType?: FileTypeResult;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export declare class NodeFileTypeParser extends FileTypeParser {
|
|
14
|
+
/**
|
|
15
|
+
@param stream - Node.js `stream.Readable` or Web API `ReadableStream`.
|
|
16
|
+
*/
|
|
17
|
+
fromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream): Promise<FileTypeResult | undefined>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor).
|
|
21
|
+
*/
|
|
22
|
+
toDetectionStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
|
|
23
|
+
}
|
|
2
24
|
|
|
3
25
|
/**
|
|
4
26
|
Detect the file type of a file path.
|
|
5
27
|
|
|
6
28
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
7
29
|
|
|
8
|
-
@param path
|
|
30
|
+
@param path
|
|
9
31
|
@returns The detected file type and MIME type or `undefined` when there is no match.
|
|
10
32
|
*/
|
|
11
33
|
export function fileTypeFromFile(path: string): Promise<FileTypeResult | undefined>;
|
|
12
34
|
|
|
35
|
+
export function fileTypeFromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream): Promise<FileTypeResult | undefined>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
Returns a `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileTypeFromFile()`.
|
|
39
|
+
|
|
40
|
+
This method can be handy to put in between a stream, but it comes with a price.
|
|
41
|
+
Internally `stream()` builds up a buffer of `sampleSize` bytes, used as a sample, to determine the file type.
|
|
42
|
+
The sample size impacts the file detection resolution.
|
|
43
|
+
A smaller sample size will result in lower probability of the best file type detection.
|
|
44
|
+
|
|
45
|
+
**Note:** This method is only available when using Node.js.
|
|
46
|
+
**Note:** Requires Node.js 14 or later.
|
|
47
|
+
|
|
48
|
+
@param readableStream - A [readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) containing a file to examine.
|
|
49
|
+
@param options - Maybe used to override the default sample-size.
|
|
50
|
+
@returns A `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileTypeFromFile()`.
|
|
51
|
+
|
|
52
|
+
@example
|
|
53
|
+
```
|
|
54
|
+
import got from 'got';
|
|
55
|
+
import {fileTypeStream} from 'file-type';
|
|
56
|
+
|
|
57
|
+
const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';
|
|
58
|
+
|
|
59
|
+
const stream1 = got.stream(url);
|
|
60
|
+
const stream2 = await fileTypeStream(stream1, {sampleSize: 1024});
|
|
61
|
+
|
|
62
|
+
if (stream2.fileType?.mime === 'image/jpeg') {
|
|
63
|
+
// stream2 can be used to stream the JPEG image (from the very beginning of the stream)
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
*/
|
|
67
|
+
export function fileTypeStream(readableStream: NodeReadableStream, options?: StreamOptions): Promise<ReadableStreamWithFileType>;
|
|
68
|
+
|
|
13
69
|
export * from './core.js';
|
package/index.js
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Node.js specific entry point.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {ReadableStream as WebReadableStream} from 'node:stream/web';
|
|
1
6
|
import * as strtok3 from 'strtok3';
|
|
2
|
-
import {FileTypeParser} from './core.js';
|
|
7
|
+
import {FileTypeParser, reasonableDetectionSizeInBytes} from './core.js';
|
|
8
|
+
|
|
9
|
+
export class NodeFileTypeParser extends FileTypeParser {
|
|
10
|
+
async fromStream(stream) {
|
|
11
|
+
const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream) : strtok3.fromStream(stream));
|
|
12
|
+
try {
|
|
13
|
+
return super.fromTokenizer(tokenizer);
|
|
14
|
+
} finally {
|
|
15
|
+
await tokenizer.close();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async toDetectionStream(readableStream, options = {}) {
|
|
20
|
+
const {default: stream} = await import('node:stream');
|
|
21
|
+
const {sampleSize = reasonableDetectionSizeInBytes} = options;
|
|
22
|
+
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
readableStream.on('error', reject);
|
|
25
|
+
|
|
26
|
+
readableStream.once('readable', () => {
|
|
27
|
+
(async () => {
|
|
28
|
+
try {
|
|
29
|
+
// Set up output stream
|
|
30
|
+
const pass = new stream.PassThrough();
|
|
31
|
+
const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass);
|
|
32
|
+
|
|
33
|
+
// Read the input stream and detect the filetype
|
|
34
|
+
const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? new Uint8Array(0);
|
|
35
|
+
try {
|
|
36
|
+
pass.fileType = await this.fromBuffer(chunk);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (error instanceof strtok3.EndOfStreamError) {
|
|
39
|
+
pass.fileType = undefined;
|
|
40
|
+
} else {
|
|
41
|
+
reject(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resolve(outputStream);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
reject(error);
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
3
54
|
|
|
4
55
|
export async function fileTypeFromFile(path, fileTypeOptions) {
|
|
5
56
|
const tokenizer = await strtok3.fromFile(path);
|
|
@@ -11,4 +62,12 @@ export async function fileTypeFromFile(path, fileTypeOptions) {
|
|
|
11
62
|
}
|
|
12
63
|
}
|
|
13
64
|
|
|
14
|
-
export
|
|
65
|
+
export async function fileTypeFromStream(stream, fileTypeOptions) {
|
|
66
|
+
return (new NodeFileTypeParser(fileTypeOptions)).fromStream(stream);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function fileTypeStream(readableStream, options = {}) {
|
|
70
|
+
return new NodeFileTypeParser().toDetectionStream(readableStream, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {fileTypeFromBuffer, fileTypeFromBlob, FileTypeParser, supportedMimeTypes, supportedExtensions} from './core.js';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file-type",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Detect the file type of a
|
|
3
|
+
"version": "19.1.0",
|
|
4
|
+
"description": "Detect the file type of a Uint8Array/ArrayBuffer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/file-type",
|
|
7
7
|
"funding": "https://github.com/sindresorhus/file-type?sponsor=1",
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"node": "./index.js",
|
|
17
|
-
"default": "./
|
|
17
|
+
"default": "./core.js"
|
|
18
18
|
},
|
|
19
19
|
"./core": "./core.js"
|
|
20
20
|
},
|
|
21
21
|
"sideEffects": false,
|
|
22
22
|
"engines": {
|
|
23
|
-
"node": ">=
|
|
23
|
+
"node": ">=18"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"test": "xo && ava && tsd"
|
|
@@ -28,8 +28,6 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"index.js",
|
|
30
30
|
"index.d.ts",
|
|
31
|
-
"browser.js",
|
|
32
|
-
"browser.d.ts",
|
|
33
31
|
"core.js",
|
|
34
32
|
"core.d.ts",
|
|
35
33
|
"supported.js",
|
|
@@ -210,24 +208,27 @@
|
|
|
210
208
|
"fbx"
|
|
211
209
|
],
|
|
212
210
|
"dependencies": {
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
211
|
+
"strtok3": "^7.1.0",
|
|
212
|
+
"token-types": "^6.0.0",
|
|
213
|
+
"uint8array-extras": "^1.3.0"
|
|
216
214
|
},
|
|
217
215
|
"devDependencies": {
|
|
218
216
|
"@tokenizer/token": "^0.3.0",
|
|
219
|
-
"@types/node": "^20.
|
|
220
|
-
"ava": "^
|
|
217
|
+
"@types/node": "^20.10.7",
|
|
218
|
+
"ava": "^6.0.1",
|
|
221
219
|
"commonmark": "^0.30.0",
|
|
222
220
|
"noop-stream": "^1.0.0",
|
|
223
|
-
"tsd": "^0.
|
|
224
|
-
"xo": "^0.
|
|
221
|
+
"tsd": "^0.30.3",
|
|
222
|
+
"xo": "^0.56.0"
|
|
225
223
|
},
|
|
226
224
|
"xo": {
|
|
227
225
|
"envs": [
|
|
228
226
|
"node",
|
|
229
227
|
"browser"
|
|
230
228
|
],
|
|
229
|
+
"ignores": [
|
|
230
|
+
"fixture"
|
|
231
|
+
],
|
|
231
232
|
"rules": {
|
|
232
233
|
"no-inner-declarations": "warn",
|
|
233
234
|
"no-await-in-loop": "warn",
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# file-type
|
|
2
2
|
|
|
3
|
-
> Detect the file type of a
|
|
3
|
+
> Detect the file type of a Uint8Array/ArrayBuffer
|
|
4
4
|
|
|
5
5
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ console.log(await fileTypeFromFile('Unicorn.png'));
|
|
|
31
31
|
//=> {ext: 'png', mime: 'image/png'}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Determine file type from a
|
|
34
|
+
Determine file type from a Uint8Array/ArrayBuffer, which may be a portion of the beginning of a file:
|
|
35
35
|
|
|
36
36
|
```js
|
|
37
37
|
import {fileTypeFromBuffer} from 'file-type';
|
|
@@ -107,7 +107,7 @@ console.log(fileType);
|
|
|
107
107
|
|
|
108
108
|
### fileTypeFromBuffer(buffer)
|
|
109
109
|
|
|
110
|
-
Detect the file type of a `
|
|
110
|
+
Detect the file type of a `Uint8Array`, or `ArrayBuffer`.
|
|
111
111
|
|
|
112
112
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
113
113
|
|
|
@@ -122,7 +122,7 @@ Or `undefined` when there is no match.
|
|
|
122
122
|
|
|
123
123
|
#### buffer
|
|
124
124
|
|
|
125
|
-
Type: `
|
|
125
|
+
Type: `Uint8Array | ArrayBuffer`
|
|
126
126
|
|
|
127
127
|
A buffer representing file data. It works best if the buffer contains the entire file. It may work with a smaller portion as well.
|
|
128
128
|
|
|
@@ -147,7 +147,7 @@ The file path to parse.
|
|
|
147
147
|
|
|
148
148
|
### fileTypeFromStream(stream)
|
|
149
149
|
|
|
150
|
-
Detect the file type of a Node.js
|
|
150
|
+
Detect the file type of a [Node.js readable stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) or a [Web API ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
|
151
151
|
|
|
152
152
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
153
153
|
|
|
@@ -168,6 +168,8 @@ A readable stream representing file data.
|
|
|
168
168
|
|
|
169
169
|
Detect the file type of a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
|
|
170
170
|
|
|
171
|
+
It will **stream** the underlying Blob, and required a [ReadableStreamBYOBReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) which **require Node.js ≥ 20**.
|
|
172
|
+
|
|
171
173
|
The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
|
|
172
174
|
|
|
173
175
|
Returns a `Promise` for an object with the detected file type:
|
|
@@ -335,7 +337,7 @@ const customDetectors = [
|
|
|
335
337
|
async tokenizer => {
|
|
336
338
|
const unicornHeader = [85, 78, 73, 67, 79, 82, 78]; // 'UNICORN' as decimal string
|
|
337
339
|
|
|
338
|
-
const buffer =
|
|
340
|
+
const buffer = new Uint8Array(7);
|
|
339
341
|
await tokenizer.peekBuffer(buffer, {length: unicornHeader.length, mayBeLess: true});
|
|
340
342
|
|
|
341
343
|
if (unicornHeader.every((value, index) => value === buffer[index])) {
|
|
@@ -346,7 +348,7 @@ const customDetectors = [
|
|
|
346
348
|
},
|
|
347
349
|
];
|
|
348
350
|
|
|
349
|
-
const buffer =
|
|
351
|
+
const buffer = new Uint8Array(new TextEncoder().encode('UNICORN'));
|
|
350
352
|
const parser = new FileTypeParser({customDetectors});
|
|
351
353
|
const fileType = await parser.fromBuffer(buffer);
|
|
352
354
|
console.log(fileType);
|
package/supported.js
CHANGED
package/util.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {StringType} from 'token-types';
|
|
2
|
+
|
|
1
3
|
export function stringToBytes(string) {
|
|
2
4
|
return [...string].map(character => character.charCodeAt(0)); // eslint-disable-line unicorn/prefer-code-point
|
|
3
5
|
}
|
|
@@ -5,12 +7,12 @@ export function stringToBytes(string) {
|
|
|
5
7
|
/**
|
|
6
8
|
Checks whether the TAR checksum is valid.
|
|
7
9
|
|
|
8
|
-
@param {
|
|
10
|
+
@param {Uint8Array} arrayBuffer - The TAR header `[offset ... offset + 512]`.
|
|
9
11
|
@param {number} offset - TAR header offset.
|
|
10
12
|
@returns {boolean} `true` if the TAR checksum is valid, otherwise `false`.
|
|
11
13
|
*/
|
|
12
|
-
export function tarHeaderChecksumMatches(
|
|
13
|
-
const readSum = Number.parseInt(
|
|
14
|
+
export function tarHeaderChecksumMatches(arrayBuffer, offset = 0) {
|
|
15
|
+
const readSum = Number.parseInt(new StringType(6).get(arrayBuffer, 148).replace(/\0.*$/, '').trim(), 8); // Read sum in header
|
|
14
16
|
if (Number.isNaN(readSum)) {
|
|
15
17
|
return false;
|
|
16
18
|
}
|
|
@@ -18,11 +20,11 @@ export function tarHeaderChecksumMatches(buffer, offset = 0) {
|
|
|
18
20
|
let sum = 8 * 0x20; // Initialize signed bit sum
|
|
19
21
|
|
|
20
22
|
for (let index = offset; index < offset + 148; index++) {
|
|
21
|
-
sum +=
|
|
23
|
+
sum += arrayBuffer[index];
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
for (let index = offset + 156; index < offset + 512; index++) {
|
|
25
|
-
sum +=
|
|
27
|
+
sum += arrayBuffer[index];
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
return readSum === sum;
|
package/browser.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type {FileTypeResult} from './core.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
Detect the file type of a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
|
|
5
|
-
|
|
6
|
-
@example
|
|
7
|
-
```
|
|
8
|
-
import {fileTypeFromStream} from 'file-type';
|
|
9
|
-
|
|
10
|
-
const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';
|
|
11
|
-
|
|
12
|
-
const response = await fetch(url);
|
|
13
|
-
const fileType = await fileTypeFromStream(response.body);
|
|
14
|
-
|
|
15
|
-
console.log(fileType);
|
|
16
|
-
//=> {ext: 'jpg', mime: 'image/jpeg'}
|
|
17
|
-
```
|
|
18
|
-
*/
|
|
19
|
-
export declare function fileTypeFromStream(stream: ReadableStream): Promise<FileTypeResult | undefined>;
|
|
20
|
-
|
|
21
|
-
export {
|
|
22
|
-
fileTypeFromBuffer,
|
|
23
|
-
fileTypeFromBlob,
|
|
24
|
-
supportedExtensions,
|
|
25
|
-
supportedMimeTypes,
|
|
26
|
-
type FileTypeResult,
|
|
27
|
-
type FileExtension,
|
|
28
|
-
type MimeType,
|
|
29
|
-
} from './core.js';
|
package/browser.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {ReadableWebToNodeStream} from 'readable-web-to-node-stream';
|
|
2
|
-
import {fileTypeFromStream as coreFileTypeFromStream} from './core.js';
|
|
3
|
-
|
|
4
|
-
export async function fileTypeFromStream(stream) {
|
|
5
|
-
const readableWebToNodeStream = new ReadableWebToNodeStream(stream);
|
|
6
|
-
const fileType = await coreFileTypeFromStream(readableWebToNodeStream);
|
|
7
|
-
await readableWebToNodeStream.close();
|
|
8
|
-
return fileType;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export {
|
|
12
|
-
fileTypeFromTokenizer,
|
|
13
|
-
fileTypeFromBuffer,
|
|
14
|
-
fileTypeStream,
|
|
15
|
-
} from './core.js';
|