You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. 'use strict';
  2. module.exports = input => {
  3. const buf = new Uint8Array(input);
  4. if (!(buf && buf.length > 1)) {
  5. return null;
  6. }
  7. const check = (header, opts) => {
  8. opts = Object.assign({
  9. offset: 0
  10. }, opts);
  11. for (let i = 0; i < header.length; i++) {
  12. if (header[i] !== buf[i + opts.offset]) {
  13. return false;
  14. }
  15. }
  16. return true;
  17. };
  18. if (check([0xFF, 0xD8, 0xFF])) {
  19. return {
  20. ext: 'jpg',
  21. mime: 'image/jpeg'
  22. };
  23. }
  24. if (check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
  25. return {
  26. ext: 'png',
  27. mime: 'image/png'
  28. };
  29. }
  30. if (check([0x47, 0x49, 0x46])) {
  31. return {
  32. ext: 'gif',
  33. mime: 'image/gif'
  34. };
  35. }
  36. if (check([0x57, 0x45, 0x42, 0x50], {offset: 8})) {
  37. return {
  38. ext: 'webp',
  39. mime: 'image/webp'
  40. };
  41. }
  42. if (check([0x46, 0x4C, 0x49, 0x46])) {
  43. return {
  44. ext: 'flif',
  45. mime: 'image/flif'
  46. };
  47. }
  48. // Needs to be before `tif` check
  49. if (
  50. (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
  51. check([0x43, 0x52], {offset: 8})
  52. ) {
  53. return {
  54. ext: 'cr2',
  55. mime: 'image/x-canon-cr2'
  56. };
  57. }
  58. if (
  59. check([0x49, 0x49, 0x2A, 0x0]) ||
  60. check([0x4D, 0x4D, 0x0, 0x2A])
  61. ) {
  62. return {
  63. ext: 'tif',
  64. mime: 'image/tiff'
  65. };
  66. }
  67. if (check([0x42, 0x4D])) {
  68. return {
  69. ext: 'bmp',
  70. mime: 'image/bmp'
  71. };
  72. }
  73. if (check([0x49, 0x49, 0xBC])) {
  74. return {
  75. ext: 'jxr',
  76. mime: 'image/vnd.ms-photo'
  77. };
  78. }
  79. if (check([0x38, 0x42, 0x50, 0x53])) {
  80. return {
  81. ext: 'psd',
  82. mime: 'image/vnd.adobe.photoshop'
  83. };
  84. }
  85. // Needs to be before the `zip` check
  86. if (
  87. check([0x50, 0x4B, 0x3, 0x4]) &&
  88. 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})
  89. ) {
  90. return {
  91. ext: 'epub',
  92. mime: 'application/epub+zip'
  93. };
  94. }
  95. // Needs to be before `zip` check
  96. // Assumes signed `.xpi` from addons.mozilla.org
  97. if (
  98. check([0x50, 0x4B, 0x3, 0x4]) &&
  99. check([0x4D, 0x45, 0x54, 0x41, 0x2D, 0x49, 0x4E, 0x46, 0x2F, 0x6D, 0x6F, 0x7A, 0x69, 0x6C, 0x6C, 0x61, 0x2E, 0x72, 0x73, 0x61], {offset: 30})
  100. ) {
  101. return {
  102. ext: 'xpi',
  103. mime: 'application/x-xpinstall'
  104. };
  105. }
  106. if (
  107. check([0x50, 0x4B]) &&
  108. (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) &&
  109. (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)
  110. ) {
  111. return {
  112. ext: 'zip',
  113. mime: 'application/zip'
  114. };
  115. }
  116. if (check([0x75, 0x73, 0x74, 0x61, 0x72], {offset: 257})) {
  117. return {
  118. ext: 'tar',
  119. mime: 'application/x-tar'
  120. };
  121. }
  122. if (
  123. check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
  124. (buf[6] === 0x0 || buf[6] === 0x1)
  125. ) {
  126. return {
  127. ext: 'rar',
  128. mime: 'application/x-rar-compressed'
  129. };
  130. }
  131. if (check([0x1F, 0x8B, 0x8])) {
  132. return {
  133. ext: 'gz',
  134. mime: 'application/gzip'
  135. };
  136. }
  137. if (check([0x42, 0x5A, 0x68])) {
  138. return {
  139. ext: 'bz2',
  140. mime: 'application/x-bzip2'
  141. };
  142. }
  143. if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
  144. return {
  145. ext: '7z',
  146. mime: 'application/x-7z-compressed'
  147. };
  148. }
  149. if (check([0x78, 0x01])) {
  150. return {
  151. ext: 'dmg',
  152. mime: 'application/x-apple-diskimage'
  153. };
  154. }
  155. if (
  156. (
  157. check([0x0, 0x0, 0x0]) &&
  158. (buf[3] === 0x18 || buf[3] === 0x20) &&
  159. check([0x66, 0x74, 0x79, 0x70], {offset: 4})
  160. ) ||
  161. check([0x33, 0x67, 0x70, 0x35]) ||
  162. (
  163. check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32]) &&
  164. check([0x6D, 0x70, 0x34, 0x31, 0x6D, 0x70, 0x34, 0x32, 0x69, 0x73, 0x6F, 0x6D], {offset: 16})
  165. ) ||
  166. check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D]) ||
  167. check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32, 0x0, 0x0, 0x0, 0x0])
  168. ) {
  169. return {
  170. ext: 'mp4',
  171. mime: 'video/mp4'
  172. };
  173. }
  174. if (check([0x0, 0x0, 0x0, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x56])) {
  175. return {
  176. ext: 'm4v',
  177. mime: 'video/x-m4v'
  178. };
  179. }
  180. if (check([0x4D, 0x54, 0x68, 0x64])) {
  181. return {
  182. ext: 'mid',
  183. mime: 'audio/midi'
  184. };
  185. }
  186. // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
  187. if (check([0x1A, 0x45, 0xDF, 0xA3])) {
  188. const sliced = buf.subarray(4, 4 + 4096);
  189. const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
  190. if (idPos >= 0) {
  191. const docTypePos = idPos + 3;
  192. const findDocType = type => Array.from(type).every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
  193. if (findDocType('matroska')) {
  194. return {
  195. ext: 'mkv',
  196. mime: 'video/x-matroska'
  197. };
  198. }
  199. if (findDocType('webm')) {
  200. return {
  201. ext: 'webm',
  202. mime: 'video/webm'
  203. };
  204. }
  205. }
  206. }
  207. if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
  208. check([0x66, 0x72, 0x65, 0x65], {offset: 4}) ||
  209. check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
  210. check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
  211. check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
  212. return {
  213. ext: 'mov',
  214. mime: 'video/quicktime'
  215. };
  216. }
  217. if (
  218. check([0x52, 0x49, 0x46, 0x46]) &&
  219. check([0x41, 0x56, 0x49], {offset: 8})
  220. ) {
  221. return {
  222. ext: 'avi',
  223. mime: 'video/x-msvideo'
  224. };
  225. }
  226. if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
  227. return {
  228. ext: 'wmv',
  229. mime: 'video/x-ms-wmv'
  230. };
  231. }
  232. if (check([0x0, 0x0, 0x1, 0xBA])) {
  233. return {
  234. ext: 'mpg',
  235. mime: 'video/mpeg'
  236. };
  237. }
  238. if (
  239. check([0x49, 0x44, 0x33]) ||
  240. check([0xFF, 0xFB])
  241. ) {
  242. return {
  243. ext: 'mp3',
  244. mime: 'audio/mpeg'
  245. };
  246. }
  247. if (
  248. check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4}) ||
  249. check([0x4D, 0x34, 0x41, 0x20])
  250. ) {
  251. return {
  252. ext: 'm4a',
  253. mime: 'audio/m4a'
  254. };
  255. }
  256. // Needs to be before `ogg` check
  257. if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
  258. return {
  259. ext: 'opus',
  260. mime: 'audio/opus'
  261. };
  262. }
  263. if (check([0x4F, 0x67, 0x67, 0x53])) {
  264. return {
  265. ext: 'ogg',
  266. mime: 'audio/ogg'
  267. };
  268. }
  269. if (check([0x66, 0x4C, 0x61, 0x43])) {
  270. return {
  271. ext: 'flac',
  272. mime: 'audio/x-flac'
  273. };
  274. }
  275. if (
  276. check([0x52, 0x49, 0x46, 0x46]) &&
  277. check([0x57, 0x41, 0x56, 0x45], {offset: 8})
  278. ) {
  279. return {
  280. ext: 'wav',
  281. mime: 'audio/x-wav'
  282. };
  283. }
  284. if (check([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) {
  285. return {
  286. ext: 'amr',
  287. mime: 'audio/amr'
  288. };
  289. }
  290. if (check([0x25, 0x50, 0x44, 0x46])) {
  291. return {
  292. ext: 'pdf',
  293. mime: 'application/pdf'
  294. };
  295. }
  296. if (check([0x4D, 0x5A])) {
  297. return {
  298. ext: 'exe',
  299. mime: 'application/x-msdownload'
  300. };
  301. }
  302. if (
  303. (buf[0] === 0x43 || buf[0] === 0x46) &&
  304. check([0x57, 0x53], {offset: 1})
  305. ) {
  306. return {
  307. ext: 'swf',
  308. mime: 'application/x-shockwave-flash'
  309. };
  310. }
  311. if (check([0x7B, 0x5C, 0x72, 0x74, 0x66])) {
  312. return {
  313. ext: 'rtf',
  314. mime: 'application/rtf'
  315. };
  316. }
  317. if (check([0x00, 0x61, 0x73, 0x6D])) {
  318. return {
  319. ext: 'wasm',
  320. mime: 'application/wasm'
  321. };
  322. }
  323. if (
  324. check([0x77, 0x4F, 0x46, 0x46]) &&
  325. (
  326. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  327. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  328. )
  329. ) {
  330. return {
  331. ext: 'woff',
  332. mime: 'font/woff'
  333. };
  334. }
  335. if (
  336. check([0x77, 0x4F, 0x46, 0x32]) &&
  337. (
  338. check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
  339. check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
  340. )
  341. ) {
  342. return {
  343. ext: 'woff2',
  344. mime: 'font/woff2'
  345. };
  346. }
  347. if (
  348. check([0x4C, 0x50], {offset: 34}) &&
  349. (
  350. check([0x00, 0x00, 0x01], {offset: 8}) ||
  351. check([0x01, 0x00, 0x02], {offset: 8}) ||
  352. check([0x02, 0x00, 0x02], {offset: 8})
  353. )
  354. ) {
  355. return {
  356. ext: 'eot',
  357. mime: 'application/octet-stream'
  358. };
  359. }
  360. if (check([0x00, 0x01, 0x00, 0x00, 0x00])) {
  361. return {
  362. ext: 'ttf',
  363. mime: 'font/ttf'
  364. };
  365. }
  366. if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
  367. return {
  368. ext: 'otf',
  369. mime: 'font/otf'
  370. };
  371. }
  372. if (check([0x00, 0x00, 0x01, 0x00])) {
  373. return {
  374. ext: 'ico',
  375. mime: 'image/x-icon'
  376. };
  377. }
  378. if (check([0x46, 0x4C, 0x56, 0x01])) {
  379. return {
  380. ext: 'flv',
  381. mime: 'video/x-flv'
  382. };
  383. }
  384. if (check([0x25, 0x21])) {
  385. return {
  386. ext: 'ps',
  387. mime: 'application/postscript'
  388. };
  389. }
  390. if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
  391. return {
  392. ext: 'xz',
  393. mime: 'application/x-xz'
  394. };
  395. }
  396. if (check([0x53, 0x51, 0x4C, 0x69])) {
  397. return {
  398. ext: 'sqlite',
  399. mime: 'application/x-sqlite3'
  400. };
  401. }
  402. if (check([0x4E, 0x45, 0x53, 0x1A])) {
  403. return {
  404. ext: 'nes',
  405. mime: 'application/x-nintendo-nes-rom'
  406. };
  407. }
  408. if (check([0x43, 0x72, 0x32, 0x34])) {
  409. return {
  410. ext: 'crx',
  411. mime: 'application/x-google-chrome-extension'
  412. };
  413. }
  414. if (
  415. check([0x4D, 0x53, 0x43, 0x46]) ||
  416. check([0x49, 0x53, 0x63, 0x28])
  417. ) {
  418. return {
  419. ext: 'cab',
  420. mime: 'application/vnd.ms-cab-compressed'
  421. };
  422. }
  423. // Needs to be before `ar` check
  424. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79])) {
  425. return {
  426. ext: 'deb',
  427. mime: 'application/x-deb'
  428. };
  429. }
  430. if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) {
  431. return {
  432. ext: 'ar',
  433. mime: 'application/x-unix-archive'
  434. };
  435. }
  436. if (check([0xED, 0xAB, 0xEE, 0xDB])) {
  437. return {
  438. ext: 'rpm',
  439. mime: 'application/x-rpm'
  440. };
  441. }
  442. if (
  443. check([0x1F, 0xA0]) ||
  444. check([0x1F, 0x9D])
  445. ) {
  446. return {
  447. ext: 'Z',
  448. mime: 'application/x-compress'
  449. };
  450. }
  451. if (check([0x4C, 0x5A, 0x49, 0x50])) {
  452. return {
  453. ext: 'lz',
  454. mime: 'application/x-lzip'
  455. };
  456. }
  457. if (check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
  458. return {
  459. ext: 'msi',
  460. mime: 'application/x-msi'
  461. };
  462. }
  463. if (check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
  464. return {
  465. ext: 'mxf',
  466. mime: 'application/mxf'
  467. };
  468. }
  469. if (check([0x47], {offset: 4}) && (check([0x47], {offset: 192}) || check([0x47], {offset: 196}))) {
  470. return {
  471. ext: 'mts',
  472. mime: 'video/mp2t'
  473. };
  474. }
  475. if (check([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) {
  476. return {
  477. ext: 'blend',
  478. mime: 'application/x-blender'
  479. };
  480. }
  481. if (check([0x42, 0x50, 0x47, 0xFB])) {
  482. return {
  483. ext: 'bpg',
  484. mime: 'image/bpg'
  485. };
  486. }
  487. return null;
  488. };