Skip to content

Commit 214ac4c

Browse files
authored
Merge pull request #51611 from nextcloud/fix/file-name-validator-case-sensitivity
fix(IFilenameValidator): correctly handle case insensitivity
2 parents 14534e0 + 254dd85 commit 214ac4c

File tree

2 files changed

+94
-6
lines changed

2 files changed

+94
-6
lines changed

lib/private/Files/FilenameValidator.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ public function getForbiddenCharacters(): array {
127127
if (empty($this->forbiddenCharacters)) {
128128
// Get always forbidden characters
129129
$forbiddenCharacters = str_split(\OCP\Constants::FILENAME_INVALID_CHARS);
130-
if ($forbiddenCharacters === false) {
131-
$forbiddenCharacters = [];
132-
}
133130

134131
// Get admin defined invalid characters
135132
$additionalChars = $this->config->getSystemValue('forbidden_filename_characters', []);
@@ -231,7 +228,8 @@ public function isForbidden(string $path): bool {
231228
return false;
232229
}
233230

234-
protected function checkForbiddenName($filename): void {
231+
protected function checkForbiddenName(string $filename): void {
232+
$filename = mb_strtolower($filename);
235233
if ($this->isForbidden($filename)) {
236234
throw new ReservedWordException($this->l10n->t('"%1$s" is a forbidden file or folder name.', [$filename]));
237235
}
@@ -295,6 +293,6 @@ private function getConfigValue(string $key, array $fallback): array {
295293
$values = $fallback;
296294
}
297295

298-
return array_map('mb_strtolower', $values);
296+
return array_map(mb_strtolower(...), $values);
299297
}
300298
};

tests/lib/Files/FilenameValidatorTest.php

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public function dataValidateFilename(): array {
146146
// needed for Windows namespaces
147147
'com1.suffix', ['.htaccess'], ['com1'], [], [], ReservedWordException::class
148148
],
149+
'forbidden basename case insensitive' => [
150+
// needed for Windows namespaces
151+
'COM1.suffix', ['.htaccess'], ['com1'], [], [], ReservedWordException::class
152+
],
149153
'forbidden basename for hidden files' => [
150154
// needed for Windows namespaces
151155
'.thumbs.db', ['.htaccess'], ['.thumbs'], [], [], ReservedWordException::class
@@ -159,6 +163,9 @@ public function dataValidateFilename(): array {
159163
'invalid extension' => [
160164
'a: b.txt', ['.htaccess'], [], ['.txt'], [], InvalidPathException::class
161165
],
166+
'invalid extension case insensitive' => [
167+
'a: b.TXT', ['.htaccess'], [], ['.txt'], [], InvalidPathException::class
168+
],
162169
'empty filename' => [
163170
'', [], [], [], [], EmptyFileNameException::class
164171
],
@@ -199,7 +206,6 @@ public function data4ByteUnicode(): array {
199206
return [
200207
['plane 1 𐪅'],
201208
['emoji 😶‍🌫️'],
202-
203209
];
204210
}
205211

@@ -284,4 +290,88 @@ public function dataIsForbidden(): array {
284290
],
285291
];
286292
}
293+
294+
/**
295+
* @dataProvider dataGetForbiddenExtensions
296+
*/
297+
public function testGetForbiddenExtensions(array $configValue, array $expectedValue): void {
298+
$validator = new FilenameValidator($this->l10n, $this->database, $this->config, $this->logger);
299+
$this->config
300+
// only once - then cached
301+
->expects(self::once())
302+
->method('getSystemValue')
303+
->with('forbidden_filename_extensions', ['.filepart'])
304+
->willReturn($configValue);
305+
306+
self::assertEqualsCanonicalizing($expectedValue, $validator->getForbiddenExtensions());
307+
}
308+
309+
public static function dataGetForbiddenExtensions(): array {
310+
return [
311+
// default
312+
[['.filepart'], ['.filepart', '.part']],
313+
// always include .part
314+
[[], ['.part']],
315+
// handle case insensitivity
316+
[['.TXT'], ['.txt', '.part']],
317+
];
318+
}
319+
320+
/**
321+
* @dataProvider dataGetForbiddenFilenames
322+
*/
323+
public function testGetForbiddenFilenames(array $configValue, array $legacyValue, array $expectedValue): void {
324+
$validator = new FilenameValidator($this->l10n, $this->database, $this->config, $this->logger);
325+
$this->config
326+
// only once - then cached
327+
->expects(self::exactly(2))
328+
->method('getSystemValue')
329+
->willReturnMap([
330+
['forbidden_filenames', ['.htaccess'], $configValue],
331+
['blacklisted_files', [], $legacyValue],
332+
]);
333+
334+
$this->logger
335+
->expects(empty($legacyValue) ? self::never() : self::once())
336+
->method('warning');
337+
338+
self::assertEqualsCanonicalizing($expectedValue, $validator->getForbiddenFilenames());
339+
}
340+
341+
public static function dataGetForbiddenFilenames(): array {
342+
return [
343+
// default
344+
[['.htaccess'], [], ['.htaccess']],
345+
// with legacy values
346+
[['.htaccess'], ['legacy'], ['.htaccess', 'legacy']],
347+
// handle case insensitivity
348+
[['FileName', '.htaccess'], ['LegAcy'], ['.htaccess', 'filename', 'legacy']],
349+
];
350+
}
351+
352+
/**
353+
* @dataProvider dataGetForbiddenBasenames
354+
*/
355+
public function testGetForbiddenBasenames(array $configValue, array $expectedValue): void {
356+
$validator = new FilenameValidator($this->l10n, $this->database, $this->config, $this->logger);
357+
$this->config
358+
// only once - then cached
359+
->expects(self::once())
360+
->method('getSystemValue')
361+
->with('forbidden_filename_basenames', [])
362+
->willReturn($configValue);
363+
364+
self::assertEqualsCanonicalizing($expectedValue, $validator->getForbiddenBasenames());
365+
}
366+
367+
public static function dataGetForbiddenBasenames(): array {
368+
return [
369+
// default
370+
[[], []],
371+
// with values
372+
[['aux', 'com0'], ['aux', 'com0']],
373+
// handle case insensitivity
374+
[['AuX', 'COM1'], ['aux', 'com1']],
375+
];
376+
}
287377
}

0 commit comments

Comments
 (0)