Skip to content

Commit 9ff15ad

Browse files
committed
fix: validate filename when creating file from template
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
1 parent 3f3af10 commit 9ff15ad

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

lib/private/Files/Template/TemplateManager.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCP\Files\File;
1616
use OCP\Files\Folder;
1717
use OCP\Files\GenericFileException;
18+
use OCP\Files\IFilenameValidator;
1819
use OCP\Files\IRootFolder;
1920
use OCP\Files\Node;
2021
use OCP\Files\NotFoundException;
@@ -63,7 +64,8 @@ public function __construct(
6364
IPreview $previewManager,
6465
IConfig $config,
6566
IFactory $l10nFactory,
66-
LoggerInterface $logger
67+
LoggerInterface $logger,
68+
private IFilenameValidator $filenameValidator,
6769
) {
6870
$this->serverContainer = $serverContainer;
6971
$this->eventDispatcher = $eventDispatcher;
@@ -157,7 +159,9 @@ public function createFromTemplate(string $filePath, string $templateId = '', st
157159
}
158160
}
159161

160-
$targetFile = $folder->newFile(basename($filePath), ($template instanceof File ? $template->fopen('rb') : null));
162+
$filename = basename($filePath);
163+
$this->filenameValidator->validateFilename($filename);
164+
$targetFile = $folder->newFile($filename, ($template instanceof File ? $template->fopen('rb') : null));
161165

162166
$this->eventDispatcher->dispatchTyped(new FileCreatedFromTemplateEvent($template, $targetFile, $templateFields));
163167
return $this->formatFile($userFolder->get($filePath));
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace lib\Files\Template;
11+
12+
use OC\AppFramework\Bootstrap\Coordinator;
13+
use OC\AppFramework\Bootstrap\RegistrationContext;
14+
use OC\Files\FilenameValidator;
15+
use OC\Files\Template\TemplateManager;
16+
use OCP\Files\Folder;
17+
use OCP\Files\GenericFileException;
18+
use OCP\Files\IRootFolder;
19+
use OCP\Files\NotFoundException;
20+
use OCP\IConfig;
21+
use OCP\IDBConnection;
22+
use OCP\IL10N;
23+
use OCP\IPreview;
24+
use OCP\L10N\IFactory;
25+
use Psr\Log\NullLogger;
26+
use Test\TestCase;
27+
28+
class TemplateManagerTest extends TestCase {
29+
30+
private IRootFolder $rootFolder;
31+
private Coordinator $bootstrapCoordinator;
32+
33+
private TemplateManager $templateManager;
34+
35+
protected function setUp(): void {
36+
parent::setUp();
37+
38+
$l10n = $this->createMock(IL10N::class);
39+
$l10n->method('t')
40+
->willReturnCallback(fn ($string, $params) => sprintf($string, ...$params));
41+
$l10nFactory = $this->createMock(IFactory::class);
42+
$l10nFactory->method('get')
43+
->willReturn($l10n);
44+
$database = $this->createMock(IDBConnection::class);
45+
$database->method('supports4ByteText')->willReturn(true);
46+
$config = $this->createMock(IConfig::class);
47+
$logger = new NullLogger();
48+
49+
$filenameValidator = new FilenameValidator(
50+
$l10nFactory,
51+
$database,
52+
$config,
53+
$logger,
54+
);
55+
56+
$serverContainer = $this->createMock(\OCP\IServerContainer::class);
57+
$eventDispatcher = $this->createMock(\OCP\EventDispatcher\IEventDispatcher::class);
58+
$this->bootstrapCoordinator = $this->createMock(Coordinator::class);
59+
$this->bootstrapCoordinator->method('getRegistrationContext')
60+
->willReturn(new RegistrationContext($logger));
61+
$this->rootFolder = $this->createMock(IRootFolder::class);
62+
$userSession = $this->createMock(\OCP\IUserSession::class);
63+
$userManager = $this->createMock(\OCP\IUserManager::class);
64+
$previewManager = $this->createMock(IPreview::class);
65+
66+
$this->templateManager = new TemplateManager(
67+
$serverContainer,
68+
$eventDispatcher,
69+
$this->bootstrapCoordinator,
70+
$this->rootFolder,
71+
$userSession,
72+
$userManager,
73+
$previewManager,
74+
$config,
75+
$l10nFactory,
76+
$logger,
77+
$filenameValidator
78+
);
79+
}
80+
81+
public function testCreateFromTemplateShoudValidateFilename(): void {
82+
$this->expectException(GenericFileException::class);
83+
84+
$fileDirectory = '/';
85+
$filePath = $fileDirectory . str_repeat('a', 251);
86+
87+
$userFolder = $this->createMock(Folder::class);
88+
$userFolder->method('get')
89+
->willReturnCallback(function ($path) use ($filePath, $fileDirectory) {
90+
if ($path === $filePath) {
91+
throw new NotFoundException();
92+
}
93+
return $this->createMock(Folder::class);
94+
});
95+
$userFolder->method('nodeExists')
96+
->willReturnCallback(function ($path) use ($filePath, $fileDirectory) {
97+
return $path === $fileDirectory;
98+
});
99+
$this->rootFolder->method('getUserFolder')
100+
->willReturn($userFolder);
101+
102+
$this->templateManager->createFromTemplate($filePath);
103+
}
104+
}

0 commit comments

Comments
 (0)