index.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. 'use strict';
  2. const {
  3. multiByteIndexOf,
  4. stringToBytes,
  5. readUInt64LE,
  6. tarHeaderChecksumMatches,
  7. uint8ArrayUtf8ByteString
  8. } = require('./util');
  9. const supported = require('./supported');
  10. const xpiZipFilename = stringToBytes('META-INF/mozilla.rsa');
  11. const oxmlContentTypes = stringToBytes('[Content_Types].xml');
  12. const oxmlRels = stringToBytes('_rels/.rels');
  13. const fileType = input => {
  14. if (!(input instanceof Uint8Array || input instanceof ArrayBuffer || Buffer.isBuffer(input))) {
  15. throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``);
  16. }
  17. const buffer = input instanceof Uint8Array ? input : new Uint8Array(input);
  18. if (!(buffer && buffer.length > 1)) {
  19. return;
  20. }
  21. const check = (header, options) => {
  22. options = {
  23. offset: 0,
  24. ...options
  25. };
  26. for (let i = 0; i < header.length; i++) {
  27. // If a bitmask is set
  28. if (options.mask) {
  29. // If header doesn't equal `buf` with bits masked off
  30. if (header[i] !== (options.mask[i] & buffer[i + options.offset])) {
  31. return false;
  32. }
  33. } else if (header[i] !== buffer[i + options.offset]) {
  34. return false;
  35. }
  36. }
  37. return true;
  38. };
  39. const checkString = (header, options) => check(stringToBytes(header), options);
  40. if (check([0xFF, 0xD8, 0xFF])) {
  41. return {
  42. ext: 'jpg',
  43. mime: 'image/jpeg'
  44. };
  45. }
  46. if (check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
  47. // APNG format (https://wiki.mozilla.org/APNG_Specification)
  48. // 1. Find the first IDAT (image data) chunk (49 44 41 54)
  49. // 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C)
  50. // Offset calculated as follows:
  51. // - 8 bytes: PNG signature
  52. // - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk
  53. const startIndex = 33;
  54. const firstImageDataChunkIndex = buffer.findIndex((el, i) => i >= startIndex && buffer[i] === 0x49 && buffer[i + 1] === 0x44 && buffer[i + 2] === 0x41 && buffer[i + 3] === 0x54);
  55. const sliced = buffer.subarray(startIndex, firstImageDataChunkIndex);
  56. if (sliced.findIndex((el, i) => sliced[i] === 0x61 && sliced[i + 1] === 0x63 && sliced[i + 2] === 0x54 && sliced[i + 3] === 0x4C) >= 0) {
  57. return {
  58. ext: 'apng',
  59. mime: 'image/apng'
  60. };
  61. }
  62. return {
  63. ext: 'png',
  64. mime: 'image/png'
  65. };
  66. }
  67. if (check([0x47, 0x49, 0x46])) {
  68. return {
  69. ext: 'gif',
  70. mime: 'image/gif'
  71. };
  72. }
  73. if (check([0x57, 0x45, 0x42, 0x50], {offset: 8})) {
  74. return {
  75. ext: 'webp',
  76. mime: 'image/webp'
  77. };
  78. }
  79. if (check([0x46, 0x4C, 0x49, 0x46])) {
  80. return {
  81. ext: 'flif',
  82. mime: 'image/flif'
  83. };
  84. }
  85. // `cr2`, `orf`, and `arw` need to be before `tif` check
  86. if (
  87. (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
  88. check([0x43, 0x52], {offset: 8})
  89. ) {
  90. return {
  91. ext: 'cr2',
  92. mime: 'image/x-canon-cr2'
  93. };
  94. }
  95. if (check([0x49, 0x49, 0x52, 0x4F, 0x08, 0x00, 0x00, 0x00, 0x18])) {
  96. return {
  97. ext: 'orf',
  98. mime: 'image/x-olympus-orf'
  99. };
  100. }
  101. if (
  102. check([0x49, 0x49, 0x2A, 0x00]) &&
  103. (check([0x10, 0xFB, 0x86, 0x01], {offset: 4}) || check([0x08, 0x00, 0x00, 0x00], {offset: 4})) &&
  104. // This pattern differentiates ARW from other TIFF-ish file types:
  105. check([0x00, 0xFE, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x01], {offset: 9})
  106. ) {
  107. return {
  108. ext: 'arw',
  109. mime: 'image/x-sony-arw'
  110. };
  111. }
  112. if (
  113. check([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00]) &&
  114. (check([0x2D, 0x00, 0xFE, 0x00], {offset: 8}) ||
  115. check([0x27, 0x00, 0xFE, 0x00], {offset: 8}))
  116. ) {
  117. return {
  118. ext: 'dng',
  119. mime: 'image/x-adobe-dng'
  120. };
  121. }
  122. if (
  123. check([0x49, 0x49, 0x2A, 0x00]) &&
  124. check([0x1C, 0x00, 0xFE, 0x00], {offset: 8})
  125. ) {
  126. return {
  127. ext: 'nef',
  128. mime: 'image/x-nikon-nef'
  129. };
  130. }
  131. if (check([0x49, 0x49, 0x55, 0x00, 0x18, 0x00, 0x00, 0x00, 0x88, 0xE7, 0x74, 0xD8])) {
  132. return {
  133. ext: 'rw2',
  134. mime: 'image/x-panasonic-rw2'
  135. };
  136. }
  137. // `raf` is here just to keep all the raw image detectors together.
  138. if (checkString('FUJIFILMCCD-RAW')) {
  139. return {
  140. ext: 'raf',
  141. mime: 'image/x-fujifilm-raf'
  142. };
  143. }
  144. if (
  145. check([0x49, 0x49, 0x2A, 0x0]) ||
  146. check([0x4D, 0x4D, 0x0, 0x2A])
  147. ) {
  148. return {
  149. ext: 'tif',
  150. mime: 'image/tiff'
  151. };
  152. }
  153. if (check([0x42, 0x4D])) {
  154. return {
  155. ext: 'bmp',
  156. mime: 'image/bmp'
  157. };
  158. }
  159. if (check([0x49, 0x49, 0xBC])) {
  160. return {
  161. ext: 'jxr',
  162. mime: 'image/vnd.ms-photo'
  163. };
  164. }
  165. if (check([0x38, 0x42, 0x50, 0x53])) {
  166. return {
  167. ext: 'psd',
  168. mime: 'image/vnd.adobe.photoshop'
  169. };
  170. }
  171. // Zip-based file formats
  172. // Need to be before the `zip` check
  173. const zipHeader = [0x50, 0x4B, 0x3, 0x4];
  174. if (check(zipHeader)) {
  175. if (
  176. check([0x6D, 0x69, 0x6D, 0x65, 0x74, 0x79, 0x70, 0x65, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x65, 0x70, 0x75, 0x62, 0x2B, 0x7A, 0x69, 0x70], {offset: 30})
  177. ) {
  178. return {
  179. ext: 'epub',
  180. mime: 'application/epub+zip'
  181. };
  182. }
  183. // Assumes signed `.xpi` from addons.mozilla.org
  184. if (check(xpiZipFilename, {offset: 30})) {
  185. return {
  186. ext: 'xpi',
  187. mime: 'application/x-xpinstall'
  188. };
  189. }
  190. if (checkString('mimetypeapplication/vnd.oasis.opendocument.text', {offset: 30})) {
  191. return {
  192. ext: 'odt',
  193. mime: 'application/vnd.oasis.opendocument.text'
  194. };
  195. }
  196. if (checkString('mimetypeapplication/vnd.oasis.opendocument.spreadsheet', {offset: 30})) {
  197. return {
  198. ext: 'ods',
  199. mime: 'application/vnd.oasis.opendocument.spreadsheet'
  200. };
  201. }
  202. if (checkString('mimetypeapplication/vnd.oasis.opendocument.presentation', {offset: 30})) {
  203. return {
  204. ext: 'odp',
  205. mime: 'application/vnd.oasis.opendocument.presentation'
  206. };
  207. }
  208. // The docx, xlsx and pptx file types extend the Office Open XML file format:
  209. // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats
  210. // We look for:
  211. // - one entry named '[Content_Types].xml' or '_rels/.rels',
  212. // - one entry indicating specific type of file.
  213. // MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it.
  214. let zipHeaderIndex = 0; // The first zip header was already found at index 0
  215. let oxmlFound = false;
  216. let type;
  217. do {
  218. const offset = zipHeaderIndex + 30;
  219. if (!oxmlFound) {
  220. oxmlFound = (check(oxmlContentTypes, {offset}) || check(oxmlRels, {offset}));
  221. }
  222. if (!type) {
  223. if (checkString('word/', {offset})) {
  224. type = {
  225. ext: 'docx',
  226. mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
  227. };
  228. } else if (checkString('ppt/', {offset})) {
  229. type = {
  230. ext: 'pptx',
  231. mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
  232. };
  233. } else if (checkString('xl/', {offset})) {
  234. type = {
  235. ext: 'xlsx',
  236. mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  237. };
  238. }
  239. }
  240. if (oxmlFound && type) {
  241. return type;
  242. }
  243. zipHeaderIndex = multiByteIndexOf(buffer, zipHeader, offset);
  244. } while (zipHeaderIndex >= 0);
  245. // No more zip parts available in the buffer, but maybe we are almost certain about the type?
  246. if (type) {
  247. return type;
  248. }
  249. }
  250. if (
  251. check([0x50, 0x4B]) &&
  252. (buffer[2] === 0x3 || buffer[2] === 0x5 || buffer[2] === 0x7) &&
  253. (buffer[3] === 0x4 || buffer[3] === 0x6 || buffer[3] === 0x8)
  254. ) {
  255. return {
  256. ext: 'zip',
  257. mime: 'application/zip'
  258. };
  259. }
  260. if (
  261. check([0x30, 0x30, 0x30, 0x30, 0x30, 0x30], {offset: 148, mask: [0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8]}) && // Valid tar checksum
  262. tarHeaderChecksumMatches(buffer)
  263. ) {
  264. return {
  265. ext: 'tar',
  266. mime: 'application/x-tar'
  267. };
  268. }
  269. if (
  270. check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
  271. (buffer[6] === 0x0 || buffer[6] === 0x1)
  272. ) {
  273. return {
  274. ext: 'rar',
  275. mime: 'application/x-rar-compressed'
  276. };
  277. }
  278. if (check([0x1F, 0x8B, 0x8])) {
  279. return {
  280. ext: 'gz',
  281. mime: 'application/gzip'
  282. };
  283. }
  284. if (check([0x42, 0x5A, 0x68])) {
  285. return {
  286. ext: 'bz2',
  287. mime: 'application/x-bzip2'
  288. };
  289. }
  290. if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
  291. return {
  292. ext: '7z',
  293. mime: 'application/x-7z-compressed'
  294. };
  295. }
  296. if (check([0x78, 0x01])) {
  297. return {
  298. ext: 'dmg',
  299. mime: 'application/x-apple-diskimage'
  300. };
  301. }
  302. // `mov` format variants
  303. if (
  304. check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // `free`
  305. check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // `mdat` MJPEG
  306. check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // `moov`
  307. check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide`
  308. ) {
  309. return {
  310. ext: 'mov',
  311. mime: 'video/quicktime'
  312. };
  313. }
  314. // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
  315. // It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box.
  316. // `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
  317. // Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
  318. if (
  319. checkString('ftyp', {offset: 4}) &&
  320. (buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII?
  321. ) {
  322. // They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect.
  323. // For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension.
  324. const brandMajor = uint8ArrayUtf8ByteString(buffer, 8, 12).replace('\0', ' ').trim();
  325. switch (brandMajor) {
  326. case 'mif1':
  327. return {ext: 'heic', mime: 'image/heif'};
  328. case 'msf1':
  329. return {ext: 'heic', mime: 'image/heif-sequence'};
  330. case 'heic': case 'heix':
  331. return {ext: 'heic', mime: 'image/heic'};
  332. case 'hevc': case 'hevx':
  333. return {ext: 'heic', mime: 'image/heic-sequence'};
  334. case 'qt':
  335. return {ext: 'mov', mime: 'video/quicktime'};
  336. case 'M4V': case 'M4VH': case 'M4VP':
  337. return {ext: 'm4v', mime: 'video/x-m4v'};
  338. case 'M4P':
  339. return {ext: 'm4p', mime: 'video/mp4'};
  340. case 'M4B':
  341. return {ext: 'm4b', mime: 'audio/mp4'};
  342. case 'M4A':
  343. return {ext: 'm4a', mime: 'audio/x-m4a'};
  344. case 'F4V':
  345. return {ext: 'f4v', mime: 'video/mp4'};
  346. case 'F4P':
  347. return {ext: 'f4p', mime: 'video/mp4'};
  348. case 'F4A':
  349. return {ext: 'f4a', mime: 'audio/mp4'};
  350. case 'F4B':
  351. return {ext: 'f4b', mime: 'audio/mp4'};
  352. default:
  353. if (brandMajor.startsWith('3g')) {
  354. if (brandMajor.startsWith('3g2')) {
  355. return {ext: '3g2', mime: 'video/3gpp2'};
  356. }
  357. return {ext: '3gp', mime: 'video/3gpp'};
  358. }
  359. return {ext: 'mp4', mime: 'video/mp4'};
  360. }
  361. }
  362. if (check([0x4D, 0x54, 0x68, 0x64])) {
  363. return {
  364. ext: 'mid',
  365. mime: 'audio/midi'
  366. };
  367. }
  368. // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
  369. if (check([0x1A, 0x45, 0xDF, 0xA3])) {
  370. const sliced = buffer.subarray(4, 4 + 4096);
  371. const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
  372. if (idPos !== -1) {
  373. const docTypePos = idPos + 3;
  374. const findDocType = type => [...type].every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
  375. if (findDocType('matroska')) {
  376. return {
  377. ext: 'mkv',
  378. mime: 'video/x-matroska'
  379. };
  380. }
  381. if (findDocType('webm')) {
  382. return {
  383. ext: 'webm',
  384. mime: 'video/webm'
  385. };
  386. }
  387. }
  388. }
  389. // RIFF file format which might be AVI, WAV, QCP, etc
  390. if (check([0x52, 0x49, 0x46, 0x46])) {
  391. if (check([0x41, 0x56, 0x49], {offset: 8})) {
  392. return {
  393. ext: 'avi',
  394. mime: 'video/vnd.avi'
  395. };
  396. }
  397. if (check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
  398. return {
  399. ext: 'wav',
  400. mime: 'audio/vnd.wave'
  401. };
  402. }
  403. // QLCM, QCP file
  404. if (check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
  405. return {
  406. ext: 'qcp',
  407. mime: 'audio/qcelp'
  408. };
  409. }
  410. }
  411. // ASF_Header_Object first 80 bytes
  412. if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
  413. // Search for header should be in first 1KB of file.
  414. let offset = 30;
  415. do {
  416. const objectSize = readUInt64LE(buffer, offset + 16);
  417. if (check([0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65], {offset})) {
  418. // Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
  419. if (check([0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B], {offset: offset + 24})) {
  420. // Found audio:
  421. return {
  422. ext: 'wma',
  423. mime: 'audio/x-ms-wma'
  424. };
  425. }
  426. if (check([0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B], {offset: offset + 24})) {
  427. // Found video:
  428. return {
  429. ext: 'wmv',
  430. mime: 'video/x-ms-asf'
  431. };
  432. }
  433. break;
  434. }
  435. offset += objectSize;
  436. } while (offset + 24 <= buffer.length);
  437. // Default to ASF generic extension
  438. return {
  439. ext: 'asf',
  440. mime: 'application/vnd.ms-asf'
  441. };
  442. }
  443. if (
  444. check([0x0, 0x0, 0x1, 0xBA]) ||
  445. check([0x0, 0x0, 0x1, 0xB3])
  446. ) {
  447. return {
  448. ext: 'mpg',
  449. mime: 'video/mpeg'
  450. };
  451. }
  452. // Check for MPEG header at different starting offsets
  453. for (let start = 0; start < 2 && start < (buffer.length - 16); start++) {
  454. if (
  455. check([0x49, 0x44, 0x33], {offset: start}) || // ID3 header
  456. check([0xFF, 0xE2], {offset: start, mask: [0xFF, 0xE6]}) // MPEG 1 or 2 Layer 3 header
  457. ) {
  458. return {
  459. ext: 'mp3',
  460. mime: 'audio/mpeg'
  461. };
  462. }
  463. if (
  464. check([0xFF, 0xE4], {offset: start, mask: [0xFF, 0xE6]}) // MPEG 1 or 2 Layer 2 header
  465. ) {
  466. return {
  467. ext: 'mp2',
  468. mime: 'audio/mpeg'
  469. };
  470. }
  471. if (
  472. check([0xFF, 0xF8], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 2 layer 0 using ADTS
  473. ) {
  474. return {
  475. ext: 'mp2',
  476. mime: 'audio/mpeg'
  477. };
  478. }
  479. if (
  480. check([0xFF, 0xF0], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 4 layer 0 using ADTS
  481. ) {
  482. return {
  483. ext: 'mp4',
  484. mime: 'audio/mpeg'
  485. };
  486. }
  487. }
  488. // Needs to be before `ogg` check
  489. if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
  490. return {
  491. ext: 'opus',
  492. mime: 'audio/opus'
  493. };
  494. }
  495. // If 'OggS' in first bytes, then OGG container
  496. if (check([0x4F, 0x67, 0x67, 0x53])) {
  497. // This is a OGG container
  498. // If ' theora' in header.
  499. if (check([0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61], {offset: 28})) {
  500. return {
  501. ext: 'ogv',
  502. mime: 'video/ogg'
  503. };
  504. }
  505. // If '\x01video' in header.
  506. if (check([0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00], {offset: 28})) {
  507. return {
  508. ext: 'ogm',
  509. mime: 'video/ogg'
  510. };
  511. }
  512. // If ' FLAC' in header https://xiph.org/flac/faq.html
  513. if (check([0x7F, 0x46, 0x4C, 0x41, 0x43], {offset: 28})) {
  514. return {
  515. ext: 'oga',
  516. mime: 'audio/ogg'
  517. };
  518. }
  519. // 'Speex ' in header https://en.wikipedia.org/wiki/Speex
  520. if (check([0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20], {offset: 28})) {
  521. return {
  522. ext: 'spx',
  523. mime: 'audio/ogg'
  524. };
  525. }
  526. // If '\x01vorbis' in header
  527. if (check([0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73], {offset: 28})) {
  528. return {
  529. ext: 'ogg',
  530. mime: 'audio/ogg'
  531. };
  532. }
  533. // Default OGG container https://www.iana.org/assignments/media-types/application/ogg
  534. return {
  535. ext: 'ogx',
  536. mime: 'application/ogg'
  537. };
  538. }
  539. if (check([0x66, 0x4C, 0x61, 0x43])) {
  540. return {
  541. ext: 'flac',
  542. mime: 'audio/x-flac'
  543. };
  544. }
  545. if (check([0x4D, 0x41, 0x43, 0x20])) { // 'MAC '
  546. return {
  547. ext: 'ape',
  548. mime: 'audio/ape'
  549. };
  550. }
  551. if (check([0x77, 0x76, 0x70, 0x6B])) { // 'wvpk'
  552. return {
  553. ext: 'wv',
  554. mime: 'audio/wavpack'
  555. };
  556. }
  557. if (check([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) {
  558. return {
  559. ext: 'amr',
  560. mime: 'audio/amr'
  561. };
  562. }
  563. if (check([0x25, 0x50, 0x44, 0x46])) {
  564. return {
  565. ext: 'pdf',
  566. mime: 'application/pdf'
  567. };
  568. }
  569. if (check([0x4D, 0x5A])) {
  570. return {
  571. ext: 'exe',
  572. mime: 'application/x-msdownload'
  573. };
  574. }
  575. if (
  576. (buffer[0] === 0x43 || buffer[0] === 0x46) &&
  577. check([0x57, 0x53], {offset: 1})
  578. ) {
  579. return {
  580. ext: 'swf',
  581. mime: 'application/x-shockwave-flash'
  582. };
  583. }
  584. if (check([0x7B, 0x5C, 0x72, 0x74, 0x66])) {
  585. return {
  586. ext: 'rtf',
  587. mime: 'application/rtf'
  588. };
  589. }
  590. if (check([0x00, 0x61, 0x73, 0x6D])) {
  591. return {
  592. ext: 'wasm',
  593. mime: 'application/wasm'
  594. };
  595. }
  596. if (
  597. check([0x77, 0x4F, 0x46, 0x46]) &&
  598. (
  599. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  600. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  601. )
  602. ) {
  603. return {
  604. ext: 'woff',
  605. mime: 'font/woff'
  606. };
  607. }
  608. if (
  609. check([0x77, 0x4F, 0x46, 0x32]) &&
  610. (
  611. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  612. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  613. )
  614. ) {
  615. return {
  616. ext: 'woff2',
  617. mime: 'font/woff2'
  618. };
  619. }
  620. if (
  621. check([0x4C, 0x50], {offset: 34}) &&
  622. (
  623. check([0x00, 0x00, 0x01], {offset: 8}) ||
  624. check([0x01, 0x00, 0x02], {offset: 8}) ||
  625. check([0x02, 0x00, 0x02], {offset: 8})
  626. )
  627. ) {
  628. return {
  629. ext: 'eot',
  630. mime: 'application/vnd.ms-fontobject'
  631. };
  632. }
  633. if (check([0x00, 0x01, 0x00, 0x00, 0x00])) {
  634. return {
  635. ext: 'ttf',
  636. mime: 'font/ttf'
  637. };
  638. }
  639. if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
  640. return {
  641. ext: 'otf',
  642. mime: 'font/otf'
  643. };
  644. }
  645. if (check([0x00, 0x00, 0x01, 0x00])) {
  646. return {
  647. ext: 'ico',
  648. mime: 'image/x-icon'
  649. };
  650. }
  651. if (check([0x00, 0x00, 0x02, 0x00])) {
  652. return {
  653. ext: 'cur',
  654. mime: 'image/x-icon'
  655. };
  656. }
  657. if (check([0x46, 0x4C, 0x56, 0x01])) {
  658. return {
  659. ext: 'flv',
  660. mime: 'video/x-flv'
  661. };
  662. }
  663. if (check([0x25, 0x21])) {
  664. return {
  665. ext: 'ps',
  666. mime: 'application/postscript'
  667. };
  668. }
  669. if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
  670. return {
  671. ext: 'xz',
  672. mime: 'application/x-xz'
  673. };
  674. }
  675. if (check([0x53, 0x51, 0x4C, 0x69])) {
  676. return {
  677. ext: 'sqlite',
  678. mime: 'application/x-sqlite3'
  679. };
  680. }
  681. if (check([0x4E, 0x45, 0x53, 0x1A])) {
  682. return {
  683. ext: 'nes',
  684. mime: 'application/x-nintendo-nes-rom'
  685. };
  686. }
  687. if (check([0x43, 0x72, 0x32, 0x34])) {
  688. return {
  689. ext: 'crx',
  690. mime: 'application/x-google-chrome-extension'
  691. };
  692. }
  693. if (
  694. check([0x4D, 0x53, 0x43, 0x46]) ||
  695. check([0x49, 0x53, 0x63, 0x28])
  696. ) {
  697. return {
  698. ext: 'cab',
  699. mime: 'application/vnd.ms-cab-compressed'
  700. };
  701. }
  702. // Needs to be before `ar` check
  703. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79])) {
  704. return {
  705. ext: 'deb',
  706. mime: 'application/x-deb'
  707. };
  708. }
  709. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) {
  710. return {
  711. ext: 'ar',
  712. mime: 'application/x-unix-archive'
  713. };
  714. }
  715. if (check([0xED, 0xAB, 0xEE, 0xDB])) {
  716. return {
  717. ext: 'rpm',
  718. mime: 'application/x-rpm'
  719. };
  720. }
  721. if (
  722. check([0x1F, 0xA0]) ||
  723. check([0x1F, 0x9D])
  724. ) {
  725. return {
  726. ext: 'Z',
  727. mime: 'application/x-compress'
  728. };
  729. }
  730. if (check([0x4C, 0x5A, 0x49, 0x50])) {
  731. return {
  732. ext: 'lz',
  733. mime: 'application/x-lzip'
  734. };
  735. }
  736. if (check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E])) {
  737. return {
  738. ext: 'msi',
  739. mime: 'application/x-msi'
  740. };
  741. }
  742. if (check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
  743. return {
  744. ext: 'mxf',
  745. mime: 'application/mxf'
  746. };
  747. }
  748. if (check([0x47], {offset: 4}) && (check([0x47], {offset: 192}) || check([0x47], {offset: 196}))) {
  749. return {
  750. ext: 'mts',
  751. mime: 'video/mp2t'
  752. };
  753. }
  754. if (check([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) {
  755. return {
  756. ext: 'blend',
  757. mime: 'application/x-blender'
  758. };
  759. }
  760. if (check([0x42, 0x50, 0x47, 0xFB])) {
  761. return {
  762. ext: 'bpg',
  763. mime: 'image/bpg'
  764. };
  765. }
  766. if (check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) {
  767. // JPEG-2000 family
  768. if (check([0x6A, 0x70, 0x32, 0x20], {offset: 20})) {
  769. return {
  770. ext: 'jp2',
  771. mime: 'image/jp2'
  772. };
  773. }
  774. if (check([0x6A, 0x70, 0x78, 0x20], {offset: 20})) {
  775. return {
  776. ext: 'jpx',
  777. mime: 'image/jpx'
  778. };
  779. }
  780. if (check([0x6A, 0x70, 0x6D, 0x20], {offset: 20})) {
  781. return {
  782. ext: 'jpm',
  783. mime: 'image/jpm'
  784. };
  785. }
  786. if (check([0x6D, 0x6A, 0x70, 0x32], {offset: 20})) {
  787. return {
  788. ext: 'mj2',
  789. mime: 'image/mj2'
  790. };
  791. }
  792. }
  793. if (check([0x46, 0x4F, 0x52, 0x4D])) {
  794. return {
  795. ext: 'aif',
  796. mime: 'audio/aiff'
  797. };
  798. }
  799. if (checkString('<?xml ')) {
  800. return {
  801. ext: 'xml',
  802. mime: 'application/xml'
  803. };
  804. }
  805. if (check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) {
  806. return {
  807. ext: 'mobi',
  808. mime: 'application/x-mobipocket-ebook'
  809. };
  810. }
  811. if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
  812. return {
  813. ext: 'ktx',
  814. mime: 'image/ktx'
  815. };
  816. }
  817. if (check([0x44, 0x49, 0x43, 0x4D], {offset: 128})) {
  818. return {
  819. ext: 'dcm',
  820. mime: 'application/dicom'
  821. };
  822. }
  823. // Musepack, SV7
  824. if (check([0x4D, 0x50, 0x2B])) {
  825. return {
  826. ext: 'mpc',
  827. mime: 'audio/x-musepack'
  828. };
  829. }
  830. // Musepack, SV8
  831. if (check([0x4D, 0x50, 0x43, 0x4B])) {
  832. return {
  833. ext: 'mpc',
  834. mime: 'audio/x-musepack'
  835. };
  836. }
  837. if (check([0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A])) {
  838. return {
  839. ext: 'ics',
  840. mime: 'text/calendar'
  841. };
  842. }
  843. if (check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) {
  844. return {
  845. ext: 'glb',
  846. mime: 'model/gltf-binary'
  847. };
  848. }
  849. if (check([0xD4, 0xC3, 0xB2, 0xA1]) || check([0xA1, 0xB2, 0xC3, 0xD4])) {
  850. return {
  851. ext: 'pcap',
  852. mime: 'application/vnd.tcpdump.pcap'
  853. };
  854. }
  855. // Sony DSD Stream File (DSF)
  856. if (check([0x44, 0x53, 0x44, 0x20])) {
  857. return {
  858. ext: 'dsf',
  859. mime: 'audio/x-dsf' // Non-standard
  860. };
  861. }
  862. if (check([0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46])) {
  863. return {
  864. ext: 'lnk',
  865. mime: 'application/x.ms.shortcut' // Invented by us
  866. };
  867. }
  868. if (check([0x62, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00])) {
  869. return {
  870. ext: 'alias',
  871. mime: 'application/x.apple.alias' // Invented by us
  872. };
  873. }
  874. if (checkString('Creative Voice File')) {
  875. return {
  876. ext: 'voc',
  877. mime: 'audio/x-voc'
  878. };
  879. }
  880. if (check([0x0B, 0x77])) {
  881. return {
  882. ext: 'ac3',
  883. mime: 'audio/vnd.dolby.dd-raw'
  884. };
  885. }
  886. if ((check([0x7E, 0x10, 0x04]) || check([0x7E, 0x18, 0x04])) && check([0x30, 0x4D, 0x49, 0x45], {offset: 4})) {
  887. return {
  888. ext: 'mie',
  889. mime: 'application/x-mie'
  890. };
  891. }
  892. if (check([0x41, 0x52, 0x52, 0x4F, 0x57, 0x31, 0x00, 0x00])) {
  893. return {
  894. ext: 'arrow',
  895. mime: 'application/x-apache-arrow'
  896. };
  897. }
  898. if (check([0x27, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], {offset: 2})) {
  899. return {
  900. ext: 'shp',
  901. mime: 'application/x-esri-shape'
  902. };
  903. }
  904. };
  905. module.exports = fileType;
  906. Object.defineProperty(fileType, 'minimumBytes', {value: 4100});
  907. fileType.stream = readableStream => new Promise((resolve, reject) => {
  908. // Using `eval` to work around issues when bundling with Webpack
  909. const stream = eval('require')('stream'); // eslint-disable-line no-eval
  910. readableStream.on('error', reject);
  911. readableStream.once('readable', () => {
  912. const pass = new stream.PassThrough();
  913. const chunk = readableStream.read(module.exports.minimumBytes) || readableStream.read();
  914. try {
  915. pass.fileType = fileType(chunk);
  916. } catch (error) {
  917. reject(error);
  918. }
  919. readableStream.unshift(chunk);
  920. if (stream.pipeline) {
  921. resolve(stream.pipeline(readableStream, pass, () => {}));
  922. } else {
  923. resolve(readableStream.pipe(pass));
  924. }
  925. });
  926. });
  927. Object.defineProperty(fileType, 'extensions', {
  928. get() {
  929. return new Set(supported.extensions);
  930. }
  931. });
  932. Object.defineProperty(fileType, 'mimeTypes', {
  933. get() {
  934. return new Set(supported.mimeTypes);
  935. }
  936. });