From e721a2507332d6efc1abe052d007177590398628 Mon Sep 17 00:00:00 2001 From: Yanick Witschi Date: Thu, 19 Sep 2024 13:55:31 +0200 Subject: [PATCH] Ensure valid FileItem instances --- config/services.php | 2 +- src/BulkyItem/FileItem.php | 19 ++++++++-- src/BulkyItem/FileItemFactory.php | 11 ++++-- .../BulkyItem/InvalidFileItemException.php | 9 +++++ tests/BulkyItem/FileItemTest.php | 36 +++++++++++++++++++ 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/Exception/BulkyItem/InvalidFileItemException.php create mode 100644 tests/BulkyItem/FileItemTest.php diff --git a/config/services.php b/config/services.php index e3712fe..9390a61 100644 --- a/config/services.php +++ b/config/services.php @@ -62,7 +62,7 @@ $services->set(FileItemFactory::class) ->args([ - service('mime_types'), + service('mime_types')->nullOnInvalid(), ]) ; diff --git a/src/BulkyItem/FileItem.php b/src/BulkyItem/FileItem.php index dac914d..e2e0708 100644 --- a/src/BulkyItem/FileItem.php +++ b/src/BulkyItem/FileItem.php @@ -5,11 +5,14 @@ namespace Terminal42\NotificationCenterBundle\BulkyItem; use Symfony\Component\Filesystem\Filesystem; +use Terminal42\NotificationCenterBundle\Exception\BulkyItem\InvalidFileItemException; class FileItem implements BulkyItemInterface { /** * @param resource $contents + * + * @throws InvalidFileItemException */ private function __construct( private $contents, @@ -17,6 +20,13 @@ private function __construct( private readonly string $mimeType, private readonly int $size, ) { + try { + \assert('' !== $this->name, 'Name must not be empty'); + \assert('' !== $this->mimeType, 'Mime type must not be empty'); + \assert($this->size >= 0, 'File size must not be smaller than 0'); + } catch (\AssertionError $e) { + throw new InvalidFileItemException($e->getMessage(), $e->getCode(), $e); + } } public function getName(): string @@ -53,10 +63,13 @@ public static function restore($contents, array $meta): BulkyItemInterface return new self($contents, $meta['name'], $meta['type'], $meta['size']); } + /** + * @throws InvalidFileItemException + */ public static function fromPath(string $path, string $name, string $mimeType, int $size): self { if (!(new Filesystem())->exists($path)) { - throw new \InvalidArgumentException(\sprintf('The file "%s" does not exist.', $path)); + throw new InvalidFileItemException(\sprintf('The file "%s" does not exist.', $path)); } return new self(fopen($path, 'r'), $name, $mimeType, $size); @@ -64,11 +77,13 @@ public static function fromPath(string $path, string $name, string $mimeType, in /** * @param resource $resource + * + * @throws InvalidFileItemException */ public static function fromStream($resource, string $name, string $mimeType, int $size): self { if (!\is_resource($resource)) { - throw new \InvalidArgumentException('$contents must be a resource.'); + throw new InvalidFileItemException('$contents must be a resource.'); } return new self($resource, $name, $mimeType, $size); diff --git a/src/BulkyItem/FileItemFactory.php b/src/BulkyItem/FileItemFactory.php index 9d9aa7f..eab41f3 100644 --- a/src/BulkyItem/FileItemFactory.php +++ b/src/BulkyItem/FileItemFactory.php @@ -8,13 +8,17 @@ use Contao\CoreBundle\Filesystem\VirtualFilesystemInterface; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Mime\MimeTypeGuesserInterface; +use Terminal42\NotificationCenterBundle\Exception\BulkyItem\InvalidFileItemException; class FileItemFactory { - public function __construct(private readonly MimeTypeGuesserInterface $mimeTypeGuesser) + public function __construct(private readonly MimeTypeGuesserInterface|null $mimeTypeGuesser = null) { } + /** + * @throws InvalidFileItemException if the information cannot be fetched automatically (e.g. missing mime type guesser service) + */ public function createFromLocalPath(string $path): FileItem { if (!(new Filesystem())->exists($path)) { @@ -22,12 +26,15 @@ public function createFromLocalPath(string $path): FileItem } $name = basename($path); - $mimeType = (string) $this->mimeTypeGuesser->guessMimeType($path); + $mimeType = (string) $this->mimeTypeGuesser?->guessMimeType($path); $size = (int) filesize($path); return FileItem::fromPath($path, $name, $mimeType, $size); } + /** + * @throws InvalidFileItemException + */ public function createFromVfsFilesystemItem(FilesystemItem $file, VirtualFilesystemInterface $virtualFilesystem): FileItem { $stream = $virtualFilesystem->readStream($file->getPath()); diff --git a/src/Exception/BulkyItem/InvalidFileItemException.php b/src/Exception/BulkyItem/InvalidFileItemException.php new file mode 100644 index 0000000..fe84df8 --- /dev/null +++ b/src/Exception/BulkyItem/InvalidFileItemException.php @@ -0,0 +1,9 @@ +expectException(InvalidFileItemException::class); + $this->expectExceptionMessage('Name must not be empty'); + + FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', '', 'image/jpg', 0); + } + + public function testCannotCreateEmptyMimeTypeFileItem(): void + { + $this->expectException(InvalidFileItemException::class); + $this->expectExceptionMessage('Mime type must not be empty'); + + FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', 'name.jpg', '', 0); + } + + public function testCannotCreateInvalidFileSizeFileItem(): void + { + $this->expectException(InvalidFileItemException::class); + $this->expectExceptionMessage('File size must not be smaller than 0'); + + FileItem::fromPath(__DIR__.'/../Fixtures/name.jpg', 'name.jpg', 'image/jpg', -42); + } +}