Skip to content

Commit

Permalink
feat: Allow to enforce Windows compatible file and folder names
Browse files Browse the repository at this point in the history
This will:
* Deny characters forbidden on Windows
* Deny files with names which are reserved on Windows
* Deny trailing dot or space
* Deny files or folders which are not case-insensitive unique in a folder

Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed May 7, 2024
1 parent 60838bf commit 30a981b
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 286 deletions.
15 changes: 15 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -1960,6 +1960,21 @@
*/
'updatedirectory' => '',

/**
* Allow to enforce Windows compatible file and folder names.
* Nextcloud by default supports all files valid on Linux,
* but as Windows has some stricter filename rules this can lead to sync errors when using Windows clients.
*
* To enforce only Windows compatible filenames, also on the webui, set this value to ``true``.
*
* This will deny filenames with characters not valid on Windows, as well as some reserved filenames and nameing rules (no trailing dot or space).
* Additionally this will enforce files to be case in-sensitivly unique in a folder.
*
* Defaults to ``false``
*
*/
'enforce_windows_compatibility' => false,

/**
* Deny a specific file or files and disallow the upload of files
* with this name. ``.htaccess`` is blocked by default.
Expand Down
29 changes: 28 additions & 1 deletion lib/private/Files/Storage/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use OCP\Server;
Expand Down Expand Up @@ -375,7 +376,7 @@ public function getWatcher($path = '', $storage = null) {
}
if (!isset($this->watcher)) {
$this->watcher = new Watcher($storage);
$globalPolicy = \OC::$server->getConfig()->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
$globalPolicy = \OCP\Server::get(IConfig::class)->getSystemValue('filesystem_check_changes', Watcher::CHECK_NEVER);
$this->watcher->setPolicy((int)$this->getMountOption('filesystem_check_changes', $globalPolicy));
}
return $this->watcher;
Expand Down Expand Up @@ -565,6 +566,32 @@ public function verifyPath($path, $fileName) {
if (\OC\Files\Filesystem::hasFilenameInvalidCharacters($fileName)) {
throw new InvalidCharacterInPathException();
}

$config = \OCP\Server::get(IConfig::class);
if ($config->getSystemValueBool('enforce_windows_compatibility', false)) {
// Windows does not allow filenames to end with a trailing dot or space
if (str_ends_with($fileName, '.') || str_ends_with($fileName, ' ')) {
throw new InvalidCharacterInPathException('Filenames must not end with a dot or space');
}

// Windows has path namespaces so e.g. `NUL` is a reserved word,
// but `NUL.txt` or `NUL.tar.gz` is considered the same and thus also reserved.
$basename = substr($fileName, 0, strpos($fileName, '.') ?: null);
if (\OC\Files\Filesystem::isFileBlacklisted($basename)) {
throw new ReservedWordException();
}

// Some windows systems are case insensitive,
// so to guarantee files can be synced we need to enfore case insensitivity
$content = $this->getDirectoryContent(dirname($path));
$fileName = strtolower($fileName);
foreach ($content as $subPath) {
if (strtolower($subPath['name']) === $fileName) {
throw new InvalidPathException('Filename is not case insensitivly unique');
}
}
}

// NOTE: $path will remain unverified for now
}

Expand Down
Loading

0 comments on commit 30a981b

Please sign in to comment.