accept 属性仅影响文件选择框显示,无法真正限制上传类型;必须 mime 类型与扩展名双写且逗号后无空格,前端需校验 file.name 扩展名和 magic bytes,后端必须基于文件头而非扩展名或 content-type 做最终校验。

accept 属性根本不能限制上传类型,它只影响文件选择框的默认显示项——写错值、漏组合、信 file.type,都会让你的“限制”在 iOS、安卓 WebView 或旧版 Safari 上彻底失效。
accept 值必须 MIME + 扩展名双写,逗号后不能有空格
单写 application/pdf 或单写 .pdf 都不可靠:Safari(尤其 iOS)依赖 MIME 类型,而部分安卓 WebView 只认扩展名;Windows 可能上报 application/x-pdf,浏览器不模糊匹配。
-
accept="application/pdf,.pdf"✅ 覆盖最广,iOS/Safari/Chrome/Edge 全部生效 -
accept="image/png,image/jpeg,.png,.jpg,.jpeg"✅ 显式列出变体,避免image/pjpeg被漏 -
accept="text/plain,.csv"✅ 逗号后无空格,旧版 Safari 才不会丢弃整个属性 -
accept="application/pdf , .pdf"❌ 逗号后有空格 → Safari 解析失败,等同于没写
前端 JS 必须遍历 files 列表校验,别信 file.type
file.type 是浏览器推测值,可为空、可伪造。把 malware.exe 改名成 report.jpg,file.type 就可能变成 image/jpeg,但内容仍是可执行文件。
- 优先用
file.name.toLowerCase().match(/\.(jpg|jpeg|png|pdf)$/i)做扩展名白名单比对 - 关键业务加 magic bytes 校验:用
FileReader读前 4 字节,PDF 是%PDF(十六进制25 50 44 46),PNG 是\x89PNG(89 50 4E 47) - 多文件场景下,
event.target.files里可能混着合法与非法文件,必须遍历每一项,不能只检查第一个 - 提示要具体:
"已选 2 个不支持的文件(scan.tif,data.zip)",而不是笼统说"类型错误"
后端才是唯一可信依据,且必须校验文件头而非扩展名
前端所有限制都可被绕过:curl、Postman、DevTools 修改 DOM、拖拽 Drop 区域……服务端收到的 Content-Type 请求头可伪造,req.file.mimetype(Node.js)或 $_FILES['file']['type'](PHP)都不可信。
- 必须读取文件二进制流前几个字节(magic bytes):
PDF开头必须是%PDF,XLSX是PK\x03\x04 - 推荐库:
Node.js用file-type,Python用python-magic,Java用Apache Tika - 验证扩展名是否与真实内容一致(防伪装),保存时剥离原始扩展名,用服务端生成的随机名 + 白名单后缀(如
abc123.png) - 上传目录禁止执行权限,静态资源走 CDN 或独立域名
真正容易被忽略的是:移动端 WebView(华为 EMUI、小米 MIUI)常直接忽略 accept,iOS Safari 可能因限制过严导致相册入口不可用——这些不是 bug,是设计如此。你写的 accept 在某些设备上根本没机会起作用,所以后端校验那一步,不能省,也不能弱。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











