Validate PSR-compatible file upload

Instead of converting the uploaded file object to an UploadedFile
instance from Symfony, because the latter is compatible with
Laravel's validation, let's re-implement the validation for the
three rules we were using.

The benefit: we can now avoid copying the uploaded file to a
temporary location just to do the wrapping.

In the next step, we will remove the temporary file and let the
uploader / Intervention Image handle the PSR stream directly.
This commit is contained in:
Franz Liedke 2020-04-09 22:45:49 +02:00
parent 085d3cdba6
commit 46ec54d52d
2 changed files with 70 additions and 17 deletions

View File

@ -10,14 +10,76 @@
namespace Flarum\User; namespace Flarum\User;
use Flarum\Foundation\AbstractValidator; use Flarum\Foundation\AbstractValidator;
use Flarum\Foundation\ValidationException;
use Psr\Http\Message\UploadedFileInterface;
use Symfony\Component\Mime\MimeTypes;
class AvatarValidator extends AbstractValidator class AvatarValidator extends AbstractValidator
{ {
protected $rules = [ /**
'avatar' => [ * Throw an exception if a model is not valid.
'required', *
'mimes:jpeg,png,bmp,gif', * @param array $attributes
'max:2048' */
] public function assertValid(array $attributes)
]; {
$this->assertFileRequired($attributes['avatar']);
$this->assertFileMimes($attributes['avatar']);
$this->assertFileSize($attributes['avatar']);
}
protected function assertFileRequired(UploadedFileInterface $file)
{
if ($file->getError() !== UPLOAD_ERR_OK) {
$this->raise('required');
}
}
protected function assertFileMimes(UploadedFileInterface $file)
{
$allowedTypes = $this->getAllowedTypes();
// Block PHP files masquerading as images
$phpExtensions = ['php', 'php3', 'php4', 'php5', 'phtml'];
$fileExtension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
if (in_array(trim(strtolower($fileExtension)), $phpExtensions)) {
$this->raise('mimes', [':values' => implode(', ', $allowedTypes)]);
}
$guessedExtension = MimeTypes::getDefault()->getExtensions($file->getClientMediaType())[0] ?? null;
if (! in_array($guessedExtension, $allowedTypes)) {
$this->raise('mimes', [':values' => implode(', ', $allowedTypes)]);
}
}
protected function assertFileSize(UploadedFileInterface $file)
{
$maxSize = $this->getMaxSize();
if ($file->getSize() / 1024 > $maxSize) {
$this->raise('max.file', [':max' => $maxSize]);
}
}
protected function raise($error, array $parameters = [])
{
$message = $this->translator->trans(
"validation.$error",
$parameters + [':attribute' => 'avatar']
);
throw new ValidationException(['avatar' => $message]);
}
protected function getMaxSize()
{
return 2048;
}
protected function getAllowedTypes()
{
return ['jpeg', 'png', 'bmp', 'gif'];
}
} }

View File

@ -18,7 +18,6 @@ use Flarum\User\Event\AvatarSaving;
use Flarum\User\UserRepository; use Flarum\User\UserRepository;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class UploadAvatarHandler class UploadAvatarHandler
{ {
@ -65,6 +64,7 @@ class UploadAvatarHandler
* @param UploadAvatar $command * @param UploadAvatar $command
* @return \Flarum\User\User * @return \Flarum\User\User
* @throws \Flarum\User\Exception\PermissionDeniedException * @throws \Flarum\User\Exception\PermissionDeniedException
* @throws \Flarum\Foundation\ValidationException
*/ */
public function handle(UploadAvatar $command) public function handle(UploadAvatar $command)
{ {
@ -82,15 +82,6 @@ class UploadAvatarHandler
$file->moveTo($tmpFile); $file->moveTo($tmpFile);
try { try {
$file = new UploadedFile(
$tmpFile,
$file->getClientFilename(),
$file->getClientMediaType(),
$file->getSize(),
$file->getError(),
true
);
$this->validator->assertValid(['avatar' => $file]); $this->validator->assertValid(['avatar' => $file]);
$image = (new ImageManager)->make($tmpFile); $image = (new ImageManager)->make($tmpFile);