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

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

利用 input 的 accept 属性进行限制


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

<input type="file">


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

5659e75b2e6044a2b08615abd8b54d94.png


限制格式1

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


b30d6746bbd343339157516bb3935124.png



限制格式2

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


f75da8cb04ed4f9fb54b939403fc7153.png



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

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


de0ffd11cdcf4d1c8078a71dc7153559.png




属性值

描述
audio/* 接受所有的声音文件。
video/* 接受所有的视频文件。



标准 MIME 类型


7869e1d8b1114ed082379afcfe90931c.png

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

888d9bca95e14b7290608d96d9b7d72b.png



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


<!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>


24a084ed0a6941bd8af3dbf1e38fcba8.png


我们可以通过 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) 里对应的规则分析出文件头,并打印输出。




文件头标志大全


参考:文件头标志大全


e6f28e2d2be04a879bd5c4da550933cf.png


比如:png 的头文件标识


94f91cd1e358477d9fa13291e2f2ed57.png


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

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

0859982b5dbd40f2bd0d9b5600b7fb49.png


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


9a94170ca88b4fdcbd32844911c0b19a.png


点击文件,选择打开,选择一张 png 图片,点击打开。


664d6b2efefd4210bf7fa190adfb0892.png


打开之后我们可以看到界面的数据如下:

ee6f5bf9f5ab465283bfd095bb59456e.png


我们可以看到前面 8 个 89 50 4E 47 0D 0A 1A 0A 符合头文件标识里的 png 类型

8b48cb32c26f45468de6a14518b07f70.png


我们再看一个 flv 文件,我们可以看到前面 4 个 46 4C 56 01 符合头文件标识里的 flv 类型


12ee290313294f9e9ac943464f6916e4.png



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

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>



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

76661819ed3441858a8156299c1b3927.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>


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

d915e84df8424fd7b5b08ebd0b25c749.png


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


795beba18c1c4810b57a6a3e4d6e3198.png



我们可以自己封装方法去校验文件的类型,比如,通过 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>


上传一张 png 图片

be4bb28a40af4f15a18a0340d518a136.png


上传一个 flv 文件

e6c69a0f0ada4bb380dfb2e3e222792b.png



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



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


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


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

21e849322f384218914fab05c42a3654.png


安装依赖:

npm i file-type@16.0.0


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

1c492a7f59b9440f93f5576a51e36099.png


可能是我 node 版本太低,我的node版本是 12.13.0


a1e9cb7955d1470db9d894bc25db2c5b.png


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


f03b19c97ce241f4bfc52467b846a4ae.png


代码如下:

<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>


测试结果:

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

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


18363a752c2a4238ba7f43163800fcf9.png


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

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


0a3f9beab4bb41debdebc949ade3807c.png



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

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


0be936f3025b4ebfa7b511a76f1aa6e1.png







目录
相关文章
|
9月前
|
JavaScript 前端开发 测试技术
在Vue项目中,常见的错误类型有哪些?
在Vue项目中,常见的错误类型有哪些?
88 2
|
3月前
|
存储 Java Linux
Java“Bad Magic Number”错误解决
Java“Bad Magic Number”错误通常发生在尝试运行不兼容或损坏的类文件时。解决方法包括确保使用正确的JDK版本、检查类文件完整性、清理和重新编译项目。
252 13
|
4月前
|
存储 Java Apache
Python Number类型详解!
本文详细介绍了 Python 中的数字类型,包括整数(int)、浮点数(float)和复数(complex),并通过示例展示了各种算术操作及其类型转换方法。Python 的 `int` 类型支持任意大小的整数,`float` 类型用于表示实数,而 `complex` 类型用于表示复数。此外,文章还对比了 Python 和 Java 在数字类型处理上的区别,如整数类型、浮点数类型、复数类型及高精度类型,并介绍了各自类型转换的方法。尽管两种语言在语法上有所差异,但其底层逻辑是相通的。通过本文,读者可以更好地理解 Python 的数字类型及其应用场景。
88 2
|
7月前
|
开发框架 JavaScript 前端开发
Vue&Element开发框架中增加工作流处理,查看申请单中整合多个处理类型的处理
Vue&Element开发框架中增加工作流处理,查看申请单中整合多个处理类型的处理
|
7月前
|
JavaScript
Vue3的使用,ref定义基本类型的响应式数据,如何创建对象类型的响应式数据,let car = {brand: ‘奔驰‘,price: 100},{{car.brand}},reactive的使用
Vue3的使用,ref定义基本类型的响应式数据,如何创建对象类型的响应式数据,let car = {brand: ‘奔驰‘,price: 100},{{car.brand}},reactive的使用
TS定义布尔值,let flag:boolean = true,定义数字类型 let a1:number = 10,赋值 let str1:string = ‘‘,打印c~.log($(str1))
TS定义布尔值,let flag:boolean = true,定义数字类型 let a1:number = 10,赋值 let str1:string = ‘‘,打印c~.log($(str1))
TS,数据类型概述,常见的基本数据类型有number/string/boolean/undefined/null,字符串用““,let food: string = ‘糖葫芦‘,布尔类型
TS,数据类型概述,常见的基本数据类型有number/string/boolean/undefined/null,字符串用““,let food: string = ‘糖葫芦‘,布尔类型
TS,类型注解 number就是类型注解,TS类型注解是一种为变量添加类型约束的方式,你定义什么类型,就只能赋值什么类型,变量命名规则,变量名称不能以数字开头,交换变量写法
TS,类型注解 number就是类型注解,TS类型注解是一种为变量添加类型约束的方式,你定义什么类型,就只能赋值什么类型,变量命名规则,变量名称不能以数字开头,交换变量写法
|
9月前
|
SQL Oracle 关系型数据库
实时计算 Flink版产品使用合集之使用JDBC方式读取Oracle的number类型时,通过什么方式进行映射
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
214 0
实时计算 Flink版产品使用合集之使用JDBC方式读取Oracle的number类型时,通过什么方式进行映射
|
9月前
|
编译器
vue3组件TS类型声明实例代码
vue3组件TS类型声明实例代码
112 0

热门文章

最新文章