From e8d545e04c45de66748d5308a0acfc569db3b16d Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Wed, 8 Jul 2026 15:58:28 +0200 Subject: [PATCH 1/2] feat(previews): add file signature check before opening files Signed-off-by: Benjamin Gaussorgues --- lib/private/Preview/Bitmap.php | 32 +++++++++++++++++++++++++---- lib/private/Preview/Font.php | 19 +++++++++++------ lib/private/Preview/HEIC.php | 4 ++-- lib/private/Preview/Illustrator.php | 16 +++++++++------ lib/private/Preview/PDF.php | 16 +++++++++------ lib/private/Preview/Photoshop.php | 16 +++++++++------ lib/private/Preview/Postscript.php | 16 +++++++++------ lib/private/Preview/SGI.php | 16 +++++++++------ lib/private/Preview/TGA.php | 16 +++++++++------ lib/private/Preview/TIFF.php | 21 +++++++++++++------ 10 files changed, 118 insertions(+), 54 deletions(-) diff --git a/lib/private/Preview/Bitmap.php b/lib/private/Preview/Bitmap.php index cbdd932159d60..f81df7bfbd37d 100644 --- a/lib/private/Preview/Bitmap.php +++ b/lib/private/Preview/Bitmap.php @@ -34,8 +34,12 @@ abstract class Bitmap extends ProviderV2 { abstract protected function getAllowedMimeTypes(): string; /** - * {@inheritDoc} + * @return list */ + abstract protected function getMagicStrings(): array; + + abstract protected function getImagickFormatHint(): string; + #[\Override] public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { $tmpPath = $this->getLocalFile($file); @@ -66,7 +70,7 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { //new bitmap image object $image = new Image(); $image->loadFromData((string)$bp); - //check if image object is valid + // Check if image object is valid return $image->valid() ? $image : null; } @@ -89,15 +93,19 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { 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($tmpPath . '[0]'); + $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); @@ -106,6 +114,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 a0c813204b798..86d3a532544d7 100644 --- a/lib/private/Preview/Font.php +++ b/lib/private/Preview/Font.php @@ -12,19 +12,26 @@ // .otf, .ttf and .pfb class Font extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/application\/(?:font-sfnt|x-font$)/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/(application|image)\/(?:font-sfnt|x-font|x-otf|x-ttf|x-pfb$)/'; } + + #[\Override] + protected function getMagicStrings(): array { + return [ + "\x00\x01\x00\x00\x00", // TTF + 'OTTO', // OTF + ]; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'ttf'; + } } diff --git a/lib/private/Preview/HEIC.php b/lib/private/Preview/HEIC.php index e56101e8e4019..76b844a6de7d2 100644 --- a/lib/private/Preview/HEIC.php +++ b/lib/private/Preview/HEIC.php @@ -102,14 +102,14 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) { // 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($tmpPath . '[0]'); + $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 2d4557a3e6d86..8a5fb0ad8bbc5 100644 --- a/lib/private/Preview/Illustrator.php +++ b/lib/private/Preview/Illustrator.php @@ -12,19 +12,23 @@ //.ai class Illustrator extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/application\/illustrator/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/application\/(illustrator|pdf)/'; } + + #[\Override] + protected function getMagicStrings(): array { + return ["\x25\x50\x44\x46"]; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'ai'; + } } diff --git a/lib/private/Preview/PDF.php b/lib/private/Preview/PDF.php index 98c9d17b8cebb..2a58246abf7b2 100644 --- a/lib/private/Preview/PDF.php +++ b/lib/private/Preview/PDF.php @@ -12,19 +12,23 @@ //.pdf class PDF extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/application\/pdf/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/application\/pdf/'; } + + #[\Override] + protected function getMagicStrings(): array { + return ['%PDF-']; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'pdf'; + } } diff --git a/lib/private/Preview/Photoshop.php b/lib/private/Preview/Photoshop.php index 8c54e603edb1e..f5d61ea74bbb7 100644 --- a/lib/private/Preview/Photoshop.php +++ b/lib/private/Preview/Photoshop.php @@ -12,19 +12,23 @@ //.psd class Photoshop extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/application\/x-photoshop/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/(application|image)\/(x-photoshop|x-psd)/'; } + + #[\Override] + protected function getMagicStrings(): array { + return ['8BPS']; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'psd'; + } } diff --git a/lib/private/Preview/Postscript.php b/lib/private/Preview/Postscript.php index f35f4fc34bb80..ecd2604ec5c83 100644 --- a/lib/private/Preview/Postscript.php +++ b/lib/private/Preview/Postscript.php @@ -12,19 +12,23 @@ //.eps class Postscript extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/application\/postscript/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/(application\/postscript|image\/x-eps)/'; } + + #[\Override] + protected function getMagicStrings(): array { + return ['%!PS']; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'ps'; + } } diff --git a/lib/private/Preview/SGI.php b/lib/private/Preview/SGI.php index ad2060ea667f2..83d78d731a472 100644 --- a/lib/private/Preview/SGI.php +++ b/lib/private/Preview/SGI.php @@ -11,19 +11,23 @@ //.sgi class SGI extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/image\/(x-)?sgi/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/image\/(x-)?sgi/'; } + + #[\Override] + protected function getMagicStrings(): array { + return ["\x01\xDA"]; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'sgi'; + } } diff --git a/lib/private/Preview/TGA.php b/lib/private/Preview/TGA.php index 034eff0b3d939..df1f645097fde 100644 --- a/lib/private/Preview/TGA.php +++ b/lib/private/Preview/TGA.php @@ -11,19 +11,23 @@ //.tga class TGA extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/image\/(x-)?t(ar)?ga/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/image\/(x-)?t(ar)?ga/'; } + + #[\Override] + protected function getMagicStrings(): array { + return []; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'tga'; + } } diff --git a/lib/private/Preview/TIFF.php b/lib/private/Preview/TIFF.php index bbf2eaaa25ff7..79dbc2ae172a4 100644 --- a/lib/private/Preview/TIFF.php +++ b/lib/private/Preview/TIFF.php @@ -12,19 +12,28 @@ //.tiff class TIFF extends Bitmap { - /** - * {@inheritDoc} - */ #[\Override] public function getMimeType(): string { return '/image\/tiff/'; } - /** - * {@inheritDoc} - */ #[\Override] protected function getAllowedMimeTypes(): string { return '/image\/tiff/'; } + + #[\Override] + protected function getMagicStrings(): array { + return [ + "II*\x00", + "MM\x00*", + "II+\x00", + "MM\x00+", + ]; + } + + #[\Override] + protected function getImagickFormatHint(): string { + return 'tiff'; + } } From 26f69a8857e8e6c75d7e91c831923081d4f2e191 Mon Sep 17 00:00:00 2001 From: Benjamin Gaussorgues Date: Tue, 28 Jul 2026 10:23:46 +0200 Subject: [PATCH 2/2] Revert "fix: Disable imagick preview providers" This reverts commit 706fe8f8179e288b43800e88f022d68357673f00. Signed-off-by: Benjamin Gaussorgues --- lib/private/Preview/IMagickSupport.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/private/Preview/IMagickSupport.php b/lib/private/Preview/IMagickSupport.php index cc32c1c3e034d..08ad28371e461 100644 --- a/lib/private/Preview/IMagickSupport.php +++ b/lib/private/Preview/IMagickSupport.php @@ -25,12 +25,10 @@ public function __construct(ICacheFactory $cacheFactory) { } public function hasExtension(): bool { - return false; return !is_null($this->imagick); } public function supportsFormat(string $format): bool { - return false; if (is_null($this->imagick)) { return false; }