Files
2026-09-15 10:35:56 +08:00

92 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\FileManager\Model\Security;
use Esun\FileManager\Model\Config;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\File\Mime;
class FilePolicy
{
public const BLOCKED_EXTENSIONS = [
'php', 'phtml', 'php3', 'php4', 'php5', 'phar', 'cgi', 'pl', 'sh', 'bin',
];
private const MIME_TYPES = [
'zip' => ['application/zip', 'application/x-zip', 'application/x-zip-compressed'],
'rar' => ['application/vnd.rar', 'application/x-rar', 'application/x-rar-compressed'],
'7z' => ['application/x-7z-compressed'],
'tar' => ['application/x-tar'],
'gz' => ['application/gzip', 'application/x-gzip'],
'bz2' => ['application/x-bzip2', 'application/x-bzip'],
'pdf' => ['application/pdf'],
'doc' => ['application/msword', 'application/x-ole-storage', 'application/CDFV2'],
'docx' => ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/zip'],
'xls' => ['application/vnd.ms-excel', 'application/x-ole-storage', 'application/CDFV2'],
'xlsx' => ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/zip'],
'ppt' => ['application/vnd.ms-powerpoint', 'application/x-ole-storage', 'application/CDFV2'],
'pptx' => ['application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/zip'],
'jpg' => ['image/jpeg'],
'jpeg' => ['image/jpeg'],
'png' => ['image/png'],
'gif' => ['image/gif'],
'webp' => ['image/webp'],
];
public function __construct(
private readonly Config $config,
private readonly Mime $mime
) {
}
/**
* Return extensions with an explicit MIME policy.
*
* @return string[]
*/
public static function getSupportedExtensions(): array
{
return array_keys(self::MIME_TYPES);
}
/**
* @return array{extension: string, mime_type: string}
* @throws LocalizedException
*/
public function validate(array $file): array
{
$name = (string) ($file['name'] ?? '');
$tmpName = (string) ($file['tmp_name'] ?? '');
$size = (int) ($file['size'] ?? 0);
$error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
if ($error !== UPLOAD_ERR_OK || $tmpName === '' || !is_uploaded_file($tmpName)) {
throw new LocalizedException(__('The file "%1" was not uploaded correctly.', $name));
}
if ($size <= 0 || $size > $this->config->getMaximumFileSizeBytes()) {
throw new LocalizedException(__('The file "%1" exceeds the configured size limit or is empty.', $name));
}
$extension = strtolower((string) pathinfo($name, PATHINFO_EXTENSION));
$parts = preg_split('/\./', strtolower($name), -1, PREG_SPLIT_NO_EMPTY) ?: [];
if (array_intersect($parts, self::BLOCKED_EXTENSIONS)) {
throw new LocalizedException(__('The file "%1" contains a forbidden extension.', $name));
}
if ($extension === '' || !in_array($extension, $this->config->getAllowedExtensions(), true)) {
throw new LocalizedException(__('The extension of "%1" is not allowed.', $name));
}
if (!isset(self::MIME_TYPES[$extension])) {
throw new LocalizedException(__('No secure MIME policy is defined for extension "%1".', $extension));
}
$mimeType = $this->mime->getMimeType($tmpName);
if (!in_array($mimeType, self::MIME_TYPES[$extension], true)) {
throw new LocalizedException(
__('The MIME type "%1" does not match the .%2 extension.', $mimeType, $extension)
);
}
return ['extension' => $extension, 'mime_type' => $mimeType];
}
}