From cbbf4c7dba188f106b0b2118f219d4cee0e512cd 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 | 33 ++++++++++++++++++++++++++--- lib/private/Preview/Font.php | 13 ++++++++++++ lib/private/Preview/HEIC.php | 4 ++-- lib/private/Preview/Illustrator.php | 10 +++++++++ lib/private/Preview/PDF.php | 10 +++++++++ lib/private/Preview/Photoshop.php | 10 +++++++++ lib/private/Preview/Postscript.php | 10 +++++++++ lib/private/Preview/SGI.php | 10 +++++++++ lib/private/Preview/TGA.php | 10 +++++++++ lib/private/Preview/TIFF.php | 15 +++++++++++++ 10 files changed, 120 insertions(+), 5 deletions(-) diff --git a/lib/private/Preview/Bitmap.php b/lib/private/Preview/Bitmap.php index a3d5fbfd4ec2e..d1dbd6f9710d9 100644 --- a/lib/private/Preview/Bitmap.php +++ b/lib/private/Preview/Bitmap.php @@ -30,6 +30,13 @@ abstract class Bitmap extends ProviderV2 { */ abstract protected function getAllowedMimeTypes(): string; + /** + * @return list + */ + abstract protected function getMagicStrings(): array; + + abstract protected function getImagickFormatHint(): string; + /** * {@inheritDoc} */ @@ -62,7 +69,7 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage { //new bitmap image object $image = new \OCP\Image(); $image->loadFromData((string)$bp); - //check if image object is valid + // Check if image object is valid return $image->valid() ? $image : null; } @@ -85,15 +92,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); @@ -102,6 +113,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 79e537f6ffb65..237edb4a95c6e 100644 --- a/lib/private/Preview/Font.php +++ b/lib/private/Preview/Font.php @@ -22,4 +22,17 @@ public function getMimeType(): string { 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 64eb48e58dfa8..3549b118d22c3 100644 --- a/lib/private/Preview/HEIC.php +++ b/lib/private/Preview/HEIC.php @@ -97,14 +97,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 bff556a31776d..c7ba39e034988 100644 --- a/lib/private/Preview/Illustrator.php +++ b/lib/private/Preview/Illustrator.php @@ -22,4 +22,14 @@ public function getMimeType(): string { 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 9de14685925df..2e02d56018d18 100644 --- a/lib/private/Preview/PDF.php +++ b/lib/private/Preview/PDF.php @@ -22,4 +22,14 @@ public function getMimeType(): string { 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 b7209120530fa..12750ce8009f4 100644 --- a/lib/private/Preview/Photoshop.php +++ b/lib/private/Preview/Photoshop.php @@ -22,4 +22,14 @@ public function getMimeType(): string { 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 04c667926aa8d..d37648c650763 100644 --- a/lib/private/Preview/Postscript.php +++ b/lib/private/Preview/Postscript.php @@ -22,4 +22,14 @@ public function getMimeType(): string { protected function getAllowedMimeTypes(): string { return '/application\/postscript/'; } + + #[\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 06ea9c0c69a47..82075154c2dd2 100644 --- a/lib/private/Preview/SGI.php +++ b/lib/private/Preview/SGI.php @@ -20,4 +20,14 @@ public function getMimeType(): string { 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 62e5aadc2af4d..0b192111d997c 100644 --- a/lib/private/Preview/TGA.php +++ b/lib/private/Preview/TGA.php @@ -20,4 +20,14 @@ public function getMimeType(): string { 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 cd81e611d0bd6..3e2e416d4efda 100644 --- a/lib/private/Preview/TIFF.php +++ b/lib/private/Preview/TIFF.php @@ -22,4 +22,19 @@ public function getMimeType(): string { 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 eee796c149613a28a5b6e642b27df11b42c8dd7f 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 bb23ef4fd54e1..8225b1e7644d7 100644 --- a/lib/private/Preview/IMagickSupport.php +++ b/lib/private/Preview/IMagickSupport.php @@ -24,12 +24,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; }