From 8d3836cb16453d969d6d8a1e25f5e650e8d10394 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 21 Dec 2018 12:48:08 +0000 Subject: [PATCH] Added support for a number of further file types and file detection methods. --- src/core/lib/FileType.mjs | 256 +++++++++++++++++-------- src/core/operations/DetectFileType.mjs | 2 +- src/core/operations/PlayMedia.mjs | 14 +- 3 files changed, 189 insertions(+), 83 deletions(-) diff --git a/src/core/lib/FileType.mjs b/src/core/lib/FileType.mjs index 443bdd36..d38ddc80 100644 --- a/src/core/lib/FileType.mjs +++ b/src/core/lib/FileType.mjs @@ -13,7 +13,7 @@ import Stream from "./Stream"; * to extract them where possible. */ const FILE_SIGNATURES = { - "Pictures": [ + "Images": [ { name: "JPEG Image", extension: "jpg", @@ -51,7 +51,165 @@ const FILE_SIGNATURES = { }, extractor: null }, - + { + name: "WEBP Image", + extension: "webp", + mime: "image/webp", + description: "", + signature: { + 8: 0x57, + 9: 0x45, + 10: 0x42, + 11: 0x50 + }, + extractor: null + }, + { + name: "TIFF Image", + extension: "tif", + mime: "image/tiff", + description: "", + signature: [ + { + 0: 0x49, + 1: 0x49, + 2: 0x2a, + 3: 0x0 + }, + { + 0: 0x4d, + 1: 0x4d, + 2: 0x0, + 3: 0x2a + } + ], + extractor: null + }, /* + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + }, + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + }, + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + }, + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + }, + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + }, + { + name: " Image", + extension: "", + mime: "image/", + description: "", + signature: { + 0: 0x, + 1: 0x, + 2: 0x, + 3: 0x + }, + extractor: null + },*/ + ], + "Video": [ + { + name: "WEBM", + extension: "webm", + mime: "video/webm", + description: "", + signature: { + 0: 0x1a, + 1: 0x45, + 2: 0xdf, + 3: 0xa3 + }, + extractor: null + }, + ], + "Audio": [ + { + name: "WAV", + extension: "wav", + mime: "audio/x-wav", + description: "", + signature: { + 0: 0x52, + 1: 0x49, + 2: 0x46, + 3: 0x46, + 8: 0x57, + 9: 0x41, + 10: 0x56, + 11: 0x45 + }, + extractor: null + }, + { + name: "OGG", + extension: "ogg", + mime: "audio/ogg", + description: "", + signature: { + 0: 0x4f, + 1: 0x67, + 2: 0x67, + 3: 0x53 + }, + extractor: null + }, ], "Documents": [ { @@ -103,13 +261,31 @@ const FILE_SIGNATURES = { /** * Checks whether a signature matches a buffer. * - * @param {Object} sig - A dictionary of offsets with values assigned to them. These - * values can be numbers for static checks, arrays of potential valid matches, or - * bespoke functions to check the validity of the buffer value at that offset. + * @param {Object|Object[]} sig - A dictionary of offsets with values assigned to them. + * These values can be numbers for static checks, arrays of potential valid matches, + * or bespoke functions to check the validity of the buffer value at that offset. * @param {Uint8Array} buf * @returns {boolean} */ function signatureMatches(sig, buf) { + if (sig instanceof Array) { + return sig.reduce((acc, s) => acc || bytesMatch(s, buf), false); + } else { + return bytesMatch(sig, buf); + } +} + + +/** + * Checks whether a set of bytes match the given buffer. + * + * @param {Object} sig - A dictionary of offsets with values assigned to them. + * These values can be numbers for static checks, arrays of potential valid matches, + * or bespoke functions to check the validity of the buffer value at that offset. + * @param {Uint8Array} buf + * @returns {boolean} + */ +function bytesMatch(sig, buf) { for (const offset in sig) { switch (typeof sig[offset]) { case "number": // Static check @@ -165,34 +341,6 @@ export function detectFileType(buf) { /* - if (buf[0] === 0xFF && buf[1] === 0xD8 && buf[2] === 0xFF) { - return { - ext: "jpg", - mime: "image/jpeg" - }; - } - - if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4E && buf[3] === 0x47) { - return { - ext: "png", - mime: "image/png" - }; - } - - if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46) { - return { - ext: "gif", - mime: "image/gif" - }; - } - - if (buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50) { - return { - ext: "webp", - mime: "image/webp" - }; - } - // needs to be before `tif` check if (((buf[0] === 0x49 && buf[1] === 0x49 && buf[2] === 0x2A && buf[3] === 0x0) || (buf[0] === 0x4D && buf[1] === 0x4D && buf[2] === 0x0 && buf[3] === 0x2A)) && buf[8] === 0x43 && buf[9] === 0x52) { return { @@ -237,13 +385,6 @@ export function detectFileType(buf) { }; } - if (buf[0] === 0x50 && buf[1] === 0x4B && (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) && (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)) { - return { - ext: "zip", - mime: "application/zip" - }; - } - if (buf[257] === 0x75 && buf[258] === 0x73 && buf[259] === 0x74 && buf[260] === 0x61 && buf[261] === 0x72) { return { ext: "tar", @@ -315,13 +456,6 @@ export function detectFileType(buf) { }; } - if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3) { - return { - ext: "webm", - mime: "video/webm" - }; - } - if (buf[0] === 0x0 && buf[1] === 0x0 && buf[2] === 0x0 && buf[3] === 0x14 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70) { return { ext: "mov", @@ -364,13 +498,6 @@ export function detectFileType(buf) { }; } - if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53) { - return { - ext: "ogg", - mime: "audio/ogg" - }; - } - if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43) { return { ext: "flac", @@ -378,13 +505,6 @@ export function detectFileType(buf) { }; } - if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 && buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45) { - return { - ext: "wav", - mime: "audio/x-wav" - }; - } - if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D && buf[4] === 0x52 && buf[5] === 0x0A) { return { ext: "amr", @@ -392,20 +512,6 @@ export function detectFileType(buf) { }; } - if (buf[0] === 0x25 && buf[1] === 0x50 && buf[2] === 0x44 && buf[3] === 0x46) { - return { - ext: "pdf", - mime: "application/pdf" - }; - } - - if (buf[0] === 0x4D && buf[1] === 0x5A) { - return { - ext: "exe", - mime: "application/x-msdownload" - }; - } - if ((buf[0] === 0x43 || buf[0] === 0x46) && buf[1] === 0x57 && buf[2] === 0x53) { return { ext: "swf", diff --git a/src/core/operations/DetectFileType.mjs b/src/core/operations/DetectFileType.mjs index bcb4d2a5..55db5edf 100644 --- a/src/core/operations/DetectFileType.mjs +++ b/src/core/operations/DetectFileType.mjs @@ -39,7 +39,7 @@ class DetectFileType extends Operation { if (!types.length) { return "Unknown file type. Have you tried checking the entropy of this data to determine whether it might be encrypted or compressed?"; } else { - let output; + let output = ""; types.forEach(type => { output += "File extension: " + type.extension + "\n" + diff --git a/src/core/operations/PlayMedia.mjs b/src/core/operations/PlayMedia.mjs index 81328a73..d0ec78cc 100644 --- a/src/core/operations/PlayMedia.mjs +++ b/src/core/operations/PlayMedia.mjs @@ -9,7 +9,7 @@ import { fromHex } from "../lib/Hex"; import Operation from "../Operation"; import OperationError from "../errors/OperationError"; import Utils from "../Utils"; -import Magic from "../lib/Magic"; +import { detectFileType } from "../lib/FileType"; /** * PlayMedia operation @@ -66,8 +66,8 @@ class PlayMedia extends Operation { // Determine file type - const type = Magic.magicFileType(input); - if (!(type && /^audio|video/.test(type.mime))) { + const types = detectFileType(input); + if (!(types && types.length && /^audio|video/.test(types[0].mime))) { throw new OperationError("Invalid or unrecognised file type"); } @@ -84,15 +84,15 @@ class PlayMedia extends Operation { async present(data) { if (!data.length) return ""; - const type = Magic.magicFileType(data); - const matches = /^audio|video/.exec(type.mime); + const types = detectFileType(data); + const matches = /^audio|video/.exec(types[0].mime); if (!matches) { throw new OperationError("Invalid file type"); } - const dataURI = `data:${type.mime};base64,${toBase64(data)}`; + const dataURI = `data:${types[0].mime};base64,${toBase64(data)}`; const element = matches[0]; - let html = `<${element} src='${dataURI}' type='${type.mime}' controls>`; + let html = `<${element} src='${dataURI}' type='${types[0].mime}' controls>`; html += "

Unsupported media type.

"; html += ``; return html;