diff --git a/README.md b/README.md index b72a066..7ea5eb5 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,15 @@ Usage: ```js -define([ - 'filevalidator', -], +import { verifyFileType } from 'filevalidator'; -function(filevalidator) { +const fileInput = document.getElementById('file-input'); - var fileInput = document.getElementById('file-input'); - - fileInput.addEventListener('change', function(e) { - var file = e.currentTarget.files[0]; - filevalidator.verifyFileType(file, ['mp3', 'wav'], function(valid) { - alert('Valid mp3 or wave file: ' + !!valid); - }); +fileInput.addEventListener('change', function(e) { + const file = e.currentTarget.files[0]; + verifyFileType(file, ['mp3', 'wav'], function(valid) { + alert('Valid mp3 or wave file: ' + !!valid); }); - }); ``` diff --git a/filevalidator.js b/filevalidator.js index 0beac3f..76fefc8 100644 --- a/filevalidator.js +++ b/filevalidator.js @@ -1,99 +1,87 @@ -define([ - 'underscore', -], +'use strict'; -function(_) { - 'use strict'; +// https://en.wikipedia.org/wiki/List_of_file_signatures +const fileSignatures = { + 'mp3': [ + // MPEG-1 Layer 3 file without an ID3 tag or with an ID3v1 tag (which's appended at the end of the file) + [0xFF, 0xFB], + // MP3 file with an ID3v2 container + [0x49, 0x44, 0x33], + // Other MP3 files (FF Fx and FF Ex – they may cause false-positives) + // Headers taken from https://www.garykessler.net/library/file_sigs.html + [0xFF, 0xE0], [0xFF, 0xE1], [0xFF, 0xE2], [0xFF, 0xE3], [0xFF, 0xE4], + [0xFF, 0xE5], [0xFF, 0xE6], [0xFF, 0xE7], [0xFF, 0xE8], [0xFF, 0xE9], + [0xFF, 0xEA], [0xFF, 0xEB], [0xFF, 0xEC], [0xFF, 0xED], [0xFF, 0xEE], [0xFF, 0xEF], + [0xFF, 0xF0], [0xFF, 0xF1], [0xFF, 0xF2], [0xFF, 0xF3], [0xFF, 0xF4], + [0xFF, 0xF5], [0xFF, 0xF6], [0xFF, 0xF7], [0xFF, 0xF8], [0xFF, 0xF9], + [0xFF, 0xFA], [0xFF, 0xFB], [0xFF, 0xFC], [0xFF, 0xFD], [0xFF, 0xFE], [0xFF, 0xFF] + ], + 'wav': [ + // Waveform Audio File Format + // Empty slots can be any byte. Can't look at only first 4 or else .avi files match + [0x52, 0x49, 0x46, 0x46, , , , , 0x57, 0x41, 0x56, 0x45] + ], +}; - // https://en.wikipedia.org/wiki/List_of_file_signatures - var fileSignatures = { - 'mp3': [ - // MPEG-1 Layer 3 file without an ID3 tag or with an ID3v1 tag (which's appended at the end of the file) - [0xFF, 0xFB], - // MP3 file with an ID3v2 container - [0x49, 0x44, 0x33], - // Other MP3 files (FF Fx and FF Ex – they may cause false-positives) - // Headers taken from https://www.garykessler.net/library/file_sigs.html - [0xFF, 0xE0], [0xFF, 0xE1], [0xFF, 0xE2], [0xFF, 0xE3], [0xFF, 0xE4], - [0xFF, 0xE5], [0xFF, 0xE6], [0xFF, 0xE7], [0xFF, 0xE8], [0xFF, 0xE9], - [0xFF, 0xEA], [0xFF, 0xEB], [0xFF, 0xEC], [0xFF, 0xED], [0xFF, 0xEE], [0xFF, 0xEF], - [0xFF, 0xF0], [0xFF, 0xF1], [0xFF, 0xF2], [0xFF, 0xF3], [0xFF, 0xF4], - [0xFF, 0xF5], [0xFF, 0xF6], [0xFF, 0xF7], [0xFF, 0xF8], [0xFF, 0xF9], - [0xFF, 0xFA], [0xFF, 0xFB], [0xFF, 0xFC], [0xFF, 0xFD], [0xFF, 0xFE], [0xFF, 0xFF] - ], - 'wav': [ - // Waveform Audio File Format - // Empty slots can be any byte. Can't look at only first 4 or else .avi files match - [0x52, 0x49, 0x46, 0x46, , , , , 0x57, 0x41, 0x56, 0x45] - ], - }; +/** + * Compare an Uint8Array with an expected file signature. + * Can't do a direct equality check since some signatures (e.gfor wav files) have wildcard slots. + * @param {array} sig - pattern from fileSignatures + * @param {Uint8Array} actual - bytes from file (should already be sliced to match length of sig) + * @returns {boolean} - do they match + */ +const compareSignature = function(sig, actual) { + if (sig.length !== actual.length) return false; + for (var i = 0, l = sig.length; i < l; i++) { + if (sig[i] !== actual[i] && typeof sig[i] !== 'undefined') return false; + } + return true; +}; - /** - * Compare an Uint8Array with an expected file signature. - * Can't do a direct equality check since some signatures (e.gfor wav files) have wildcard slots. - * @param {array} sig - pattern from fileSignatures - * @param {Uint8Array} actual - bytes from file (should already be sliced to match length of sig) - * @returns {boolean} - do they match - */ - var compareSignature = function(sig, actual) { - if (sig.length !== actual.length) return false; - for (var i = 0, l = sig.length; i < l; i++) { - if (sig[i] !== actual[i] && typeof sig[i] !== 'undefined') return false; - } - return true; - }; +/** + * @param {Uint8Array} uint8 + * @param {string} type + * @returns {boolean} + */ +const matchesFileType = function(uint8, type) { + return fileSignatures[type].find(function(sig) { + return compareSignature(sig, uint8.subarray(0, sig.length)); + }); +}; - /** - * @param {Uint8Array} uint8 - * @param {string} type - * @returns {boolean} - */ - var matchesFileType = function(uint8, type) { - return _.find(fileSignatures[type], function(sig) { - return compareSignature(sig, uint8.subarray(0, sig.length)); - }); - }; - - /** - * Detect, through file signature / mime sniffing detection, if a given File - * matches an expected type or types. The types supported are the keys in - * fileSignatures above. - * @param {File} file - * @param {(string|string[])} types - e.g. 'mp3' or ['mp3', 'wav'] - * @param {function} - callback which is passed a boolean - */ - var verifyFileType = function(file, types, cb) { - if (_.isString(types)) types = [types]; - - // Calculate the longest file signature for any of the requested - // types, so we know how many bytes of this file to look at. - var bytesNeeded = types.reduce(function(prevMax, type, idx, arr) { - var sigs = fileSignatures[type]; - return Math.max.apply(this, [prevMax].concat(sigs.map(function(sig) { - return sig.length; - }))); - }, 0); +/** + * Detect, through file signature / mime sniffing detection, if a given File + * matches an expected type or types. The types supported are the keys in + * fileSignatures above. + * @param {File} file + * @param {(string|string[])} types - e.g. 'mp3' or ['mp3', 'wav'] + * @param {function} - callback which is passed a boolean + */ +const verifyFileType = function(file, types, cb) { + if (typeof types === 'string') types = [types]; - // Load file into ArrayBuffer and see if its first few bytes match - // the signature of any of our requested types. Let callback know. - var reader = new FileReader(); - reader.onload = function(e) { - // Load only as many bytes from the array buffer as necessary - var arrayBuffer = e.currentTarget.result; - var bytes = new Uint8Array(arrayBuffer, 0, bytesNeeded); - var match = _.find(types, function(type) { - return matchesFileType(bytes, type); - }); - cb(match); - }; - reader.readAsArrayBuffer(file); - }; + // Calculate the longest file signature for any of the requested + // types, so we know how many bytes of this file to look at. + const bytesNeeded = types.reduce(function(prevMax, type, idx, arr) { + var sigs = fileSignatures[type]; + return Math.max.apply(this, [prevMax].concat(sigs.map(function(sig) { + return sig.length; + }))); + }, 0); - // Expose public interface - var FileDetector = { - verifyFileType: verifyFileType - }; - - return FileDetector; -}); + // Load file into ArrayBuffer and see if its first few bytes match + // the signature of any of our requested types. Let callback know. + const reader = new FileReader(); + reader.addEventListener('load', function(e) { + // Load only as many bytes from the array buffer as necessary + var arrayBuffer = e.currentTarget.result; + var bytes = new Uint8Array(arrayBuffer, 0, bytesNeeded); + var match = _.find(types, function(type) { + return matchesFileType(bytes, type); + }); + cb(match); + }); + reader.readAsArrayBuffer(file); +}; +export { verifyFileType }; diff --git a/package.json b/package.json index 89f824a..95ae1f8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "filevalidator", - "version": "1.0.1", + "version": "2.0.0", "description": "File signature validation in JavaScript", + "type": "module", "main": "filevalidator.js", "author": "Phil Freo (http://philfreo.com/)", "license": "MIT"