accept属性仅控制文件选择框的默认过滤,非上传安全机制;必须双写mime类型与扩展名且无空格;前端需校验文件扩展名和magic bytes;服务端必须基于二进制流校验真实mime及扩展名一致性。

accept 属性只影响文件选择框,不是上传过滤器
它不会阻止用户上传非法文件,只是让浏览器在弹出对话框时默认隐藏不匹配的类型。用户点“所有文件”、拖拽、粘贴、删掉 DOM 中的 accept 属性,照样能选 .exe、.html 甚至 .php。iOS Safari 可能因此禁用相册入口;华为 EMUI、小米 MIUI 的 WebView 常直接忽略该属性——这不是 bug,是设计如此。
常见错误现象包括:accept="image/*" 下用户仍传了 shell.php;设了 multiple 后用户一次勾选 .pdf 和 .zip,event.target.files 里两个都在。
accept 值必须 MIME + 扩展名双写,且逗号后不能有空格
单靠 MIME 类型或单靠扩展名都容易失效。必须双写,且格式严格:
-
accept="application/pdf,.pdf"✅ 最小可靠组合,覆盖 Chrome/Safari/iOS -
accept="application/vnd.openxmlformats-officedocument.wordprocessingml.document,.docx"✅.docx必须带完整 MIME -
accept="image/png,image/jpeg,.png,.jpg,.jpeg"✅ 显式列出变体,避免image/pjpeg被漏 -
accept="text/plain,.csv"✅ 无空格,旧版 Safari 才不丢整个属性 -
accept="image/*,.png"❌ Safari 可能丢掉.png -
accept=".pdf, .docx"❌ 空格导致 Safari 解析失败
前端 JS 必须校验 file.name 和 magic bytes,别信 file.type
file.type 是浏览器推测值,可为空、可伪造:把 malware.exe 改名成 report.jpg,file.type 就可能变成 image/jpeg,但内容仍是可执行文件。
真正轻量可靠的校验要分两步:
- 先用
file.name.toLowerCase().endsWith('.pdf')或正则/\.(pdf|docx)$/i快速过滤明显违规扩展名 - 再用
FileReader读取前 4 字节比对 magic bytes:new Uint8Array(await file.slice(0, 4).arrayBuffer()) - Pdf 头必须是
[0x25, 0x50, 0x44, 0x46](即%PDF),PNG 是[0x89, 0x50, 0x4E, 0x47] - 设了
multiple就必须遍历Array.from(event.target.files)每一项,不能只检查第一个 - 校验失败时立刻执行
e.target.value = "",别等submit触发
后端才是唯一可信边界,必须基于二进制流校验
所有前端限制都可被绕过:curl、Postman、DevTools 修改 DOM、拖拽 Drop 区域……服务端收到的 Content-Type 请求头、req.file.mimetype(Node.js)或 $_FILES['file']['type'](PHP)全不可信。
真实校验必须基于二进制流:
- 用
file-type(Node.js)或python-magic(Python)读取文件头,获取真实 MIME - 检查扩展名是否在白名单中,且与真实 MIME 逻辑一致(例:
.xlsx文件头必须是PK\x03\x04) - 保存时剥离原始扩展名,由服务端根据检测结果附加安全后缀
- 设置服务器级大小上限(如 Nginx 的
client_max_body_size,Flask 的MAX_CONTENT_LENGTH)
最容易被忽略的是:即使前端 JS 用 file.type 判断,该字段由浏览器根据文件名或头部推测,不可信;Node.js 中用 file-type 库、Python 中用 python-magic 才算真正校验。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











