aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-07-15 16:28:43 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2024-07-16 12:49:09 +0200
commit69341e4306259430533cf66fd0a76688c7c8e6ab (patch)
tree31fad47a816de299ef3a6de2bf66e6af76911133 /lib
parent465cee2da3a0ad072de20d3fa718d5f1760b2806 (diff)
downloadnextcloud-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.php19
-rw-r--r--lib/private/Files/Storage/Common.php76
-rw-r--r--lib/private/Files/View.php8
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);