file-type-checker
Version:
Detect and validate file types by their signatures (✨magic numbers✨)
168 lines (167 loc) • 6.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isHeicSignatureIncluded = exports.isAvifStringIncluded = exports.isFileContaineJfiforExifHeader = exports.isFlvStringIncluded = exports.isftypStringIncluded = exports.findMatroskaDocTypeElements = exports.fetchFromObject = exports.getFileChunk = void 0;
/**
* Takes a file content in different types, convert it into array of numbers and returns a chunk of the required size
*
* @param file - File content represents in Array<number> / ArrayBuffer / Uint8Array
* @param fileChunkLength - Required file chunk length
*
* @returns {Array<number>} File chunk of the required size represents in Array<number>
*/
function getFileChunk(file, fileChunkLength = 32 // default length - 32 bytes
) {
const fileToCheck = file instanceof ArrayBuffer ? new Uint8Array(file) : file;
let chunk = [];
if ((Array.isArray(file) && isArrayofNumbers(file)) ||
file instanceof ArrayBuffer ||
file instanceof Uint8Array) {
chunk = Array.from(fileToCheck.slice(0, fileChunkLength));
}
else {
throw new TypeError(`Expected the \`file\` argument to be of type \`Array<number>\`, \`Uint8Array\`, or \`ArrayBuffer\`, got \`${typeof file}\``);
}
if (!isLegalChunk(chunk))
throw new TypeError(`File content contains illegal values`);
return chunk;
}
exports.getFileChunk = getFileChunk;
/**
* Determine if array of numbers is a legal file chunk
*
* @param fileChunk File content represents in Array<number>
*
* @returns {boolean} True if the file content is verified, otherwise false
*/
function isLegalChunk(fileChunk) {
return fileChunk.every((num) => typeof num === "number" && !isNaN(num));
}
/**
* Fetch a property of a object by its name
*
* @param obj The required object
* @param prop The property name
*
* @returns {FileInfo} A property of the rquired object
*/
// eslint-disable-next-line
function fetchFromObject(obj, prop) {
const _index = prop.indexOf(".");
if (_index > -1) {
return fetchFromObject(obj[prop.slice(0, _index)], prop.slice(_index + 1));
}
return obj[prop];
}
exports.fetchFromObject = fetchFromObject;
/**
* Identify whether a valid 'mkv'/'web' file is 'mkv' or 'webm'.
* By checking for the presence of the "DocType" element in the 'webm' header.
* Or by checking the presence of the "Segment" element in the 'mkv' header.
*
* @param fileChunk - A chunk from the beginning of a file content, represents in array of numbers
*
* @returns {string | undefined} 'webm' if found webm string A property of the rquired object
*/
function findMatroskaDocTypeElements(fileChunk) {
const webmString = "webm";
const mkvString = "matroska";
const byteString = fileChunk.map((num) => String.fromCharCode(num)).join("");
if (byteString.includes(webmString)) {
return "webm";
}
if (byteString.includes(mkvString)) {
return "mkv";
}
return undefined; // File type not identified
}
exports.findMatroskaDocTypeElements = findMatroskaDocTypeElements;
/**
* Determine if array of numbers contains the "fytp" string.
* M4V files typically have a "ftyp" box in the first few bytes, which can be checked by searching for the string "ftyp" in the buffer.
*
* @param fileChunk A chunk from the beginning of a file content, represents in array of numbers
*
* @returns {boolean} True if found the "ftyp" string in the fileChunk, otherwise false
*/
function isftypStringIncluded(fileChunk) {
const ftypSignature = [0x66, 0x74, 0x79, 0x70]; // "ftyp" signature
// Check the first few bytes for the "ftyp" signature
for (let i = 0; i < fileChunk.length - ftypSignature.length; i++) {
let found = true;
for (let j = 0; j < ftypSignature.length; j++) {
if (fileChunk[i + j] !== ftypSignature[j]) {
found = false;
break;
}
}
if (found) {
return true;
}
}
return false;
}
exports.isftypStringIncluded = isftypStringIncluded;
/**
* Determine if array of numbers contains the "FLV" string.
* FLV files typically have a "FLV" string in the first few bytes of the file, which can be checked using TextDecoder or similar.
*
* @param fileChunk A chunk from the beginning of a file content, represents in array of numbers
*
* @returns {boolean} True if found the "FLV" string in the fileChunk, otherwise false
*/
function isFlvStringIncluded(fileChunk) {
const signature = fileChunk.slice(0, 3);
const signatureString = new TextDecoder().decode(new Uint8Array(signature));
return signatureString.includes("FLV");
}
exports.isFlvStringIncluded = isFlvStringIncluded;
function isFileContaineJfiforExifHeader(file) {
// Check if the fourth byte is one of the known JFIF or EXIF header markers
const headerMarker = file[3];
if (headerMarker === 0xe0 || // JFIF
headerMarker === 0xe1 // EXIF
) {
return true; // It's a JPEG file
}
return false;
}
exports.isFileContaineJfiforExifHeader = isFileContaineJfiforExifHeader;
/**
* Determine if array of numbers contains the "ftypavif" string.
* AVIF files typically have a "ftypavif" string at bytes 5-12 of the file, which can be checked using TextDecoder or similar.
*
* @param fileChunk A chunk from the beginning of a file content, represents in array of numbers
*
* @returns {boolean} True if found the "AVIF" string in the fileChunk, otherwise false
*/
function isAvifStringIncluded(fileChunk) {
// Convert the relevant slice of the file chunk from hexadecimal to characters
const signature = fileChunk
.slice(4, 12)
.map((hex) => String.fromCharCode(hex))
.join("");
return signature === "ftypavif";
}
exports.isAvifStringIncluded = isAvifStringIncluded;
// eslint-disable-next-line
function isArrayofNumbers(arr) {
return arr.every((element) => typeof element === "number");
}
/**
* Determine if a file chunk contains a HEIC file box.
* HEIC files typically have an 'ftyp' box with specific major brand signatures
* such as 'heic', 'hevc', 'mif1', and 'msf1' which can be checked by searching
* for these strings in the file chunk.
*
* @param fileChunk A chunk from the beginning of a file content, represented as an array of numbers.
* @returns {boolean} True if found a HEIC signature in the fileChunk, otherwise false.
*/
function isHeicSignatureIncluded(fileChunk) {
// Convert the first part of the file chunk to a string to check for signatures
const byteString = fileChunk.map((num) => String.fromCharCode(num)).join("");
// List of possible HEIC 'ftyp' signatures
const heicSignatures = ["ftypheic", "ftyphevc", "ftypmif1", "ftypmsf1"];
// Check if any of the HEIC signatures are included in the byte string
return heicSignatures.some((signature) => byteString.includes(signature));
}
exports.isHeicSignatureIncluded = isHeicSignatureIncluded;