file-type 21.3.3 → 22.0.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.
@@ -0,0 +1,53 @@
1
+ import {ReadableStream as NodeReadableStream} from 'node:stream/web';
2
+ import {expectType} from 'tsd';
3
+ import {
4
+ type FileTypeResult,
5
+ type FileTypeResult as FileTypeResultBrowser,
6
+ type AnyWebReadableByteStreamWithFileType,
7
+ fileTypeFromBlob,
8
+ fileTypeFromBuffer,
9
+ fileTypeFromFile,
10
+ fileTypeFromStream,
11
+ fileTypeStream,
12
+ FileTypeParser,
13
+ } from './index.js';
14
+
15
+ // `fileTypeStream`: accepts StreamOptions & FileTypeOptions
16
+ (async () => {
17
+ const webStream = new ReadableStream<Uint8Array>();
18
+ expectType<AnyWebReadableByteStreamWithFileType>(await fileTypeStream(webStream, {sampleSize: 256}));
19
+ expectType<AnyWebReadableByteStreamWithFileType>(await fileTypeStream(webStream, {sampleSize: 256, customDetectors: []}));
20
+ expectType<AnyWebReadableByteStreamWithFileType>(await fileTypeStream(webStream, {signal: AbortSignal.timeout(1000)}));
21
+ })();
22
+
23
+ // `FileTypeParser`: tests generic input types and options
24
+ (async () => {
25
+ const fileTypeParser = new FileTypeParser({customDetectors: [], signal: AbortSignal.timeout(1000)});
26
+ const fileTypeParserWithMpeg = new FileTypeParser({mpegOffsetTolerance: 10});
27
+ const webStream = new ReadableStream<Uint8Array>();
28
+ const nodeWebStream = new NodeReadableStream<Uint8Array>();
29
+
30
+ expectType<FileTypeResult | undefined>(await fileTypeParser.fromStream(webStream));
31
+ expectType<FileTypeResult | undefined>(await fileTypeParser.fromStream(nodeWebStream));
32
+
33
+ expectType<AnyWebReadableByteStreamWithFileType>(await fileTypeParser.toDetectionStream(webStream, {sampleSize: 256}));
34
+ })();
35
+
36
+ // `fileTypeFromStream`: accepts FileTypeOptions
37
+ (async () => {
38
+ const webStream = new ReadableStream<Uint8Array>();
39
+ expectType<FileTypeResult | undefined>(await fileTypeFromStream(webStream, {signal: AbortSignal.timeout(1000)}));
40
+ })();
41
+
42
+ // Test that Blob overload returns browser-specific result
43
+ expectType<Promise<FileTypeResultBrowser | undefined>>(fileTypeFromBlob(new Blob([])));
44
+
45
+ // `fileTypeFromFile`: accepts a file path and options
46
+ expectType<Promise<FileTypeResult | undefined>>(fileTypeFromFile('file.bin'));
47
+ expectType<Promise<FileTypeResult | undefined>>(fileTypeFromFile('file.bin', {signal: AbortSignal.timeout(1000)}));
48
+
49
+ // `FileTypeParser#fromFile`: accepts a file path
50
+ (async () => {
51
+ const fileTypeParser = new FileTypeParser();
52
+ expectType<Promise<FileTypeResult | undefined>>(fileTypeParser.fromFile('file.bin'));
53
+ })();
@@ -0,0 +1,65 @@
1
+ export const maximumUntrustedSkipSizeInBytes = 16 * 1024 * 1024;
2
+
3
+ export class ParserHardLimitError extends Error {}
4
+
5
+ export function getSafeBound(value, maximum, reason) {
6
+ if (
7
+ !Number.isFinite(value)
8
+ || value < 0
9
+ || value > maximum
10
+ ) {
11
+ throw new ParserHardLimitError(`${reason} has invalid size ${value} (maximum ${maximum} bytes)`);
12
+ }
13
+
14
+ return value;
15
+ }
16
+
17
+ export async function safeIgnore(tokenizer, length, {maximumLength = maximumUntrustedSkipSizeInBytes, reason = 'skip'} = {}) {
18
+ const safeLength = getSafeBound(length, maximumLength, reason);
19
+ await tokenizer.ignore(safeLength);
20
+ }
21
+
22
+ export async function safeReadBuffer(tokenizer, buffer, options, {maximumLength = buffer.length, reason = 'read'} = {}) {
23
+ const length = options?.length ?? buffer.length;
24
+ const safeLength = getSafeBound(length, maximumLength, reason);
25
+ return tokenizer.readBuffer(buffer, {
26
+ ...options,
27
+ length: safeLength,
28
+ });
29
+ }
30
+
31
+ export function checkBytes(buffer, headers, options) {
32
+ options = {
33
+ offset: 0,
34
+ ...options,
35
+ };
36
+
37
+ for (const [index, header] of headers.entries()) {
38
+ // If a bitmask is set
39
+ if (options.mask) {
40
+ // If header doesn't equal `buf` with bits masked off
41
+ if (header !== (options.mask[index] & buffer[index + options.offset])) {
42
+ return false;
43
+ }
44
+ } else if (header !== buffer[index + options.offset]) {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ return true;
50
+ }
51
+
52
+ export function hasUnknownFileSize(tokenizer) {
53
+ const fileSize = tokenizer.fileInfo.size;
54
+ return (
55
+ !Number.isFinite(fileSize)
56
+ || fileSize === Number.MAX_SAFE_INTEGER
57
+ );
58
+ }
59
+
60
+ export function hasExceededUnknownSizeScanBudget(tokenizer, startOffset, maximumBytes) {
61
+ return (
62
+ hasUnknownFileSize(tokenizer)
63
+ && tokenizer.position - startOffset > maximumBytes
64
+ );
65
+ }
@@ -1,3 +1,5 @@
1
+ // MIME media subtypes prefixed with `x-ft-` are custom and defined by us. They are neither formally registered with IANA nor based on any informal conventions.
2
+
1
3
  export const extensions = [
2
4
  'jpg',
3
5
  'png',
@@ -179,6 +181,9 @@ export const extensions = [
179
181
  'tar.gz',
180
182
  'reg',
181
183
  'dat',
184
+ 'key',
185
+ 'numbers',
186
+ 'pages',
182
187
  ];
183
188
 
184
189
  export const mimeTypes = [
@@ -262,7 +267,7 @@ export const mimeTypes = [
262
267
  'application/x-unix-archive',
263
268
  'application/x-rpm',
264
269
  'application/x-compress',
265
- 'application/x-lzip',
270
+ 'application/lzip',
266
271
  'application/x-cfb',
267
272
  'application/x-mie',
268
273
  'application/mxf',
@@ -291,8 +296,8 @@ export const mimeTypes = [
291
296
  'model/gltf-binary',
292
297
  'application/vnd.tcpdump.pcap',
293
298
  'audio/x-dsf', // Non-standard
294
- 'application/x.ms.shortcut', // Invented by us
295
- 'application/x.apple.alias', // Invented by us
299
+ 'application/x-ms-shortcut', // Informal, used by freedesktop.org shared-mime-info
300
+ 'application/x-ft-apple.alias',
296
301
  'audio/x-voc',
297
302
  'audio/vnd.dolby.dd-raw',
298
303
  'audio/x-m4a',
@@ -332,11 +337,11 @@ export const mimeTypes = [
332
337
  'application/x-ace-compressed',
333
338
  'application/avro',
334
339
  'application/vnd.iccprofile',
335
- 'application/x.autodesk.fbx', // Invented by us
340
+ 'application/x-ft-fbx',
336
341
  'application/vnd.visio',
337
342
  'application/vnd.android.package-archive',
338
- 'application/vnd.google.draco', // Invented by us
339
- 'application/x-lz4', // Invented by us
343
+ 'application/x-ft-draco',
344
+ 'application/x-lz4', // Informal, used by freedesktop.org shared-mime-info
340
345
  'application/vnd.openxmlformats-officedocument.presentationml.template',
341
346
  'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
342
347
  'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
@@ -357,4 +362,7 @@ export const mimeTypes = [
357
362
  'application/x-ms-regedit',
358
363
  'application/x-ft-windows-registry-hive',
359
364
  'application/x-jmp-data',
365
+ 'application/vnd.apple.keynote',
366
+ 'application/vnd.apple.numbers',
367
+ 'application/vnd.apple.pages',
360
368
  ];
@@ -32,7 +32,7 @@ Checks whether the TAR checksum is valid.
32
32
  @returns {boolean} `true` if the TAR checksum is valid, otherwise `false`.
33
33
  */
34
34
  export function tarHeaderChecksumMatches(arrayBuffer, offset = 0) {
35
- const readSum = Number.parseInt(new StringType(6).get(arrayBuffer, 148).replace(/\0.*$/, '').trim(), 8); // Read sum in header
35
+ const readSum = Number.parseInt(new StringType(6).get(arrayBuffer, 148).replace(/\0.*$/v, '').trim(), 8); // Read sum in header
36
36
  if (Number.isNaN(readSum)) {
37
37
  return false;
38
38
  }
@@ -55,6 +55,6 @@ ID3 UINT32 sync-safe tokenizer token.
55
55
  28 bits (representing up to 256MB) integer, the msb is 0 to avoid "false syncsignals".
56
56
  */
57
57
  export const uint32SyncSafeToken = {
58
- get: (buffer, offset) => (buffer[offset + 3] & 0x7F) | ((buffer[offset + 2]) << 7) | ((buffer[offset + 1]) << 14) | ((buffer[offset]) << 21),
58
+ get: (buffer, offset) => (buffer[offset + 3] & 0x7F) | ((buffer[offset + 2] & 0x7F) << 7) | ((buffer[offset + 1] & 0x7F) << 14) | ((buffer[offset] & 0x7F) << 21),
59
59
  len: 4,
60
60
  };
package/index.d.ts DELETED
@@ -1,98 +0,0 @@
1
- /**
2
- Typings for Node.js specific entry point.
3
- */
4
-
5
- import type {Readable as NodeReadableStream} from 'node:stream';
6
- import type {AnyWebByteStream} from 'strtok3';
7
- import {
8
- type FileTypeResult,
9
- type StreamOptions,
10
- type AnyWebReadableStream,
11
- type AnyWebReadableByteStreamWithFileType,
12
- type FileTypeOptions,
13
- FileTypeParser as DefaultFileTypeParser,
14
- } from './core.js';
15
-
16
- export type ReadableStreamWithFileType = NodeReadableStream & {
17
- readonly fileType?: FileTypeResult;
18
- };
19
-
20
- /**
21
- Extending `FileTypeParser` with Node.js engine specific functions.
22
- */
23
- export declare class FileTypeParser extends DefaultFileTypeParser {
24
- /**
25
- @param stream - Node.js `stream.Readable` or web `ReadableStream`.
26
- */
27
- fromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream): Promise<FileTypeResult | undefined>;
28
-
29
- fromFile(filePath: string): Promise<FileTypeResult | undefined>;
30
-
31
- /**
32
- Works the same way as {@link fileTypeStream}, additionally taking into account custom detectors (if any were provided to the constructor).
33
- */
34
- toDetectionStream(readableStream: NodeReadableStream, options?: FileTypeOptions & StreamOptions): Promise<ReadableStreamWithFileType>;
35
- toDetectionStream(webStream: AnyWebReadableStream<Uint8Array>, options?: FileTypeOptions & StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
36
- }
37
-
38
- /**
39
- Detect the file type of a file path.
40
-
41
- The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the file.
42
-
43
- This is for Node.js only.
44
-
45
- To read from a [`File`](https://developer.mozilla.org/docs/Web/API/File), see `fileTypeFromBlob()`.
46
-
47
- 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.
48
-
49
- @returns The detected file type and MIME type or `undefined` when there is no match.
50
- */
51
- export function fileTypeFromFile(filePath: string, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
52
-
53
- /**
54
- Detect the file type of a [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
55
-
56
- If the engine is Node.js, this may also be a [Node.js `stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
57
-
58
- Direct support for Node.js streams will be dropped in the future, when Node.js streams can be converted to Web streams (see [`toWeb()`](https://nodejs.org/api/stream.html#streamreadabletowebstreamreadable-options)).
59
-
60
- 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.
61
-
62
- @param stream - A [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [Node.js `stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable) streaming a file to examine.
63
- @param options - Options to override default behaviour.
64
- @returns A `Promise` for an object with the detected file type, or `undefined` when there is no match.
65
- */
66
- export function fileTypeFromStream(stream: AnyWebReadableStream<Uint8Array> | NodeReadableStream, options?: FileTypeOptions): Promise<FileTypeResult | undefined>;
67
-
68
- /**
69
- 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()`.
70
-
71
- This method can be handy to put in between a stream, but it comes with a price.
72
- Internally `stream()` builds up a buffer of `sampleSize` bytes, used as a sample, to determine the file type.
73
- The sample size impacts the file detection resolution.
74
- A smaller sample size will result in lower probability of the best file type detection.
75
-
76
- @param readableStream - A [web `ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) or [Node.js `stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable), streaming a file to examine.
77
- @param options - May be used to override the default sample size.
78
- @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()`.
79
-
80
- @example
81
- ```
82
- import got from 'got';
83
- import {fileTypeStream} from 'file-type';
84
-
85
- const url = 'https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg';
86
-
87
- const stream1 = got.stream(url);
88
- const stream2 = await fileTypeStream(stream1, {sampleSize: 1024});
89
-
90
- if (stream2.fileType?.mime === 'image/jpeg') {
91
- // stream2 can be used to stream the JPEG image (from the very beginning of the stream)
92
- }
93
- ```
94
- */
95
- export function fileTypeStream(readableStream: NodeReadableStream, options?: FileTypeOptions & StreamOptions): Promise<ReadableStreamWithFileType>;
96
- export function fileTypeStream(webStream: AnyWebByteStream, options?: FileTypeOptions & StreamOptions): Promise<AnyWebReadableByteStreamWithFileType>;
97
-
98
- export * from './core.js';
package/index.js DELETED
@@ -1,126 +0,0 @@
1
- /**
2
- Node.js specific entry point.
3
- */
4
-
5
- import {ReadableStream as WebReadableStream} from 'node:stream/web';
6
- import {pipeline, PassThrough, Readable} from 'node:stream';
7
- import fs from 'node:fs/promises';
8
- import {constants as fileSystemConstants} from 'node:fs';
9
- import * as strtok3 from 'strtok3';
10
- import {
11
- FileTypeParser as DefaultFileTypeParser,
12
- reasonableDetectionSizeInBytes,
13
- normalizeSampleSize,
14
- } from './core.js';
15
-
16
- function isTokenizerStreamBoundsError(error) {
17
- if (
18
- !(error instanceof RangeError)
19
- || error.message !== 'offset is out of bounds'
20
- || typeof error.stack !== 'string'
21
- ) {
22
- return false;
23
- }
24
-
25
- // Some malformed or non-byte Node.js streams can surface this tokenizer-internal range error.
26
- // Note: This stack-trace check is fragile and may break if strtok3 restructures its internals.
27
- return /strtok3[/\\]lib[/\\]stream[/\\]/.test(error.stack);
28
- }
29
-
30
- export class FileTypeParser extends DefaultFileTypeParser {
31
- async fromStream(stream) {
32
- const tokenizer = await (stream instanceof WebReadableStream ? strtok3.fromWebStream(stream, this.getTokenizerOptions()) : strtok3.fromStream(stream, this.getTokenizerOptions()));
33
- try {
34
- return await super.fromTokenizer(tokenizer);
35
- } catch (error) {
36
- if (isTokenizerStreamBoundsError(error)) {
37
- return;
38
- }
39
-
40
- throw error;
41
- } finally {
42
- await tokenizer.close();
43
- }
44
- }
45
-
46
- async fromFile(path) {
47
- // TODO: Remove this when `strtok3.fromFile()` safely rejects non-regular filesystem objects without a pathname race.
48
- const fileHandle = await fs.open(path, fileSystemConstants.O_RDONLY | fileSystemConstants.O_NONBLOCK);
49
- const fileStat = await fileHandle.stat();
50
- if (!fileStat.isFile()) {
51
- await fileHandle.close();
52
- return;
53
- }
54
-
55
- const tokenizer = new strtok3.FileTokenizer(fileHandle, {
56
- ...this.getTokenizerOptions(),
57
- fileInfo: {
58
- path,
59
- size: fileStat.size,
60
- },
61
- });
62
- try {
63
- return await super.fromTokenizer(tokenizer);
64
- } finally {
65
- await tokenizer.close();
66
- }
67
- }
68
-
69
- async toDetectionStream(readableStream, options = {}) {
70
- if (!(readableStream instanceof Readable)) {
71
- return super.toDetectionStream(readableStream, options);
72
- }
73
-
74
- const sampleSize = normalizeSampleSize(options.sampleSize ?? reasonableDetectionSizeInBytes);
75
-
76
- return new Promise((resolve, reject) => {
77
- readableStream.on('error', reject);
78
-
79
- readableStream.once('readable', () => {
80
- (async () => {
81
- try {
82
- // Set up output stream
83
- const pass = new PassThrough();
84
- const outputStream = pipeline ? pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass);
85
-
86
- // Read the input stream and detect the filetype
87
- const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? new Uint8Array(0);
88
- try {
89
- pass.fileType = await this.fromBuffer(chunk);
90
- } catch (error) {
91
- if (error instanceof strtok3.EndOfStreamError) {
92
- pass.fileType = undefined;
93
- } else {
94
- reject(error);
95
- }
96
- }
97
-
98
- resolve(outputStream);
99
- } catch (error) {
100
- reject(error);
101
- }
102
- })();
103
- });
104
- });
105
- }
106
- }
107
-
108
- export async function fileTypeFromFile(path, options) {
109
- return (new FileTypeParser(options)).fromFile(path, options);
110
- }
111
-
112
- export async function fileTypeFromStream(stream, options) {
113
- return (new FileTypeParser(options)).fromStream(stream);
114
- }
115
-
116
- export async function fileTypeStream(readableStream, options = {}) {
117
- return new FileTypeParser(options).toDetectionStream(readableStream, options);
118
- }
119
-
120
- export {
121
- fileTypeFromTokenizer,
122
- fileTypeFromBuffer,
123
- fileTypeFromBlob,
124
- supportedMimeTypes,
125
- supportedExtensions,
126
- } from './core.js';