index.js 21 KB

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