PHP 怎么识别上传文件的 MIME 类型
来源:17golang原创
时间:2026-09-06 10:16:01 499浏览 收藏
PHP 识别上传文件的 MIME 类型,应该读取服务器保存的临时文件,而不是直接相信浏览器传来的 $_FILES['upload']['type'] 或原始扩展名。最小写法是 new finfo(FILEINFO_MIME_TYPE),再调用 file($_FILES['upload']['tmp_name'])。得到 image/jpeg、image/png 这类纯 MIME 值后,与服务端允许列表比较,最后才决定是否保存。
- 先检查
$_FILES['upload']['error'],成功后再读取tmp_name。 - 用
finfo(FILEINFO_MIME_TYPE)判断文件内容,不把客户端的type当成安全结论。 - MIME 允许列表、大小限制、随机文件名和
move_uploaded_file()要组成一条完整链路。
先读上传临时文件,不要相信客户端 MIME
上传表单提交后,PHP 会把文件放到服务器临时目录,并在 $_FILES 中给出 name、type、size、tmp_name 和 error。其中 type 是浏览器提供的 MIME 提示,PHP 手册明确提醒它没有在服务端校验;真正用于判断的输入应是服务器临时文件。

因此,顺序不要写成“看扩展名就保存”。先看错误码,再确认临时文件确实由 PHP 上传产生,最后交给 Fileinfo。这样即使用户把一个脚本改名成 photo.jpg,文件名也不会直接改变判断依据。
用 finfo 获取纯 MIME,并和允许列表比较
FILEINFO_MIME_TYPE 返回不带字符集的 MIME 类型,适合做白名单比较;FILEINFO_MIME 可能附带字符集,例如 text/plain; charset=us-ascii,更适合展示或记录。上传接口通常需要前者。
| 字段或函数 | 用途 | 安全判断 |
|---|---|---|
$_FILES['upload']['name'] | 客户端原始文件名 | 不直接拼接到保存路径 |
$_FILES['upload']['type'] | 浏览器上报的 MIME 提示 | 不能单独作为校验结果 |
tmp_name | 服务器临时文件路径 | 交给 finfo 读取内容 |
finfo(FILEINFO_MIME_TYPE) | 识别文件内容类型 | 和服务端允许列表严格比较 |

示例中,$allowed 是接口自己的业务边界,而不是“所有被 finfo 识别出来的类型都允许”。例如头像接口只接受三种图片 MIME;PDF 上传接口应维护另一份列表,不能把同一套规则到处复用。
把类型判断放进完整上传链路
下面的例子把文件上传到当前目录的 uploads 子目录。它先处理 PHP 上传错误,再检查大小、来源、MIME 和保存结果。生产环境还应把上传目录放到不能直接执行脚本的位置,并把拒绝原因写入应用日志。
$maxBytes || !is_uploaded_file($file['tmp_name'])) {
throw new RuntimeException('文件大小或上传来源不符合要求');
}
$finfo = new finfo(FILEINFO_MIME_TYPE); // 从服务器临时文件读取内容类型。
$mime = $finfo->file($file['tmp_name']);
$extensions = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
];
if ($mime === false || !isset($extensions[$mime])) {
throw new RuntimeException('不允许的文件类型');
}
$uploadDir = __DIR__ . '/uploads';
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0750, true)) {
throw new RuntimeException('上传目录创建失败');
}
$target = $uploadDir . '/' . bin2hex(random_bytes(16)) . '.' . $extensions[$mime];
if (!move_uploaded_file($file['tmp_name'], $target)) {
throw new RuntimeException('文件保存失败');
}
echo '上传成功';
?>
这里的扩展名来自 MIME 白名单映射,不来自用户输入。随机文件名避免了原始名称中的路径片段和特殊字符;move_uploaded_file() 只负责把已经通过判断的临时文件移动到目标位置,不能替代前面的类型和大小检查。
处理 finfo 误判、空文件和扩展名陷阱
MIME 识别不是“图片安全检测”的同义词。损坏文件、极短文件或格式内部带有异常内容时,识别结果可能是 false、通用二进制类型,或者与业务预期不一致。对图片服务,必要时还要在解码阶段确认尺寸、重新编码,并限制资源消耗。
另外,UPLOAD_ERR_OK 只说明 PHP 这次上传流程成功,不代表文件类型合规;finfo 返回图片 MIME 也不意味着可以信任原始文件名。日志中建议记录请求标识、识别出的 MIME、文件大小和拒绝原因,避免记录完整临时路径或用户提交的敏感名称。
常见问题
为什么不直接用 $_FILES['upload']['type']?
它由客户端提供,只能作为展示提示。服务端应读取 tmp_name,使用 finfo 自己判断。
FILEINFO_MIME 和 FILEINFO_MIME_TYPE 有什么区别?
前者可能返回 MIME 加字符集,后者只返回 MIME 类型。做精确白名单比较时通常选择 FILEINFO_MIME_TYPE。
finfo 判断通过后还要检查扩展名吗?
可以检查,但扩展名应由服务端根据允许的 MIME 映射生成,而不是拿用户提交的扩展名决定是否放行。
-
255 收藏
-
459 收藏
-
346 收藏
-
371 收藏
-
282 收藏
-
168 收藏
-
498 收藏
-
132 收藏
-
495 收藏
-
文章 · php教程 | 7小时前 | 时区 · 后端开发 · php教程 · 日期时间 · 不可变对象 · php 时区转换 DateTimeZone DateTimeImmutable setTimezone184 收藏
-
125 收藏
-
373 收藏
-
220 收藏
-
263 收藏
-
306 收藏
-
332 收藏
-
150 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习