网站首页 > 技术文章 正文
文件上传是工作中常见的业务需求,很多情况下,我们需要限制文件的上传类型,比如只能上传图片。通常我们是通过input元素的accept属性来限制文件的类型:
<input id="file" type="file" accept="image/*" />
或者通过截取文件名后缀的方式来判断:
const ext = file.name.slice(file.name.lastIndexOf('.') + 1);
这样做看似没有毛病,但如果把其他文件的后缀名改为图片格式,就可以成功突破这个限制。以上两种方式都不严谨,存在一定的安全隐患。那么应该如何解决这个问题呢?
一、查看文件的头信息
所有文件在计算机中都是以二进制形式进行存储的,但二进制数据是不方便做判断的,我们可以利用 vscode 插件hexdump for VSCode以十六进制的形式查看二进制文件。安装完成后,点击右上角的小图标,即可查看文件的十六进制信息:
那么,我们分别查看一下jpg png gif的十六进制头信息:
多打开几个文件试试,你会发现同一种类型的文件,他们的头信息是完全相同的。接下来,我们就可以根据头信息来判断文件类型了。
二、根据头信息判断文件类型
1. 将文件转为十六进制字符串
在获取文件对象后,我们可以通过FileReader API来读取文件的内容,然后将结果转为Unicode编码,再转为十六进制,以下是我封装的将文件转为十六进制字符串的方法:
async blobToString(blob) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function() {
const res = reader.result
.split("") // 将读取结果分割为数组
.map(v => v.charCodeAt()) // 转为 Unicode 编码
.map(v => v.toString(16).toUpperCase()) // 转为十六进制,再转大写
.map(v => v.padStart(2, "0")) // 个位数补0
.join(" "); // 转为字符串
resolve(res)
}
reader.readAsBinaryString(blob) // 将文件读取为二进制字符串
})
}
2. 判断文件类型
其实没有必要将整个文件转为十六进制,我们只需要截取文件的前几个字节,然后将截取后的文件转为十六进制,再进行比对就可以了:
// 判断是否为 jpg 格式
async function isJpg(file) {
const res = await blobToString(file.slice(0, 3))
return res === 'FF D8 FF'
}
// 判断是否为 png 格式
async function isPng(file) {
const res = await blobToString(file.slice(0, 4))
return res === '89 50 4E 47'
}
// 判断是否为 gif 格式
async function isGif(file) {
const res = await blobToString(file.slice(0, 4))
return res === '47 49 46 38'
}
3. 完整代码
<!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>Document</title>
</head>
<body>
<input id="file" type="file" />
<script>
file.addEventListener('change', async e => {
const file = e.target.files[0]
const flag = await isImage(file)
if (flag) {
alert('上传格式通过!')
} else {
alert('请上传正确的格式!')
}
})
// 判断是否为图片
async function isImage(file) {
return (await isGif(file)) || (await isPng(file)) || (await isJpg(file))
}
// 判断是否为 jpg 格式
async function isJpg(file) {
const res = await blobToString(file.slice(0, 3))
return res === 'FF D8 FF'
}
// 判断是否为 png 格式
async function isPng(file) {
const res = await blobToString(file.slice(0, 4))
return res === '89 50 4E 47'
}
// 判断是否为 gif 格式
async function isGif(file) {
const res = await blobToString(file.slice(0, 4))
return res === '47 49 46 38'
}
// 将文件转为十六进制字符串
async function blobToString(blob) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function () {
const res = reader.result
.split('') // 将读取结果分割为数组
.map(v => v.charCodeAt()) // 转为 Unicode 编码
.map(v => v.toString(16).toUpperCase()) // 转为十六进制,再转大写
.map(v => v.padStart(2, '0')) // 个位数补0
.join(' ') // 转为字符串
resolve(res)
}
reader.readAsBinaryString(blob) // 将文件读取为二进制字符串
})
}
</script>
</body>
</html>
三、总结
通过文件头信息,我们除了可以判断文件的类型,还可以读取文件相关的元信息,比如图片的尺寸、位深度、色彩类型和压缩算法等,只是这些信息所在的位置不一样。
按照以上方式,大家同样可以判断其他格式的文件,常用文件的文件头[1]。如果你还嫌麻烦,可以使用现成的第三库来实现这个功能,比如 file-type[2] 这个库,有兴趣的同学可以试一试。
如果觉得有用,记得点个赞呦!听说喜欢点赞的你将在年底有一个完美的收官!
Demo地址: https://github.com/frontend-afei/upload-strict-demo
参考资料
[1]
常用文件的文件头: https://www.jianshu.com/p/e7bf459e5604
[2]
file-type: https://github.com/sindresorhus/file-type#readme
- 上一篇: 如果iconfont停止服务了,我们怎么办
- 下一篇: 一次bug意识到了自己还只是个初级程序员
猜你喜欢
- 2024-10-10 让编辑器支持word的复制黏贴,支持截屏的黏贴
- 2024-10-10 大文件上传优化(切片、断点续传、秒传)
- 2024-10-10 你知道前端对图片的处理方式吗(前端实现图片编辑)
- 2024-10-10 JavaScript异步图像上传(javascript 异步操作)
- 2024-10-10 javascript对文件和进制操作的一些方法汇总
- 2024-10-10 Node + H5 实现大文件分片上传、断点续传
- 2024-10-10 input上传图片并压缩(vue,前端,js)
- 2024-10-10 leaflet地图截图批量导出(leaflet地图旋转)
- 2024-10-10 Spring WebSocket传递多媒体消息(websocket springmvc)
- 2024-10-10 JS上传文件判断文件类型(js如何判断文件是否存在)
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)