diff options
Diffstat (limited to 'apps/dav/lib/Upload')
-rw-r--r-- | apps/dav/lib/Upload/CleanupService.php | 10 | ||||
-rw-r--r-- | apps/dav/lib/Upload/RootCollection.php | 14 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadAutoMkcolPlugin.php | 68 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadFolder.php | 7 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadHome.php | 79 |
5 files changed, 145 insertions, 33 deletions
diff --git a/apps/dav/lib/Upload/CleanupService.php b/apps/dav/lib/Upload/CleanupService.php index 36b75280504..ffa6bad533c 100644 --- a/apps/dav/lib/Upload/CleanupService.php +++ b/apps/dav/lib/Upload/CleanupService.php @@ -10,20 +10,18 @@ namespace OCA\DAV\Upload; use OCA\DAV\BackgroundJob\UploadCleanup; use OCP\BackgroundJob\IJobList; -use OCP\IUserSession; class CleanupService { public function __construct( - private IUserSession $userSession, private IJobList $jobList, ) { } - public function addJob(string $folder) { - $this->jobList->add(UploadCleanup::class, ['uid' => $this->userSession->getUser()->getUID(), 'folder' => $folder]); + public function addJob(string $uid, string $folder) { + $this->jobList->add(UploadCleanup::class, ['uid' => $uid, 'folder' => $folder]); } - public function removeJob(string $folder) { - $this->jobList->remove(UploadCleanup::class, ['uid' => $this->userSession->getUser()->getUID(), 'folder' => $folder]); + public function removeJob(string $uid, string $folder) { + $this->jobList->remove(UploadCleanup::class, ['uid' => $uid, 'folder' => $folder]); } } diff --git a/apps/dav/lib/Upload/RootCollection.php b/apps/dav/lib/Upload/RootCollection.php index 6b12363367a..cd7ab7f5e0a 100644 --- a/apps/dav/lib/Upload/RootCollection.php +++ b/apps/dav/lib/Upload/RootCollection.php @@ -9,6 +9,9 @@ declare(strict_types=1); */ namespace OCA\DAV\Upload; +use OCP\Files\IRootFolder; +use OCP\IUserSession; +use OCP\Share\IManager; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAVACL\PrincipalBackend; @@ -18,6 +21,9 @@ class RootCollection extends AbstractPrincipalCollection { PrincipalBackend\BackendInterface $principalBackend, string $principalPrefix, private CleanupService $cleanupService, + private IRootFolder $rootFolder, + private IUserSession $userSession, + private IManager $shareManager, ) { parent::__construct($principalBackend, $principalPrefix); } @@ -26,7 +32,13 @@ class RootCollection extends AbstractPrincipalCollection { * @inheritdoc */ public function getChildForPrincipal(array $principalInfo): UploadHome { - return new UploadHome($principalInfo, $this->cleanupService); + return new UploadHome( + $principalInfo, + $this->cleanupService, + $this->rootFolder, + $this->userSession, + $this->shareManager, + ); } /** diff --git a/apps/dav/lib/Upload/UploadAutoMkcolPlugin.php b/apps/dav/lib/Upload/UploadAutoMkcolPlugin.php new file mode 100644 index 00000000000..a7030ba1133 --- /dev/null +++ b/apps/dav/lib/Upload/UploadAutoMkcolPlugin.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Upload; + +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\ICollection; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use function Sabre\Uri\split as uriSplit; + +/** + * Class that allows automatically creating non-existing collections on file + * upload. + * + * Since this functionality is not WebDAV compliant, it needs a special + * header to be activated. + */ +class UploadAutoMkcolPlugin extends ServerPlugin { + + private Server $server; + + public function initialize(Server $server): void { + $server->on('beforeMethod:PUT', [$this, 'beforeMethod']); + $this->server = $server; + } + + /** + * @throws NotFound a node expected to exist cannot be found + */ + public function beforeMethod(RequestInterface $request, ResponseInterface $response): bool { + if ($request->getHeader('X-NC-WebDAV-Auto-Mkcol') !== '1') { + return true; + } + + [$path,] = uriSplit($request->getPath()); + + if ($this->server->tree->nodeExists($path)) { + return true; + } + + $parts = explode('/', trim($path, '/')); + $rootPath = array_shift($parts); + $node = $this->server->tree->getNodeForPath('/' . $rootPath); + + if (!($node instanceof ICollection)) { + // the root node is not a collection, let SabreDAV handle it + return true; + } + + foreach ($parts as $part) { + if (!$node->childExists($part)) { + $node->createDirectory($part); + } + + $node = $node->getChild($part); + } + + return true; + } +} diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index 8ea0591ab16..8890d472f87 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -11,6 +11,8 @@ use OC\Files\ObjectStore\ObjectStoreStorage; use OCA\DAV\Connector\Sabre\Directory; use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; use OCP\Files\Storage\IStorage; +use OCP\ICacheFactory; +use OCP\Server; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; @@ -19,6 +21,7 @@ class UploadFolder implements ICollection { private Directory $node, private CleanupService $cleanupService, private IStorage $storage, + private string $uid, ) { } @@ -60,7 +63,7 @@ class UploadFolder implements ICollection { /** @var ObjectStoreStorage $storage */ $objectStore = $this->storage->getObjectStore(); if ($objectStore instanceof IObjectStoreMultiPartUpload) { - $cache = \OC::$server->getMemCacheFactory()->createDistributed(ChunkingV2Plugin::CACHE_KEY); + $cache = Server::get(ICacheFactory::class)->createDistributed(ChunkingV2Plugin::CACHE_KEY); $uploadSession = $cache->get($this->getName()); if ($uploadSession) { $uploadId = $uploadSession[ChunkingV2Plugin::UPLOAD_ID]; @@ -87,7 +90,7 @@ class UploadFolder implements ICollection { $this->node->delete(); // Background cleanup job is not needed anymore - $this->cleanupService->removeJob($this->getName()); + $this->cleanupService->removeJob($this->uid, $this->getName()); } public function getName() { diff --git a/apps/dav/lib/Upload/UploadHome.php b/apps/dav/lib/Upload/UploadHome.php index 739c38feb30..4042f1c4101 100644 --- a/apps/dav/lib/Upload/UploadHome.php +++ b/apps/dav/lib/Upload/UploadHome.php @@ -7,17 +7,37 @@ */ namespace OCA\DAV\Upload; -use OC\Files\Filesystem; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IUserSession; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; class UploadHome implements ICollection { + private string $uid; + private ?Folder $uploadFolder = null; + public function __construct( - private array $principalInfo, - private CleanupService $cleanupService, + private readonly array $principalInfo, + private readonly CleanupService $cleanupService, + private readonly IRootFolder $rootFolder, + private readonly IUserSession $userSession, + private readonly \OCP\Share\IManager $shareManager, ) { + [$prefix, $name] = \Sabre\Uri\split($principalInfo['uri']); + if ($prefix === 'principals/shares') { + $this->uid = $this->shareManager->getShareByToken($name)->getShareOwner(); + } else { + $user = $this->userSession->getUser(); + if (!$user) { + throw new Forbidden('Not logged in'); + } + + $this->uid = $user->getUID(); + } } public function createFile($name, $data = null) { @@ -28,16 +48,26 @@ class UploadHome implements ICollection { $this->impl()->createDirectory($name); // Add a cleanup job - $this->cleanupService->addJob($name); + $this->cleanupService->addJob($this->uid, $name); } public function getChild($name): UploadFolder { - return new UploadFolder($this->impl()->getChild($name), $this->cleanupService, $this->getStorage()); + return new UploadFolder( + $this->impl()->getChild($name), + $this->cleanupService, + $this->getStorage(), + $this->uid, + ); } public function getChildren(): array { return array_map(function ($node) { - return new UploadFolder($node, $this->cleanupService, $this->getStorage()); + return new UploadFolder( + $node, + $this->cleanupService, + $this->getStorage(), + $this->uid, + ); }, $this->impl()->getChildren()); } @@ -62,28 +92,29 @@ class UploadHome implements ICollection { return $this->impl()->getLastModified(); } - /** - * @return Directory - */ - private function impl() { - $view = $this->getView(); - $rootInfo = $view->getFileInfo(''); - return new Directory($view, $rootInfo); + private function getUploadFolder(): Folder { + if ($this->uploadFolder === null) { + $path = '/' . $this->uid . '/uploads'; + try { + $folder = $this->rootFolder->get($path); + if (!$folder instanceof Folder) { + throw new \Exception('Upload folder is a file'); + } + $this->uploadFolder = $folder; + } catch (NotFoundException $e) { + $this->uploadFolder = $this->rootFolder->newFolder($path); + } + } + return $this->uploadFolder; } - private function getView() { - $rootView = new View(); - $user = \OC::$server->getUserSession()->getUser(); - Filesystem::initMountPoints($user->getUID()); - if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) { - $rootView->mkdir('/' . $user->getUID() . '/uploads'); - } - return new View('/' . $user->getUID() . '/uploads'); + private function impl(): Directory { + $folder = $this->getUploadFolder(); + $view = new View($folder->getPath()); + return new Directory($view, $folder); } private function getStorage() { - $view = $this->getView(); - $storage = $view->getFileInfo('')->getStorage(); - return $storage; + return $this->getUploadFolder()->getStorage(); } } |