美文网首页前端Web前端之路让前端飞
前端——利用File signature精准校验文件类型

前端——利用File signature精准校验文件类型

作者: CBDxin | 来源:发表于2021-03-20 11:47 被阅读0次

    背景:在进行文件上传时,往往需要对上传文件的类型进行限制。最简单也是最常用的文件类型校验方法,是直接校验文件的拓展名,但由于拓展名可以手动随意修改,因此这种方式并不保险。那么有没有什么方法可以准确地判断出上传文件的类型呢?

    File signature

    File signature是指在文件中用于标识文件格式的字节,通常在文件的开头放置一小段字节(大多数为2-4个字节)。不同的文件类型都有着对应的文件签名,通过 List of file signaturesAll File Signatures我们可以查询到各个文件类型的签名。

    通过File signature校验文件类型

    检验思路

    1. 获取文件,并将文件转化为ArrayBuffer
      文件可通过网络获取或本地获取,对应的转化方法:
    /**
    * 获取网络文件并转化为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('');
     })
    }
    
    /**
    * 获取本地文件并转化为ArrayBuffer
    */
    
    //input[type="file"].onchange 回调事件
    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便是所需ArrayBuffer
           const ArrayBuffer = e.target.result;
          //do something
        }
      });
    }
    
    1. 将buffer转化为16进制字符串
      通过TypedArray进行转化:
    Array.prototype.map.call(new Uint8Array(ArrayBuffer), (x) => x.toString(16).padStart(2, '0')).join('');
    

    但TypedArray将会使用系统默认的字节顺序(详情见TypedArray or DataView: Understanding byte order),而使用DataView 则会默认使用从大到小的顺序:

    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;
    }
    
    1. 根据文件后缀名截取对应文件签名并进行比对
      如针对pdf的文件签名
        {
          extension: 'ppt',
          signature: '006E1EF0',
          size: 4,
          offset: 512,
        },
    

    可截取16进制字符串中的第offset到offset+size个个byte与signature进行比对

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

    优化

    可根据文件的后缀名得到对应文件签名的offset与size,直接截取文件中文件签名的内容,而无需加载文件的全部二进制数据。

    完整代码

    //文件签名列表,作为文件上传类型的白名单
    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);
        }
      });
    }
    

    相关文章

      网友评论

        本文标题:前端——利用File signature精准校验文件类型

        本文链接:https://www.haomeiwen.com/subject/dmwicltx.html