92 lines
3.7 KiB
PHP
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];
|
|
}
|
|
}
|