diff options
Diffstat (limited to 'apps/dav/lib/Upload')
-rw-r--r-- | apps/dav/lib/Upload/AssemblyStream.php | 22 | ||||
-rw-r--r-- | apps/dav/lib/Upload/ChunkingPlugin.php | 3 | ||||
-rw-r--r-- | apps/dav/lib/Upload/ChunkingV2Plugin.php | 5 | ||||
-rw-r--r-- | apps/dav/lib/Upload/CleanupService.php | 20 | ||||
-rw-r--r-- | apps/dav/lib/Upload/FutureFile.php | 12 | ||||
-rw-r--r-- | apps/dav/lib/Upload/PartFile.php | 12 | ||||
-rw-r--r-- | apps/dav/lib/Upload/RootCollection.php | 24 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadAutoMkcolPlugin.php | 68 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadFile.php | 8 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadFolder.php | 23 | ||||
-rw-r--r-- | apps/dav/lib/Upload/UploadHome.php | 87 |
11 files changed, 189 insertions, 95 deletions
diff --git a/apps/dav/lib/Upload/AssemblyStream.php b/apps/dav/lib/Upload/AssemblyStream.php index 736905d01c2..642a8604b17 100644 --- a/apps/dav/lib/Upload/AssemblyStream.php +++ b/apps/dav/lib/Upload/AssemblyStream.php @@ -75,6 +75,10 @@ class AssemblyStream implements \Icewind\Streams\File { $offset = $this->size + $offset; } + if ($offset === $this->pos) { + return true; + } + if ($offset > $this->size) { return false; } @@ -95,7 +99,7 @@ class AssemblyStream implements \Icewind\Streams\File { $stream = $this->getStream($this->nodes[$nodeIndex]); $nodeOffset = $offset - $nodeStart; - if (fseek($stream, $nodeOffset) === -1) { + if ($nodeOffset > 0 && fseek($stream, $nodeOffset) === -1) { return false; } $this->currentNode = $nodeIndex; @@ -126,9 +130,14 @@ class AssemblyStream implements \Icewind\Streams\File { } } - do { + $collectedData = ''; + // read data until we either got all the data requested or there is no more stream left + while ($count > 0 && !is_null($this->currentStream)) { $data = fread($this->currentStream, $count); $read = strlen($data); + + $count -= $read; + $collectedData .= $data; $this->currentNodeRead += $read; if (feof($this->currentStream)) { @@ -145,14 +154,11 @@ class AssemblyStream implements \Icewind\Streams\File { $this->currentStream = null; } } - // if no data read, try again with the next node because - // returning empty data can make the caller think there is no more - // data left to read - } while ($read === 0 && !is_null($this->currentStream)); + } // update position - $this->pos += $read; - return $data; + $this->pos += strlen($collectedData); + return $collectedData; } /** diff --git a/apps/dav/lib/Upload/ChunkingPlugin.php b/apps/dav/lib/Upload/ChunkingPlugin.php index cf4d65168cc..8cc8f7d6c61 100644 --- a/apps/dav/lib/Upload/ChunkingPlugin.php +++ b/apps/dav/lib/Upload/ChunkingPlugin.php @@ -9,6 +9,7 @@ namespace OCA\DAV\Upload; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\Exception\Forbidden; +use OCP\AppFramework\Http; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\INode; @@ -89,7 +90,7 @@ class ChunkingPlugin extends ServerPlugin { $response = $this->server->httpResponse; $response->setHeader('Content-Length', '0'); - $response->setStatus($fileExists ? 204 : 201); + $response->setStatus($fileExists ? Http::STATUS_NO_CONTENT : Http::STATUS_CREATED); return false; } diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 846b4ea42ba..07452dc0593 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -18,6 +18,7 @@ use OC\Memcache\Redis; use OC_Hook; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; +use OCP\AppFramework\Http; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; @@ -124,7 +125,7 @@ class ChunkingV2Plugin extends ServerPlugin { self::UPLOAD_TARGET_ID => $targetFile->getId(), ], 86400); - $response->setStatus(201); + $response->setStatus(Http::STATUS_CREATED); return true; } @@ -235,7 +236,7 @@ class ChunkingV2Plugin extends ServerPlugin { $response = $this->server->httpResponse; $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $response->setHeader('Content-Length', '0'); - $response->setStatus($destinationExists ? 204 : 201); + $response->setStatus($destinationExists ? Http::STATUS_NO_CONTENT : Http::STATUS_CREATED); return false; } diff --git a/apps/dav/lib/Upload/CleanupService.php b/apps/dav/lib/Upload/CleanupService.php index 2b939941c55..ffa6bad533c 100644 --- a/apps/dav/lib/Upload/CleanupService.php +++ b/apps/dav/lib/Upload/CleanupService.php @@ -10,24 +10,18 @@ namespace OCA\DAV\Upload; use OCA\DAV\BackgroundJob\UploadCleanup; use OCP\BackgroundJob\IJobList; -use OCP\IUserSession; class CleanupService { - /** @var IUserSession */ - private $userSession; - /** @var IJobList */ - private $jobList; - - public function __construct(IUserSession $userSession, IJobList $jobList) { - $this->userSession = $userSession; - $this->jobList = $jobList; + public function __construct( + 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/FutureFile.php b/apps/dav/lib/Upload/FutureFile.php index 5ef15bd2b56..ba37c56978d 100644 --- a/apps/dav/lib/Upload/FutureFile.php +++ b/apps/dav/lib/Upload/FutureFile.php @@ -20,18 +20,14 @@ use Sabre\DAV\IFile; * @package OCA\DAV\Upload */ class FutureFile implements \Sabre\DAV\IFile { - /** @var Directory */ - private $root; - /** @var string */ - private $name; - /** * @param Directory $root * @param string $name */ - public function __construct(Directory $root, $name) { - $this->root = $root; - $this->name = $name; + public function __construct( + private Directory $root, + private $name, + ) { } /** diff --git a/apps/dav/lib/Upload/PartFile.php b/apps/dav/lib/Upload/PartFile.php index a711fe083b9..11900997a90 100644 --- a/apps/dav/lib/Upload/PartFile.php +++ b/apps/dav/lib/Upload/PartFile.php @@ -16,14 +16,10 @@ use Sabre\DAV\IFile; * but handled directly by external storage services like S3 with Multipart Upload */ class PartFile implements IFile { - /** @var Directory */ - private $root; - /** @var array */ - private $partInfo; - - public function __construct(Directory $root, array $partInfo) { - $this->root = $root; - $this->partInfo = $partInfo; + public function __construct( + private Directory $root, + private array $partInfo, + ) { } /** diff --git a/apps/dav/lib/Upload/RootCollection.php b/apps/dav/lib/Upload/RootCollection.php index 1adab40671b..cd7ab7f5e0a 100644 --- a/apps/dav/lib/Upload/RootCollection.php +++ b/apps/dav/lib/Upload/RootCollection.php @@ -9,26 +9,36 @@ 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; class RootCollection extends AbstractPrincipalCollection { - /** @var CleanupService */ - private $cleanupService; - - public function __construct(PrincipalBackend\BackendInterface $principalBackend, + public function __construct( + PrincipalBackend\BackendInterface $principalBackend, string $principalPrefix, - CleanupService $cleanupService) { + private CleanupService $cleanupService, + private IRootFolder $rootFolder, + private IUserSession $userSession, + private IManager $shareManager, + ) { parent::__construct($principalBackend, $principalPrefix); - $this->cleanupService = $cleanupService; } /** * @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/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php index ae64760e0ce..7301e855cfe 100644 --- a/apps/dav/lib/Upload/UploadFile.php +++ b/apps/dav/lib/Upload/UploadFile.php @@ -12,11 +12,9 @@ use OCA\DAV\Connector\Sabre\File; use Sabre\DAV\IFile; class UploadFile implements IFile { - /** @var File */ - private $file; - - public function __construct(File $file) { - $this->file = $file; + public function __construct( + private File $file, + ) { } public function put($data) { diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index 3f84f7a8597..8890d472f87 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -11,21 +11,18 @@ 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; class UploadFolder implements ICollection { - /** @var Directory */ - private $node; - /** @var CleanupService */ - private $cleanupService; - /** @var IStorage */ - private $storage; - - public function __construct(Directory $node, CleanupService $cleanupService, IStorage $storage) { - $this->node = $node; - $this->cleanupService = $cleanupService; - $this->storage = $storage; + public function __construct( + private Directory $node, + private CleanupService $cleanupService, + private IStorage $storage, + private string $uid, + ) { } public function createFile($name, $data = null) { @@ -66,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]; @@ -93,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 3e7e3c6c986..4042f1c4101 100644 --- a/apps/dav/lib/Upload/UploadHome.php +++ b/apps/dav/lib/Upload/UploadHome.php @@ -7,21 +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 { - /** @var array */ - private $principalInfo; - /** @var CleanupService */ - private $cleanupService; - - public function __construct(array $principalInfo, CleanupService $cleanupService) { - $this->principalInfo = $principalInfo; - $this->cleanupService = $cleanupService; + private string $uid; + private ?Folder $uploadFolder = null; + + public function __construct( + 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) { @@ -32,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()); } @@ -66,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(); } } |