本文介绍一种基于 MIME 类型检测与文件扩展名双重验证的可靠方法,解决仅依赖后缀名或 getimagesize() 导致的视频类型误判问题,确保图像、视频、文档三类文件被精准分类。
本文介绍一种基于 mime 类型检测与文件扩展名双重验证的可靠方法,解决仅依赖后缀名或 `getimagesize()` 导致的视频类型误判问题,确保图像、视频、文档三类文件被精准分类。
在 PHP 文件上传处理中,仅通过文件扩展名(如 .mp4)或 getimagesize() 判断类型存在严重缺陷:前者易被伪造,后者对非图像文件(如视频)直接返回 false 或警告,无法用于视频识别。更健壮的方案是结合
文件扩展名预过滤
与
真实 MIME 类型检测
(推荐使用 mime_content_type() 或 finfo_open()),从而实现高准确率的类型分类。
以下是一个生产就绪的分类函数示例:
✅
关键注意事项:
PHP 8.5.5
PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。
下载
❗ mime_content_type() 在 PHP 8.1+ 已标记为废弃,请优先使用 finfo_open(FILEINFO_MIME_TYPE) —— 它更准确、更安全,且不受 magic.mime 文件缺失影响;
❗ 始终先校验 is_uploaded_file() 和文件存在性,防止路径遍历或空文件攻击;
❗ 扩展名仅作初筛,
绝不可作为唯一判断依据
;真实 MIME 是内容层面的指纹;
❗ 对于文档类(尤其 Office 文件),建议额外使用 ziparchive 或专用库(如 PhpSpreadsheet)做二次结构校验,防范伪装文件;
⚠️ getimagesize() 仅适用于图像,对视频返回 false 并触发警告,
不能用于视频识别
——这是原文中逻辑错误的根本原因。
总结:构建可靠的文件类型识别机制,需坚持「扩展名快速过滤 + MIME 内容检测」双校验原则,并选用 finfo 替代过时函数。这不仅能准确区分图像、视频与文档,更能显著提升应用安全性与鲁棒性。
立即学习
“
PHP免费学习笔记(深入)
”;
function classifyUploadedFile($tmpPath, $allowedTypes = ['image', 'video', 'document']) {
if (!is_uploaded_file($tmpPath) || !file_exists($tmpPath)) {
throw new InvalidArgumentException('Invalid uploaded file path.');
}
// 获取小写扩展名
$ext = strtolower(pathinfo($tmpPath, PATHINFO_EXTENSION));
// 预定义类型映射(扩展名 + 对应 MIME 类型组)
$typeRules = [
'image' => [
'extensions' => ['gif', 'jpg', 'jpeg', 'png', 'webp', 'bmp'],
'mime_patterns' => ['image/']
],
'video' => [
'extensions' => ['mp4', 'mov', 'avi', 'mkv', 'webm', 'flv'],
'mime_patterns' => ['video/']
],
'document' => [
'extensions' => ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv'],
'mime_patterns' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument', 'text/plain', 'text/csv']
]
];
// 第一步:扩展名快速筛选(低成本)
foreach ($typeRules as $type => $rules) {
if (in_array($ext, $rules['extensions'])) {
// 第二步:MIME 类型深度验证(高可靠性)
$mime = @mime_content_type($tmpPath); // 注意:PHP 8.1+ 已弃用,建议改用 finfo
if (!$mime) {
// 回退方案:使用 finfo(更推荐、更稳定)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $tmpPath);
finfo_close($finfo);
}
// 匹配 MIME:支持前缀匹配(如 'video/')和精确匹配
$mimeMatched = false;
foreach ($rules['mime_patterns'] as $pattern) {
if (strpos($mime, $pattern) === 0 || $mime === $pattern) {
$mimeMatched = true;
break;
}
}
if ($mimeMatched && in_array($type, $allowedTypes)) {
return $type; // 成功识别
}
}
}
return 'unknown';
}
// 使用示例
if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
$type = classifyUploadedFile($_FILES['file']['tmp_name']);
switch ($type) {
case 'image':
echo "✅ 处理图像文件...";
// 例如:生成缩略图、调用 getimagesize() 安全校验
break;
case 'video':
echo "✅ 处理视频文件...";
// 例如:调用 FFmpeg 检查关键帧、提取封面
break;
case 'document':
echo "✅ 处理文档文件...";
// 例如:限制 PDF 大小、检查 Office 文档宏风险
break;
default:
throw new RuntimeException("不支持的文件类型: {$_FILES['file']['name']} (MIME: {$mime})");
}
}