Skip to content

Commit 491fee9

Browse files
committed
feat(previews): add file signature check before opening files
Signed-off-by: Benjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>
1 parent 2c91898 commit 491fee9

10 files changed

Lines changed: 119 additions & 4 deletions

File tree

lib/private/Preview/Bitmap.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ abstract class Bitmap extends ProviderV2 {
4949
*/
5050
abstract protected function getAllowedMimeTypes(): string;
5151

52+
/**
53+
* @return list<string>
54+
*/
55+
abstract protected function getMagicStrings(): array;
56+
57+
abstract protected function getImagickFormatHint(): string;
58+
5259
/**
5360
* {@inheritDoc}
5461
*/
@@ -104,15 +111,19 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
104111
private function getResizedPreview($tmpPath, $maxX, $maxY) {
105112
$bp = new Imagick();
106113

114+
if (!$this->isMagicStringSupported($tmpPath)) {
115+
throw new \Exception('Invalid image type: magic string not recognized');
116+
}
117+
107118
// Validate mime type
108-
$bp->pingImage($tmpPath . '[0]');
119+
$bp->pingImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]');
109120
$mimeType = $bp->getImageMimeType();
110121
if (!preg_match($this->getAllowedMimeTypes(), $mimeType)) {
111122
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
112123
}
113124

114125
// Layer 0 contains either the bitmap or a flat representation of all vector layers
115-
$bp->readImage($tmpPath . '[0]');
126+
$bp->readImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]');
116127

117128
$bp = $this->resize($bp, $maxX, $maxY);
118129

@@ -121,6 +132,22 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) {
121132
return $bp;
122133
}
123134

135+
private function isMagicStringSupported(string $filepath): bool {
136+
$signatures = $this->getMagicStrings();
137+
if (empty($signatures)) {
138+
return true;
139+
}
140+
$length = array_reduce($signatures, static fn (int $carry, string $signature) => max($carry, strlen($signature)), 0);
141+
$firstBytes = file_get_contents($filepath, false, null, 0, $length);
142+
foreach ($signatures as $signature) {
143+
if (str_starts_with($firstBytes, $signature)) {
144+
return true;
145+
}
146+
}
147+
148+
return false;
149+
}
150+
124151
/**
125152
* Returns a resized \Imagick object
126153
*

lib/private/Preview/Font.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,17 @@ public function getMimeType(): string {
3737
protected function getAllowedMimeTypes(): string {
3838
return '/(application|image)\/(?:font-sfnt|x-font|x-otf|x-ttf|x-pfb$)/';
3939
}
40+
41+
#[\Override]
42+
protected function getMagicStrings(): array {
43+
return [
44+
"\x00\x01\x00\x00\x00", // TTF
45+
'OTTO', // OTF
46+
];
47+
}
48+
49+
#[\Override]
50+
protected function getImagickFormatHint(): string {
51+
return 'ttf';
52+
}
4053
}

lib/private/Preview/HEIC.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) {
116116

117117
// Some HEIC files just contain (or at least are identified as) other formats
118118
// like JPEG. We just need to check if the image is safe to process.
119-
$bp->pingImage($tmpPath . '[0]');
119+
$bp->pingImage('heic:' . $tmpPath . '[0]');
120120
$mimeType = $bp->getImageMimeType();
121121
if (!preg_match('/^image\/(x-)?(png|jpeg|gif|bmp|tiff|webp|hei(f|c)|avif)$/', $mimeType)) {
122122
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
123123
}
124124

125125
// Layer 0 contains either the bitmap or a flat representation of all vector layers
126-
$bp->readImage($tmpPath . '[0]');
126+
$bp->readImage('heic:' . $tmpPath . '[0]');
127127

128128
// Fix orientation from EXIF
129129
$bp->autoOrient();

lib/private/Preview/Illustrator.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,14 @@ public function getMimeType(): string {
3838
protected function getAllowedMimeTypes(): string {
3939
return '/application\/(illustrator|pdf)/';
4040
}
41+
42+
#[\Override]
43+
protected function getMagicStrings(): array {
44+
return ["\x25\x50\x44\x46"];
45+
}
46+
47+
#[\Override]
48+
protected function getImagickFormatHint(): string {
49+
return 'ai';
50+
}
4151
}

lib/private/Preview/PDF.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,14 @@ public function getMimeType(): string {
3838
protected function getAllowedMimeTypes(): string {
3939
return '/application\/pdf/';
4040
}
41+
42+
#[\Override]
43+
protected function getMagicStrings(): array {
44+
return ['%PDF-'];
45+
}
46+
47+
#[\Override]
48+
protected function getImagickFormatHint(): string {
49+
return 'pdf';
50+
}
4151
}

lib/private/Preview/Photoshop.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,14 @@ public function getMimeType(): string {
3838
protected function getAllowedMimeTypes(): string {
3939
return '/(application|image)\/(x-photoshop|x-psd)/';
4040
}
41+
42+
#[\Override]
43+
protected function getMagicStrings(): array {
44+
return ['8BPS'];
45+
}
46+
47+
#[\Override]
48+
protected function getImagickFormatHint(): string {
49+
return 'psd';
50+
}
4151
}

lib/private/Preview/Postscript.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,14 @@ public function getMimeType(): string {
3838
protected function getAllowedMimeTypes(): string {
3939
return '/application\/postscript/';
4040
}
41+
42+
#[\Override]
43+
protected function getMagicStrings(): array {
44+
return ['%!PS'];
45+
}
46+
47+
#[\Override]
48+
protected function getImagickFormatHint(): string {
49+
return 'ps';
50+
}
4151
}

lib/private/Preview/SGI.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,14 @@ public function getMimeType(): string {
3737
protected function getAllowedMimeTypes(): string {
3838
return '/image\/(x-)?sgi/';
3939
}
40+
41+
#[\Override]
42+
protected function getMagicStrings(): array {
43+
return ["\x01\xDA"];
44+
}
45+
46+
#[\Override]
47+
protected function getImagickFormatHint(): string {
48+
return 'sgi';
49+
}
4050
}

lib/private/Preview/TGA.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,14 @@ public function getMimeType(): string {
3737
protected function getAllowedMimeTypes(): string {
3838
return '/image\/(x-)?t(ar)?ga/';
3939
}
40+
41+
#[\Override]
42+
protected function getMagicStrings(): array {
43+
return [];
44+
}
45+
46+
#[\Override]
47+
protected function getImagickFormatHint(): string {
48+
return 'tga';
49+
}
4050
}

lib/private/Preview/TIFF.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,19 @@ public function getMimeType(): string {
3838
protected function getAllowedMimeTypes(): string {
3939
return '/image\/tiff/';
4040
}
41+
42+
#[\Override]
43+
protected function getMagicStrings(): array {
44+
return [
45+
"II*\x00",
46+
"MM\x00*",
47+
"II+\x00",
48+
"MM\x00+",
49+
];
50+
}
51+
52+
#[\Override]
53+
protected function getImagickFormatHint(): string {
54+
return 'tiff';
55+
}
4156
}

0 commit comments

Comments
 (0)