diff --git a/lib/private/Preview/Bitmap.php b/lib/private/Preview/Bitmap.php index ffe5bc88856ce..3093092585822 100644 --- a/lib/private/Preview/Bitmap.php +++ b/lib/private/Preview/Bitmap.php @@ -38,6 +38,25 @@ */ abstract class Bitmap extends ProviderV2 { + /** + * List of MIME types that this preview provider is allowed to process. + * + * These should correspond to the MIME types *identified* by Imagemagick + * for files to be processed by this provider. These do / will not + * necessarily need to match the MIME types stored in the database + * (which are identified by IMimeTypeDetector). + * + * @return string Regular expression + */ + abstract protected function getAllowedMimeTypes(): string; + + /** + * @return list + */ + abstract protected function getMagicStrings(): array; + + abstract protected function getImagickFormatHint(): string; + /** * {@inheritDoc} */ @@ -87,12 +106,25 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { * @param int $maxY * * @return \Imagick + * + * @throws \Exception */ private function getResizedPreview($tmpPath, $maxX, $maxY) { $bp = new Imagick(); + if (!$this->isMagicStringSupported($tmpPath)) { + throw new \Exception('Invalid image type: magic string not recognized'); + } + + // Validate mime type + $bp->pingImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]'); + $mimeType = $bp->getImageMimeType(); + if (!preg_match($this->getAllowedMimeTypes(), $mimeType)) { + throw new \Exception('File mime type does not match the preview provider: ' . $mimeType); + } + // Layer 0 contains either the bitmap or a flat representation of all vector layers - $bp->readImage($tmpPath . '[0]'); + $bp->readImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]'); $bp = $this->resize($bp, $maxX, $maxY); @@ -101,6 +133,22 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) { return $bp; } + private function isMagicStringSupported(string $filepath): bool { + $signatures = $this->getMagicStrings(); + if (empty($signatures)) { + return true; + } + $length = array_reduce($signatures, static fn (int $carry, string $signature) => max($carry, strlen($signature)), 0); + $firstBytes = file_get_contents($filepath, false, null, 0, $length); + foreach ($signatures as $signature) { + if (str_starts_with($firstBytes, $signature)) { + return true; + } + } + + return false; + } + /** * Returns a resized \Imagick object * diff --git a/lib/private/Preview/Font.php b/lib/private/Preview/Font.php index baf29e1defcfa..bf5934529f6ca 100644 --- a/lib/private/Preview/Font.php +++ b/lib/private/Preview/Font.php @@ -30,4 +30,22 @@ class Font extends Bitmap { public function getMimeType(): string { return '/application\/(?:font-sfnt|x-font$)/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/(application|image)\/(?:font-sfnt|x-font|x-otf|x-ttf|x-pfb$)/'; + } + + protected function getMagicStrings(): array { + return [ + "\x00\x01\x00\x00\x00", // TTF + 'OTTO', // OTF + ]; + } + + protected function getImagickFormatHint(): string { + return 'ttf'; + } } diff --git a/lib/private/Preview/HEIC.php b/lib/private/Preview/HEIC.php index 71df98f9ac66e..ce3de1eea0b0f 100644 --- a/lib/private/Preview/HEIC.php +++ b/lib/private/Preview/HEIC.php @@ -44,7 +44,7 @@ class HEIC extends ProviderV2 { * {@inheritDoc} */ public function getMimeType(): string { - return '/image\/hei(f|c)/'; + return '/image\/(x-)?hei(f|c)/'; } /** @@ -108,12 +108,22 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { * @param int $maxY * * @return \Imagick + * + * @throws \Exception */ private function getResizedPreview($tmpPath, $maxX, $maxY) { $bp = new \Imagick(); + // Some HEIC files just contain (or at least are identified as) other formats + // like JPEG. We just need to check if the image is safe to process. + $bp->pingImage('heic:' . $tmpPath . '[0]'); + $mimeType = $bp->getImageMimeType(); + if (!preg_match('/^image\/(x-)?(png|jpeg|gif|bmp|tiff|webp|hei(f|c)|avif)$/', $mimeType)) { + throw new \Exception('File mime type does not match the preview provider: ' . $mimeType); + } + // Layer 0 contains either the bitmap or a flat representation of all vector layers - $bp->readImage($tmpPath . '[0]'); + $bp->readImage('heic:' . $tmpPath . '[0]'); // Fix orientation from EXIF $bp->autoOrient(); diff --git a/lib/private/Preview/Illustrator.php b/lib/private/Preview/Illustrator.php index 3d926c304c549..2c7deb02cb755 100644 --- a/lib/private/Preview/Illustrator.php +++ b/lib/private/Preview/Illustrator.php @@ -31,4 +31,19 @@ class Illustrator extends Bitmap { public function getMimeType(): string { return '/application\/illustrator/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/application\/(illustrator|pdf)/'; + } + + protected function getMagicStrings(): array { + return ["\x25\x50\x44\x46"]; + } + + protected function getImagickFormatHint(): string { + return 'ai'; + } } diff --git a/lib/private/Preview/PDF.php b/lib/private/Preview/PDF.php index 405fd1545f967..194f1c4ce7d7a 100644 --- a/lib/private/Preview/PDF.php +++ b/lib/private/Preview/PDF.php @@ -31,4 +31,19 @@ class PDF extends Bitmap { public function getMimeType(): string { return '/application\/pdf/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/application\/pdf/'; + } + + protected function getMagicStrings(): array { + return ['%PDF-']; + } + + protected function getImagickFormatHint(): string { + return 'pdf'; + } } diff --git a/lib/private/Preview/Photoshop.php b/lib/private/Preview/Photoshop.php index 9d79abf8191c2..9bf226a801ccb 100644 --- a/lib/private/Preview/Photoshop.php +++ b/lib/private/Preview/Photoshop.php @@ -31,4 +31,19 @@ class Photoshop extends Bitmap { public function getMimeType(): string { return '/application\/x-photoshop/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/(application|image)\/(x-photoshop|x-psd)/'; + } + + protected function getMagicStrings(): array { + return ['8BPS']; + } + + protected function getImagickFormatHint(): string { + return 'psd'; + } } diff --git a/lib/private/Preview/Postscript.php b/lib/private/Preview/Postscript.php index 06ea7fc803757..e2b6c022b639b 100644 --- a/lib/private/Preview/Postscript.php +++ b/lib/private/Preview/Postscript.php @@ -31,4 +31,19 @@ class Postscript extends Bitmap { public function getMimeType(): string { return '/application\/postscript/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/application\/postscript/'; + } + + protected function getMagicStrings(): array { + return ['%!PS']; + } + + protected function getImagickFormatHint(): string { + return 'ps'; + } } diff --git a/lib/private/Preview/SGI.php b/lib/private/Preview/SGI.php index 9f392d2252f25..a9cd9528093ca 100644 --- a/lib/private/Preview/SGI.php +++ b/lib/private/Preview/SGI.php @@ -28,6 +28,21 @@ class SGI extends Bitmap { * {@inheritDoc} */ public function getMimeType(): string { - return '/image\/sgi/'; + return '/image\/(x-)?sgi/'; + } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/image\/(x-)?sgi/'; + } + + protected function getMagicStrings(): array { + return ["\x01\xDA"]; + } + + protected function getImagickFormatHint(): string { + return 'sgi'; } } diff --git a/lib/private/Preview/TGA.php b/lib/private/Preview/TGA.php index cb591be22319f..2044c794f4820 100644 --- a/lib/private/Preview/TGA.php +++ b/lib/private/Preview/TGA.php @@ -28,6 +28,21 @@ class TGA extends Bitmap { * {@inheritDoc} */ public function getMimeType(): string { - return '/image\/t(ar)?ga/'; + return '/image\/(x-)?t(ar)?ga/'; + } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/image\/(x-)?t(ar)?ga/'; + } + + protected function getMagicStrings(): array { + return []; + } + + protected function getImagickFormatHint(): string { + return 'tga'; } } diff --git a/lib/private/Preview/TIFF.php b/lib/private/Preview/TIFF.php index 1a5d957d3ee4b..8c0d0706443de 100644 --- a/lib/private/Preview/TIFF.php +++ b/lib/private/Preview/TIFF.php @@ -31,4 +31,24 @@ class TIFF extends Bitmap { public function getMimeType(): string { return '/image\/tiff/'; } + + /** + * {@inheritDoc} + */ + protected function getAllowedMimeTypes(): string { + return '/image\/tiff/'; + } + + protected function getMagicStrings(): array { + return [ + "II*\x00", + "MM\x00*", + "II+\x00", + "MM\x00+", + ]; + } + + protected function getImagickFormatHint(): string { + return 'tiff'; + } } diff --git a/lib/private/PreviewManager.php b/lib/private/PreviewManager.php index 7469d4ced8f68..0d08ef3eba57d 100644 --- a/lib/private/PreviewManager.php +++ b/lib/private/PreviewManager.php @@ -360,8 +360,7 @@ protected function registerCoreProviders() { $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes()); // SVG, Office and Bitmap require imagick - /** @psalm-suppress TypeDoesNotContainType */ - if (false) { + if (extension_loaded('imagick')) { $checkImagick = new \Imagick(); $imagickProviders = [