Skip to content

Commit e8d545e

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

10 files changed

Lines changed: 118 additions & 54 deletions

File tree

lib/private/Preview/Bitmap.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,12 @@ abstract class Bitmap extends ProviderV2 {
3434
abstract protected function getAllowedMimeTypes(): string;
3535

3636
/**
37-
* {@inheritDoc}
37+
* @return list<string>
3838
*/
39+
abstract protected function getMagicStrings(): array;
40+
41+
abstract protected function getImagickFormatHint(): string;
42+
3943
#[\Override]
4044
public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
4145
$tmpPath = $this->getLocalFile($file);
@@ -66,7 +70,7 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
6670
//new bitmap image object
6771
$image = new Image();
6872
$image->loadFromData((string)$bp);
69-
//check if image object is valid
73+
// Check if image object is valid
7074
return $image->valid() ? $image : null;
7175
}
7276

@@ -89,15 +93,19 @@ public function getThumbnail(File $file, int $maxX, int $maxY): ?IImage {
8993
private function getResizedPreview($tmpPath, $maxX, $maxY) {
9094
$bp = new Imagick();
9195

96+
if (!$this->isMagicStringSupported($tmpPath)) {
97+
throw new \Exception('Invalid image type: magic string not recognized');
98+
}
99+
92100
// Validate mime type
93-
$bp->pingImage($tmpPath . '[0]');
101+
$bp->pingImage($this->getImagickFormatHint() . ':' . $tmpPath . '[0]');
94102
$mimeType = $bp->getImageMimeType();
95103
if (!preg_match($this->getAllowedMimeTypes(), $mimeType)) {
96104
throw new \Exception('File mime type does not match the preview provider: ' . $mimeType);
97105
}
98106

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

102110
$bp = $this->resize($bp, $maxX, $maxY);
103111

@@ -106,6 +114,22 @@ private function getResizedPreview($tmpPath, $maxX, $maxY) {
106114
return $bp;
107115
}
108116

117+
private function isMagicStringSupported(string $filepath): bool {
118+
$signatures = $this->getMagicStrings();
119+
if (empty($signatures)) {
120+
return true;
121+
}
122+
$length = array_reduce($signatures, static fn (int $carry, string $signature) => max($carry, strlen($signature)), 0);
123+
$firstBytes = file_get_contents($filepath, false, null, 0, $length);
124+
foreach ($signatures as $signature) {
125+
if (str_starts_with($firstBytes, $signature)) {
126+
return true;
127+
}
128+
}
129+
130+
return false;
131+
}
132+
109133
/**
110134
* Returns a resized \Imagick object
111135
*

lib/private/Preview/Font.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,26 @@
1212

1313
// .otf, .ttf and .pfb
1414
class Font extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/application\/(?:font-sfnt|x-font$)/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/(application|image)\/(?:font-sfnt|x-font|x-otf|x-ttf|x-pfb$)/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return [
28+
"\x00\x01\x00\x00\x00", // TTF
29+
'OTTO', // OTF
30+
];
31+
}
32+
33+
#[\Override]
34+
protected function getImagickFormatHint(): string {
35+
return 'ttf';
36+
}
3037
}

lib/private/Preview/HEIC.php

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

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

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

114114
// Fix orientation from EXIF
115115
$bp->autoOrient();

lib/private/Preview/Illustrator.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@
1212

1313
//.ai
1414
class Illustrator extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/application\/illustrator/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/application\/(illustrator|pdf)/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return ["\x25\x50\x44\x46"];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'ai';
33+
}
3034
}

lib/private/Preview/PDF.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@
1212

1313
//.pdf
1414
class PDF extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/application\/pdf/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/application\/pdf/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return ['%PDF-'];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'pdf';
33+
}
3034
}

lib/private/Preview/Photoshop.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@
1212

1313
//.psd
1414
class Photoshop extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/application\/x-photoshop/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/(application|image)\/(x-photoshop|x-psd)/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return ['8BPS'];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'psd';
33+
}
3034
}

lib/private/Preview/Postscript.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,23 @@
1212

1313
//.eps
1414
class Postscript extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/application\/postscript/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/(application\/postscript|image\/x-eps)/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return ['%!PS'];
28+
}
29+
30+
#[\Override]
31+
protected function getImagickFormatHint(): string {
32+
return 'ps';
33+
}
3034
}

lib/private/Preview/SGI.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@
1111

1212
//.sgi
1313
class SGI extends Bitmap {
14-
/**
15-
* {@inheritDoc}
16-
*/
1714
#[\Override]
1815
public function getMimeType(): string {
1916
return '/image\/(x-)?sgi/';
2017
}
2118

22-
/**
23-
* {@inheritDoc}
24-
*/
2519
#[\Override]
2620
protected function getAllowedMimeTypes(): string {
2721
return '/image\/(x-)?sgi/';
2822
}
23+
24+
#[\Override]
25+
protected function getMagicStrings(): array {
26+
return ["\x01\xDA"];
27+
}
28+
29+
#[\Override]
30+
protected function getImagickFormatHint(): string {
31+
return 'sgi';
32+
}
2933
}

lib/private/Preview/TGA.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,23 @@
1111

1212
//.tga
1313
class TGA extends Bitmap {
14-
/**
15-
* {@inheritDoc}
16-
*/
1714
#[\Override]
1815
public function getMimeType(): string {
1916
return '/image\/(x-)?t(ar)?ga/';
2017
}
2118

22-
/**
23-
* {@inheritDoc}
24-
*/
2519
#[\Override]
2620
protected function getAllowedMimeTypes(): string {
2721
return '/image\/(x-)?t(ar)?ga/';
2822
}
23+
24+
#[\Override]
25+
protected function getMagicStrings(): array {
26+
return [];
27+
}
28+
29+
#[\Override]
30+
protected function getImagickFormatHint(): string {
31+
return 'tga';
32+
}
2933
}

lib/private/Preview/TIFF.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,28 @@
1212

1313
//.tiff
1414
class TIFF extends Bitmap {
15-
/**
16-
* {@inheritDoc}
17-
*/
1815
#[\Override]
1916
public function getMimeType(): string {
2017
return '/image\/tiff/';
2118
}
2219

23-
/**
24-
* {@inheritDoc}
25-
*/
2620
#[\Override]
2721
protected function getAllowedMimeTypes(): string {
2822
return '/image\/tiff/';
2923
}
24+
25+
#[\Override]
26+
protected function getMagicStrings(): array {
27+
return [
28+
"II*\x00",
29+
"MM\x00*",
30+
"II+\x00",
31+
"MM\x00+",
32+
];
33+
}
34+
35+
#[\Override]
36+
protected function getImagickFormatHint(): string {
37+
return 'tiff';
38+
}
3039
}

0 commit comments

Comments
 (0)