createMock(IL10N::class); $l10n->method('t') ->willReturnCallback(fn ($string, $params) => sprintf($string, ...$params)); $this->l10n = $this->createMock(IFactory::class); $this->l10n ->method('get') ->with('core') ->willReturn($l10n); $this->config = $this->createMock(IConfig::class); $this->logger = $this->createMock(LoggerInterface::class); } /** * @dataProvider dataValidateFilename */ public function testValidateFilename( string $filename, array $forbiddenNames, array $forbiddenBasenames, array $forbiddenExtensions, array $forbiddenCharacters, ?string $exception, ): void { /** @var FilenameValidator&MockObject */ $validator = $this->getMockBuilder(FilenameValidator::class) ->onlyMethods([ 'getForbiddenBasenames', 'getForbiddenCharacters', 'getForbiddenExtensions', 'getForbiddenFilenames', ]) ->setConstructorArgs([$this->l10n, $this->config, $this->logger]) ->getMock(); $validator->method('getForbiddenBasenames') ->willReturn($forbiddenBasenames); $validator->method('getForbiddenCharacters') ->willReturn($forbiddenCharacters); $validator->method('getForbiddenExtensions') ->willReturn($forbiddenExtensions); $validator->method('getForbiddenFilenames') ->willReturn($forbiddenNames); if ($exception !== null) { $this->expectException($exception); } else { $this->expectNotToPerformAssertions(); } $validator->validateFilename($filename); } /** * @dataProvider dataValidateFilename */ public function testIsFilenameValid( string $filename, array $forbiddenNames, array $forbiddenBasenames, array $forbiddenExtensions, array $forbiddenCharacters, ?string $exception, ): void { /** @var FilenameValidator&MockObject */ $validator = $this->getMockBuilder(FilenameValidator::class) ->onlyMethods([ 'getForbiddenBasenames', 'getForbiddenExtensions', 'getForbiddenFilenames', 'getForbiddenCharacters', ]) ->setConstructorArgs([$this->l10n, $this->config, $this->logger]) ->getMock(); $validator->method('getForbiddenBasenames') ->willReturn($forbiddenBasenames); $validator->method('getForbiddenCharacters') ->willReturn($forbiddenCharacters); $validator->method('getForbiddenExtensions') ->willReturn($forbiddenExtensions); $validator->method('getForbiddenFilenames') ->willReturn($forbiddenNames); $this->assertEquals($exception === null, $validator->isFilenameValid($filename)); } public function dataValidateFilename(): array { return [ 'valid name' => [ 'a: b.txt', ['.htaccess'], [], [], [], null ], 'valid name with some more parameters' => [ 'a: b.txt', ['.htaccess'], [], ['exe'], ['~'], null ], 'valid name checks only the full name' => [ '.htaccess.sample', ['.htaccess'], [], [], [], null ], 'forbidden name' => [ '.htaccess', ['.htaccess'], [], [], [], ReservedWordException::class ], 'forbidden name - name is case insensitive' => [ 'COM1', ['.htaccess', 'com1'], [], [], [], ReservedWordException::class ], 'forbidden basename' => [ // needed for Windows namespaces 'com1.suffix', ['.htaccess'], ['com1'], [], [], ReservedWordException::class ], 'forbidden basename for hidden files' => [ // needed for Windows namespaces '.thumbs.db', ['.htaccess'], ['.thumbs'], [], [], ReservedWordException::class ], 'invalid character' => [ 'a: b.txt', ['.htaccess'], [], [], [':'], InvalidCharacterInPathException::class ], 'invalid path' => [ '../../foo.bar', ['.htaccess'], [], [], ['/', '\\'], InvalidCharacterInPathException::class, ], 'invalid extension' => [ 'a: b.txt', ['.htaccess'], [], ['.txt'], [], InvalidPathException::class ], 'empty filename' => [ '', [], [], [], [], EmptyFileNameException::class ], 'reserved unix name "."' => [ '.', [], [], [], [], InvalidPathException::class ], 'reserved unix name ".."' => [ '..', [], [], [], [], ReservedWordException::class ], 'too long filename "."' => [ str_repeat('a', 251), [], [], [], [], FileNameTooLongException::class ], // make sure to not split the list entries as they migh contain Unicode sequences // in this example the "face in clouds" emoji contains the clouds emoji so only having clouds is ok ['🌫️.txt', ['.htaccess'], [], [], ['πŸ˜Άβ€πŸŒ«οΈ'], null], // This is the reverse: clouds are forbidden -> so is also the face in the clouds emoji ['πŸ˜Άβ€πŸŒ«οΈ.txt', ['.htaccess'], [], [], ['🌫️'], InvalidCharacterInPathException::class], ]; } /** * @dataProvider dataIsForbidden */ public function testIsForbidden(string $filename, array $forbiddenNames, array $forbiddenBasenames, bool $expected): void { /** @var FilenameValidator&MockObject */ $validator = $this->getMockBuilder(FilenameValidator::class) ->onlyMethods(['getForbiddenFilenames', 'getForbiddenBasenames']) ->setConstructorArgs([$this->l10n, $this->config, $this->logger]) ->getMock(); $validator->method('getForbiddenBasenames') ->willReturn($forbiddenBasenames); $validator->method('getForbiddenFilenames') ->willReturn($forbiddenNames); $this->assertEquals($expected, $validator->isForbidden($filename)); } public function dataIsForbidden(): array { return [ 'valid name' => [ 'a: b.txt', ['.htaccess'], [], false ], 'valid name with some more parameters' => [ 'a: b.txt', ['.htaccess'], [], false ], 'valid name as only full forbidden should be matched' => [ '.htaccess.sample', ['.htaccess'], [], false, ], 'forbidden name' => [ '.htaccess', ['.htaccess'], [], true ], 'forbidden name - name is case insensitive' => [ 'COM1', ['.htaccess', 'com1'], [], true, ], 'forbidden name - basename is checked' => [ // needed for Windows namespaces 'com1.suffix', ['.htaccess'], ['com1'], true ], 'forbidden name - basename is checked also with multiple extensions' => [ // needed for Windows namespaces 'com1.tar.gz', ['.htaccess'], ['com1'], true ], ]; } }