Background: When uploading files, you need to restrict the type of uploaded files. The simplest and most common method of checking file types is to check the extension name of the file directly, but since the extension name can be manually modified, this method is not safe. So is there any way to determine exactly what type of file to upload?

File signature

File signature refers to the bytes used in a File to identify the format of the File, usually placing a small number of bytes (most 2-4 bytes) at the beginning of the File. Different file types have corresponding file signatures. We can query the signatures of each file type through List of File Signatures and All File signatures.

Verify File types using File signature

Test ideas

  1. Get the file and convert the file to an ArrayBuffer

Files can be obtained from the network or locally. The corresponding conversion method is as follows:

/** * get the network file and convert it to ArrayBuffer */
function loadFile(fileUrl) {
 return new Promise((resolve, reject) = > {
   const xhr = new XMLHttpRequest();
   xhr.onload = function () {
     if (xhr.status === 200) { resolve(xhr.response); }}; xhr.onerror = reject; xhr.open('GET', fileUrl, true);
   xhr.responseType = 'arraybuffer';
   xhr.send(' '); })}Copy the code
/** * get the local file and convert it to ArrayBuffer */

// Input [type="file"]. Onchange Callback event
function onFileChange(file) {
  return new Promise(async (reslove, reject) => {
    if(! file) {return reslove(false);
    }
   const FR = new FileReader();
   const fileChunk = file.slice(offset, size + offset);
   FR.readAsArrayBuffer(fileChunk);
   FR.onload = (e) = > {
       //e.target.result is the required ArrayBuffer
       const ArrayBuffer= e. arget. Result;//do something}}); }Copy the code
  1. Convert buffer to a hexadecimal string

Convert using TypedArray:

Array.prototype.map.call(new Uint8Array(ArrayBuffer), (x) = > x.toString(16).padStart(2.'0')).join(' ');
Copy the code

TypedArray, however, will use the system’s default byte order (see TypedArray or DataView: Understanding Byte Order for details), whereas DataView defaults to use the order from largest to smallest:

const view = new DataView(ArrayBuffer);
let hex= ' ';
for (let i = 0; i < view.byteLength; i += 1) {
  let byte = Number(view.getUint8(i)).toString(16).toUpperCase();
  byte.length === 1 && (byte = '0' + byte);
  hex += byte;
}
Copy the code
  1. The corresponding file signature is intercepted according to the file name extension and compared

For example, a PDF file signature

    {
      extension: 'ppt',
      signature: '006E1EF0',
      size: 4,
      offset: 512,
    },
Copy the code

Each byte from offset to offset+size in the hexadecimal string can be compared with signature

const fileSignature = hex.slice(offset * 2, (size + offset) * 2);
Copy the code

To optimize the

The offset and size of the file signature can be obtained according to the file name extension, and the content of the file signature can be intercepted directly without loading all binary data of the file.

The complete code

// Const fileSignatures = {PDF: [{extension: 'PDF ', signature: '25504446', size: 4, offset: 0},], / /... }; function getfileSignature(file, size, offset) { return new Promise((resolve, reject) => { try { const FR = new FileReader(); const fileChunk = file.slice(offset, size + offset); FR.readAsArrayBuffer(fileChunk); FR.onload = (e) => { const view = new DataView(e.target.result as ArrayBufferLike); let fileSignature = ''; for (let i = 0; i < view.byteLength; i += 1) { let byte = Number(view.getUint8(i)).toString(16).toUpperCase(); byte.length === 1 && (byte = '0' + byte); fileSignature += byte; } resolve(fileSignature); }; } catch (e) { reject(e); }}); } function judgeFileType(file) { return new Promise(async (reslove, reject) => { if (! file) { return reslove(false); } try { const fileInfoArr = file.name.split('.'); const fileType = fileInfoArr[fileInfoArr.length - 1]; if (fileSignatures[fileType]) { for (let i = 0; i < fileSignatures[fileType].length; i++) { const fileSignature = await getfileSignature(file, fileSignatures[fileType][i].size, fileSignatures[fileType][i].offset); if (fileSignature === fileSignatures[fileType][i].signature) { return reslove(true); } else if (i === fileSignatures[fileType].length - 1) { return reslove(false); } } } else { return reslove(false); } } catch (e) { reject(e); }}); }Copy the code