登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

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/jpegimage/png 这类纯 MIME 值后,与服务端允许列表比较,最后才决定是否保存。

要点速览
  • 先检查 $_FILES['upload']['error'],成功后再读取 tmp_name
  • finfo(FILEINFO_MIME_TYPE) 判断文件内容,不把客户端的 type 当成安全结论。
  • MIME 允许列表、大小限制、随机文件名和 move_uploaded_file() 要组成一条完整链路。

先读上传临时文件,不要相信客户端 MIME

上传表单提交后,PHP 会把文件放到服务器临时目录,并在 $_FILES 中给出 nametypesizetmp_nameerror。其中 type 是浏览器提供的 MIME 提示,PHP 手册明确提醒它没有在服务端校验;真正用于判断的输入应是服务器临时文件。

PHP 上传文件中客户端元数据、服务器临时文件、finfo 和 MIME 结果的信任边界关系图
图1:上传文件的客户端元数据只能作为提示,MIME 判断应从服务器临时文件读取。

因此,顺序不要写成“看扩展名就保存”。先看错误码,再确认临时文件确实由 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)识别文件内容类型和服务端允许列表严格比较
PHP FILEINFO_MIME_TYPE 将临时文件连接到 MIME 允许列表并分出接受保存和拒绝记录的关系图
图2:MIME 结果先进入允许列表,扩展名只用于最终命名,不能替代内容判断。

示例中,$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 映射生成,而不是拿用户提交的扩展名决定是否放行。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>