Skip to content

Commit

Permalink
Merge pull request #47583 from nextcloud/backport/47185/stable30
Browse files Browse the repository at this point in the history
[stable30] fix: Filename validation should only forbid `create` and `update`
  • Loading branch information
AndyScherzinger authored Aug 28, 2024
2 parents f014c52 + 32a8b5e commit 318a6f7
Show file tree
Hide file tree
Showing 19 changed files with 312 additions and 225 deletions.
2 changes: 1 addition & 1 deletion apps/dav/lib/Connector/Sabre/Directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N
$path = $this->path . '/' . $name;
if (is_null($info)) {
try {
$this->fileView->verifyPath($this->path, $name);
$this->fileView->verifyPath($this->path, $name, true);
$info = $this->fileView->getFileInfo($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
Expand Down
107 changes: 69 additions & 38 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
namespace OCA\DAV\Connector\Sabre;

use OC\AppFramework\Http\Request;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCP\Constants;
use OCP\Files\ForbiddenException;
use OCP\Files\IFilenameValidator;
use OCP\Files\InvalidPathException;
use OCP\Files\StorageNotAvailableException;
use OCP\FilesMetadata\Exceptions\FilesMetadataException;
use OCP\FilesMetadata\Exceptions\FilesMetadataNotFoundException;
Expand All @@ -19,6 +22,7 @@
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IFile;
Expand Down Expand Up @@ -64,33 +68,27 @@ class FilesPlugin extends ServerPlugin {

/** Reference to main server object */
private ?Server $server = null;
private Tree $tree;
private IUserSession $userSession;

/**
* Whether this is public webdav.
* If true, some returned information will be stripped off.
* @param Tree $tree
* @param IConfig $config
* @param IRequest $request
* @param IPreview $previewManager
* @param IUserSession $userSession
* @param bool $isPublic Whether this is public WebDAV. If true, some returned information will be stripped off.
* @param bool $downloadAttachment
* @return void
*/
private bool $isPublic;
private bool $downloadAttachment;
private IConfig $config;
private IRequest $request;
private IPreview $previewManager;

public function __construct(Tree $tree,
IConfig $config,
IRequest $request,
IPreview $previewManager,
IUserSession $userSession,
bool $isPublic = false,
bool $downloadAttachment = true) {
$this->tree = $tree;
$this->config = $config;
$this->request = $request;
$this->userSession = $userSession;
$this->isPublic = $isPublic;
$this->downloadAttachment = $downloadAttachment;
$this->previewManager = $previewManager;
public function __construct(
private Tree $tree,
private IConfig $config,
private IRequest $request,
private IPreview $previewManager,
private IUserSession $userSession,
private IFilenameValidator $validator,
private bool $isPublic = false,
private bool $downloadAttachment = true,
) {
}

/**
Expand Down Expand Up @@ -140,33 +138,66 @@ public function initialize(Server $server) {
}
});
$this->server->on('beforeMove', [$this, 'checkMove']);
$this->server->on('beforeCopy', [$this, 'checkCopy']);
}

/**
* Plugin that checks if a move can actually be performed.
* Plugin that checks if a copy can actually be performed.
*
* @param string $source source path
* @param string $destination destination path
* @throws Forbidden
* @throws NotFound
* @param string $target target path
* @throws NotFound If the source does not exist
* @throws InvalidPath If the target is invalid
*/
public function checkMove($source, $destination) {
public function checkCopy($source, $target): void {
$sourceNode = $this->tree->getNodeForPath($source);
if (!$sourceNode instanceof Node) {
return;
}
[$sourceDir,] = \Sabre\Uri\split($source);
[$destinationDir,] = \Sabre\Uri\split($destination);

if ($sourceDir !== $destinationDir) {
$sourceNodeFileInfo = $sourceNode->getFileInfo();
if ($sourceNodeFileInfo === null) {
throw new NotFound($source . ' does not exist');
// Ensure source exists
$sourceNodeFileInfo = $sourceNode->getFileInfo();
if ($sourceNodeFileInfo === null) {
throw new NotFound($source . ' does not exist');
}
// Ensure the target name is valid
try {
[$targetPath, $targetName] = \Sabre\Uri\split($target);
$this->validator->validateFilename($targetName);
} catch (InvalidPathException $e) {
throw new InvalidPath($e->getMessage(), false);
}
// Ensure the target path is valid
$segments = array_slice(explode('/', $targetPath), 2);
foreach ($segments as $segment) {
if ($this->validator->isFilenameValid($segment) === false) {
$l = \OCP\Server::get(IFactory::class)->get('dav');
throw new InvalidPath($l->t('Invalid target path'));
}
}
}

if (!$sourceNodeFileInfo->isDeletable()) {
throw new Forbidden($source . " cannot be deleted");
}
/**
* Plugin that checks if a move can actually be performed.
*
* @param string $source source path
* @param string $target target path
* @throws Forbidden If the source is not deletable
* @throws NotFound If the source does not exist
* @throws InvalidPath If the target name is invalid
*/
public function checkMove(string $source, string $target): void {
$sourceNode = $this->tree->getNodeForPath($source);
if (!$sourceNode instanceof Node) {
return;
}

// First check copyable (move only needs additional delete permission)
$this->checkCopy($source, $target);
// The source needs to be deletable for moving
$sourceNodeFileInfo = $sourceNode->getFileInfo();
if (!$sourceNodeFileInfo->isDeletable()) {
throw new Forbidden($source . ' cannot be deleted');
}
}

Expand Down
6 changes: 3 additions & 3 deletions apps/dav/lib/Connector/Sabre/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ public function getPath() {
* @throws \Sabre\DAV\Exception\Forbidden
*/
public function setName($name) {
// rename is only allowed if the update privilege is granted
if (!($this->info->isUpdateable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) {
// rename is only allowed if the delete privilege is granted
// (basically rename is a copy with delete of the original node)
if (!($this->info->isDeletable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) {
throw new \Sabre\DAV\Exception\Forbidden();
}

Expand All @@ -129,7 +130,6 @@ public function setName($name) {
// verify path of the target
$this->verifyPath($newPath);


if (!$this->fileView->rename($this->path, $newPath)) {
throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath);
}
Expand Down
2 changes: 2 additions & 0 deletions apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Folder;
use OCP\Files\IFilenameValidator;
use OCP\Files\Mount\IMountManager;
use OCP\IConfig;
use OCP\IDBConnection;
Expand Down Expand Up @@ -129,6 +130,7 @@ public function createServer(string $baseUri,
$this->request,
$this->previewManager,
$this->userSession,
\OCP\Server::get(IFilenameValidator::class),
false,
!$this->config->getSystemValue('debug', false)
)
Expand Down
14 changes: 10 additions & 4 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,13 @@
use OCP\AppFramework\Http\Response;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\IFilenameValidator;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IPreview;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\Profiler\IProfiler;
use OCP\SabrePluginEvent;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -236,15 +240,17 @@ public function __construct(IRequest $request, string $baseUri) {
$user = $userSession->getUser();
if ($user !== null) {
$view = \OC\Files\Filesystem::getView();
$config = \OCP\Server::get(IConfig::class);
$this->server->addPlugin(
new FilesPlugin(
$this->server->tree,
\OC::$server->getConfig(),
$config,
$this->request,
\OC::$server->getPreviewManager(),
\OC::$server->getUserSession(),
\OCP\Server::get(IPreview::class),
\OCP\Server::get(IUserSession::class),
\OCP\Server::get(IFilenameValidator::class),
false,
!\OC::$server->getConfig()->getSystemValue('debug', false)
$config->getSystemValueBool('debug', false) === false,
)
);
$this->server->addPlugin(new ChecksumUpdatePlugin());
Expand Down
Loading

0 comments on commit 318a6f7

Please sign in to comment.