Skip to content

Commit 73e8ccb

Browse files
Merge pull request #47387 from nextcloud/backport/47372/stable30
[stable30] fix: `FilenameValidator::isForbidden` should only check forbidden files
2 parents e5a14f6 + ec7675c commit 73e8ccb

3 files changed

Lines changed: 31 additions & 28 deletions

File tree

config/config.sample.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2008,8 +2008,9 @@
20082008
'updatedirectory' => '',
20092009

20102010
/**
2011-
* Block a specific file or files and disallow the upload of files
2012-
* with this name. ``.htaccess`` is blocked by default.
2011+
* Block a specific file or files and disallow the upload of files with this name.
2012+
* This blocks any access to those files (read and write).
2013+
* ``.htaccess`` is blocked by default.
20132014
*
20142015
* WARNING: USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING.
20152016
*
@@ -2021,6 +2022,7 @@
20212022

20222023
/**
20232024
* Disallow the upload of files with specific basenames.
2025+
* Matching existing files can no longer be updated and in matching folders no files can be created anymore.
20242026
*
20252027
* The basename is the name of the file without the extension,
20262028
* e.g. for "archive.tar.gz" the basename would be "archive".
@@ -2034,6 +2036,7 @@
20342036
/**
20352037
* Block characters from being used in filenames. This is useful if you
20362038
* have a filesystem or OS which does not support certain characters like windows.
2039+
* Matching existing files can no longer be updated and in matching folders no files can be created anymore.
20372040
*
20382041
* The '/' and '\' characters are always forbidden, as well as all characters in the ASCII range [0-31].
20392042
*
@@ -2046,6 +2049,7 @@
20462049

20472050
/**
20482051
* Deny extensions from being used for filenames.
2052+
* Matching existing files can no longer be updated and in matching folders no files can be created anymore.
20492053
*
20502054
* The '.part' extension is always forbidden, as this is used internally by Nextcloud.
20512055
*

lib/private/Files/FilenameValidator.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,7 @@ public function validateFilename(string $filename): void {
198198
}
199199
}
200200

201-
if ($this->isForbidden($filename)) {
202-
throw new ReservedWordException();
203-
}
201+
$this->checkForbiddenName($filename);
204202

205203
$this->checkForbiddenExtension($filename);
206204

@@ -227,18 +225,25 @@ public function isForbidden(string $path): bool {
227225
return true;
228226
}
229227

228+
// Filename is not forbidden
229+
return false;
230+
}
231+
232+
protected function checkForbiddenName($filename): void {
233+
if ($this->isForbidden($filename)) {
234+
throw new ReservedWordException($this->l10n->t('"%1$s" is a forbidden file or folder name.', [$filename]));
235+
}
236+
230237
// Check for forbidden basenames - basenames are the part of the file until the first dot
231238
// (except if the dot is the first character as this is then part of the basename "hidden files")
232239
$basename = substr($filename, 0, strpos($filename, '.', 1) ?: null);
233240
$forbiddenNames = $this->getForbiddenBasenames();
234241
if (in_array($basename, $forbiddenNames)) {
235-
return true;
242+
throw new ReservedWordException($this->l10n->t('"%1$s" is a forbidden prefix for file or folder names.', [$filename]));
236243
}
237-
238-
// Filename is not forbidden
239-
return false;
240244
}
241245

246+
242247
/**
243248
* Check if a filename contains any of the forbidden characters
244249
* @param string $filename
@@ -252,7 +257,7 @@ protected function checkForbiddenCharacters(string $filename): void {
252257

253258
foreach ($this->getForbiddenCharacters() as $char) {
254259
if (str_contains($filename, $char)) {
255-
throw new InvalidCharacterInPathException($this->l10n->t('Invalid character "%1$s" in filename', [$char]));
260+
throw new InvalidCharacterInPathException($this->l10n->t('"%1$s" is not allowed inside a file or folder name.', [$char]));
256261
}
257262
}
258263
}
@@ -268,7 +273,11 @@ protected function checkForbiddenExtension(string $filename): void {
268273
$forbiddenExtensions = $this->getForbiddenExtensions();
269274
foreach ($forbiddenExtensions as $extension) {
270275
if (str_ends_with($filename, $extension)) {
271-
throw new InvalidPathException($this->l10n->t('Invalid filename extension "%1$s"', [$extension]));
276+
if (str_starts_with($extension, '.')) {
277+
throw new InvalidPathException($this->l10n->t('"%1$s" is a forbidden file type.', [$extension]));
278+
} else {
279+
throw new InvalidPathException($this->l10n->t('Filenames must not end with "%1$s".', [$extension]));
280+
}
272281
}
273282
}
274283
}

tests/lib/Files/FilenameValidatorTest.php

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,13 @@ public function dataInvalidAsciiCharacters(): array {
252252
/**
253253
* @dataProvider dataIsForbidden
254254
*/
255-
public function testIsForbidden(string $filename, array $forbiddenNames, array $forbiddenBasenames, bool $expected): void {
255+
public function testIsForbidden(string $filename, array $forbiddenNames, bool $expected): void {
256256
/** @var FilenameValidator&MockObject */
257257
$validator = $this->getMockBuilder(FilenameValidator::class)
258-
->onlyMethods(['getForbiddenFilenames', 'getForbiddenBasenames'])
258+
->onlyMethods(['getForbiddenFilenames'])
259259
->setConstructorArgs([$this->l10n, $this->database, $this->config, $this->logger])
260260
->getMock();
261261

262-
$validator->method('getForbiddenBasenames')
263-
->willReturn($forbiddenBasenames);
264262
$validator->method('getForbiddenFilenames')
265263
->willReturn($forbiddenNames);
266264

@@ -270,27 +268,19 @@ public function testIsForbidden(string $filename, array $forbiddenNames, array $
270268
public function dataIsForbidden(): array {
271269
return [
272270
'valid name' => [
273-
'a: b.txt', ['.htaccess'], [], false
271+
'a: b.txt', ['.htaccess'], false
274272
],
275273
'valid name with some more parameters' => [
276-
'a: b.txt', ['.htaccess'], [], false
274+
'a: b.txt', ['.htaccess'], false
277275
],
278276
'valid name as only full forbidden should be matched' => [
279-
'.htaccess.sample', ['.htaccess'], [], false,
277+
'.htaccess.sample', ['.htaccess'], false,
280278
],
281279
'forbidden name' => [
282-
'.htaccess', ['.htaccess'], [], true
280+
'.htaccess', ['.htaccess'], true
283281
],
284282
'forbidden name - name is case insensitive' => [
285-
'COM1', ['.htaccess', 'com1'], [], true,
286-
],
287-
'forbidden name - basename is checked' => [
288-
// needed for Windows namespaces
289-
'com1.suffix', ['.htaccess'], ['com1'], true
290-
],
291-
'forbidden name - basename is checked also with multiple extensions' => [
292-
// needed for Windows namespaces
293-
'com1.tar.gz', ['.htaccess'], ['com1'], true
283+
'COM1', ['.htaccess', 'com1'], true,
294284
],
295285
];
296286
}

0 commit comments

Comments
 (0)