aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/Upload
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/Upload')
-rw-r--r--apps/dav/lib/Upload/AssemblyStream.php22
-rw-r--r--apps/dav/lib/Upload/ChunkingPlugin.php3
-rw-r--r--apps/dav/lib/Upload/ChunkingV2Plugin.php5
-rw-r--r--apps/dav/lib/Upload/CleanupService.php20
-rw-r--r--apps/dav/lib/Upload/FutureFile.php12
-rw-r--r--apps/dav/lib/Upload/PartFile.php12
-rw-r--r--apps/dav/lib/Upload/RootCollection.php24
-rw-r--r--apps/dav/lib/Upload/UploadAutoMkcolPlugin.php68
-rw-r--r--apps/dav/lib/Upload/UploadFile.php8
-rw-r--r--apps/dav/lib/Upload/UploadFolder.php23
-rw-r--r--apps/dav/lib/Upload/UploadHome.php87
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();
}
}