index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  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');
  6. module.exports = input => {
  7. const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
  8. if (!(buf && buf.length > 1)) {
  9. return null;
  10. }
  11. const check = (header, options) => {
  12. options = Object.assign({
  13. offset: 0
  14. }, options);
  15. for (let i = 0; i < header.length; i++) {
  16. // If a bitmask is set
  17. if (options.mask) {
  18. // If header doesn't equal `buf` with bits masked off
  19. if (header[i] !== (options.mask[i] & buf[i + options.offset])) {
  20. return false;
  21. }
  22. } else if (header[i] !== buf[i + options.offset]) {
  23. return false;
  24. }
  25. }
  26. return true;
  27. };
  28. const checkString = (header, options) => check(toBytes(header), options);
  29. if (check([0xFF, 0xD8, 0xFF])) {
  30. return {
  31. ext: 'jpg',
  32. mime: 'image/jpeg'
  33. };
  34. }
  35. if (check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
  36. return {
  37. ext: 'png',
  38. mime: 'image/png'
  39. };
  40. }
  41. if (check([0x47, 0x49, 0x46])) {
  42. return {
  43. ext: 'gif',
  44. mime: 'image/gif'
  45. };
  46. }
  47. if (check([0x57, 0x45, 0x42, 0x50], {offset: 8})) {
  48. return {
  49. ext: 'webp',
  50. mime: 'image/webp'
  51. };
  52. }
  53. if (check([0x46, 0x4C, 0x49, 0x46])) {
  54. return {
  55. ext: 'flif',
  56. mime: 'image/flif'
  57. };
  58. }
  59. // Needs to be before `tif` check
  60. if (
  61. (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
  62. check([0x43, 0x52], {offset: 8})
  63. ) {
  64. return {
  65. ext: 'cr2',
  66. mime: 'image/x-canon-cr2'
  67. };
  68. }
  69. if (
  70. check([0x49, 0x49, 0x2A, 0x0]) ||
  71. check([0x4D, 0x4D, 0x0, 0x2A])
  72. ) {
  73. return {
  74. ext: 'tif',
  75. mime: 'image/tiff'
  76. };
  77. }
  78. if (check([0x42, 0x4D])) {
  79. return {
  80. ext: 'bmp',
  81. mime: 'image/bmp'
  82. };
  83. }
  84. if (check([0x49, 0x49, 0xBC])) {
  85. return {
  86. ext: 'jxr',
  87. mime: 'image/vnd.ms-photo'
  88. };
  89. }
  90. if (check([0x38, 0x42, 0x50, 0x53])) {
  91. return {
  92. ext: 'psd',
  93. mime: 'image/vnd.adobe.photoshop'
  94. };
  95. }
  96. // Zip-based file formats
  97. // Need to be before the `zip` check
  98. if (check([0x50, 0x4B, 0x3, 0x4])) {
  99. if (
  100. 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})
  101. ) {
  102. return {
  103. ext: 'epub',
  104. mime: 'application/epub+zip'
  105. };
  106. }
  107. // Assumes signed `.xpi` from addons.mozilla.org
  108. if (check(xpiZipFilename, {offset: 30})) {
  109. return {
  110. ext: 'xpi',
  111. mime: 'application/x-xpinstall'
  112. };
  113. }
  114. if (checkString('mimetypeapplication/vnd.oasis.opendocument.text', {offset: 30})) {
  115. return {
  116. ext: 'odt',
  117. mime: 'application/vnd.oasis.opendocument.text'
  118. };
  119. }
  120. if (checkString('mimetypeapplication/vnd.oasis.opendocument.spreadsheet', {offset: 30})) {
  121. return {
  122. ext: 'ods',
  123. mime: 'application/vnd.oasis.opendocument.spreadsheet'
  124. };
  125. }
  126. if (checkString('mimetypeapplication/vnd.oasis.opendocument.presentation', {offset: 30})) {
  127. return {
  128. ext: 'odp',
  129. mime: 'application/vnd.oasis.opendocument.presentation'
  130. };
  131. }
  132. // The docx, xlsx and pptx file types extend the Office Open XML file format:
  133. // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats
  134. // We look for:
  135. // - one entry named '[Content_Types].xml' or '_rels/.rels',
  136. // - one entry indicating specific type of file.
  137. // MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it.
  138. 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);
  139. let zipHeaderIndex = 0; // The first zip header was already found at index 0
  140. let oxmlFound = false;
  141. let type = null;
  142. do {
  143. const offset = zipHeaderIndex + 30;
  144. if (!oxmlFound) {
  145. oxmlFound = (check(oxmlContentTypes, {offset}) || check(oxmlRels, {offset}));
  146. }
  147. if (!type) {
  148. if (checkString('word/', {offset})) {
  149. type = {
  150. ext: 'docx',
  151. mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
  152. };
  153. } else if (checkString('ppt/', {offset})) {
  154. type = {
  155. ext: 'pptx',
  156. mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
  157. };
  158. } else if (checkString('xl/', {offset})) {
  159. type = {
  160. ext: 'xlsx',
  161. mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  162. };
  163. }
  164. }
  165. if (oxmlFound && type) {
  166. return type;
  167. }
  168. zipHeaderIndex = findNextZipHeaderIndex(buf, offset);
  169. } while (zipHeaderIndex >= 0);
  170. // No more zip parts available in the buffer, but maybe we are almost certain about the type?
  171. if (type) {
  172. return type;
  173. }
  174. }
  175. if (
  176. check([0x50, 0x4B]) &&
  177. (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) &&
  178. (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)
  179. ) {
  180. return {
  181. ext: 'zip',
  182. mime: 'application/zip'
  183. };
  184. }
  185. if (check([0x75, 0x73, 0x74, 0x61, 0x72], {offset: 257})) {
  186. return {
  187. ext: 'tar',
  188. mime: 'application/x-tar'
  189. };
  190. }
  191. if (
  192. check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
  193. (buf[6] === 0x0 || buf[6] === 0x1)
  194. ) {
  195. return {
  196. ext: 'rar',
  197. mime: 'application/x-rar-compressed'
  198. };
  199. }
  200. if (check([0x1F, 0x8B, 0x8])) {
  201. return {
  202. ext: 'gz',
  203. mime: 'application/gzip'
  204. };
  205. }
  206. if (check([0x42, 0x5A, 0x68])) {
  207. return {
  208. ext: 'bz2',
  209. mime: 'application/x-bzip2'
  210. };
  211. }
  212. if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
  213. return {
  214. ext: '7z',
  215. mime: 'application/x-7z-compressed'
  216. };
  217. }
  218. if (check([0x78, 0x01])) {
  219. return {
  220. ext: 'dmg',
  221. mime: 'application/x-apple-diskimage'
  222. };
  223. }
  224. if (check([0x33, 0x67, 0x70, 0x35]) || // 3gp5
  225. (
  226. check([0x0, 0x0, 0x0]) && check([0x66, 0x74, 0x79, 0x70], {offset: 4}) &&
  227. (
  228. check([0x6D, 0x70, 0x34, 0x31], {offset: 8}) || // MP41
  229. check([0x6D, 0x70, 0x34, 0x32], {offset: 8}) || // MP42
  230. check([0x69, 0x73, 0x6F, 0x6D], {offset: 8}) || // ISOM
  231. check([0x69, 0x73, 0x6F, 0x32], {offset: 8}) || // ISO2
  232. check([0x6D, 0x6D, 0x70, 0x34], {offset: 8}) || // MMP4
  233. check([0x4D, 0x34, 0x56], {offset: 8}) || // M4V
  234. check([0x64, 0x61, 0x73, 0x68], {offset: 8}) // DASH
  235. )
  236. )) {
  237. return {
  238. ext: 'mp4',
  239. mime: 'video/mp4'
  240. };
  241. }
  242. if (check([0x4D, 0x54, 0x68, 0x64])) {
  243. return {
  244. ext: 'mid',
  245. mime: 'audio/midi'
  246. };
  247. }
  248. // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
  249. if (check([0x1A, 0x45, 0xDF, 0xA3])) {
  250. const sliced = buf.subarray(4, 4 + 4096);
  251. const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
  252. if (idPos !== -1) {
  253. const docTypePos = idPos + 3;
  254. const findDocType = type => [...type].every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
  255. if (findDocType('matroska')) {
  256. return {
  257. ext: 'mkv',
  258. mime: 'video/x-matroska'
  259. };
  260. }
  261. if (findDocType('webm')) {
  262. return {
  263. ext: 'webm',
  264. mime: 'video/webm'
  265. };
  266. }
  267. }
  268. }
  269. if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
  270. check([0x66, 0x72, 0x65, 0x65], {offset: 4}) ||
  271. check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
  272. check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
  273. check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
  274. return {
  275. ext: 'mov',
  276. mime: 'video/quicktime'
  277. };
  278. }
  279. // RIFF file format which might be AVI, WAV, QCP, etc
  280. if (check([0x52, 0x49, 0x46, 0x46])) {
  281. if (check([0x41, 0x56, 0x49], {offset: 8})) {
  282. return {
  283. ext: 'avi',
  284. mime: 'video/vnd.avi'
  285. };
  286. }
  287. if (check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
  288. return {
  289. ext: 'wav',
  290. mime: 'audio/vnd.wave'
  291. };
  292. }
  293. // QLCM, QCP file
  294. if (check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
  295. return {
  296. ext: 'qcp',
  297. mime: 'audio/qcelp'
  298. };
  299. }
  300. }
  301. if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
  302. return {
  303. ext: 'wmv',
  304. mime: 'video/x-ms-wmv'
  305. };
  306. }
  307. if (
  308. check([0x0, 0x0, 0x1, 0xBA]) ||
  309. check([0x0, 0x0, 0x1, 0xB3])
  310. ) {
  311. return {
  312. ext: 'mpg',
  313. mime: 'video/mpeg'
  314. };
  315. }
  316. if (check([0x66, 0x74, 0x79, 0x70, 0x33, 0x67], {offset: 4})) {
  317. return {
  318. ext: '3gp',
  319. mime: 'video/3gpp'
  320. };
  321. }
  322. // Check for MPEG header at different starting offsets
  323. for (let start = 0; start < 2 && start < (buf.length - 16); start++) {
  324. if (
  325. check([0x49, 0x44, 0x33], {offset: start}) || // ID3 header
  326. check([0xFF, 0xE2], {offset: start, mask: [0xFF, 0xE2]}) // MPEG 1 or 2 Layer 3 header
  327. ) {
  328. return {
  329. ext: 'mp3',
  330. mime: 'audio/mpeg'
  331. };
  332. }
  333. if (
  334. check([0xFF, 0xE4], {offset: start, mask: [0xFF, 0xE4]}) // MPEG 1 or 2 Layer 2 header
  335. ) {
  336. return {
  337. ext: 'mp2',
  338. mime: 'audio/mpeg'
  339. };
  340. }
  341. if (
  342. check([0xFF, 0xF8], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 2 layer 0 using ADTS
  343. ) {
  344. return {
  345. ext: 'mp2',
  346. mime: 'audio/mpeg'
  347. };
  348. }
  349. if (
  350. check([0xFF, 0xF0], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 4 layer 0 using ADTS
  351. ) {
  352. return {
  353. ext: 'mp4',
  354. mime: 'audio/mpeg'
  355. };
  356. }
  357. }
  358. if (
  359. check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4}) ||
  360. check([0x4D, 0x34, 0x41, 0x20])
  361. ) {
  362. return { // MPEG-4 layer 3 (audio)
  363. ext: 'm4a',
  364. mime: 'audio/mp4' // RFC 4337
  365. };
  366. }
  367. // Needs to be before `ogg` check
  368. if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
  369. return {
  370. ext: 'opus',
  371. mime: 'audio/opus'
  372. };
  373. }
  374. // If 'OggS' in first bytes, then OGG container
  375. if (check([0x4F, 0x67, 0x67, 0x53])) {
  376. // This is a OGG container
  377. // If ' theora' in header.
  378. if (check([0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61], {offset: 28})) {
  379. return {
  380. ext: 'ogv',
  381. mime: 'video/ogg'
  382. };
  383. }
  384. // If '\x01video' in header.
  385. if (check([0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00], {offset: 28})) {
  386. return {
  387. ext: 'ogm',
  388. mime: 'video/ogg'
  389. };
  390. }
  391. // If ' FLAC' in header https://xiph.org/flac/faq.html
  392. if (check([0x7F, 0x46, 0x4C, 0x41, 0x43], {offset: 28})) {
  393. return {
  394. ext: 'oga',
  395. mime: 'audio/ogg'
  396. };
  397. }
  398. // 'Speex ' in header https://en.wikipedia.org/wiki/Speex
  399. if (check([0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20], {offset: 28})) {
  400. return {
  401. ext: 'spx',
  402. mime: 'audio/ogg'
  403. };
  404. }
  405. // If '\x01vorbis' in header
  406. if (check([0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73], {offset: 28})) {
  407. return {
  408. ext: 'ogg',
  409. mime: 'audio/ogg'
  410. };
  411. }
  412. // Default OGG container https://www.iana.org/assignments/media-types/application/ogg
  413. return {
  414. ext: 'ogx',
  415. mime: 'application/ogg'
  416. };
  417. }
  418. if (check([0x66, 0x4C, 0x61, 0x43])) {
  419. return {
  420. ext: 'flac',
  421. mime: 'audio/x-flac'
  422. };
  423. }
  424. if (check([0x4D, 0x41, 0x43, 0x20])) { // 'MAC '
  425. return {
  426. ext: 'ape',
  427. mime: 'audio/ape'
  428. };
  429. }
  430. if (check([0x77, 0x76, 0x70, 0x6B])) { // 'wvpk'
  431. return {
  432. ext: 'wv',
  433. mime: 'audio/wavpack'
  434. };
  435. }
  436. if (check([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) {
  437. return {
  438. ext: 'amr',
  439. mime: 'audio/amr'
  440. };
  441. }
  442. if (check([0x25, 0x50, 0x44, 0x46])) {
  443. return {
  444. ext: 'pdf',
  445. mime: 'application/pdf'
  446. };
  447. }
  448. if (check([0x4D, 0x5A])) {
  449. return {
  450. ext: 'exe',
  451. mime: 'application/x-msdownload'
  452. };
  453. }
  454. if (
  455. (buf[0] === 0x43 || buf[0] === 0x46) &&
  456. check([0x57, 0x53], {offset: 1})
  457. ) {
  458. return {
  459. ext: 'swf',
  460. mime: 'application/x-shockwave-flash'
  461. };
  462. }
  463. if (check([0x7B, 0x5C, 0x72, 0x74, 0x66])) {
  464. return {
  465. ext: 'rtf',
  466. mime: 'application/rtf'
  467. };
  468. }
  469. if (check([0x00, 0x61, 0x73, 0x6D])) {
  470. return {
  471. ext: 'wasm',
  472. mime: 'application/wasm'
  473. };
  474. }
  475. if (
  476. check([0x77, 0x4F, 0x46, 0x46]) &&
  477. (
  478. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  479. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  480. )
  481. ) {
  482. return {
  483. ext: 'woff',
  484. mime: 'font/woff'
  485. };
  486. }
  487. if (
  488. check([0x77, 0x4F, 0x46, 0x32]) &&
  489. (
  490. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  491. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  492. )
  493. ) {
  494. return {
  495. ext: 'woff2',
  496. mime: 'font/woff2'
  497. };
  498. }
  499. if (
  500. check([0x4C, 0x50], {offset: 34}) &&
  501. (
  502. check([0x00, 0x00, 0x01], {offset: 8}) ||
  503. check([0x01, 0x00, 0x02], {offset: 8}) ||
  504. check([0x02, 0x00, 0x02], {offset: 8})
  505. )
  506. ) {
  507. return {
  508. ext: 'eot',
  509. mime: 'application/vnd.ms-fontobject'
  510. };
  511. }
  512. if (check([0x00, 0x01, 0x00, 0x00, 0x00])) {
  513. return {
  514. ext: 'ttf',
  515. mime: 'font/ttf'
  516. };
  517. }
  518. if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
  519. return {
  520. ext: 'otf',
  521. mime: 'font/otf'
  522. };
  523. }
  524. if (check([0x00, 0x00, 0x01, 0x00])) {
  525. return {
  526. ext: 'ico',
  527. mime: 'image/x-icon'
  528. };
  529. }
  530. if (check([0x00, 0x00, 0x02, 0x00])) {
  531. return {
  532. ext: 'cur',
  533. mime: 'image/x-icon'
  534. };
  535. }
  536. if (check([0x46, 0x4C, 0x56, 0x01])) {
  537. return {
  538. ext: 'flv',
  539. mime: 'video/x-flv'
  540. };
  541. }
  542. if (check([0x25, 0x21])) {
  543. return {
  544. ext: 'ps',
  545. mime: 'application/postscript'
  546. };
  547. }
  548. if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
  549. return {
  550. ext: 'xz',
  551. mime: 'application/x-xz'
  552. };
  553. }
  554. if (check([0x53, 0x51, 0x4C, 0x69])) {
  555. return {
  556. ext: 'sqlite',
  557. mime: 'application/x-sqlite3'
  558. };
  559. }
  560. if (check([0x4E, 0x45, 0x53, 0x1A])) {
  561. return {
  562. ext: 'nes',
  563. mime: 'application/x-nintendo-nes-rom'
  564. };
  565. }
  566. if (check([0x43, 0x72, 0x32, 0x34])) {
  567. return {
  568. ext: 'crx',
  569. mime: 'application/x-google-chrome-extension'
  570. };
  571. }
  572. if (
  573. check([0x4D, 0x53, 0x43, 0x46]) ||
  574. check([0x49, 0x53, 0x63, 0x28])
  575. ) {
  576. return {
  577. ext: 'cab',
  578. mime: 'application/vnd.ms-cab-compressed'
  579. };
  580. }
  581. // Needs to be before `ar` check
  582. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79])) {
  583. return {
  584. ext: 'deb',
  585. mime: 'application/x-deb'
  586. };
  587. }
  588. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) {
  589. return {
  590. ext: 'ar',
  591. mime: 'application/x-unix-archive'
  592. };
  593. }
  594. if (check([0xED, 0xAB, 0xEE, 0xDB])) {
  595. return {
  596. ext: 'rpm',
  597. mime: 'application/x-rpm'
  598. };
  599. }
  600. if (
  601. check([0x1F, 0xA0]) ||
  602. check([0x1F, 0x9D])
  603. ) {
  604. return {
  605. ext: 'Z',
  606. mime: 'application/x-compress'
  607. };
  608. }
  609. if (check([0x4C, 0x5A, 0x49, 0x50])) {
  610. return {
  611. ext: 'lz',
  612. mime: 'application/x-lzip'
  613. };
  614. }
  615. if (check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
  616. return {
  617. ext: 'msi',
  618. mime: 'application/x-msi'
  619. };
  620. }
  621. if (check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
  622. return {
  623. ext: 'mxf',
  624. mime: 'application/mxf'
  625. };
  626. }
  627. if (check([0x47], {offset: 4}) && (check([0x47], {offset: 192}) || check([0x47], {offset: 196}))) {
  628. return {
  629. ext: 'mts',
  630. mime: 'video/mp2t'
  631. };
  632. }
  633. if (check([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) {
  634. return {
  635. ext: 'blend',
  636. mime: 'application/x-blender'
  637. };
  638. }
  639. if (check([0x42, 0x50, 0x47, 0xFB])) {
  640. return {
  641. ext: 'bpg',
  642. mime: 'image/bpg'
  643. };
  644. }
  645. if (check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) {
  646. // JPEG-2000 family
  647. if (check([0x6A, 0x70, 0x32, 0x20], {offset: 20})) {
  648. return {
  649. ext: 'jp2',
  650. mime: 'image/jp2'
  651. };
  652. }
  653. if (check([0x6A, 0x70, 0x78, 0x20], {offset: 20})) {
  654. return {
  655. ext: 'jpx',
  656. mime: 'image/jpx'
  657. };
  658. }
  659. if (check([0x6A, 0x70, 0x6D, 0x20], {offset: 20})) {
  660. return {
  661. ext: 'jpm',
  662. mime: 'image/jpm'
  663. };
  664. }
  665. if (check([0x6D, 0x6A, 0x70, 0x32], {offset: 20})) {
  666. return {
  667. ext: 'mj2',
  668. mime: 'image/mj2'
  669. };
  670. }
  671. }
  672. if (check([0x46, 0x4F, 0x52, 0x4D, 0x00])) {
  673. return {
  674. ext: 'aif',
  675. mime: 'audio/aiff'
  676. };
  677. }
  678. if (checkString('<?xml ')) {
  679. return {
  680. ext: 'xml',
  681. mime: 'application/xml'
  682. };
  683. }
  684. if (check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) {
  685. return {
  686. ext: 'mobi',
  687. mime: 'application/x-mobipocket-ebook'
  688. };
  689. }
  690. // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
  691. if (check([0x66, 0x74, 0x79, 0x70], {offset: 4})) {
  692. if (check([0x6D, 0x69, 0x66, 0x31], {offset: 8})) {
  693. return {
  694. ext: 'heic',
  695. mime: 'image/heif'
  696. };
  697. }
  698. if (check([0x6D, 0x73, 0x66, 0x31], {offset: 8})) {
  699. return {
  700. ext: 'heic',
  701. mime: 'image/heif-sequence'
  702. };
  703. }
  704. if (check([0x68, 0x65, 0x69, 0x63], {offset: 8}) || check([0x68, 0x65, 0x69, 0x78], {offset: 8})) {
  705. return {
  706. ext: 'heic',
  707. mime: 'image/heic'
  708. };
  709. }
  710. if (check([0x68, 0x65, 0x76, 0x63], {offset: 8}) || check([0x68, 0x65, 0x76, 0x78], {offset: 8})) {
  711. return {
  712. ext: 'heic',
  713. mime: 'image/heic-sequence'
  714. };
  715. }
  716. }
  717. if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
  718. return {
  719. ext: 'ktx',
  720. mime: 'image/ktx'
  721. };
  722. }
  723. return null;
  724. };