diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-07-15 16:28:43 +0200 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2024-07-16 12:49:09 +0200 |
commit | 69341e4306259430533cf66fd0a76688c7c8e6ab (patch) | |
tree | 31fad47a816de299ef3a6de2bf66e6af76911133 /lib | |
parent | 465cee2da3a0ad072de20d3fa718d5f1760b2806 (diff) | |
download | nextcloud-server-69341e4306259430533cf66fd0a76688c7c8e6ab.tar.gz nextcloud-server-69341e4306259430533cf66fd0a76688c7c8e6ab.zip |
refactor: Migrate filename validation logic from `Storage` to `FilenameValidator`
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/private/Files/FilenameValidator.php | 19 | ||||
-rw-r--r-- | lib/private/Files/Storage/Common.php | 76 | ||||
-rw-r--r-- | lib/private/Files/View.php | 8 |
3 files changed, 35 insertions, 68 deletions
diff --git a/lib/private/Files/FilenameValidator.php b/lib/private/Files/FilenameValidator.php index 0026dfdf451..b1ce8e02b13 100644 --- a/lib/private/Files/FilenameValidator.php +++ b/lib/private/Files/FilenameValidator.php @@ -11,9 +11,11 @@ use OCP\Files\EmptyFileNameException; use OCP\Files\FileNameTooLongException; use OCP\Files\IFilenameValidator; use OCP\Files\InvalidCharacterInPathException; +use OCP\Files\InvalidDirectoryException; use OCP\Files\InvalidPathException; use OCP\Files\ReservedWordException; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IL10N; use OCP\L10N\IFactory; use Psr\Log\LoggerInterface; @@ -46,6 +48,7 @@ class FilenameValidator implements IFilenameValidator { public function __construct( IFactory $l10nFactory, + private IDBConnection $database, private IConfig $config, private LoggerInterface $logger, ) { @@ -173,8 +176,9 @@ class FilenameValidator implements IFilenameValidator { } // the special directories . and .. would cause never ending recursion + // we check the trimmed name here to ensure unexpected trimming will not cause severe issues if ($trimmed === '.' || $trimmed === '..') { - throw new ReservedWordException(); + throw new InvalidDirectoryException($this->l10n->t('Dot files are not allowed')); } // 255 characters is the limit on common file systems (ext/xfs) @@ -183,6 +187,17 @@ class FilenameValidator implements IFilenameValidator { throw new FileNameTooLongException(); } + if (!$this->database->supports4ByteText()) { + // verify database - e.g. mysql only 3-byte chars + if (preg_match('%(?: + \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 +)%xs', $filename)) { + throw new InvalidCharacterInPathException(); + } + } + if ($this->isForbidden($filename)) { throw new ReservedWordException(); } @@ -263,7 +278,7 @@ class FilenameValidator implements IFilenameValidator { * @return string[] */ private function getConfigValue(string $key, array $fallback): array { - $values = $this->config->getSystemValue($key, ['.filepart']); + $values = $this->config->getSystemValue($key, $fallback); if (!is_array($values)) { $this->logger->error('Invalid system config value for "' . $key . '" is ignored.'); $values = $fallback; diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index e613e435f03..a8f8c05ab54 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -16,14 +16,10 @@ use OC\Files\Cache\Watcher; use OC\Files\Filesystem; use OC\Files\Storage\Wrapper\Jail; use OC\Files\Storage\Wrapper\Wrapper; -use OCP\Files\EmptyFileNameException; -use OCP\Files\FileNameTooLongException; use OCP\Files\ForbiddenException; use OCP\Files\GenericFileException; -use OCP\Files\InvalidCharacterInPathException; -use OCP\Files\InvalidDirectoryException; +use OCP\Files\IFilenameValidator; use OCP\Files\InvalidPathException; -use OCP\Files\ReservedWordException; use OCP\Files\Storage\ILockingStorage; use OCP\Files\Storage\IStorage; use OCP\Files\Storage\IWriteStreamStorage; @@ -57,10 +53,9 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { protected $mountOptions = []; protected $owner = null; - /** @var ?bool */ - private $shouldLogLocks = null; - /** @var ?LoggerInterface */ - private $logger; + private ?bool $shouldLogLocks = null; + private ?LoggerInterface $logger = null; + private ?IFilenameValidator $filenameValidator = null; public function __construct($parameters) { } @@ -496,68 +491,21 @@ abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage { * @throws InvalidPathException */ public function verifyPath($path, $fileName) { - // verify empty and dot files - $trimmed = trim($fileName); - if ($trimmed === '') { - throw new EmptyFileNameException(); - } - - if (\OC\Files\Filesystem::isIgnoredDir($trimmed)) { - throw new InvalidDirectoryException(); - } - - if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) { - // verify database - e.g. mysql only 3-byte chars - if (preg_match('%(?: - \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 - | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 - | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 -)%xs', $fileName)) { - throw new InvalidCharacterInPathException(); - } - } - - // 255 characters is the limit on common file systems (ext/xfs) - // oc_filecache has a 250 char length limit for the filename - if (isset($fileName[250])) { - throw new FileNameTooLongException(); - } + $this->getFilenameValidator() + ->validateFilename($fileName); // NOTE: $path will remain unverified for now - $this->verifyPosixPath($fileName); } /** - * @param string $fileName - * @throws InvalidPathException + * Get the filename validator + * (cached for performance) */ - protected function verifyPosixPath($fileName) { - $invalidChars = \OCP\Util::getForbiddenFileNameChars(); - $this->scanForInvalidCharacters($fileName, $invalidChars); - - $fileName = trim($fileName); - $reservedNames = ['*']; - if (in_array($fileName, $reservedNames)) { - throw new ReservedWordException(); - } - } - - /** - * @param string $fileName - * @param string[] $invalidChars - * @throws InvalidPathException - */ - private function scanForInvalidCharacters(string $fileName, array $invalidChars) { - foreach ($invalidChars as $char) { - if (str_contains($fileName, $char)) { - throw new InvalidCharacterInPathException(); - } - } - - $sanitizedFileName = filter_var($fileName, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW); - if ($sanitizedFileName !== $fileName) { - throw new InvalidCharacterInPathException(); + protected function getFilenameValidator(): IFilenameValidator { + if ($this->filenameValidator === null) { + $this->filenameValidator = \OCP\Server::get(IFilenameValidator::class); } + return $this->filenameValidator; } /** diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 0150a3e117a..26d419842d6 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -716,6 +716,12 @@ class View { return false; } + try { + $this->verifyPath(dirname($target), basename($target)); + } catch (InvalidPathException) { + return false; + } + $this->lockFile($source, ILockingProvider::LOCK_SHARED, true); try { $this->lockFile($target, ILockingProvider::LOCK_SHARED, true); @@ -739,8 +745,6 @@ class View { } } if ($run) { - $this->verifyPath(dirname($target), basename($target)); - $manager = Filesystem::getMountManager(); $mount1 = $this->getMount($source); $mount2 = $this->getMount($target); |