You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

69 lines
2.2 KiB

<?php
namespace App\Support;
use Illuminate\Validation\ValidationException;
/**
* 上传文件名校验:只信任最后一个后缀,拒绝空字节与危险后缀段。
*/
final class UploadFilenameGuard
{
/** @var list<string> */
public const DENIED_EXTENSIONS = [
'php', 'phtml', 'phar', 'php3', 'php4', 'php5', 'php7', 'php8',
'pht', 'phps', 'shtml', 'cgi', 'exe', 'js', 'html', 'htm', 'svg',
];
/**
* @param list<string> $allowedExtensions
*/
public static function lastAllowedExtension(string $originalName, array $allowedExtensions): string
{
if ($originalName === '' || str_contains($originalName, "\0") || str_contains(strtolower($originalName), '%00')) {
throw ValidationException::withMessages([
'file' => ['文件名非法'],
]);
}
$normalized = str_replace('\\', '/', $originalName);
$basename = basename($normalized);
if ($basename === '' || $basename === '.' || $basename === '..') {
throw ValidationException::withMessages([
'file' => ['文件名非法'],
]);
}
$parts = explode('.', $basename);
if (count($parts) < 2) {
throw ValidationException::withMessages([
'file' => ['不支持的文件类型'],
]);
}
$last = strtolower((string) array_pop($parts));
if ($last === '' || ! in_array($last, $allowedExtensions, true)) {
throw ValidationException::withMessages([
'file' => ['不支持的文件类型'],
]);
}
if (in_array($last, self::DENIED_EXTENSIONS, true)) {
throw ValidationException::withMessages([
'file' => ['不支持的文件类型'],
]);
}
foreach ($parts as $part) {
$segment = strtolower((string) $part);
if ($segment !== '' && in_array($segment, self::DENIED_EXTENSIONS, true)) {
throw ValidationException::withMessages([
'file' => ['不支持的文件类型'],
]);
}
}
return $last;
}
}