accept属性仅提示文件选择器,非安全过滤器;须双写mime与扩展名且无空格;前端需校验扩展名及magic bytes;后端必须基于文件头校验真实类型。

accept 属性只影响文件选择器界面,不拦截上传
写了 accept=".pdf,.xlsx" 之后用户依然能选中 .exe、.html,甚至拖拽上传——这不是 bug,是所有浏览器的统一行为。iOS Safari 点“所有文件”、安卓 WebView 忽略该属性、Chrome 开发者工具里删掉 accept 属性再提交,全都能绕过。它本质是给系统文件选择器的一个提示,不是过滤器。
accept 值必须 MIME + 扩展名双写,且逗号后不能有空格
单写 accept="application/pdf" 在 iOS Safari 上大概率失效;单写 accept=".pdf" 在部分桌面 Chrome 中可能漏掉系统注册为 application/x-pdf 的 PDF 文件。真正覆盖广的写法是:
-
accept="application/pdf,.pdf"✅ -
accept="image/png,image/jpeg,.png,.jpg"✅(显式列出常见变体) -
accept="text/csv,.csv"✅ -
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,.xlsx"✅(Excel 最稳写法) -
accept="application/pdf , .pdf"❌(逗号后空格 → Safari 解析失败) -
accept="image/*,.pdf"❌(image/*会压制后续精确匹配,且在某些 Android WebView 中直接失效)
前端必须监听 change 事件做二次校验
用户点完“打开”按钮后,event.target.files 已经包含全部选中的文件对象,此时 accept 属性早已失去作用。你必须手动检查每一项:
- 别信
file.type:它为空很常见,且可被伪造(把hack.html改名成report.jpg,file.type就变成image/jpeg) - 优先用扩展名白名单:
file.name.toLowerCase().match(/\.(pdf|xlsx|csv)$/i) - 关键业务加 magic bytes 校验:用
FileReader读前 4 字节,PDF 必须以%PDF(十六进制25 50 44 46)开头,XLSX 必须是PK\x03\x04 - 用了
multiple就必须遍历:Array.from(event.target.files).forEach(checkFile),不能只看files[0]
后端才是唯一可信校验点,且必须基于文件头
前端所有限制都可被 curl、Postman、DevTools 或拖拽绕过。req.headers['content-type'] 和 req.file.mimetype(Node.js)或 $_FILES['file']['type'](PHP)全不可信。真正有效的做法是:
- 读取上传文件二进制流的前几个字节,用库识别真实 MIME(如 Node.js 的
file-type,Python 的python-magic) - 验证扩展名是否与内容一致(防伪装),例如
report.pdf实际是 ZIP,则拒绝 - 保存时剥离原始扩展名,用服务端生成的随机名 + 白名单后缀(如
abc123.pdf) - 上传目录禁止执行权限,静态资源走 CDN 或独立域名
最容易被忽略的是:移动端 WebView(尤其是华为、小米定制系统)常完全忽略 accept,而开发者还在前端日志里打点“已拦截”,结果恶意文件直通后端。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











