file-type 7.7.0 → 9.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.
Files changed (3) hide show
  1. package/index.js +109 -54
  2. package/package.json +2 -2
  3. package/readme.md +5 -1
package/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
- const toBytes = s => Array.from(s).map(c => c.charCodeAt(0));
2
+ const toBytes = s => [...s].map(c => c.charCodeAt(0));
3
3
  const xpiZipFilename = toBytes('META-INF/mozilla.rsa');
4
4
  const oxmlContentTypes = toBytes('[Content_Types].xml');
5
5
  const oxmlRels = toBytes('_rels/.rels');
6
6
 
7
7
  module.exports = input => {
8
- const buf = (input instanceof Uint8Array) ? input : new Uint8Array(input);
8
+ const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
9
9
 
10
10
  if (!(buf && buf.length > 1)) {
11
11
  return null;
@@ -151,41 +151,54 @@ module.exports = input => {
151
151
  };
152
152
  }
153
153
 
154
- // https://github.com/file/file/blob/master/magic/Magdir/msooxml
155
- if (check(oxmlContentTypes, {offset: 30}) || check(oxmlRels, {offset: 30})) {
156
- const sliced = buf.subarray(4, 4 + 2000);
157
- const nextZipHeaderIndex = arr => arr.findIndex((el, i, arr) => arr[i] === 0x50 && arr[i + 1] === 0x4B && arr[i + 2] === 0x3 && arr[i + 3] === 0x4);
158
- const header2Pos = nextZipHeaderIndex(sliced);
159
-
160
- if (header2Pos !== -1) {
161
- const slicedAgain = buf.subarray(header2Pos + 8, header2Pos + 8 + 1000);
162
- const header3Pos = nextZipHeaderIndex(slicedAgain);
163
-
164
- if (header3Pos !== -1) {
165
- const offset = 8 + header2Pos + header3Pos + 30;
166
-
167
- if (checkString('word/', {offset})) {
168
- return {
169
- ext: 'docx',
170
- mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
171
- };
172
- }
173
-
174
- if (checkString('ppt/', {offset})) {
175
- return {
176
- ext: 'pptx',
177
- mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
178
- };
179
- }
180
-
181
- if (checkString('xl/', {offset})) {
182
- return {
183
- ext: 'xlsx',
184
- mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
185
- };
186
- }
154
+ // The docx, xlsx and pptx file types extend the Office Open XML file format:
155
+ // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats
156
+ // We look for:
157
+ // - one entry named '[Content_Types].xml' or '_rels/.rels',
158
+ // - one entry indicating specific type of file.
159
+ // MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it.
160
+ const findNextZipHeaderIndex = (arr, startAt = 0) => arr.findIndex((el, i, arr) => i >= startAt && arr[i] === 0x50 && arr[i + 1] === 0x4B && arr[i + 2] === 0x3 && arr[i + 3] === 0x4);
161
+
162
+ let zipHeaderIndex = 0; // The first zip header was already found at index 0
163
+ let oxmlFound = false;
164
+ let type = null;
165
+
166
+ do {
167
+ const offset = zipHeaderIndex + 30;
168
+
169
+ if (!oxmlFound) {
170
+ oxmlFound = (check(oxmlContentTypes, {offset}) || check(oxmlRels, {offset}));
171
+ }
172
+
173
+ if (!type) {
174
+ if (checkString('word/', {offset})) {
175
+ type = {
176
+ ext: 'docx',
177
+ mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
178
+ };
179
+ } else if (checkString('ppt/', {offset})) {
180
+ type = {
181
+ ext: 'pptx',
182
+ mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
183
+ };
184
+ } else if (checkString('xl/', {offset})) {
185
+ type = {
186
+ ext: 'xlsx',
187
+ mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
188
+ };
187
189
  }
188
190
  }
191
+
192
+ if (oxmlFound && type) {
193
+ return type;
194
+ }
195
+
196
+ zipHeaderIndex = findNextZipHeaderIndex(buf, offset);
197
+ } while (zipHeaderIndex >= 0);
198
+
199
+ // No more zip parts available in the buffer, but maybe we are almost certain about the type?
200
+ if (type) {
201
+ return type;
189
202
  }
190
203
  }
191
204
 
@@ -278,7 +291,7 @@ module.exports = input => {
278
291
 
279
292
  if (idPos !== -1) {
280
293
  const docTypePos = idPos + 3;
281
- const findDocType = type => Array.from(type).every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
294
+ const findDocType = type => [...type].every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
282
295
 
283
296
  if (findDocType('matroska')) {
284
297
  return {
@@ -307,14 +320,27 @@ module.exports = input => {
307
320
  };
308
321
  }
309
322
 
310
- if (
311
- check([0x52, 0x49, 0x46, 0x46]) &&
312
- check([0x41, 0x56, 0x49], {offset: 8})
313
- ) {
314
- return {
315
- ext: 'avi',
316
- mime: 'video/x-msvideo'
317
- };
323
+ // RIFF file format which might be AVI, WAV, QCP, etc
324
+ if (check([0x52, 0x49, 0x46, 0x46])) {
325
+ if (check([0x41, 0x56, 0x49], {offset: 8})) {
326
+ return {
327
+ ext: 'avi',
328
+ mime: 'video/vnd.avi'
329
+ };
330
+ }
331
+ if (check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
332
+ return {
333
+ ext: 'wav',
334
+ mime: 'audio/vnd.wave'
335
+ };
336
+ }
337
+ // QLCM, QCP file
338
+ if (check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
339
+ return {
340
+ ext: 'qcp',
341
+ mime: 'audio/qcelp'
342
+ };
343
+ }
318
344
  }
319
345
 
320
346
  if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
@@ -361,15 +387,33 @@ module.exports = input => {
361
387
  mime: 'audio/mpeg'
362
388
  };
363
389
  }
390
+
391
+ if (
392
+ check([0xFF, 0xF8], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 2 layer 0 using ADTS
393
+ ) {
394
+ return {
395
+ ext: 'mp2',
396
+ mime: 'audio/mpeg'
397
+ };
398
+ }
399
+
400
+ if (
401
+ check([0xFF, 0xF0], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 4 layer 0 using ADTS
402
+ ) {
403
+ return {
404
+ ext: 'mp4',
405
+ mime: 'audio/mpeg'
406
+ };
407
+ }
364
408
  }
365
409
 
366
410
  if (
367
411
  check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4}) ||
368
412
  check([0x4D, 0x34, 0x41, 0x20])
369
413
  ) {
370
- return {
414
+ return { // MPEG-4 layer 3 (audio)
371
415
  ext: 'm4a',
372
- mime: 'audio/m4a'
416
+ mime: 'audio/mp4' // RFC 4337
373
417
  };
374
418
  }
375
419
 
@@ -437,13 +481,17 @@ module.exports = input => {
437
481
  };
438
482
  }
439
483
 
440
- if (
441
- check([0x52, 0x49, 0x46, 0x46]) &&
442
- check([0x57, 0x41, 0x56, 0x45], {offset: 8})
443
- ) {
484
+ if (check([0x4D, 0x41, 0x43, 0x20])) { // 'MAC '
485
+ return {
486
+ ext: 'ape',
487
+ mime: 'audio/ape'
488
+ };
489
+ }
490
+
491
+ if (check([0x77, 0x76, 0x70, 0x6B])) { // 'wvpk'
444
492
  return {
445
- ext: 'wav',
446
- mime: 'audio/x-wav'
493
+ ext: 'wv',
494
+ mime: 'audio/wavpack'
447
495
  };
448
496
  }
449
497
 
@@ -528,7 +576,7 @@ module.exports = input => {
528
576
  ) {
529
577
  return {
530
578
  ext: 'eot',
531
- mime: 'application/octet-stream'
579
+ mime: 'application/vnd.ms-fontobject'
532
580
  };
533
581
  }
534
582
 
@@ -556,7 +604,7 @@ module.exports = input => {
556
604
  if (check([0x00, 0x00, 0x02, 0x00])) {
557
605
  return {
558
606
  ext: 'cur',
559
- mime: 'image/x-win-bitmap'
607
+ mime: 'image/x-icon'
560
608
  };
561
609
  }
562
610
 
@@ -770,5 +818,12 @@ module.exports = input => {
770
818
  }
771
819
  }
772
820
 
821
+ if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
822
+ return {
823
+ ext: 'ktx',
824
+ mime: 'image/ktx'
825
+ };
826
+ }
827
+
773
828
  return null;
774
829
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-type",
3
- "version": "7.7.0",
3
+ "version": "9.0.0",
4
4
  "description": "Detect the file type of a Buffer/Uint8Array",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/file-type",
@@ -10,7 +10,7 @@
10
10
  "url": "sindresorhus.com"
11
11
  },
12
12
  "engines": {
13
- "node": ">=4"
13
+ "node": ">=6"
14
14
  },
15
15
  "scripts": {
16
16
  "test": "xo && ava"
package/readme.md CHANGED
@@ -76,7 +76,7 @@ Or `null` when no match.
76
76
 
77
77
  Type: `Buffer` `Uint8Array`
78
78
 
79
- It only needs the first 4100 bytes.
79
+ It only needs the first 4100 bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
80
80
 
81
81
 
82
82
  ## Supported file types
@@ -114,6 +114,7 @@ It only needs the first 4100 bytes.
114
114
  - [`opus`](https://en.wikipedia.org/wiki/Opus_(audio_format))
115
115
  - [`flac`](https://en.wikipedia.org/wiki/FLAC)
116
116
  - [`wav`](https://en.wikipedia.org/wiki/WAV)
117
+ - [`qcp`](https://en.wikipedia.org/wiki/QCP)
117
118
  - [`amr`](https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec)
118
119
  - [`pdf`](https://en.wikipedia.org/wiki/Portable_Document_Format)
119
120
  - [`epub`](https://en.wikipedia.org/wiki/EPUB)
@@ -161,6 +162,9 @@ It only needs the first 4100 bytes.
161
162
  - [`xml`](https://en.wikipedia.org/wiki/XML)
162
163
  - [`heic`](http://nokiatech.github.io/heif/technical.html)
163
164
  - [`cur`](https://en.wikipedia.org/wiki/ICO_(file_format))
165
+ - [`ktx`](https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/)
166
+ - [`ape`](https://en.wikipedia.org/wiki/Monkey%27s_Audio) - Monkey's Audio
167
+ - [`wv`](https://en.wikipedia.org/wiki/WavPack) - WavPack
164
168
 
165
169
  *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).*
166
170