file-type 10.9.0 → 11.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.
Files changed (5) hide show
  1. package/index.d.ts +191 -125
  2. package/index.js +160 -116
  3. package/package.json +28 -10
  4. package/readme.md +24 -7
  5. package/util.js +58 -0
package/index.d.ts CHANGED
@@ -1,139 +1,205 @@
1
1
  /// <reference types="node"/>
2
2
  import {Readable as ReadableStream} from 'stream';
3
3
 
4
- export type FileType =
5
- | 'jpg'
6
- | 'png'
7
- | 'gif'
8
- | 'webp'
9
- | 'flif'
10
- | 'cr2'
11
- | 'tif'
12
- | 'bmp'
13
- | 'jxr'
14
- | 'psd'
15
- | 'zip'
16
- | 'tar'
17
- | 'rar'
18
- | 'gz'
19
- | 'bz2'
20
- | '7z'
21
- | 'dmg'
22
- | 'mp4'
23
- | 'm4v'
24
- | 'mid'
25
- | 'mkv'
26
- | 'webm'
27
- | 'mov'
28
- | 'avi'
29
- | 'wmv'
30
- | 'mpg'
31
- | 'mp2'
32
- | 'mp3'
33
- | 'm4a'
34
- | 'ogg'
35
- | 'opus'
36
- | 'flac'
37
- | 'wav'
38
- | 'qcp'
39
- | 'amr'
40
- | 'pdf'
41
- | 'epub'
42
- | 'mobi'
43
- | 'exe'
44
- | 'swf'
45
- | 'rtf'
46
- | 'woff'
47
- | 'woff2'
48
- | 'eot'
49
- | 'ttf'
50
- | 'otf'
51
- | 'ico'
52
- | 'flv'
53
- | 'ps'
54
- | 'xz'
55
- | 'sqlite'
56
- | 'nes'
57
- | 'crx'
58
- | 'xpi'
59
- | 'cab'
60
- | 'deb'
61
- | 'ar'
62
- | 'rpm'
63
- | 'Z'
64
- | 'lz'
65
- | 'msi'
66
- | 'mxf'
67
- | 'mts'
68
- | 'wasm'
69
- | 'blend'
70
- | 'bpg'
71
- | 'docx'
72
- | 'pptx'
73
- | 'xlsx'
74
- | '3gp'
75
- | 'jp2'
76
- | 'jpm'
77
- | 'jpx'
78
- | 'mj2'
79
- | 'aif'
80
- | 'odt'
81
- | 'ods'
82
- | 'odp'
83
- | 'xml'
84
- | 'heic'
85
- | 'cur'
86
- | 'ktx'
87
- | 'ape'
88
- | 'wv'
89
- | 'asf'
90
- | 'wma'
91
- | 'wmv'
92
- | 'dcm'
93
- | 'mpc'
94
- | 'ics'
95
- | 'glb'
96
- | 'pcap';
97
-
98
- export interface FileTypeResult {
99
- /**
100
- * One of the supported [file types](https://github.com/sindresorhus/file-type#supported-file-types).
101
- */
102
- ext: FileType;
4
+ declare namespace fileType {
5
+ type FileType =
6
+ | 'jpg'
7
+ | 'png'
8
+ | 'gif'
9
+ | 'webp'
10
+ | 'flif'
11
+ | 'cr2'
12
+ | 'orf'
13
+ | 'arw'
14
+ | 'dng'
15
+ | 'nef'
16
+ | 'tif'
17
+ | 'bmp'
18
+ | 'jxr'
19
+ | 'psd'
20
+ | 'zip'
21
+ | 'tar'
22
+ | 'rar'
23
+ | 'gz'
24
+ | 'bz2'
25
+ | '7z'
26
+ | 'dmg'
27
+ | 'mp4'
28
+ | 'mid'
29
+ | 'mkv'
30
+ | 'webm'
31
+ | 'mov'
32
+ | 'avi'
33
+ | 'wmv'
34
+ | 'mpg'
35
+ | 'mp2'
36
+ | 'mp3'
37
+ | 'm4a'
38
+ | 'ogg'
39
+ | 'opus'
40
+ | 'flac'
41
+ | 'wav'
42
+ | 'qcp'
43
+ | 'amr'
44
+ | 'pdf'
45
+ | 'epub'
46
+ | 'mobi'
47
+ | 'exe'
48
+ | 'swf'
49
+ | 'rtf'
50
+ | 'woff'
51
+ | 'woff2'
52
+ | 'eot'
53
+ | 'ttf'
54
+ | 'otf'
55
+ | 'ico'
56
+ | 'flv'
57
+ | 'ps'
58
+ | 'xz'
59
+ | 'sqlite'
60
+ | 'nes'
61
+ | 'crx'
62
+ | 'xpi'
63
+ | 'cab'
64
+ | 'deb'
65
+ | 'ar'
66
+ | 'rpm'
67
+ | 'Z'
68
+ | 'lz'
69
+ | 'msi'
70
+ | 'mxf'
71
+ | 'mts'
72
+ | 'wasm'
73
+ | 'blend'
74
+ | 'bpg'
75
+ | 'docx'
76
+ | 'pptx'
77
+ | 'xlsx'
78
+ | '3gp'
79
+ | '3g2'
80
+ | 'jp2'
81
+ | 'jpm'
82
+ | 'jpx'
83
+ | 'mj2'
84
+ | 'aif'
85
+ | 'odt'
86
+ | 'ods'
87
+ | 'odp'
88
+ | 'xml'
89
+ | 'heic'
90
+ | 'cur'
91
+ | 'ktx'
92
+ | 'ape'
93
+ | 'wv'
94
+ | 'asf'
95
+ | 'wma'
96
+ | 'wmv'
97
+ | 'dcm'
98
+ | 'mpc'
99
+ | 'ics'
100
+ | 'glb'
101
+ | 'pcap'
102
+ | 'dsf'
103
+ | 'lnk'
104
+ | 'alias'
105
+ | 'voc'
106
+ | 'ac3'
107
+ | 'm4a'
108
+ | 'm4b'
109
+ | 'm4p'
110
+ | 'm4v'
111
+ | 'f4a'
112
+ | 'f4b'
113
+ | 'f4p'
114
+ | 'f4v';
103
115
 
104
- /**
105
- * The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
106
- */
107
- mime: string;
116
+ interface FileTypeResult {
117
+ /**
118
+ One of the supported [file types](https://github.com/sindresorhus/file-type#supported-file-types).
119
+ */
120
+ ext: FileType;
121
+
122
+ /**
123
+ The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
124
+ */
125
+ mime: string;
126
+ }
127
+
128
+ type ReadableStreamWithFileType = ReadableStream & {
129
+ readonly fileType?: FileTypeResult;
130
+ };
108
131
  }
109
132
 
110
- export type ReadableStreamWithFileType = ReadableStream & {
111
- readonly fileType: FileTypeResult | null;
112
- };
133
+ declare const fileType: {
134
+ /**
135
+ Detect the file type of a `Buffer`/`Uint8Array`/`ArrayBuffer`. 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.
136
+
137
+ @param buffer - It only needs the first `.minimumBytes` bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
138
+ @returns The detected file type and MIME type or `undefined` when there was no match.
139
+
140
+ @example
141
+ ```
142
+ import readChunk = require('read-chunk');
143
+ import fileType = require('file-type');
144
+
145
+ const buffer = readChunk.sync('unicorn.png', 0, fileType.minimumBytes);
146
+
147
+ fileType(buffer);
148
+ //=> {ext: 'png', mime: 'image/png'}
149
+
113
150
 
114
- export interface FileTypeModule {
115
- (buffer: Buffer | Uint8Array): FileTypeResult | null;
151
+ // Or from a remote location:
152
+
153
+ import * as http from 'http';
154
+
155
+ const url = 'https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif';
156
+
157
+ http.get(url, response => {
158
+ response.on('readable', () => {
159
+ const chunk = response.read(fileType.minimumBytes);
160
+ response.destroy();
161
+ console.log(fileType(chunk));
162
+ //=> {ext: 'gif', mime: 'image/gif'}
163
+ });
164
+ });
165
+ ```
166
+ */
167
+ (buffer: Buffer | Uint8Array | ArrayBuffer): fileType.FileTypeResult | undefined;
116
168
 
117
169
  /**
118
- * The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hard-code it.
119
- */
170
+ The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hard-code it.
171
+ */
120
172
  readonly minimumBytes: number;
121
173
 
122
174
  /**
123
- * Detect the file type of a readable stream.
124
- *
125
- * @param readableStream - A readable stream containing a file to examine, see: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
126
- * @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 `fileType()`.
127
- */
128
- readonly stream: (readableStream: ReadableStream) => Promise<ReadableStreamWithFileType>;
129
- }
175
+ Detect the file type of a readable stream.
176
+
177
+ @param readableStream - A readable stream containing a file to examine, see: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
178
+ @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 `fileType()`.
130
179
 
131
- /**
132
- * Detect the file type of a `Buffer`/`Uint8Array`. 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.
133
- *
134
- * @param buffer - It only needs the first `.minimumBytes` bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
135
- * @returns An object with the detected file type and MIME type or `null` when there was no match.
136
- */
137
- declare const fileType: FileTypeModule;
180
+ @example
181
+ ```
182
+ import * as fs from 'fs';
183
+ import * as crypto from 'crypto';
184
+ import fileType = require('file-type');
185
+
186
+ (async () => {
187
+ const read = fs.createReadStream('encrypted.enc');
188
+ const decipher = crypto.createDecipheriv(alg, key, iv);
189
+
190
+ const stream = await fileType.stream(read.pipe(decipher));
191
+
192
+ console.log(stream.fileType);
193
+ //=> {ext: 'mov', mime: 'video/quicktime'}
194
+
195
+ const write = fs.createWriteStream(`decrypted.${stream.fileType.ext}`);
196
+ stream.pipe(write);
197
+ })();
198
+ ```
199
+ */
200
+ readonly stream: (
201
+ readableStream: ReadableStream
202
+ ) => Promise<fileType.ReadableStreamWithFileType>;
203
+ };
138
204
 
139
- export default fileType;
205
+ export = fileType;
package/index.js CHANGED
@@ -1,30 +1,19 @@
1
1
  'use strict';
2
- const toBytes = s => [...s].map(c => c.charCodeAt(0));
3
- const xpiZipFilename = toBytes('META-INF/mozilla.rsa');
4
- const oxmlContentTypes = toBytes('[Content_Types].xml');
5
- const oxmlRels = toBytes('_rels/.rels');
2
+ const {stringToBytes, readUInt64LE, tarHeaderChecksumMatches, uint8ArrayUtf8ByteString} = require('./util');
6
3
 
7
- function readUInt64LE(buf, offset = 0) {
8
- let n = buf[offset];
9
- let mul = 1;
10
- let i = 0;
11
- while (++i < 8) {
12
- mul *= 0x100;
13
- n += buf[offset + i] * mul;
14
- }
15
-
16
- return n;
17
- }
4
+ const xpiZipFilename = stringToBytes('META-INF/mozilla.rsa');
5
+ const oxmlContentTypes = stringToBytes('[Content_Types].xml');
6
+ const oxmlRels = stringToBytes('_rels/.rels');
18
7
 
19
8
  const fileType = input => {
20
- if (!(input instanceof Uint8Array || Buffer.isBuffer(input))) {
21
- throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\`, got \`${typeof input}\``);
9
+ if (!(input instanceof Uint8Array || input instanceof ArrayBuffer || Buffer.isBuffer(input))) {
10
+ throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``);
22
11
  }
23
12
 
24
- const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
13
+ const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
25
14
 
26
- if (!(buf && buf.length > 1)) {
27
- return null;
15
+ if (!(buffer && buffer.length > 1)) {
16
+ return;
28
17
  }
29
18
 
30
19
  const check = (header, options) => {
@@ -36,10 +25,10 @@ const fileType = input => {
36
25
  // If a bitmask is set
37
26
  if (options.mask) {
38
27
  // If header doesn't equal `buf` with bits masked off
39
- if (header[i] !== (options.mask[i] & buf[i + options.offset])) {
28
+ if (header[i] !== (options.mask[i] & buffer[i + options.offset])) {
40
29
  return false;
41
30
  }
42
- } else if (header[i] !== buf[i + options.offset]) {
31
+ } else if (header[i] !== buffer[i + options.offset]) {
43
32
  return false;
44
33
  }
45
34
  }
@@ -47,7 +36,7 @@ const fileType = input => {
47
36
  return true;
48
37
  };
49
38
 
50
- const checkString = (header, options) => check(toBytes(header), options);
39
+ const checkString = (header, options) => check(stringToBytes(header), options);
51
40
 
52
41
  if (check([0xFF, 0xD8, 0xFF])) {
53
42
  return {
@@ -84,7 +73,7 @@ const fileType = input => {
84
73
  };
85
74
  }
86
75
 
87
- // Needs to be before `tif` check
76
+ // `cr2`, `orf`, and `arw` need to be before `tif` check
88
77
  if (
89
78
  (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
90
79
  check([0x43, 0x52], {offset: 8})
@@ -95,6 +84,34 @@ const fileType = input => {
95
84
  };
96
85
  }
97
86
 
87
+ if (check([0x49, 0x49, 0x52, 0x4F, 0x08, 0x00, 0x00, 0x00, 0x18])) {
88
+ return {
89
+ ext: 'orf',
90
+ mime: 'image/x-olympus-orf'
91
+ };
92
+ }
93
+
94
+ if (check([0x49, 0x49, 0x2A, 0x00, 0x10, 0xFB, 0x86, 0x01])) {
95
+ return {
96
+ ext: 'arw',
97
+ mime: 'image/x-sony-arw'
98
+ };
99
+ }
100
+
101
+ if (check([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2D])) {
102
+ return {
103
+ ext: 'dng',
104
+ mime: 'image/x-adobe-dng'
105
+ };
106
+ }
107
+
108
+ if (check([0x49, 0x49, 0x2A, 0x00, 0x30, 0x3D, 0x72, 0x01, 0x1C])) {
109
+ return {
110
+ ext: 'nef',
111
+ mime: 'image/x-nikon-nef'
112
+ };
113
+ }
114
+
98
115
  if (
99
116
  check([0x49, 0x49, 0x2A, 0x0]) ||
100
117
  check([0x4D, 0x4D, 0x0, 0x2A])
@@ -177,7 +194,7 @@ const fileType = input => {
177
194
 
178
195
  let zipHeaderIndex = 0; // The first zip header was already found at index 0
179
196
  let oxmlFound = false;
180
- let type = null;
197
+ let type;
181
198
 
182
199
  do {
183
200
  const offset = zipHeaderIndex + 30;
@@ -209,7 +226,7 @@ const fileType = input => {
209
226
  return type;
210
227
  }
211
228
 
212
- zipHeaderIndex = findNextZipHeaderIndex(buf, offset);
229
+ zipHeaderIndex = findNextZipHeaderIndex(buffer, offset);
213
230
  } while (zipHeaderIndex >= 0);
214
231
 
215
232
  // No more zip parts available in the buffer, but maybe we are almost certain about the type?
@@ -220,8 +237,8 @@ const fileType = input => {
220
237
 
221
238
  if (
222
239
  check([0x50, 0x4B]) &&
223
- (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) &&
224
- (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)
240
+ (buffer[2] === 0x3 || buffer[2] === 0x5 || buffer[2] === 0x7) &&
241
+ (buffer[3] === 0x4 || buffer[3] === 0x6 || buffer[3] === 0x8)
225
242
  ) {
226
243
  return {
227
244
  ext: 'zip',
@@ -229,7 +246,10 @@ const fileType = input => {
229
246
  };
230
247
  }
231
248
 
232
- if (check([0x75, 0x73, 0x74, 0x61, 0x72], {offset: 257})) {
249
+ if (
250
+ check([0x30, 0x30, 0x30, 0x30, 0x30, 0x30], {offset: 148, mask: [0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8]}) && // Valid tar checksum
251
+ tarHeaderChecksumMatches(buffer)
252
+ ) {
233
253
  return {
234
254
  ext: 'tar',
235
255
  mime: 'application/x-tar'
@@ -238,7 +258,7 @@ const fileType = input => {
238
258
 
239
259
  if (
240
260
  check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
241
- (buf[6] === 0x0 || buf[6] === 0x1)
261
+ (buffer[6] === 0x0 || buffer[6] === 0x1)
242
262
  ) {
243
263
  return {
244
264
  ext: 'rar',
@@ -274,25 +294,70 @@ const fileType = input => {
274
294
  };
275
295
  }
276
296
 
277
- if (check([0x33, 0x67, 0x70, 0x35]) || // 3gp5
278
- (
279
- check([0x0, 0x0, 0x0]) && check([0x66, 0x74, 0x79, 0x70], {offset: 4}) &&
280
- (
281
- check([0x6D, 0x70, 0x34, 0x31], {offset: 8}) || // MP41
282
- check([0x6D, 0x70, 0x34, 0x32], {offset: 8}) || // MP42
283
- check([0x69, 0x73, 0x6F, 0x6D], {offset: 8}) || // ISOM
284
- check([0x69, 0x73, 0x6F, 0x32], {offset: 8}) || // ISO2
285
- check([0x6D, 0x6D, 0x70, 0x34], {offset: 8}) || // MMP4
286
- check([0x4D, 0x34, 0x56], {offset: 8}) || // M4V
287
- check([0x64, 0x61, 0x73, 0x68], {offset: 8}) // DASH
288
- )
289
- )) {
297
+ // `mov` format variants
298
+ if (
299
+ check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // `free`
300
+ check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // `mdat` MJPEG
301
+ check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // `moov`
302
+ check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
303
+ ) {
290
304
  return {
291
- ext: 'mp4',
292
- mime: 'video/mp4'
305
+ ext: 'mov',
306
+ mime: 'video/quicktime'
293
307
  };
294
308
  }
295
309
 
310
+ // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
311
+ // It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box.
312
+ // `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
313
+ // Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
314
+ if (
315
+ check([0x66, 0x74, 0x79, 0x70], {offset: 4}) && // `ftyp`
316
+ (buffer[8] & 0x60) !== 0x00 && (buffer[9] & 0x60) !== 0x00 && (buffer[10] & 0x60) !== 0x00 && (buffer[11] & 0x60) !== 0x00 // Brand major
317
+ ) {
318
+ // They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
319
+ // For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
320
+ const brandMajor = uint8ArrayUtf8ByteString(buffer, 8, 12);
321
+ switch (brandMajor) {
322
+ case 'mif1':
323
+ return {ext: 'heic', mime: 'image/heif'};
324
+ case 'msf1':
325
+ return {ext: 'heic', mime: 'image/heif-sequence'};
326
+ case 'heic': case 'heix':
327
+ return {ext: 'heic', mime: 'image/heic'};
328
+ case 'hevc': case 'hevx':
329
+ return {ext: 'heic', mime: 'image/heic-sequence'};
330
+ case 'qt ':
331
+ return {ext: 'mov', mime: 'video/quicktime'};
332
+ case 'M4V ': case 'M4VH': case 'M4VP':
333
+ return {ext: 'm4v', mime: 'video/x-m4v'};
334
+ case 'M4P ':
335
+ return {ext: 'm4p', mime: 'video/mp4'};
336
+ case 'M4B ':
337
+ return {ext: 'm4b', mime: 'audio/mp4'};
338
+ case 'M4A ':
339
+ return {ext: 'm4a', mime: 'audio/x-m4a'};
340
+ case 'F4V ':
341
+ return {ext: 'f4v', mime: 'video/mp4'};
342
+ case 'F4P ':
343
+ return {ext: 'f4p', mime: 'video/mp4'};
344
+ case 'F4A ':
345
+ return {ext: 'f4a', mime: 'audio/mp4'};
346
+ case 'F4B ':
347
+ return {ext: 'f4b', mime: 'audio/mp4'};
348
+ default:
349
+ if (brandMajor.startsWith('3g')) {
350
+ if (brandMajor.startsWith('3g2')) {
351
+ return {ext: '3g2', mime: 'video/3gpp2'};
352
+ }
353
+
354
+ return {ext: '3gp', mime: 'video/3gpp'};
355
+ }
356
+
357
+ return {ext: 'mp4', mime: 'video/mp4'};
358
+ }
359
+ }
360
+
296
361
  if (check([0x4D, 0x54, 0x68, 0x64])) {
297
362
  return {
298
363
  ext: 'mid',
@@ -302,7 +367,7 @@ const fileType = input => {
302
367
 
303
368
  // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
304
369
  if (check([0x1A, 0x45, 0xDF, 0xA3])) {
305
- const sliced = buf.subarray(4, 4 + 4096);
370
+ const sliced = buffer.subarray(4, 4 + 4096);
306
371
  const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
307
372
 
308
373
  if (idPos !== -1) {
@@ -325,18 +390,6 @@ const fileType = input => {
325
390
  }
326
391
  }
327
392
 
328
- if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
329
- check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // Type: `free`
330
- check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
331
- check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
332
- check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // Type: `moov`
333
- check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
334
- return {
335
- ext: 'mov',
336
- mime: 'video/quicktime'
337
- };
338
- }
339
-
340
393
  // RIFF file format which might be AVI, WAV, QCP, etc
341
394
  if (check([0x52, 0x49, 0x46, 0x46])) {
342
395
  if (check([0x41, 0x56, 0x49], {offset: 8})) {
@@ -368,7 +421,7 @@ const fileType = input => {
368
421
 
369
422
  let offset = 30;
370
423
  do {
371
- const objectSize = readUInt64LE(buf, offset + 16);
424
+ const objectSize = readUInt64LE(buffer, offset + 16);
372
425
  if (check([0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65], {offset})) {
373
426
  // Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
374
427
  if (check([0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B], {offset: offset + 24})) {
@@ -391,7 +444,7 @@ const fileType = input => {
391
444
  }
392
445
 
393
446
  offset += objectSize;
394
- } while (offset + 24 <= buf.length);
447
+ } while (offset + 24 <= buffer.length);
395
448
 
396
449
  // Default to ASF generic extension
397
450
  return {
@@ -410,18 +463,11 @@ const fileType = input => {
410
463
  };
411
464
  }
412
465
 
413
- if (check([0x66, 0x74, 0x79, 0x70, 0x33, 0x67], {offset: 4})) {
414
- return {
415
- ext: '3gp',
416
- mime: 'video/3gpp'
417
- };
418
- }
419
-
420
466
  // Check for MPEG header at different starting offsets
421
- for (let start = 0; start < 2 && start < (buf.length - 16); start++) {
467
+ for (let start = 0; start < 2 && start < (buffer.length - 16); start++) {
422
468
  if (
423
469
  check([0x49, 0x44, 0x33], {offset: start}) || // ID3 header
424
- check([0xFF, 0xE2], {offset: start, mask: [0xFF, 0xE2]}) // MPEG 1 or 2 Layer 3 header
470
+ check([0xFF, 0xE2], {offset: start, mask: [0xFF, 0xE6]}) // MPEG 1 or 2 Layer 3 header
425
471
  ) {
426
472
  return {
427
473
  ext: 'mp3',
@@ -430,7 +476,7 @@ const fileType = input => {
430
476
  }
431
477
 
432
478
  if (
433
- check([0xFF, 0xE4], {offset: start, mask: [0xFF, 0xE4]}) // MPEG 1 or 2 Layer 2 header
479
+ check([0xFF, 0xE4], {offset: start, mask: [0xFF, 0xE6]}) // MPEG 1 or 2 Layer 2 header
434
480
  ) {
435
481
  return {
436
482
  ext: 'mp2',
@@ -457,15 +503,6 @@ const fileType = input => {
457
503
  }
458
504
  }
459
505
 
460
- if (
461
- check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4})
462
- ) {
463
- return { // MPEG-4 layer 3 (audio)
464
- ext: 'm4a',
465
- mime: 'audio/mp4' // RFC 4337
466
- };
467
- }
468
-
469
506
  // Needs to be before `ogg` check
470
507
  if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
471
508
  return {
@@ -568,7 +605,7 @@ const fileType = input => {
568
605
  }
569
606
 
570
607
  if (
571
- (buf[0] === 0x43 || buf[0] === 0x46) &&
608
+ (buffer[0] === 0x43 || buffer[0] === 0x46) &&
572
609
  check([0x57, 0x53], {offset: 1})
573
610
  ) {
574
611
  return {
@@ -838,37 +875,6 @@ const fileType = input => {
838
875
  };
839
876
  }
840
877
 
841
- // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
842
- if (check([0x66, 0x74, 0x79, 0x70], {offset: 4})) {
843
- if (check([0x6D, 0x69, 0x66, 0x31], {offset: 8})) {
844
- return {
845
- ext: 'heic',
846
- mime: 'image/heif'
847
- };
848
- }
849
-
850
- if (check([0x6D, 0x73, 0x66, 0x31], {offset: 8})) {
851
- return {
852
- ext: 'heic',
853
- mime: 'image/heif-sequence'
854
- };
855
- }
856
-
857
- if (check([0x68, 0x65, 0x69, 0x63], {offset: 8}) || check([0x68, 0x65, 0x69, 0x78], {offset: 8})) {
858
- return {
859
- ext: 'heic',
860
- mime: 'image/heic'
861
- };
862
- }
863
-
864
- if (check([0x68, 0x65, 0x76, 0x63], {offset: 8}) || check([0x68, 0x65, 0x76, 0x78], {offset: 8})) {
865
- return {
866
- ext: 'heic',
867
- mime: 'image/heic-sequence'
868
- };
869
- }
870
- }
871
-
872
878
  if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
873
879
  return {
874
880
  ext: 'ktx',
@@ -920,26 +926,64 @@ const fileType = input => {
920
926
  };
921
927
  }
922
928
 
923
- return null;
929
+ // Sony DSD Stream File (DSF)
930
+ if (check([0x44, 0x53, 0x44, 0x20])) {
931
+ return {
932
+ ext: 'dsf',
933
+ mime: 'audio/x-dsf' // Non-standard
934
+ };
935
+ }
936
+
937
+ if (check([0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46])) {
938
+ return {
939
+ ext: 'lnk',
940
+ mime: 'application/x.ms.shortcut' // Invented by us
941
+ };
942
+ }
943
+
944
+ if (check([0x62, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00])) {
945
+ return {
946
+ ext: 'alias',
947
+ mime: 'application/x.apple.alias' // Invented by us
948
+ };
949
+ }
950
+
951
+ if (checkString('Creative Voice File')) {
952
+ return {
953
+ ext: 'voc',
954
+ mime: 'audio/x-voc'
955
+ };
956
+ }
957
+
958
+ if (check([0x0B, 0x77])) {
959
+ return {
960
+ ext: 'ac3',
961
+ mime: 'audio/vnd.dolby.dd-raw'
962
+ };
963
+ }
924
964
  };
925
965
 
926
966
  module.exports = fileType;
927
- module.exports.default = fileType;
928
967
 
929
968
  Object.defineProperty(fileType, 'minimumBytes', {value: 4100});
930
969
 
931
- module.exports.stream = readableStream => new Promise(resolve => {
970
+ fileType.stream = readableStream => new Promise((resolve, reject) => {
932
971
  // Using `eval` to work around issues when bundling with Webpack
933
972
  const stream = eval('require')('stream'); // eslint-disable-line no-eval
934
973
 
935
974
  readableStream.once('readable', () => {
936
975
  const pass = new stream.PassThrough();
937
976
  const chunk = readableStream.read(module.exports.minimumBytes) || readableStream.read();
938
- pass.fileType = fileType(chunk);
977
+ try {
978
+ pass.fileType = fileType(chunk);
979
+ } catch (error) {
980
+ reject(error);
981
+ }
982
+
939
983
  readableStream.unshift(chunk);
940
984
 
941
985
  if (stream.pipeline) {
942
- resolve(stream.pipeline(readableStream, pass));
986
+ resolve(stream.pipeline(readableStream, pass, () => {}));
943
987
  } else {
944
988
  resolve(readableStream.pipe(pass));
945
989
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "file-type",
3
- "version": "10.9.0",
4
- "description": "Detect the file type of a Buffer/Uint8Array",
3
+ "version": "11.1.0",
4
+ "description": "Detect the file type of a Buffer/Uint8Array/ArrayBuffer",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/file-type",
7
7
  "author": {
@@ -13,11 +13,12 @@
13
13
  "node": ">=6"
14
14
  },
15
15
  "scripts": {
16
- "test": "xo && ava && tsd-check"
16
+ "test": "xo && ava && tsd"
17
17
  },
18
18
  "files": [
19
19
  "index.js",
20
- "index.d.ts"
20
+ "index.d.ts",
21
+ "util.js"
21
22
  ],
22
23
  "keywords": [
23
24
  "mime",
@@ -45,6 +46,10 @@
45
46
  "webp",
46
47
  "flif",
47
48
  "cr2",
49
+ "orf",
50
+ "arw",
51
+ "dng",
52
+ "nef",
48
53
  "tif",
49
54
  "bmp",
50
55
  "jxr",
@@ -57,7 +62,6 @@
57
62
  "7z",
58
63
  "dmg",
59
64
  "mp4",
60
- "m4v",
61
65
  "mid",
62
66
  "mkv",
63
67
  "webm",
@@ -118,14 +122,28 @@
118
122
  "wma",
119
123
  "ics",
120
124
  "glb",
121
- "pcap"
125
+ "pcap",
126
+ "dsf",
127
+ "lnk",
128
+ "alias",
129
+ "voc",
130
+ "ac3",
131
+ "3g2",
132
+ "m4a",
133
+ "m4b",
134
+ "m4p",
135
+ "m4v",
136
+ "f4a",
137
+ "f4b",
138
+ "f4p",
139
+ "f4v"
122
140
  ],
123
141
  "devDependencies": {
124
- "@types/node": "^11.10.4",
125
- "ava": "^1.2.1",
142
+ "@types/node": "^11.12.2",
143
+ "ava": "^1.4.1",
126
144
  "pify": "^4.0.1",
127
- "read-chunk": "^3.0.0",
128
- "tsd-check": "^0.3.0",
145
+ "read-chunk": "^3.2.0",
146
+ "tsd": "^0.7.1",
129
147
  "xo": "^0.24.0"
130
148
  }
131
149
  }
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # file-type [![Build Status](https://travis-ci.org/sindresorhus/file-type.svg?branch=master)](https://travis-ci.org/sindresorhus/file-type)
2
2
 
3
- > Detect the file type of a Buffer/Uint8Array
3
+ > Detect the file type of a Buffer/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
 
@@ -95,11 +95,11 @@ Returns an `Object` with:
95
95
  - `ext` - One of the [supported file types](#supported-file-types)
96
96
  - `mime` - The [MIME type](https://en.wikipedia.org/wiki/Internet_media_type)
97
97
 
98
- Or `null` when there is no match.
98
+ Or `undefined` when there is no match.
99
99
 
100
100
  #### input
101
101
 
102
- Type: `Buffer` `Uint8Array`
102
+ Type: `Buffer | Uint8Array | ArrayBuffer`
103
103
 
104
104
  It only needs the first `.minimumBytes` bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
105
105
 
@@ -129,7 +129,11 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
129
129
  - [`gif`](https://en.wikipedia.org/wiki/GIF)
130
130
  - [`webp`](https://en.wikipedia.org/wiki/WebP)
131
131
  - [`flif`](https://en.wikipedia.org/wiki/Free_Lossless_Image_Format)
132
- - [`cr2`](https://fileinfo.com/extension/cr2)
132
+ - [`cr2`](https://fileinfo.com/extension/cr2) - Canon Raw image file (v2)
133
+ - [`orf`](https://en.wikipedia.org/wiki/ORF_format) - Olympus Raw image file
134
+ - [`arw`](https://en.wikipedia.org/wiki/Raw_image_format#ARW) - Sony Alpha Raw image file
135
+ - [`dng`](https://en.wikipedia.org/wiki/Digital_Negative) - Adobe Digital Negative image file
136
+ - [`nef`](https://www.nikonusa.com/en/learn-and-explore/a/products-and-innovation/nikon-electronic-format-nef.html) - Nikon Electronic Format image file
133
137
  - [`tif`](https://en.wikipedia.org/wiki/Tagged_Image_File_Format)
134
138
  - [`bmp`](https://en.wikipedia.org/wiki/BMP_file_format)
135
139
  - [`jxr`](https://en.wikipedia.org/wiki/JPEG_XR)
@@ -142,7 +146,6 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
142
146
  - [`7z`](https://en.wikipedia.org/wiki/7z)
143
147
  - [`dmg`](https://en.wikipedia.org/wiki/Apple_Disk_Image)
144
148
  - [`mp4`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#Filename_extensions)
145
- - [`m4v`](https://en.wikipedia.org/wiki/M4V)
146
149
  - [`mid`](https://en.wikipedia.org/wiki/MIDI)
147
150
  - [`mkv`](https://en.wikipedia.org/wiki/Matroska)
148
151
  - [`webm`](https://en.wikipedia.org/wiki/WebM)
@@ -193,7 +196,6 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
193
196
  - [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML)
194
197
  - [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML)
195
198
  - [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML)
196
- - [`3gp`](https://en.wikipedia.org/wiki/3GP_and_3G2)
197
199
  - [`jp2`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
198
200
  - [`jpm`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
199
201
  - [`jpx`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
@@ -216,10 +218,25 @@ Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream
216
218
  - [`ics`](https://en.wikipedia.org/wiki/ICalendar#Data_format) - iCalendar
217
219
  - [`glb`](https://github.com/KhronosGroup/glTF) - GL Transmission Format
218
220
  - [`pcap`](https://wiki.wireshark.org/Development/LibpcapFileFormat) - Libpcap File Format
221
+ - [`dsf`](https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf) - Sony DSD Stream File (DSF)
222
+ - [`lnk`](https://en.wikipedia.org/wiki/Shortcut_%28computing%29#Microsoft_Windows) - Microsoft Windows file shortcut
223
+ - [`alias`](https://en.wikipedia.org/wiki/Alias_%28Mac_OS%29) - macOS Alias file
224
+ - [`voc`](https://wiki.multimedia.cx/index.php/Creative_Voice) - Creative Voice File
225
+ - [`ac3`](https://www.atsc.org/standard/a522012-digital-audio-compression-ac-3-e-ac-3-standard-12172012/) - ATSC A/52 Audio File
226
+ - [`3gp`](https://en.wikipedia.org/wiki/3GP_and_3G2#3GP) - Multimedia container format defined by the Third Generation Partnership Project (3GPP) for 3G UMTS multimedia services
227
+ - [`3g2`](https://en.wikipedia.org/wiki/3GP_and_3G2#3G2) - Multimedia container format defined by the 3GPP2 for 3G CDMA2000 multimedia services
228
+ - [`m4v`](https://en.wikipedia.org/wiki/M4V) - MPEG-4 Visual bitstreams
229
+ - [`m4p`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#Filename_extensions) - MPEG-4 files with audio streams encrypted by FairPlay Digital Rights Management as were sold through the iTunes Store
230
+ - [`m4a`](https://en.wikipedia.org/wiki/M4A) - Audio-only MPEG-4 files
231
+ - [`m4b`](https://en.wikipedia.org/wiki/M4B) - Audiobook and podcast MPEG-4 files, which also contain metadata including chapter markers, images, and hyperlinks
232
+ - [`f4v`](https://en.wikipedia.org/wiki/Flash_Video) - ISO base media file format used by Adobe Flash Player
233
+ - [`f4p`](https://en.wikipedia.org/wiki/Flash_Video) - ISO base media file format protected by Adobe Access DRM used by Adobe Flash Player
234
+ - [`f4a`](https://en.wikipedia.org/wiki/Flash_Video) - Audio-only ISO base media file format used by Adobe Flash Player
235
+ - [`f4b`](https://en.wikipedia.org/wiki/Flash_Video) - Audiobook and podcast ISO base media file format used by Adobe Flash Player
219
236
 
220
237
  *SVG isn't included as it requires the whole file to be read, but you can get it [here](https://github.com/sindresorhus/is-svg).*
221
238
 
222
- *Pull request welcome for additional commonly used file types.*
239
+ *Pull requests are welcome for additional commonly used file types.*
223
240
 
224
241
 
225
242
  ## Related
package/util.js ADDED
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ exports.stringToBytes = string => [...string].map(character => character.charCodeAt(0));
4
+
5
+ const uint8ArrayUtf8ByteString = (array, start, end) => {
6
+ return String.fromCharCode(...array.slice(start, end));
7
+ };
8
+
9
+ exports.readUInt64LE = (buffer, offset = 0) => {
10
+ let n = buffer[offset];
11
+ let mul = 1;
12
+ let i = 0;
13
+
14
+ while (++i < 8) {
15
+ mul *= 0x100;
16
+ n += buffer[offset + i] * mul;
17
+ }
18
+
19
+ return n;
20
+ };
21
+
22
+ exports.tarHeaderChecksumMatches = buffer => { // Does not check if checksum field characters are valid
23
+ if (buffer.length < 512) { // `tar` header size, cannot compute checksum without it
24
+ return false;
25
+ }
26
+
27
+ const MASK_8TH_BIT = 0x80;
28
+
29
+ let sum = 256; // Intitalize sum, with 256 as sum of 8 spaces in checksum field
30
+ let signedBitSum = 0; // Initialize signed bit sum
31
+
32
+ for (let i = 0; i < 148; i++) {
33
+ const byte = buffer[i];
34
+ sum += byte;
35
+ signedBitSum += byte & MASK_8TH_BIT; // Add signed bit to signed bit sum
36
+ }
37
+
38
+ // Skip checksum field
39
+
40
+ for (let i = 156; i < 512; i++) {
41
+ const byte = buffer[i];
42
+ sum += byte;
43
+ signedBitSum += byte & MASK_8TH_BIT; // Add signed bit to signed bit sum
44
+ }
45
+
46
+ const readSum = parseInt(uint8ArrayUtf8ByteString(buffer, 148, 154), 8); // Read sum in header
47
+
48
+ // Some implementations compute checksum incorrectly using signed bytes
49
+ return (
50
+ // Checksum in header equals the sum we calculated
51
+ readSum === sum ||
52
+
53
+ // Checksum in header equals sum we calculated plus signed-to-unsigned delta
54
+ readSum === (sum - (signedBitSum << 1))
55
+ );
56
+ };
57
+
58
+ exports.uint8ArrayUtf8ByteString = uint8ArrayUtf8ByteString;