最稳妥的方法是先用pathinfo()提取扩展名再正则匹配,避免路径干扰和误判;正确写法为/^(jpg|jpeg|png|gif|webp)$/i,需配合MIME校验与上传目录权限控制。

直接用 preg_match() 匹配扩展名字符串最稳妥
别去正则整个文件路径,先用 pathinfo() 提取扩展名,再对纯扩展名做匹配——这样避免路径中点号干扰、大小写混乱、多点名(如 archive.tar.gz)误判。
常见错误是写 /\.jpg|png|gif$/i,它实际等价于 /\.jpg/ 或 /png/ 或 /gif$/i,导致 logo.png 被中间的 png 子串匹配成功,而 script.php.bak 也可能意外命中 php。
- 正确写法必须把多后缀包进捕获组:
/^(jpg|jpeg|png|gif|webp)$/i - 注意开头的
^和结尾的$,强制全字符串匹配 - 扩展名本身不含点,所以正则里不要带
\. - 大小写敏感?加
i修饰符比手动写[jJ][pP][gG]干净得多
pathinfo() 是提取扩展名的首选,不是备选
有人用 strrchr() 或 explode() 截取后缀,但遇到 .htaccess(无后缀)、file.tar.gz(双后缀)、image.jpeg(大小写混用)就容易出错。
pathinfo($filename, PATHINFO_EXTENSION) 内部已处理这些边界情况,返回的就是干净的扩展名小写字符串(如 jpeg),后续正则可直接喂给 preg_match()。
立即学习“PHP免费学习笔记(深入)”;
- 若传入
""或null,pathinfo()返回空字符串,preg_match()自然不匹配——无需额外判空 - Windows 路径中的反斜杠不影响
pathinfo()行为 - 不要自己拼
basename($filename, '.' . $ext),那只是删后缀,不是验证
上传场景下,扩展名验证必须和 MIME 类型分离
仅靠正则匹配扩展名防不了伪造。攻击者把恶意脚本命名为 shell.jpg.php,pathinfo() 取到的是 php,看似合法;或把 PHP 文件改成 photo.jpg,扩展名校验通过,但服务端仍可能执行。
正则扩展名检查只是第一道过滤,必须配合:
-
finfo_open(FILEINFO_MIME_TYPE)检查真实文件类型 - 上传目录禁用脚本执行(如 Apache 的
Options -ExecCGI) - 保存时重命名文件,剥离原始扩展名(如用
uniqid() . '.jpg')
否则,preg_match('/^(jpg|png|gif)$/i', 'php') 返回 1,但你已经把 Webshell 放进 webroot 了。
需要支持 .tar.gz 这类复合后缀?别硬塞进一个正则
想用单条正则同时匹配 js、css、tar.gz、zip,会写出类似 /^(js|css|(tar\.gz)|zip)$/i 的东西——但 tar\.gz 中的点是字面量,而 pathinfo() 对 archive.tar.gz 返回的是 gz,不是 tar.gz。
真要支持多级后缀,得自己解析:
- 先用
pathinfo()拿到末级扩展名(gz) - 再检查完整文件名是否以
.tar.gz结尾(str_ends_with($filename, '.tar.gz')) - 或用
explode('.', $filename)取最后两段,组合判断
试图用一条正则覆盖所有复合后缀,只会让逻辑变脆、难维护、易漏判。



















