aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/Upload/ChunkingPlugin.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/Upload/ChunkingPlugin.php')
-rw-r--r--apps/dav/lib/Upload/ChunkingPlugin.php114
1 files changed, 114 insertions, 0 deletions
diff --git a/apps/dav/lib/Upload/ChunkingPlugin.php b/apps/dav/lib/Upload/ChunkingPlugin.php
new file mode 100644
index 00000000000..8cc8f7d6c61
--- /dev/null
+++ b/apps/dav/lib/Upload/ChunkingPlugin.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2017 ownCloud GmbH
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+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;
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+
+class ChunkingPlugin extends ServerPlugin {
+
+ /** @var Server */
+ private $server;
+ /** @var FutureFile */
+ private $sourceNode;
+
+ /**
+ * @inheritdoc
+ */
+ public function initialize(Server $server) {
+ $server->on('beforeMove', [$this, 'beforeMove']);
+ $this->server = $server;
+ }
+
+ /**
+ * @param string $sourcePath source path
+ * @param string $destination destination path
+ * @return bool|void
+ * @throws BadRequest
+ * @throws NotFound
+ */
+ public function beforeMove($sourcePath, $destination) {
+ $this->sourceNode = $this->server->tree->getNodeForPath($sourcePath);
+ if (!$this->sourceNode instanceof FutureFile) {
+ // skip handling as the source is not a chunked FutureFile
+ return;
+ }
+
+ try {
+ /** @var INode $destinationNode */
+ $destinationNode = $this->server->tree->getNodeForPath($destination);
+ if ($destinationNode instanceof Directory) {
+ throw new BadRequest("The given destination $destination is a directory.");
+ }
+ } catch (NotFound $e) {
+ // If the destination does not exist yet it's not a directory either ;)
+ }
+
+ $this->verifySize();
+ return $this->performMove($sourcePath, $destination);
+ }
+
+ /**
+ * Move handler for future file.
+ *
+ * This overrides the default move behavior to prevent Sabre
+ * to delete the target file before moving. Because deleting would
+ * lose the file id and metadata.
+ *
+ * @param string $path source path
+ * @param string $destination destination path
+ * @return bool|void false to stop handling, void to skip this handler
+ */
+ public function performMove($path, $destination) {
+ $fileExists = $this->server->tree->nodeExists($destination);
+ // do a move manually, skipping Sabre's default "delete" for existing nodes
+ try {
+ $this->server->tree->move($path, $destination);
+ } catch (Forbidden $e) {
+ $sourceNode = $this->server->tree->getNodeForPath($path);
+ if ($sourceNode instanceof FutureFile) {
+ $sourceNode->delete();
+ }
+ throw $e;
+ }
+
+ // trigger all default events (copied from CorePlugin::move)
+ $this->server->emit('afterMove', [$path, $destination]);
+ $this->server->emit('afterUnbind', [$path]);
+ $this->server->emit('afterBind', [$destination]);
+
+ $response = $this->server->httpResponse;
+ $response->setHeader('Content-Length', '0');
+ $response->setStatus($fileExists ? Http::STATUS_NO_CONTENT : Http::STATUS_CREATED);
+
+ return false;
+ }
+
+ /**
+ * @throws BadRequest
+ */
+ private function verifySize() {
+ $expectedSize = $this->server->httpRequest->getHeader('OC-Total-Length');
+ if ($expectedSize === null) {
+ return;
+ }
+ $actualSize = $this->sourceNode->getSize();
+
+ // casted to string because cast to float cause equality for non equal numbers
+ // and integer has the problem of limited size on 32 bit systems
+ if ((string)$expectedSize !== (string)$actualSize) {
+ throw new BadRequest("Chunks on server do not sum up to $expectedSize but to $actualSize bytes");
+ }
+ }
+}