Skip to content

Commit fb3269f

Browse files
Merge pull request #66312 from nextcloud/backport/66285/stable33
[stable33] fix(theming): only accept canonical svg paths for themed icons
2 parents eab4303 + 43b57f5 commit fb3269f

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

‎apps/theming/lib/Controller/IconController.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public function __construct(
5555
#[NoCSRFRequired]
5656
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
5757
public function getThemedIcon(string $app, string $image): Response {
58+
// The cache key is built from the raw path, so only canonical svg paths are
59+
// accepted to ensure every cached file belongs to exactly one icon.
60+
if (preg_match('/^([a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\/)*[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.svg$/D', $image) !== 1) {
61+
return new NotFoundResponse();
62+
}
63+
5864
if ($app !== 'core' && !$this->appManager->isEnabledForUser($app)) {
5965
$app = 'core';
6066
$image = 'favicon.png';

‎apps/theming/tests/Controller/IconControllerTest.php‎

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use OCP\AppFramework\Http;
1818
use OCP\AppFramework\Http\DataDisplayResponse;
1919
use OCP\AppFramework\Http\FileDisplayResponse;
20+
use OCP\AppFramework\Http\NotFoundResponse;
2021
use OCP\AppFramework\Utility\ITimeFactory;
2122
use OCP\Files\File;
2223
use OCP\Files\NotFoundException;
@@ -73,15 +74,52 @@ private function iconFileMock($filename, $data): SimpleFile {
7374
return new SimpleFile($icon);
7475
}
7576

76-
public function testGetThemedIcon(): void {
77-
$file = $this->iconFileMock('icon-core-filetypes_folder.svg', 'filecontent');
77+
public static function dataGetThemedIcon(): array {
78+
return [
79+
['filetypes/folder.svg', 'icon-core-filetypes_folder.svg'],
80+
['filetypes/x-office-document.svg', 'icon-core-filetypes_x-office-document.svg'],
81+
['actions/add.svg', 'icon-core-actions_add.svg'],
82+
['app_dark.svg', 'icon-core-app_dark.svg'],
83+
['logo/logo.v2.svg', 'icon-core-logo_logo.v2.svg'],
84+
];
85+
}
86+
87+
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'dataGetThemedIcon')]
88+
public function testGetThemedIcon(string $image, string $cacheKey): void {
89+
$file = $this->iconFileMock($cacheKey, 'filecontent');
7890
$this->imageManager->expects($this->once())
7991
->method('getCachedImage')
80-
->with('icon-core-filetypes_folder.svg')
92+
->with($cacheKey)
8193
->willReturn($file);
8294
$expected = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => 'image/svg+xml']);
8395
$expected->cacheFor(86400, false, true);
84-
$this->assertEquals($expected, $this->iconController->getThemedIcon('core', 'filetypes/folder.svg'));
96+
$this->assertEquals($expected, $this->iconController->getThemedIcon('core', $image));
97+
}
98+
99+
public static function dataGetThemedIconInvalidImage(): array {
100+
return [
101+
'not an svg' => ['background/eduardo-neves-pedra-azul.jpg'],
102+
'png' => ['favicon.png'],
103+
'missing extension' => ['filetypes/folder'],
104+
'double dot' => ['filetypes/fol..der.svg'],
105+
'dot segment' => ['filetypes/./folder.svg'],
106+
'parent segment' => ['filetypes/../filetypes/folder.svg'],
107+
'leading slash' => ['/filetypes/folder.svg'],
108+
'double slash' => ['filetypes//folder.svg'],
109+
'trailing newline' => ["filetypes/folder.svg\n"],
110+
'extension only' => ['.svg'],
111+
];
112+
}
113+
114+
#[\PHPUnit\Framework\Attributes\DataProvider(methodName: 'dataGetThemedIconInvalidImage')]
115+
public function testGetThemedIconInvalidImage(string $image): void {
116+
$this->imageManager->expects($this->never())
117+
->method('getCachedImage');
118+
$this->imageManager->expects($this->never())
119+
->method('setCachedImage');
120+
$this->iconBuilder->expects($this->never())
121+
->method('colorSvg');
122+
$this->assertInstanceOf(NotFoundResponse::class, $this->iconController->getThemedIcon('core', $image));
85123
}
86124

87125
public function testGetFaviconDefault(): void {

0 commit comments

Comments
 (0)