• vue 里怎么通过魔数(magic number)去限制上传文件类型?


    利用 input 的 accept 属性进行限制

    accept 属性规定了可通过文件上传提交的服务器接受的文件类型。

    <input type="file">
    
    • 1

    不限制,上传这里的话就会显示所有的文件。

    在这里插入图片描述

    限制格式1

    <input type="file" accept=".pdf, .xls, .txt">
    
    • 1

    在这里插入图片描述

    限制格式2

    <input type="file" accept="image/gif, video/mp4">
    
    • 1

    在这里插入图片描述

    加了之后就会变成自定义文件类型的选择

    <input type="file" accept="video/*, image/*">
    
    • 1

    在这里插入图片描述

    属性值

    描述
    audio/*接受所有的声音文件。
    video/*接受所有的视频文件。
    image/*接受所有的图像文件。
    MIME_type一个有效的 MIME 类型,不带参数。请参阅 IANA MIME 类型,获得标准 MIME 类型的完整列表。

    标准 MIME 类型

    在这里插入图片描述

    该属性只能用于提示用户选择什么样的类型,因为用户还是可以选择所有文件进行其他类型文件的上传。

    在这里插入图片描述

    利用上传文件的对象去限制

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>上传限制文件类型title>
    head>
    <body>
        <input type="file" onchange="handleChange(event)">
        <script>
            function handleChange(event) {
                console.log(event.target.files)
            }
        script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    我们可以通过 file 对象的 name 或者 type 属性去校验文件类型进行限制。

    上面两种,单纯的添加属性,以及以文件后缀名(扩展名)、文件类型进行截取的方式来进行限制,很容易绕过限制,进行上传。为了避免这些情况,我们可以根据文件的头信息,来判断文件真正的格式。

    下面先了解一下魔数(magic number)。

    什么是魔数(magic number)?

    魔数这个词在不同领域代表不同的含义。在计算机领域,魔数有两个含义,一指用来判断文件类型的魔数;二指程序代码中的魔数,也称魔法值。

    判断文件类型的魔数

    很多类型的文件,其起始的几个字节的内容是固定的(或是有意填充,或是本就如此)。因此这几个字节的内容也被称为魔数 (magic number),因为根据这几个字节的内容就可以确定文件类型。

    例如:

    • FreeBSD 上 ELF 文件的 magic number 就是文件的前四个字节依次为"7f 45 4c 46",对应的 ascii 字符串即 “^?ELF”。
    • tar 文件的 magic number 是从第 257 个字节起为 “ustar”。
    • PE 文件中,在 DOS-根之后是一个 32 位的签名以及魔数 0x00004550 (IMAGE_NT_SIGNATURE) 意为“NT签名”,也就是PE签名;十六进制数 45 和 50 分别代表 ASCII 码字母 P 和 E,它使任何 PE 文件都是一个有效的 MS-DOS 可执行文件。
    • Java 的 .class 文件,开头的 4 个字节 0xCAFEBABE,这是 Java 初代开发小组最喜欢的一种咖啡 Peet’s Coffee 的爱称。

    Linux 操作系统下没有文件扩展名,判断文件类型凭借的就是文件的内容,也就是魔数。

    linux 命令 file 实现原理:大致就是读取一个文件的前面 1024 个字节,然后根据 magic (/etc/magic 或者 /usr/share/misc/magic) 里对应的规则分析出文件头,并打印输出。

    文件头标志大全

    参考:文件头标志大全

    在这里插入图片描述

    比如:png 的头文件标识

    在这里插入图片描述

    利用十六进制编辑器查看魔数

    大家可以先安装十六进制编辑器【软件推荐】免费的Windows十六进制编辑器推荐,我这边安装的是 HxD 编辑器。

    在这里插入图片描述

    下面打开一个 png 文件看看。

    在这里插入图片描述
    点击文件,选择打开,选择一张 png 图片,点击打开。

    在这里插入图片描述
    打开之后我们可以看到界面的数据如下:

    在这里插入图片描述
    我们可以看到前面 8 个 89 50 4E 47 0D 0A 1A 0A 符合头文件标识里的 png 类型

    在这里插入图片描述
    我们再看一个 flv 文件,我们可以看到前面 4 个 46 4C 56 01 符合头文件标识里的 flv 类型

    在这里插入图片描述

    在这里插入图片描述

    怎么获取文件的十六进制数据进行限制?

    File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说,FileReader, URL.createObjectURL(), createImageBitmap() (en-US), 及 XMLHttpRequest.send() 都能处理 Blob 和 File。

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>上传限制文件类型title>
    head>
    <body>
        <input type="file" onchange="handleChange(event)">
        <script>
            function handleChange(event) {
                console.log(event.target.files)
                const [ file ] = event.target.files;
                console.log(file);
                const fileReader = new FileReader();
                // readAsArrayBuffer 开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
                fileReader.readAsArrayBuffer(file);
                // 该事件在读取操作完成时触发。
                fileReader.onload = function(e) {
                    console.log("读取文件 ArrayBuffer 数据成功", e);
                    console.log(fileReader.result);
                }
                // 该事件在读取操作发生错误时触发。
                fileReader.onerror = function(err) {
                    console.error("读取文件 ArrayBuffer 数据失败", err);
                }
            }
        script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    我们上传一个 png 的图片,看看打印的数据:

    在这里插入图片描述

    接下来需要将这个 ArrayBuffer 数据转化成16进制的数据,我们改造一下代码:

    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>上传限制文件类型title>
    head>
    <body>
        <input type="file" onchange="handleChange(event)">
        <script>
            // ArrayBuffer 转 16进度字符串
            function ab2hex(buffer){
                // Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
                const hexArr = Array.prototype.map.call(
                    new Uint8Array(buffer),
                    function(bit) {
                        console.log(bit)
                        return ("00" + bit.toString(16)).slice(-2).toUpperCase();
                    }
                )
                return hexArr.join(" ");
            }
    
            function handleChange(event) {
                console.log(event.target.files)
                const [ file ] = event.target.files;
                console.log(file);
                const fileReader = new FileReader();
                // readAsArrayBuffer 开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
                fileReader.readAsArrayBuffer(file.slice(0, 8));
                // 该事件在读取操作完成时触发。
                fileReader.onload = function(e) {
                    console.log("读取文件 ArrayBuffer 数据成功", e);
                    const hex = ab2hex(fileReader.result);
                    console.log(hex);
                    console.log("文件类型是png吗?", hex === "89 50 4E 47 0D 0A 1A 0A");
                }
                // 该事件在读取操作发生错误时触发。
                fileReader.onerror = function(err) {
                    console.error("读取文件 ArrayBuffer 数据失败", err);
                }
            }
        script>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    上传一张 png,测试结果如下,发现没有问题

    在这里插入图片描述

    上传一个 flv ,结果如下:输出了 hex 为 46 4C 56 01 01 00 00 00,前面 4 个满足 flv 类型。

    在这里插入图片描述

    我们可以自己封装方法去校验文件的类型,比如,通过 file 跟截取的长度去封装一个返回 hex 的公共函数,然后其他的类型函数做判断处理。

    我用 vue 实现一下类似的方法:

    <template>
      <div class="about">
        <h1>kaimo 上传限制文件类型h1>
        <input type="file" @change="handleChange($event)" />
      div>
    template>
    
    <script>
    export default {
      name: "home",
      data() {
        return {};
      },
      methods: {
        // ArrayBuffer 转 16进度字符串
        ab2hex(buffer) {
          // Uint8Array 数组类型表示一个 8 位无符号整型数组,创建时内容被初始化为 0。创建完后,可以以对象的方式或使用数组下标索引的方式引用数组中的元素。
          const hexArr = Array.prototype.map.call(
            new Uint8Array(buffer),
            function (bit) {
              console.log(bit);
              return ("00" + bit.toString(16)).slice(-2).toUpperCase();
            }
          );
          return hexArr.join(" ");
        },
        // 获取文件 16 进度字符串
        getFileHex(file, num) {
          return new Promise((resolve, reject) => {
            const fileReader = new FileReader();
            // readAsArrayBuffer 开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
            fileReader.readAsArrayBuffer(file.slice(0, num));
            // 该事件在读取操作完成时触发。
            fileReader.onload = e => {
              console.log("读取文件 ArrayBuffer 数据成功", e);
              const hex = this.ab2hex(fileReader.result);
              console.log(hex);
              resolve(hex);
            };
            // 该事件在读取操作发生错误时触发。
            fileReader.onerror = err => {
              console.error("读取文件 ArrayBuffer 数据失败", err);
              reject(err);
            };
          });
        },
        // 校验是否是png
        async checkFileIsPng(file) {
          const res = await this.getFileHex(file, 8);
          console.log("校验 png hex", res);
          return res === "89 50 4E 47 0D 0A 1A 0A";
        },
        // 校验是否是flv
        async checkFileIsFlv(file) {
          const res = await this.getFileHex(file, 4);
          console.log("校验 flv hex", res);
          return res === "46 4C 56 01";
        },
        // 上传文件改变
        async handleChange(event) {
          console.log(event.target.files);
          const [file] = event.target.files;
          console.log(file);
          console.log("文件类型是png吗?", await this.checkFileIsPng(file));
          console.log("文件类型是flv吗?", await this.checkFileIsFlv(file));
        },
      },
    };
    script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    上传一张 png 图片

    在这里插入图片描述

    上传一个 flv 文件

    在这里插入图片描述

    这样一个一个写有点麻烦,有没有更好的方式去检测二进制的文件格式。有,那就是使用 file-type 这个库。

    利用 file-type 库进行文件类型的限制

    file-type 库用于检测 Buffer/Uint8Array/ArrayBuffer 的文件类型。检测基于二进制的文件格式,而不是基于文本的格式,如 .txt、.csv、.svg 等。

    原理:通过检查缓冲区的魔数来检测文件类型。

    在这里插入图片描述

    安装依赖:

    npm i file-type@16.0.0
    
    • 1

    这里我们使用 16.0.0 https://github.com/sindresorhus/file-type/tree/v16.0.0版本的,我试了一下 17.1.4 版本的,使用 file-type 的时候报错如下:

    在这里插入图片描述

    可能是我 node 版本太低,我的node版本是 12.13.0
    在这里插入图片描述

    用法:从缓冲区确定文件类型,缓冲区可能是文件开头的一部分:

    在这里插入图片描述

    代码如下:

    <template>
      <div class="file-type">
        <h1>kaimo 测试 file-type 库h1>
        <input type="file" @change="handleChange($event)" />
      div>
    template>
    
    <script>
    const FileType = require('file-type');
    
    export default {
      methods: {
        // 上传文件改变
        handleChange(event) {
          console.log(event.target.files);
          const [file] = event.target.files;
          console.log(file);
          const fileReader = new FileReader();
          // readAsArrayBuffer 开始读取指定的 Blob中的内容,一旦完成,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
          fileReader.readAsArrayBuffer(file);
          // 该事件在读取操作完成时触发。
          fileReader.onload = (async (e) => {
            console.log("读取文件 ArrayBuffer 数据成功", e);
            console.log(fileReader.result);
            console.log(await FileType.fromBuffer(fileReader.result));
          });
          // 该事件在读取操作发生错误时触发。
          fileReader.onerror = (err) => {
            console.error("读取文件 ArrayBuffer 数据失败", err);
          };
        },
      },
    };
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    测试结果:

    上传 png 文件,打印出来了:

    {ext: 'png', mime: 'image/png'}
    
    • 1

    在这里插入图片描述

    上传 flv 文件,打印出来了:

    {ext: 'flv', mime: 'video/x-flv'}
    
    • 1

    在这里插入图片描述

    这样就很方便我们去判断上传的文件类型。

    另外,你要查看更多支持校验的类型在 supported.js 里面。

    在这里插入图片描述

    参考资料

    拓展资料

  • 相关阅读:
    Util应用框架核心(一) - 服务配置
    Day09 系统设置模块设计
    【Go语言】Gin 框架教程
    分布式 PostgreSQL 集群(Citus)官方示例 - 多租户应用程序实战
    MySQL 45 讲 | 13 为什么表数据删掉一半,表文件大小不变?
    熬夜肝出囊括Java后端95%的面试题解析
    for...in 与 for...of 的用法与区别
    【JAVA】网页版登录注册系统2.0
    Verilog实战学习到RiscV - 3 : ICEStick 评估板点灯
    马克·雷伯特访谈:机器人的未来及波士顿动力的创新之路
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/125988421