From 4e070255a0d2f1659a7526a25118097bf3ba46c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 6 May 2021 18:26:42 +0200 Subject: [PATCH 01/15] First attempt to make multipartPart upload working MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/BackgroundJob/UploadCleanup.php | 5 + apps/dav/lib/Connector/Sabre/Directory.php | 1 + apps/dav/lib/Connector/Sabre/Node.php | 8 + apps/dav/lib/Server.php | 2 + apps/dav/lib/Upload/ChunkingV2Plugin.php | 230 ++++++++++++++++++ apps/dav/lib/Upload/FutureFile.php | 4 + apps/dav/lib/Upload/UploadFile.php | 8 + apps/dav/lib/Upload/UploadFolder.php | 10 +- apps/dav/lib/Upload/UploadHome.php | 20 +- lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + .../Files/ObjectStore/ObjectStoreStorage.php | 112 ++++++++- lib/private/Files/ObjectStore/S3.php | 50 +++- lib/private/Files/View.php | 19 ++ .../IObjectStoreMultiPartUpload.php | 54 ++++ .../Files/Storage/IChunkedFileWrite.php | 71 ++++++ 18 files changed, 591 insertions(+), 9 deletions(-) create mode 100644 apps/dav/lib/Upload/ChunkingV2Plugin.php create mode 100644 lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php create mode 100644 lib/public/Files/Storage/IChunkedFileWrite.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 7d625b2f51407..385f101d795f6 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -311,6 +311,7 @@ 'OCA\\DAV\\Traits\\PrincipalProxyTrait' => $baseDir . '/../lib/Traits/PrincipalProxyTrait.php', 'OCA\\DAV\\Upload\\AssemblyStream' => $baseDir . '/../lib/Upload/AssemblyStream.php', 'OCA\\DAV\\Upload\\ChunkingPlugin' => $baseDir . '/../lib/Upload/ChunkingPlugin.php', + 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => $baseDir . '/../lib/Upload/ChunkingV2Plugin.php', 'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => $baseDir . '/../lib/Upload/FutureFile.php', 'OCA\\DAV\\Upload\\RootCollection' => $baseDir . '/../lib/Upload/RootCollection.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 2a7190448c0c6..82cdb009ccdf5 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -326,6 +326,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Traits\\PrincipalProxyTrait' => __DIR__ . '/..' . '/../lib/Traits/PrincipalProxyTrait.php', 'OCA\\DAV\\Upload\\AssemblyStream' => __DIR__ . '/..' . '/../lib/Upload/AssemblyStream.php', 'OCA\\DAV\\Upload\\ChunkingPlugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingPlugin.php', + 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingV2Plugin.php', 'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => __DIR__ . '/..' . '/../lib/Upload/FutureFile.php', 'OCA\\DAV\\Upload\\RootCollection' => __DIR__ . '/..' . '/../lib/Upload/RootCollection.php', diff --git a/apps/dav/lib/BackgroundJob/UploadCleanup.php b/apps/dav/lib/BackgroundJob/UploadCleanup.php index 3a6f296deafa1..49894f86751a5 100644 --- a/apps/dav/lib/BackgroundJob/UploadCleanup.php +++ b/apps/dav/lib/BackgroundJob/UploadCleanup.php @@ -70,6 +70,10 @@ protected function run($argument) { return; } + $files = $uploadFolder->getDirectoryListing(); + + $davPath = 'uploads/' . $uid . '/' . $uploadFolder->getName(); + // Remove if all files have an mtime of more than a day $time = $this->time->getTime() - 60 * 60 * 24; @@ -93,6 +97,7 @@ protected function run($argument) { }, $initial); if ($expire) { + $this->customPropertiesService->delete($uid, $davPath); $uploadFolder->delete(); $this->jobList->remove(self::class, $argument); } diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index b575a051b2a20..a335d9f020601 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -39,6 +39,7 @@ use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Upload\FutureFile; use OCP\Files\FileInfo; use OCP\Files\Folder; use OCP\Files\ForbiddenException; diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 2b5661eec633a..7371678433ae9 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -269,6 +269,14 @@ public function getInternalFileId() { return $this->info->getId(); } + public function getInternalPath(): string { + return $this->info->getInternalPath(); + } + + public function getAbsoluteInternalPath(): string { + return $this->info->getPath(); + } + /** * @param string $user * @return int diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index f98dba229256d..49a02ed92f1f5 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -72,6 +72,7 @@ use OCA\DAV\Upload\ChunkingPlugin; use OCP\AppFramework\Http\Response; use OCP\Diagnostics\IEventLogger; +use OCA\DAV\Upload\ChunkingV2Plugin; use OCP\EventDispatcher\IEventDispatcher; use OCP\IRequest; use OCP\Profiler\IProfiler; @@ -215,6 +216,7 @@ public function __construct(IRequest $request, string $baseUri) { $this->server->addPlugin(new CopyEtagHeaderPlugin()); $this->server->addPlugin(new RequestIdHeaderPlugin(\OC::$server->get(IRequest::class))); + $this->server->addPlugin(new ChunkingV2Plugin()); $this->server->addPlugin(new ChunkingPlugin()); // allow setup of additional plugins diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php new file mode 100644 index 0000000000000..636f2c7903beb --- /dev/null +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -0,0 +1,230 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\DAV\Upload; + +use OC\Files\View; +use OCA\DAV\Connector\Sabre\Directory; +use OCP\Files\Storage\IChunkedFileWrite; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageInvalidException; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\PreconditionFailed; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\Uri; + +class ChunkingV2Plugin extends ServerPlugin { + + /** @var Server */ + private $server; + /** @var UploadFolder */ + private $uploadFolder; + + private const TEMP_TARGET = '.target'; + + private const OBJECT_UPLOAD_TARGET = '{http://nextcloud.org/ns}upload-target'; + private const OBJECT_UPLOAD_CHUNKTOKEN = '{http://nextcloud.org/ns}upload-chunktoken'; + + private const DESTINATION_HEADER = 'X-Chunking-Destination'; + + /** + * @inheritdoc + */ + public function initialize(Server $server) { + $server->on('afterMethod:MKCOL', [$this, 'beforeMkcol']); + // 200 priority to call after the custom properties backend is registered + $server->on('beforeMethod:PUT', [$this, 'beforePut'], 200); + $server->on('beforeMethod:DELETE', [$this, 'beforeDelete'], 200); + $server->on('beforeMove', [$this, 'beforeMove'], 90); + + $this->server = $server; + } + + /** + * @param string $path + * @param bool $createIfNotExists + * @return FutureFile|UploadFile|\Sabre\DAV\ICollection|\Sabre\DAV\INode + */ + private function getTargetFile(string $path, bool $createIfNotExists = false) { + try { + $targetFile = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + if ($createIfNotExists) { + $this->uploadFolder->createFile(self::TEMP_TARGET); + } + $targetFile = $this->uploadFolder->getChild(self::TEMP_TARGET); + } + return $targetFile; + } + + public function beforeMkcol(RequestInterface $request, ResponseInterface $response): bool { + $this->uploadFolder = $this->server->tree->getNodeForPath($request->getPath()); + try { + $this->checkPrerequisites(); + $storage = $this->getStorage(); + } catch (StorageInvalidException | BadRequest $e) { + return true; + } + + $targetPath = $this->server->httpRequest->getHeader(self::DESTINATION_HEADER); + if (!$targetPath) { + return true; + } + + $targetFile = $this->getTargetFile($targetPath, true); + + $uploadId = $storage->beginChunkedFile($targetFile->getInternalPath()); + + // DAV properties on the UploadFolder are used in order to properly cleanup stale chunked file writes and to persist the target path + $this->server->updateProperties($request->getPath(), [ + self::OBJECT_UPLOAD_CHUNKTOKEN => $uploadId, + self::OBJECT_UPLOAD_TARGET => $targetPath, + ]); + + $response->setStatus(201); + return true; + } + + public function beforePut(RequestInterface $request, ResponseInterface $response): bool { + $this->uploadFolder = $this->server->tree->getNodeForPath(dirname($request->getPath())); + try { + $this->checkPrerequisites(); + $storage = $this->getStorage(); + } catch (StorageInvalidException | BadRequest $e) { + return true; + } + + $properties = $this->server->getProperties(dirname($request->getPath()) . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); + $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; + $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + if (empty($targetPath) || empty($uploadId)) { + throw new PreconditionFailed('Missing metadata for chunked upload'); + } + $partId = (int)basename($request->getPath()); + + if (!($partId >= 1 && $partId <= 10000)) { + throw new BadRequest('Invalid chunk id'); + } + + $targetFile = $this->getTargetFile($targetPath); + $stream = $request->getBodyAsStream(); + $storage->putChunkedFilePart($targetFile->getInternalPath(), $uploadId, (string)$partId, $stream, (int)$request->getHeader('Content-Length')); + + $response->setStatus(201); + return false; + } + + public function beforeMove($sourcePath, $destination): bool { + $this->uploadFolder = $this->server->tree->getNodeForPath(dirname($sourcePath)); + try { + $this->checkPrerequisites(); + $this->getStorage(); + } catch (StorageInvalidException | BadRequest $e) { + return true; + } + $properties = $this->server->getProperties(dirname($sourcePath) . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); + $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; + $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + if (empty($targetPath) || empty($uploadId)) { + throw new PreconditionFailed('Missing metadata for chunked upload'); + } + + $targetFile = $this->getTargetFile($targetPath); + + [$destinationDir, $destinationName] = Uri\split($destination); + /** @var Directory $destinationParent */ + $destinationParent = $this->server->tree->getNodeForPath($destinationDir); + $destinationExists = $destinationParent->childExists($destinationName); + + $rootView = new View(); + $rootView->writeChunkedFile($targetFile->getAbsoluteInternalPath(), $uploadId); + if (!$destinationExists) { + $destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName; + $rootView->rename($targetFile->getAbsoluteInternalPath(), $destinationInView); + } + + $sourceNode = $this->server->tree->getNodeForPath($sourcePath); + if ($sourceNode instanceof FutureFile) { + $this->uploadFolder->delete(); + } + + $this->server->emit('afterMove', [$sourcePath, $destination]); + $this->server->emit('afterUnbind', [$sourcePath]); + $this->server->emit('afterBind', [$destination]); + + $response = $this->server->httpResponse; + $response->setHeader('Content-Length', '0'); + $response->setStatus($destinationExists ? 204 : 201); + return false; + } + + public function beforeDelete(RequestInterface $request, ResponseInterface $response) { + $this->uploadFolder = $this->server->tree->getNodeForPath($request->getPath()); + try { + if (!$this->uploadFolder instanceof UploadFolder) { + return true; + } + $storage = $this->getStorage(); + } catch (StorageInvalidException | BadRequest $e) { + return true; + } + + $properties = $this->server->getProperties($request->getPath() . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); + $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; + $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + if (!$targetPath || !$uploadId) { + return true; + } + $targetFile = $this->getTargetFile($targetPath); + $storage->cancelChunkedFile($targetFile->getInternalPath(), $uploadId); + return true; + } + + /** @throws BadRequest */ + private function checkPrerequisites(): void { + if (!$this->uploadFolder instanceof UploadFolder || !$this->server->httpRequest->getHeader(self::DESTINATION_HEADER)) { + throw new BadRequest('Chunking destination header not set'); + } + } + + /** + * @return IChunkedFileWrite + * @throws BadRequest + * @throws StorageInvalidException + */ + private function getStorage(): IStorage { + $this->checkPrerequisites(); + $storage = $this->uploadFolder->getStorage(); + if (!$storage->instanceOfStorage(IChunkedFileWrite::class)) { + throw new StorageInvalidException('Storage does not support chunked file write'); + } + /** @var IChunkedFileWrite $storage */ + return $storage; + } +} diff --git a/apps/dav/lib/Upload/FutureFile.php b/apps/dav/lib/Upload/FutureFile.php index eba550a62daca..d9534519f5280 100644 --- a/apps/dav/lib/Upload/FutureFile.php +++ b/apps/dav/lib/Upload/FutureFile.php @@ -66,6 +66,10 @@ public function get() { return AssemblyStream::wrap($nodes); } + public function getPath() { + return $this->root->getFileInfo()->getInternalPath() . '/.file'; + } + /** * @inheritdoc */ diff --git a/apps/dav/lib/Upload/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php index 49a2fadecf6c8..f68f2d2abf898 100644 --- a/apps/dav/lib/Upload/UploadFile.php +++ b/apps/dav/lib/Upload/UploadFile.php @@ -72,4 +72,12 @@ public function setName($name) { public function getLastModified() { return $this->file->getLastModified(); } + + public function getInternalPath(): string { + return $this->file->getInternalPath(); + } + + public function getAbsoluteInternalPath(): string { + return $this->file->getFileInfo()->getPath(); + } } diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index bb7c494cee356..e6c9525940e72 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -25,6 +25,7 @@ namespace OCA\DAV\Upload; use OCA\DAV\Connector\Sabre\Directory; +use OCP\Files\Storage\IStorage; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; @@ -34,10 +35,13 @@ class UploadFolder implements ICollection { private $node; /** @var CleanupService */ private $cleanupService; + /** @var IStorage */ + private $storage; - public function __construct(Directory $node, CleanupService $cleanupService) { + public function __construct(Directory $node, CleanupService $cleanupService, IStorage $storage) { $this->node = $node; $this->cleanupService = $cleanupService; + $this->storage = $storage; } public function createFile($name, $data = null) { @@ -94,4 +98,8 @@ public function setName($name) { public function getLastModified() { return $this->node->getLastModified(); } + + public function getStorage() { + return $this->storage; + } } diff --git a/apps/dav/lib/Upload/UploadHome.php b/apps/dav/lib/Upload/UploadHome.php index 35d47b6a82aba..15401a2c98bc3 100644 --- a/apps/dav/lib/Upload/UploadHome.php +++ b/apps/dav/lib/Upload/UploadHome.php @@ -55,12 +55,12 @@ public function createDirectory($name) { } public function getChild($name): UploadFolder { - return new UploadFolder($this->impl()->getChild($name), $this->cleanupService); + return new UploadFolder($this->impl()->getChild($name), $this->cleanupService, $this->getStorage()); } public function getChildren(): array { return array_map(function ($node) { - return new UploadFolder($node, $this->cleanupService); + return new UploadFolder($node, $this->cleanupService, $this->getStorage()); }, $this->impl()->getChildren()); } @@ -89,14 +89,24 @@ public function getLastModified() { * @return Directory */ private function impl() { + $view = $this->getView(); + $rootInfo = $view->getFileInfo(''); + return new Directory($view, $rootInfo); + } + + 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'); } - $view = new View('/' . $user->getUID() . '/uploads'); - $rootInfo = $view->getFileInfo(''); - return new Directory($view, $rootInfo); + return new View('/' . $user->getUID() . '/uploads'); + } + + private function getStorage() { + $view = $this->getView(); + $storage = $view->getFileInfo('')->getStorage(); + return $storage; } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index e9c50b812e053..d99612fcab673 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -336,6 +336,7 @@ 'OCP\\Files\\Notify\\INotifyHandler' => $baseDir . '/lib/public/Files/Notify/INotifyHandler.php', 'OCP\\Files\\Notify\\IRenameChange' => $baseDir . '/lib/public/Files/Notify/IRenameChange.php', 'OCP\\Files\\ObjectStore\\IObjectStore' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ObjectStore\\IObjectStoreMultiPartUpload' => $baseDir . '/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php', 'OCP\\Files\\ReservedWordException' => $baseDir . '/lib/public/Files/ReservedWordException.php', 'OCP\\Files\\Search\\ISearchBinaryOperator' => $baseDir . '/lib/public/Files/Search/ISearchBinaryOperator.php', 'OCP\\Files\\Search\\ISearchComparison' => $baseDir . '/lib/public/Files/Search/ISearchComparison.php', @@ -353,6 +354,7 @@ 'OCP\\Files\\StorageInvalidException' => $baseDir . '/lib/public/Files/StorageInvalidException.php', 'OCP\\Files\\StorageNotAvailableException' => $baseDir . '/lib/public/Files/StorageNotAvailableException.php', 'OCP\\Files\\StorageTimeoutException' => $baseDir . '/lib/public/Files/StorageTimeoutException.php', + 'OCP\\Files\\Storage\\IChunkedFileWrite' => $baseDir . '/lib/public/Files/Storage/IChunkedFileWrite.php', 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => $baseDir . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => $baseDir . '/lib/public/Files/Storage/ILockingStorage.php', 'OCP\\Files\\Storage\\INotifyStorage' => $baseDir . '/lib/public/Files/Storage/INotifyStorage.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 5a85e16d1b42c..2af64383d0806 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -369,6 +369,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Notify\\INotifyHandler' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/INotifyHandler.php', 'OCP\\Files\\Notify\\IRenameChange' => __DIR__ . '/../../..' . '/lib/public/Files/Notify/IRenameChange.php', 'OCP\\Files\\ObjectStore\\IObjectStore' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStore.php', + 'OCP\\Files\\ObjectStore\\IObjectStoreMultiPartUpload' => __DIR__ . '/../../..' . '/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php', 'OCP\\Files\\ReservedWordException' => __DIR__ . '/../../..' . '/lib/public/Files/ReservedWordException.php', 'OCP\\Files\\Search\\ISearchBinaryOperator' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchBinaryOperator.php', 'OCP\\Files\\Search\\ISearchComparison' => __DIR__ . '/../../..' . '/lib/public/Files/Search/ISearchComparison.php', @@ -386,6 +387,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\StorageInvalidException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageInvalidException.php', 'OCP\\Files\\StorageNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageNotAvailableException.php', 'OCP\\Files\\StorageTimeoutException' => __DIR__ . '/../../..' . '/lib/public/Files/StorageTimeoutException.php', + 'OCP\\Files\\Storage\\IChunkedFileWrite' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IChunkedFileWrite.php', 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/ILockingStorage.php', 'OCP\\Files\\Storage\\INotifyStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/INotifyStorage.php', diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 0ec1140cd24e0..0305d9dd7767e 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -29,6 +29,8 @@ */ namespace OC\Files\ObjectStore; +use Aws\S3\Exception\S3Exception; +use Aws\S3\Exception\S3MultipartUploadException; use Icewind\Streams\CallbackWrapper; use Icewind\Streams\CountWrapper; use Icewind\Streams\IteratorDirectory; @@ -38,11 +40,15 @@ use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; use OCP\Files\FileInfo; +use OCP\Files\GenericFileException; use OCP\Files\NotFoundException; use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; +use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IStorage; +use OCP\ICache; -class ObjectStoreStorage extends \OC\Files\Storage\Common { +class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite { use CopyDirectory; /** @@ -62,6 +68,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { private $logger; + /** @var ICache */ + private $uploadCache; + public function __construct($params) { if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { $this->objectStore = $params['objectstore']; @@ -82,11 +91,11 @@ public function __construct($params) { } $this->logger = \OC::$server->getLogger(); + $this->uploadCache = \OC::$server->getMemCacheFactory()->createDistributed('objectstore'); } public function mkdir($path) { $path = $this->normalizePath($path); - if ($this->file_exists($path)) { return false; } @@ -612,4 +621,103 @@ private function copyFile(ICacheEntry $sourceEntry, string $to) { throw $e; } } + + public function beginChunkedFile(string $targetPath): string { + $this->validateUploadCache(); + if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) { + throw new GenericFileException('Object store does not support multipart upload'); + } + $cacheEntry = $this->getCache()->get($targetPath); + $urn = $this->getURN($cacheEntry->getId()); + $uploadId = $this->objectStore->initiateMultipartUpload($urn); + $this->uploadCache->set($this->getUploadCacheKey($urn, $uploadId, 'uploadId'), $uploadId); + return $uploadId; + } + + /** + * + * @throws GenericFileException + */ + public function putChunkedFilePart(string $targetPath, string $writeToken, string $chunkId, $data, $size = null): void { + $this->validateUploadCache(); + if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) { + throw new GenericFileException('Object store does not support multipart upload'); + } + $cacheEntry = $this->getCache()->get($targetPath); + $urn = $this->getURN($cacheEntry->getId()); + $uploadId = $this->uploadCache->get($this->getUploadCacheKey($urn, $writeToken, 'uploadId')); + + $result = $this->objectStore->uploadMultipartPart($urn, $uploadId, (int)$chunkId, $data, $size); + + $parts = $this->uploadCache->get($this->getUploadCacheKey($urn, $uploadId, 'parts')); + if (!$parts) { + $parts = []; + } + $parts[$chunkId] = [ + 'PartNumber' => $chunkId, + 'ETag' => trim($result->get('ETag'), '"') + ]; + $this->uploadCache->set($this->getUploadCacheKey($urn, $uploadId, 'parts'), $parts); + } + + public function writeChunkedFile(string $targetPath, string $writeToken): int { + $this->validateUploadCache(); + if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) { + throw new GenericFileException('Object store does not support multipart upload'); + } + $cacheEntry = $this->getCache()->get($targetPath); + $urn = $this->getURN($cacheEntry->getId()); + $uploadId = $this->uploadCache->get($this->getUploadCacheKey($urn, $writeToken, 'uploadId')); + $parts = $this->uploadCache->get($this->getUploadCacheKey($urn, $uploadId, 'parts')); + try { + $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, array_values($parts)); + $stat = $this->stat($targetPath); + $mtime = time(); + if (is_array($stat)) { + $stat['size'] = $size; + $stat['mtime'] = $mtime; + $stat['mimetype'] = $this->getMimeType($targetPath); + $this->getCache()->update($stat['fileid'], $stat); + } + } catch (S3MultipartUploadException | S3Exception $e) { + $this->objectStore->abortMultipartUpload($urn, $uploadId); + $this->logger->logException($e, [ + 'app' => 'objectstore', + 'message' => 'Could not compete multipart upload ' . $urn. ' with uploadId ' . $uploadId + ]); + throw new GenericFileException('Could not write chunked file'); + } finally { + $this->clearCache($urn, $uploadId); + } + return $size; + } + + public function cancelChunkedFile(string $targetPath, string $writeToken): void { + $this->validateUploadCache(); + if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) { + throw new GenericFileException('Object store does not support multipart upload'); + } + $cacheEntry = $this->getCache()->get($targetPath); + $urn = $this->getURN($cacheEntry->getId()); + $uploadId = $this->uploadCache->get($this->getUploadCacheKey($urn, $writeToken, 'uploadId')); + $this->objectStore->abortMultipartUpload($urn, $uploadId); + $this->clearCache($urn, $uploadId); + } + + /** + * @throws GenericFileException + */ + private function validateUploadCache(): void { + if ($this->uploadCache instanceof NullCache || $this->uploadCache instanceof ArrayCache) { + throw new GenericFileException('ChunkedFileWrite not available: A cross-request persistent cache is required'); + } + } + + private function getUploadCacheKey($urn, $uploadId, $chunkId = null): string { + return $urn . '-' . $uploadId . '-' . ($chunkId ? $chunkId . '-' : ''); + } + + private function clearCache($urn, $uploadId): void { + $this->uploadCache->clear($this->getUploadCacheKey($urn, $uploadId)); + } } diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index 6492145fb63b0..e8f4bd610b21e 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -23,9 +23,12 @@ */ namespace OC\Files\ObjectStore; +use Aws\Result; +use Exception; use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; -class S3 implements IObjectStore { +class S3 implements IObjectStore, IObjectStoreMultiPartUpload { use S3ConnectionTrait; use S3ObjectTrait; @@ -41,4 +44,49 @@ public function __construct($parameters) { public function getStorageId() { return $this->id; } + + public function initiateMultipartUpload(string $urn): string { + $upload = $this->getConnection()->createMultipartUpload([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + ]); + $uploadId = $upload->get('UploadId'); + if ($uploadId === null) { + throw new Exception('No upload id returned'); + } + return (string)$uploadId; + } + + public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result { + return $this->getConnection()->uploadPart([ + 'Body' => $stream, + 'Bucket' => $this->bucket, + 'Key' => $urn, + 'ContentLength' => $size, + 'PartNumber' => $partId, + 'UploadId' => $uploadId, + ]); + } + + public function completeMultipartUpload(string $urn, string $uploadId, array $result): int { + $this->getConnection()->completeMultipartUpload([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + 'UploadId' => $uploadId, + 'MultipartUpload' => ['Parts' => $result], + ]); + $stat = $this->getConnection()->headObject([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + ]); + return (int)$stat->get('ContentLength'); + } + + public function abortMultipartUpload($urn, $uploadId): void { + $this->getConnection()->abortMultipartUpload([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + 'UploadId' => $uploadId + ]); + } } diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index 3cd7504025322..ec4c3df78f500 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -61,7 +61,10 @@ use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; use OCP\Files\ReservedWordException; +use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IStorage; +use OCP\Files\StorageInvalidException; +use OCP\ILogger; use OCP\IUser; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; @@ -706,6 +709,22 @@ public function file_put_contents($path, $data) { } } + /** + * @param string $path + * @param string $chunkToken + * @return false|mixed|null + * @throws LockedException + * @throws StorageInvalidException + */ + public function writeChunkedFile(string $path, string $chunkToken) { + /** @var IStorage|null $storage */ + [$storage, ] = Filesystem::resolvePath($path); + if (!$storage || !$storage->instanceOfStorage(IChunkedFileWrite::class)) { + throw new StorageInvalidException('path is not a chunked file write storage'); + } + return $this->basicOperation('writeChunkedFile', $path, ['update', 'write'], $chunkToken); + } + /** * @param string $path * @return bool|mixed diff --git a/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php new file mode 100644 index 0000000000000..cd94fdc184d31 --- /dev/null +++ b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php @@ -0,0 +1,54 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + + +namespace OCP\Files\ObjectStore; + +use Aws\Result; + +/** + * @since 22.0.0 + */ +interface IObjectStoreMultiPartUpload { + /** + * @since 22.0.0 + */ + public function initiateMultipartUpload(string $urn): string; + + /** + * @since 22.0.0 + */ + public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result; + + /** + * @since 22.0.0 + */ + public function completeMultipartUpload(string $urn, string $uploadId, array $result): int; + + /** + * @since 22.0.0 + */ + public function abortMultipartUpload(string $urn, string $uploadId): void; +} diff --git a/lib/public/Files/Storage/IChunkedFileWrite.php b/lib/public/Files/Storage/IChunkedFileWrite.php new file mode 100644 index 0000000000000..10eeac2ab4e3a --- /dev/null +++ b/lib/public/Files/Storage/IChunkedFileWrite.php @@ -0,0 +1,71 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + + +namespace OCP\Files\Storage; + +use OCP\Files\GenericFileException; + +/** + * @since 22.0.0 + */ +interface IChunkedFileWrite extends IStorage { + + /** + * @param string $targetPath Relative target path in the storage + * @return string writeToken to be used with the other methods to uniquely identify the file write operation + * @throws GenericFileException + * @since 22.0.0 + */ + public function beginChunkedFile(string $targetPath): string; + + /** + * @param string $targetPath + * @param string $writeToken + * @param string $chunkId + * @param resource $data + * @param int|null $size + * @throws GenericFileException + * @since 22.0.0 + */ + public function putChunkedFilePart(string $targetPath, string $writeToken, string $chunkId, $data, int $size = null): void; + + /** + * @param string $targetPath + * @param string $writeToken + * @return int + * @throws GenericFileException + * @since 22.0.0 + */ + public function writeChunkedFile(string $targetPath, string $writeToken): int; + + /** + * @param string $targetPath + * @param string $writeToken + * @throws GenericFileException + * @since 22.0.0 + */ + public function cancelChunkedFile(string $targetPath, string $writeToken): void; +} From 86dde8a72be1ba8910243e808ce1972c62ec87fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 21 May 2021 13:17:30 +0200 Subject: [PATCH 02/15] Move dav property deletion to dedicated service to cleanup on background job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/BackgroundJob/UploadCleanup.php | 5 +- .../dav/lib/Connector/Sabre/ServerFactory.php | 2 + apps/dav/lib/DAV/CustomPropertiesBackend.php | 16 ++--- apps/dav/lib/Server.php | 2 + .../lib/Service/CustomPropertiesService.php | 60 +++++++++++++++++++ .../Sabre/CustomPropertiesBackendTest.php | 2 + .../unit/DAV/CustomPropertiesBackendTest.php | 15 ++++- 9 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 apps/dav/lib/Service/CustomPropertiesService.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 385f101d795f6..ea79b9a519775 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -297,6 +297,7 @@ 'OCA\\DAV\\Search\\EventsSearchProvider' => $baseDir . '/../lib/Search/EventsSearchProvider.php', 'OCA\\DAV\\Search\\TasksSearchProvider' => $baseDir . '/../lib/Search/TasksSearchProvider.php', 'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php', + 'OCA\\DAV\\Service\\CustomPropertiesService' => $baseDir . '/../lib/Service/CustomPropertiesService.php', 'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php', 'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 82cdb009ccdf5..b91ffee6c6fdd 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -312,6 +312,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Search\\EventsSearchProvider' => __DIR__ . '/..' . '/../lib/Search/EventsSearchProvider.php', 'OCA\\DAV\\Search\\TasksSearchProvider' => __DIR__ . '/..' . '/../lib/Search/TasksSearchProvider.php', 'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php', + 'OCA\\DAV\\Service\\CustomPropertiesService' => __DIR__ . '/..' . '/../lib/Service/CustomPropertiesService.php', 'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php', 'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php', diff --git a/apps/dav/lib/BackgroundJob/UploadCleanup.php b/apps/dav/lib/BackgroundJob/UploadCleanup.php index 49894f86751a5..0942348345d68 100644 --- a/apps/dav/lib/BackgroundJob/UploadCleanup.php +++ b/apps/dav/lib/BackgroundJob/UploadCleanup.php @@ -28,6 +28,7 @@ namespace OCA\DAV\BackgroundJob; use OC\User\NoUserException; +use OCA\DAV\Service\CustomPropertiesService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\IJobList; @@ -42,12 +43,14 @@ class UploadCleanup extends TimedJob { private IRootFolder $rootFolder; private IJobList $jobList; + private CustomPropertiesService $customPropertiesService; private LoggerInterface $logger; - public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList, LoggerInterface $logger) { + public function __construct(ITimeFactory $time, IRootFolder $rootFolder, IJobList $jobList, CustomPropertiesService $customPropertiesService, LoggerInterface $logger) { parent::__construct($time); $this->rootFolder = $rootFolder; $this->jobList = $jobList; + $this->customPropertiesService = $customPropertiesService; $this->logger = $logger; // Run once a day diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 4c57f3412e3f9..0371c4a62383e 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -35,6 +35,7 @@ use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\DAV\ViewOnlyPlugin; use OCA\DAV\Files\BrowserErrorPagePlugin; +use OCA\DAV\Service\CustomPropertiesService; use OCP\Files\Mount\IMountManager; use OCP\IConfig; use OCP\IDBConnection; @@ -190,6 +191,7 @@ public function createServer(string $baseUri, new \OCA\DAV\DAV\CustomPropertiesBackend( $objectTree, $this->databaseConnection, + \OC::$server->get(CustomPropertiesService::class), $this->userSession->getUser() ) ) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index a1da75e9ec190..14edf17febc0a 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -28,6 +28,7 @@ use Exception; use OCA\DAV\Connector\Sabre\Directory; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCA\DAV\Service\CustomPropertiesService; use OCP\IDBConnection; use OCP\IUser; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; @@ -116,6 +117,11 @@ class CustomPropertiesBackend implements BackendInterface { */ private $connection; + /** + * @var CustomPropertiesService + */ + private $customPropertiesService; + /** * @var IUser */ @@ -140,6 +146,7 @@ public function __construct( ) { $this->tree = $tree; $this->connection = $connection; + $this->customPropertiesService = $customPropertiesService; $this->user = $user; } @@ -218,13 +225,8 @@ public function propPatch($path, PropPatch $propPatch) { * @param string $path path of node for which to delete properties */ public function delete($path) { - $statement = $this->connection->prepare( - 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' - ); - $statement->execute([$this->user->getUID(), $this->formatPath($path)]); - $statement->closeCursor(); - - unset($this->userCache[$path]); + $this->customPropertiesService->delete($this->user->getUID(), $path); + unset($this->cache[$path]); } /** diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 49a02ed92f1f5..9a4483df1c49e 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -68,6 +68,7 @@ use OCA\DAV\Files\LazySearchBackend; use OCA\DAV\Profiler\ProfilerPlugin; use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin; +use OCA\DAV\Service\CustomPropertiesService; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\Upload\ChunkingPlugin; use OCP\AppFramework\Http\Response; @@ -269,6 +270,7 @@ public function __construct(IRequest $request, string $baseUri) { new CustomPropertiesBackend( $this->server->tree, \OC::$server->getDatabaseConnection(), + \OC::$server->get(CustomPropertiesService::class), \OC::$server->getUserSession()->getUser() ) ) diff --git a/apps/dav/lib/Service/CustomPropertiesService.php b/apps/dav/lib/Service/CustomPropertiesService.php new file mode 100644 index 0000000000000..cf5f9b8af8da3 --- /dev/null +++ b/apps/dav/lib/Service/CustomPropertiesService.php @@ -0,0 +1,60 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +declare(strict_types=1); + + +namespace OCA\DAV\Service; + +use OCP\IDBConnection; + +class CustomPropertiesService { + + /** @var IDBConnection */ + private $connection; + + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + public function delete(string $userId, string $path): void { + $statement = $this->connection->prepare( + 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' + ); + $result = $statement->execute([$userId, $this->formatPath($path)]); + $result->closeCursor(); + } + + /** + * long paths are hashed to ensure they fit in the database + * + * @param string $path + * @return string + */ + private function formatPath(string $path): string { + if (strlen($path) > 250) { + return sha1($path); + } + return $path; + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php index 48658f3ffa36f..5e5e9077ab03e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php @@ -38,6 +38,7 @@ use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; +use OCA\DAV\Service\CustomPropertiesService; use OCP\IUser; use Sabre\DAV\Tree; @@ -89,6 +90,7 @@ protected function setUp(): void { $this->plugin = new \OCA\DAV\DAV\CustomPropertiesBackend( $this->tree, \OC::$server->getDatabaseConnection(), + $this->createMock(CustomPropertiesService::class), $this->user ); } diff --git a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php index a6bbeba16a3a1..c006a00b56ee0 100644 --- a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php @@ -28,8 +28,10 @@ namespace OCA\DAV\Tests\DAV; use OCA\DAV\DAV\CustomPropertiesBackend; +use OCA\DAV\Service\CustomPropertiesService; use OCP\IDBConnection; use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Tree; @@ -40,16 +42,19 @@ */ class CustomPropertiesBackendTest extends TestCase { - /** @var Tree | \PHPUnit\Framework\MockObject\MockObject */ + /** @var Tree | MockObject */ private $tree; /** @var IDBConnection */ private $dbConnection; - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject */ + /** @var CustomPropertiesService */ + private $customPropertiesService; + + /** @var IUser | MockObject */ private $user; - /** @var CustomPropertiesBackend | \PHPUnit\Framework\MockObject\MockObject */ + /** @var CustomPropertiesBackend */ private $backend; protected function setUp(): void { @@ -61,10 +66,12 @@ protected function setUp(): void { ->with() ->willReturn('dummy_user_42'); $this->dbConnection = \OC::$server->getDatabaseConnection(); + $this->customPropertiesService = new CustomPropertiesService($this->dbConnection); $this->backend = new CustomPropertiesBackend( $this->tree, $this->dbConnection, + $this->customPropertiesService, $this->user ); } @@ -122,9 +129,11 @@ protected function getProps(string $user, string $path) { public function testPropFindNoDbCalls() { $db = $this->createMock(IDBConnection::class); + $service = new CustomPropertiesService($db); $backend = new CustomPropertiesBackend( $this->tree, $db, + $service, $this->user ); From 60466b877cd3cc92c09e22de652bcba25530ee37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 28 Jul 2021 15:30:20 +0200 Subject: [PATCH 03/15] Avoid slow redis query through iterating over KEYS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- lib/private/Files/ObjectStore/ObjectStoreStorage.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 0305d9dd7767e..6b9252daab3af 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -713,11 +713,12 @@ private function validateUploadCache(): void { } } - private function getUploadCacheKey($urn, $uploadId, $chunkId = null): string { - return $urn . '-' . $uploadId . '-' . ($chunkId ? $chunkId . '-' : ''); + private function getUploadCacheKey($urn, $uploadId, $key = null): string { + return $urn . '-' . $uploadId . '-' . ($key ? $key . '-' : ''); } private function clearCache($urn, $uploadId): void { - $this->uploadCache->clear($this->getUploadCacheKey($urn, $uploadId)); + $this->uploadCache->remove($this->getUploadCacheKey($urn, $uploadId, 'uploadId')); + $this->uploadCache->remove($this->getUploadCacheKey($urn, $uploadId, 'parts')); } } From 77d268744ab48bcc0ad578c988687c56167d0629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 5 Aug 2021 16:04:52 +0200 Subject: [PATCH 04/15] Processing callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Upload/ChunkingV2Plugin.php | 60 ++++++++++++++----- .../Files/ObjectStore/ObjectStoreStorage.php | 24 +++++++- lib/private/Files/ObjectStore/S3.php | 11 +++- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 636f2c7903beb..6e9aa413eebee 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -25,8 +25,10 @@ namespace OCA\DAV\Upload; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\FilesPlugin; use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IStorage; use OCP\Files\StorageInvalidException; @@ -35,6 +37,8 @@ use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Xml\Element\Response; +use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\Uri; @@ -144,7 +148,7 @@ public function beforeMove($sourcePath, $destination): bool { $this->uploadFolder = $this->server->tree->getNodeForPath(dirname($sourcePath)); try { $this->checkPrerequisites(); - $this->getStorage(); + $storage = $this->getStorage(); } catch (StorageInvalidException | BadRequest $e) { return true; } @@ -162,25 +166,51 @@ public function beforeMove($sourcePath, $destination): bool { $destinationParent = $this->server->tree->getNodeForPath($destinationDir); $destinationExists = $destinationParent->childExists($destinationName); + // Using a multipart status here in order to be able to sent the actual status after processing the move + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $rootView = new View(); - $rootView->writeChunkedFile($targetFile->getAbsoluteInternalPath(), $uploadId); - if (!$destinationExists) { - $destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName; - $rootView->rename($targetFile->getAbsoluteInternalPath(), $destinationInView); + if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { + $lastTick = time(); + $storage->processingCallback('writeChunkedFile', function () use ($lastTick) { + if ($lastTick < time()) { + \OC_Util::obEnd(); + echo ' '; + flush(); + } + $lastTick = time(); + }); } - $sourceNode = $this->server->tree->getNodeForPath($sourcePath); - if ($sourceNode instanceof FutureFile) { - $this->uploadFolder->delete(); - } + $this->server->httpResponse->setBody(function () use ($targetFile, $rootView, $uploadId, $destinationName, $destinationParent, $destinationExists, $sourcePath, $destination) { + $rootView->writeChunkedFile($targetFile->getAbsoluteInternalPath(), $uploadId); + $destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName; + if (!$destinationExists) { + $rootView->rename($targetFile->getAbsoluteInternalPath(), $destinationInView); + } - $this->server->emit('afterMove', [$sourcePath, $destination]); - $this->server->emit('afterUnbind', [$sourcePath]); - $this->server->emit('afterBind', [$destination]); + $sourceNode = $this->server->tree->getNodeForPath($sourcePath); + if ($sourceNode instanceof FutureFile) { + $this->uploadFolder->delete(); + } - $response = $this->server->httpResponse; - $response->setHeader('Content-Length', '0'); - $response->setStatus($destinationExists ? 204 : 201); + $this->server->emit('afterMove', [$sourcePath, $destination]); + $this->server->emit('afterUnbind', [$sourcePath]); + $this->server->emit('afterBind', [$destination]); + + $response = new Response( + $destination, + ['200' => [ + FilesPlugin::SIZE_PROPERTYNAME => $rootView->filesize($destinationInView) + ]], + $destinationExists ? 204 : 201 + ); + echo $this->server->xml->write( + '{DAV:}multistatus', + new MultiStatus([$response]) + ); + }); return false; } diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 6b9252daab3af..7740e0929d725 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -71,6 +71,9 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil /** @var ICache */ private $uploadCache; + /** @var array */ + private $processingCallbacks; + public function __construct($params) { if (isset($params['objectstore']) && $params['objectstore'] instanceof IObjectStore) { $this->objectStore = $params['objectstore']; @@ -660,6 +663,17 @@ public function putChunkedFilePart(string $targetPath, string $writeToken, strin $this->uploadCache->set($this->getUploadCacheKey($urn, $uploadId, 'parts'), $parts); } + public function processingCallback(string $method, callable $callback) { + if (in_array($method, ['writeChunkedFile'])) { + if (!isset($this->processingCallbacks[$method])) { + $this->processingCallbacks[$method] = []; + } + $this->processingCallbacks[$method][] = $callback; + return; + } + throw new \Exception('Invalid handler method for processing callback'); + } + public function writeChunkedFile(string $targetPath, string $writeToken): int { $this->validateUploadCache(); if (!$this->objectStore instanceof IObjectStoreMultiPartUpload) { @@ -670,7 +684,15 @@ public function writeChunkedFile(string $targetPath, string $writeToken): int { $uploadId = $this->uploadCache->get($this->getUploadCacheKey($urn, $writeToken, 'uploadId')); $parts = $this->uploadCache->get($this->getUploadCacheKey($urn, $uploadId, 'parts')); try { - $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, array_values($parts)); + if ($this->objectStore instanceof S3) { + $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, array_values($parts), function () { + foreach ($this->processingCallbacks['writeChunkedFile'] as $callback) { + $callback(); + } + }); + } else { + $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, array_values($parts)); + } $stat = $this->stat($targetPath); $mtime = time(); if (is_array($stat)) { diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index e8f4bd610b21e..da06192cc6928 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -68,12 +68,21 @@ public function uploadMultipartPart(string $urn, string $uploadId, int $partId, ]); } - public function completeMultipartUpload(string $urn, string $uploadId, array $result): int { + public function completeMultipartUpload(string $urn, string $uploadId, array $result, callable $processingCallback = null): int { $this->getConnection()->completeMultipartUpload([ 'Bucket' => $this->bucket, 'Key' => $urn, 'UploadId' => $uploadId, 'MultipartUpload' => ['Parts' => $result], + '@http' => [ + // the progress callback is called by CURLOPT_PROGRESSFUNCTION which would get called regularly + // https://curl.se/libcurl/c/CURLOPT_PROGRESSFUNCTION.html + 'progress' => function (/* $downloadTotalSize, $downloadSizeSoFar, $uploadTotalSize, $uploadSizeSoFar */) use ($processingCallback) { + if ($processingCallback) { + $processingCallback(); + } + } + ] ]); $stat = $this->getConnection()->headObject([ 'Bucket' => $this->bucket, From 097ba914a7179cd08dea4a2fc0d21cba27b07637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Sat, 4 Sep 2021 01:07:26 +0200 Subject: [PATCH 05/15] Increase chunking capability version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Capabilities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index b8096d3395ab5..b5c178578fb10 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -36,7 +36,7 @@ public function __construct(IConfig $config) { public function getCapabilities() { $capabilities = [ 'dav' => [ - 'chunking' => '1.0', + 'chunking' => '2.0' ] ]; if ($this->config->getSystemValueBool('bulkupload.enabled', true)) { From 7a04860701b3ac4935b5727c235f45c8ae866c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 7 Sep 2021 09:02:01 +0200 Subject: [PATCH 06/15] Quota checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Upload/ChunkingV2Plugin.php | 30 ++++++++++++++++++- .../Files/ObjectStore/ObjectStoreStorage.php | 4 ++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 6e9aa413eebee..98e78ed7f834e 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -33,6 +33,7 @@ use OCP\Files\Storage\IStorage; use OCP\Files\StorageInvalidException; use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\InsufficientStorage; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Server; @@ -137,8 +138,33 @@ public function beforePut(RequestInterface $request, ResponseInterface $response } $targetFile = $this->getTargetFile($targetPath); + $cacheEntry = $storage->getCache()->get($targetFile->getInternalPath()); + $tempTargetFile = null; + + $additionalSize = (int)$request->getHeader('Content-Length'); + if ($this->uploadFolder->childExists(self::TEMP_TARGET)) { + // FIXME Quota checking will not work for existing files that way + $tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET); + $tempTargetCache = $storage->getCache()->get($tempTargetFile->getInternalPath()); + + [$destinationDir, $destinationName] = Uri\split($targetPath); + /** @var Directory $destinationParent */ + $destinationParent = $this->server->tree->getNodeForPath($destinationDir); + $free = $storage->free_space($destinationParent->getInternalPath()); + $newSize = $tempTargetCache->getSize() + $additionalSize; + if ($free >= 0 && ($tempTargetCache->getSize() > $free || $newSize > $free)) { + throw new InsufficientStorage("Insufficient space in $targetPath"); + } + } + $stream = $request->getBodyAsStream(); - $storage->putChunkedFilePart($targetFile->getInternalPath(), $uploadId, (string)$partId, $stream, (int)$request->getHeader('Content-Length')); + $storage->putChunkedFilePart($targetFile->getInternalPath(), $uploadId, (string)$partId, $stream, $additionalSize); + // FIXME add return value to putChunkedFilePart to validate against size + + $storage->getCache()->update($cacheEntry->getId(), ['size' => $cacheEntry->getSize() + $additionalSize]); + if ($tempTargetFile) { + $storage->getPropagator()->propagateChange($tempTargetFile->getInternalPath(), time(), $additionalSize); + } $response->setStatus(201); return false; @@ -155,6 +181,8 @@ public function beforeMove($sourcePath, $destination): bool { $properties = $this->server->getProperties(dirname($sourcePath) . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + + // FIXME: check if $destination === TARGET if (empty($targetPath) || empty($uploadId)) { throw new PreconditionFailed('Missing metadata for chunked upload'); } diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index 7740e0929d725..ac6ede4a28252 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -683,9 +683,11 @@ public function writeChunkedFile(string $targetPath, string $writeToken): int { $urn = $this->getURN($cacheEntry->getId()); $uploadId = $this->uploadCache->get($this->getUploadCacheKey($urn, $writeToken, 'uploadId')); $parts = $this->uploadCache->get($this->getUploadCacheKey($urn, $uploadId, 'parts')); + $sortedParts = array_values($parts); + sort($sortedParts); try { if ($this->objectStore instanceof S3) { - $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, array_values($parts), function () { + $size = $this->objectStore->completeMultipartUpload($urn, $uploadId, $sortedParts, function () { foreach ($this->processingCallbacks['writeChunkedFile'] as $callback) { $callback(); } From 2b80ca880685ca7b9d31dc56e5b9cf08e0c8085e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 7 Sep 2021 09:05:32 +0200 Subject: [PATCH 07/15] Use multipart upload in the web ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/files/js/file-upload.js | 26 +++++++++++++++++++++++--- apps/files/js/jquery.fileupload.js | 6 ++++++ core/src/files/client.js | 9 +++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index 7d6bde6e0f91c..ced5fa86940d6 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -269,8 +269,15 @@ OC.FileUpload.prototype = { && this.getFile().size > this.uploader.fileUploadParam.maxChunkSize ) { data.isChunked = true; + var headers = {}; + if (OC.getCapabilities().dav.chunking === '2.0') { + headers = { + 'X-Chunking-Destination': this.getTargetDestination() + }; + } + chunkFolderPromise = this.uploader.davClient.createDirectory( - 'uploads/' + OC.getCurrentUser().uid + '/' + this.getId() + 'uploads/' + OC.getCurrentUser().uid + '/' + this.getId(), headers ); // TODO: if fails, it means same id already existed, need to retry } else { @@ -309,17 +316,24 @@ OC.FileUpload.prototype = { } if (size) { headers['OC-Total-Length'] = size; - + } + if (OC.getCapabilities().dav.chunking === '2.0') { + headers['X-Chunking-Destination'] = this.getTargetDestination(); } return this.uploader.davClient.move( 'uploads/' + uid + '/' + this.getId() + '/.file', - 'files/' + uid + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()), + this.getTargetDestination(), true, headers ); }, + getTargetDestination: function() { + var uid = OC.getCurrentUser().uid; + return 'files/' + uid + '/' + OC.joinPaths(this.getFullPath(), this.getFileName()); + }, + _deleteChunkFolder: function() { // delete transfer directory for this upload this.uploader.davClient.remove( @@ -1326,6 +1340,12 @@ OC.Uploader.prototype = _.extend({ } var range = data.contentRange.split(' ')[1]; var chunkId = range.split('/')[0].split('-')[0]; + if (OC.getCapabilities().dav.chunking === '2.0') { + // Calculate chunk index for usage with s3 + chunkId = Math.ceil((data.chunkSize+Number(chunkId)) / upload.uploader.fileUploadParam.maxChunkSize); + data.headers['X-Chunking-Destination'] = upload.getTargetDestination(); + } + data.url = OC.getRootPath() + '/remote.php/dav/uploads' + '/' + OC.getCurrentUser().uid + diff --git a/apps/files/js/jquery.fileupload.js b/apps/files/js/jquery.fileupload.js index 9b382ccae3914..f0ae854c87453 100644 --- a/apps/files/js/jquery.fileupload.js +++ b/apps/files/js/jquery.fileupload.js @@ -733,6 +733,12 @@ promise = dfd.promise(), jqXHR, upload; + + // Dynamically adjust the chunk size for Chunking V2 to fit into the 10000 chunk limit + if ((OC.getCapabilities().dav.chunking === '2.0') && file.size/mcs > 10000) { + mcs = Math.ceil(file.size/10000) + } + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || options.data) { return false; diff --git a/core/src/files/client.js b/core/src/files/client.js index 2c71fbe46e100..9d32fefdfc4c6 100644 --- a/core/src/files/client.js +++ b/core/src/files/client.js @@ -758,7 +758,7 @@ import escapeHTML from 'escape-html' return promise }, - _simpleCall: function(method, path) { + _simpleCall: function(method, path, headers) { if (!path) { throw 'Missing argument "path"' } @@ -769,7 +769,8 @@ import escapeHTML from 'escape-html' this._client.request( method, - this._buildUrl(path) + this._buildUrl(path), + headers ? headers : {} ).then( function(result) { if (self._isSuccessStatus(result.status)) { @@ -790,8 +791,8 @@ import escapeHTML from 'escape-html' * * @returns {Promise} */ - createDirectory: function(path) { - return this._simpleCall('MKCOL', path) + createDirectory: function(path, headers) { + return this._simpleCall('MKCOL', path, headers) }, /** From 20f536cba6e8722f9de62486591984f2a6243c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 14 Oct 2021 14:56:22 +0200 Subject: [PATCH 08/15] Parse X-OC-MTime and X-OC-CTime through chunking v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Upload/ChunkingV2Plugin.php | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 98e78ed7f834e..de7deda8dfa8f 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -198,6 +198,17 @@ public function beforeMove($sourcePath, $destination): bool { $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + // allow sync clients to send the modification and creation time along in a header + $updateFileInfo = []; + if ($this->server->httpRequest->getHeader('X-OC-MTime') !== null) { + $updateFileInfo['mtime'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-MTime')); + $this->server->httpResponse->setHeader('X-OC-MTime', 'accepted'); + } + if ($this->server->httpRequest->getHeader('X-OC-CTime') !== null) { + $updateFileInfo['creation_time'] = $this->sanitizeMtime($this->server->httpRequest->getHeader('X-OC-CTime')); + $this->server->httpResponse->setHeader('X-OC-CTime', 'accepted'); + } + $rootView = new View(); if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { $lastTick = time(); @@ -211,13 +222,15 @@ public function beforeMove($sourcePath, $destination): bool { }); } - $this->server->httpResponse->setBody(function () use ($targetFile, $rootView, $uploadId, $destinationName, $destinationParent, $destinationExists, $sourcePath, $destination) { + $this->server->httpResponse->setBody(function () use ($targetFile, $rootView, $uploadId, $destinationName, $destinationParent, $destinationExists, $sourcePath, $destination, $updateFileInfo) { $rootView->writeChunkedFile($targetFile->getAbsoluteInternalPath(), $uploadId); $destinationInView = $destinationParent->getFileInfo()->getPath() . '/' . $destinationName; if (!$destinationExists) { $rootView->rename($targetFile->getAbsoluteInternalPath(), $destinationInView); } + $rootView->putFileInfo($destinationInView, $updateFileInfo); + $sourceNode = $this->server->tree->getNodeForPath($sourcePath); if ($sourceNode instanceof FutureFile) { $this->uploadFolder->delete(); @@ -285,4 +298,12 @@ private function getStorage(): IStorage { /** @var IChunkedFileWrite $storage */ return $storage; } + + protected function sanitizeMtime($mtimeFromRequest) { + if (!is_numeric($mtimeFromRequest)) { + throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).'); + } + + return (int)$mtimeFromRequest; + } } From 6ffa247853b257cf6ecb1fb1e7d56426e72936eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 14 Oct 2021 17:09:48 +0200 Subject: [PATCH 09/15] Handle quota properly and add part handling to storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 2 +- apps/dav/lib/Upload/ChunkingV2Plugin.php | 27 +++++++++-- apps/dav/lib/Upload/UploadFile.php | 4 ++ apps/dav/tests/unit/CapabilitiesTest.php | 2 +- build/psalm-baseline.xml | 2 +- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + .../Files/ObjectStore/ObjectStoreStorage.php | 5 +- lib/private/Files/ObjectStore/S3.php | 11 +++++ .../IObjectStoreMultiPartUpload.php | 15 ++++-- .../Files/Storage/IChunkedFileWrite.php | 10 ++-- .../Storage/IProcessingCallbackStorage.php | 46 +++++++++++++++++++ 12 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 lib/public/Files/Storage/IProcessingCallbackStorage.php diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index 14edf17febc0a..fa03dd2730eb7 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -226,7 +226,7 @@ public function propPatch($path, PropPatch $propPatch) { */ public function delete($path) { $this->customPropertiesService->delete($this->user->getUID(), $path); - unset($this->cache[$path]); + unset($this->userCache[$path]); } /** diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index de7deda8dfa8f..629f18a8af26f 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -29,7 +29,9 @@ use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; use OCP\Files\Storage\IChunkedFileWrite; +use OCP\Files\Storage\IProcessingCallbackStorage; use OCP\Files\Storage\IStorage; use OCP\Files\StorageInvalidException; use Sabre\DAV\Exception\BadRequest; @@ -118,6 +120,10 @@ public function beforeMkcol(RequestInterface $request, ResponseInterface $respon public function beforePut(RequestInterface $request, ResponseInterface $response): bool { $this->uploadFolder = $this->server->tree->getNodeForPath(dirname($request->getPath())); + if (!$this->uploadFolder instanceof UploadFolder) { + return true; + } + try { $this->checkPrerequisites(); $storage = $this->getStorage(); @@ -143,7 +149,7 @@ public function beforePut(RequestInterface $request, ResponseInterface $response $additionalSize = (int)$request->getHeader('Content-Length'); if ($this->uploadFolder->childExists(self::TEMP_TARGET)) { - // FIXME Quota checking will not work for existing files that way + /** @var UploadFile $tempTargetFile */ $tempTargetFile = $this->uploadFolder->getChild(self::TEMP_TARGET); $tempTargetCache = $storage->getCache()->get($tempTargetFile->getInternalPath()); @@ -159,7 +165,6 @@ public function beforePut(RequestInterface $request, ResponseInterface $response $stream = $request->getBodyAsStream(); $storage->putChunkedFilePart($targetFile->getInternalPath(), $uploadId, (string)$partId, $stream, $additionalSize); - // FIXME add return value to putChunkedFilePart to validate against size $storage->getCache()->update($cacheEntry->getId(), ['size' => $cacheEntry->getSize() + $additionalSize]); if ($tempTargetFile) { @@ -211,6 +216,22 @@ public function beforeMove($sourcePath, $destination): bool { $rootView = new View(); if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { + /** @var ObjectStoreStorage $storage */ + $objectStore = $storage->getObjectStore(); + if ($objectStore instanceof IObjectStoreMultiPartUpload) { + $parts = $objectStore->getMultipartUploads($storage->getURN($targetFile->getId()), $uploadId); + $size = 0; + foreach ($parts as $part) { + $size += $part['Size']; + } + $free = $storage->free_space($destinationParent->getInternalPath()); + if ($free >= 0 && ($size > $free)) { + throw new InsufficientStorage("Insufficient space in $targetPath"); + } + } + } + if ($storage->instanceOfStorage(IProcessingCallbackStorage::class)) { + /** @var IProcessingCallbackStorage $storage */ $lastTick = time(); $storage->processingCallback('writeChunkedFile', function () use ($lastTick) { if ($lastTick < time()) { @@ -245,7 +266,7 @@ public function beforeMove($sourcePath, $destination): bool { ['200' => [ FilesPlugin::SIZE_PROPERTYNAME => $rootView->filesize($destinationInView) ]], - $destinationExists ? 204 : 201 + $destinationExists ? '204' : '201' ); echo $this->server->xml->write( '{DAV:}multistatus', diff --git a/apps/dav/lib/Upload/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php index f68f2d2abf898..f0960ddf6989b 100644 --- a/apps/dav/lib/Upload/UploadFile.php +++ b/apps/dav/lib/Upload/UploadFile.php @@ -45,6 +45,10 @@ public function get() { return $this->file->get(); } + public function getId() { + return $this->file->getId(); + } + public function getContentType() { return $this->file->getContentType(); } diff --git a/apps/dav/tests/unit/CapabilitiesTest.php b/apps/dav/tests/unit/CapabilitiesTest.php index 6a9a0c1180a82..281cd5a8cc21a 100644 --- a/apps/dav/tests/unit/CapabilitiesTest.php +++ b/apps/dav/tests/unit/CapabilitiesTest.php @@ -40,7 +40,7 @@ public function testGetCapabilities() { $capabilities = new Capabilities($config); $expected = [ 'dav' => [ - 'chunking' => '1.0', + 'chunking' => '2.0', ], ]; $this->assertSame($expected, $capabilities->getCapabilities()); diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index c5a1e69e793c1..dd056fec8e80e 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1,4 +1,4 @@ - +s diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index d99612fcab673..893e9fca7b91a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -358,6 +358,7 @@ 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => $baseDir . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => $baseDir . '/lib/public/Files/Storage/ILockingStorage.php', 'OCP\\Files\\Storage\\INotifyStorage' => $baseDir . '/lib/public/Files/Storage/INotifyStorage.php', + 'OCP\\Files\\Storage\\IProcessingCallbackStorage' => $baseDir . '/lib/public/Files/Storage/IProcessingCallbackStorage.php', 'OCP\\Files\\Storage\\IReliableEtagStorage' => $baseDir . '/lib/public/Files/Storage/IReliableEtagStorage.php', 'OCP\\Files\\Storage\\IStorage' => $baseDir . '/lib/public/Files/Storage/IStorage.php', 'OCP\\Files\\Storage\\IStorageFactory' => $baseDir . '/lib/public/Files/Storage/IStorageFactory.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2af64383d0806..a32226294e628 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -391,6 +391,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Files\\Storage\\IDisableEncryptionStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IDisableEncryptionStorage.php', 'OCP\\Files\\Storage\\ILockingStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/ILockingStorage.php', 'OCP\\Files\\Storage\\INotifyStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/INotifyStorage.php', + 'OCP\\Files\\Storage\\IProcessingCallbackStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IProcessingCallbackStorage.php', 'OCP\\Files\\Storage\\IReliableEtagStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IReliableEtagStorage.php', 'OCP\\Files\\Storage\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorage.php', 'OCP\\Files\\Storage\\IStorageFactory' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorageFactory.php', diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index ac6ede4a28252..b5b8e8a65f4e3 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -45,10 +45,11 @@ use OCP\Files\ObjectStore\IObjectStore; use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; use OCP\Files\Storage\IChunkedFileWrite; +use OCP\Files\Storage\IProcessingCallbackStorage; use OCP\Files\Storage\IStorage; use OCP\ICache; -class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite { +class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite, IProcessingCallbackStorage { use CopyDirectory; /** @@ -663,7 +664,7 @@ public function putChunkedFilePart(string $targetPath, string $writeToken, strin $this->uploadCache->set($this->getUploadCacheKey($urn, $uploadId, 'parts'), $parts); } - public function processingCallback(string $method, callable $callback) { + public function processingCallback(string $method, callable $callback): void { if (in_array($method, ['writeChunkedFile'])) { if (!isset($this->processingCallbacks[$method])) { $this->processingCallbacks[$method] = []; diff --git a/lib/private/Files/ObjectStore/S3.php b/lib/private/Files/ObjectStore/S3.php index da06192cc6928..078d91240050c 100644 --- a/lib/private/Files/ObjectStore/S3.php +++ b/lib/private/Files/ObjectStore/S3.php @@ -68,6 +68,17 @@ public function uploadMultipartPart(string $urn, string $uploadId, int $partId, ]); } + public function getMultipartUploads(string $urn, string $uploadId): array { + $parts = $this->getConnection()->listParts([ + 'Bucket' => $this->bucket, + 'Key' => $urn, + 'UploadId' => $uploadId + ]); + return array_map(function ($part) { + return $part; + }, $parts->get('Parts')); + } + public function completeMultipartUpload(string $urn, string $uploadId, array $result, callable $processingCallback = null): int { $this->getConnection()->completeMultipartUpload([ 'Bucket' => $this->bucket, diff --git a/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php index cd94fdc184d31..bb7ca003536a4 100644 --- a/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php +++ b/lib/public/Files/ObjectStore/IObjectStoreMultiPartUpload.php @@ -29,26 +29,31 @@ use Aws\Result; /** - * @since 22.0.0 + * @since 23.0.0 */ interface IObjectStoreMultiPartUpload { /** - * @since 22.0.0 + * @since 23.0.0 */ public function initiateMultipartUpload(string $urn): string; /** - * @since 22.0.0 + * @since 23.0.0 */ public function uploadMultipartPart(string $urn, string $uploadId, int $partId, $stream, $size): Result; /** - * @since 22.0.0 + * @since 23.0.0 */ public function completeMultipartUpload(string $urn, string $uploadId, array $result): int; /** - * @since 22.0.0 + * @since 23.0.0 */ public function abortMultipartUpload(string $urn, string $uploadId): void; + + /** + * @since 23.0.0 + */ + public function getMultipartUploads(string $urn, string $uploadId): array; } diff --git a/lib/public/Files/Storage/IChunkedFileWrite.php b/lib/public/Files/Storage/IChunkedFileWrite.php index 10eeac2ab4e3a..b875b7eed417f 100644 --- a/lib/public/Files/Storage/IChunkedFileWrite.php +++ b/lib/public/Files/Storage/IChunkedFileWrite.php @@ -29,7 +29,7 @@ use OCP\Files\GenericFileException; /** - * @since 22.0.0 + * @since 23.0.0 */ interface IChunkedFileWrite extends IStorage { @@ -37,7 +37,7 @@ interface IChunkedFileWrite extends IStorage { * @param string $targetPath Relative target path in the storage * @return string writeToken to be used with the other methods to uniquely identify the file write operation * @throws GenericFileException - * @since 22.0.0 + * @since 23.0.0 */ public function beginChunkedFile(string $targetPath): string; @@ -48,7 +48,7 @@ public function beginChunkedFile(string $targetPath): string; * @param resource $data * @param int|null $size * @throws GenericFileException - * @since 22.0.0 + * @since 23.0.0 */ public function putChunkedFilePart(string $targetPath, string $writeToken, string $chunkId, $data, int $size = null): void; @@ -57,7 +57,7 @@ public function putChunkedFilePart(string $targetPath, string $writeToken, strin * @param string $writeToken * @return int * @throws GenericFileException - * @since 22.0.0 + * @since 23.0.0 */ public function writeChunkedFile(string $targetPath, string $writeToken): int; @@ -65,7 +65,7 @@ public function writeChunkedFile(string $targetPath, string $writeToken): int; * @param string $targetPath * @param string $writeToken * @throws GenericFileException - * @since 22.0.0 + * @since 23.0.0 */ public function cancelChunkedFile(string $targetPath, string $writeToken): void; } diff --git a/lib/public/Files/Storage/IProcessingCallbackStorage.php b/lib/public/Files/Storage/IProcessingCallbackStorage.php new file mode 100644 index 0000000000000..d91a9298fac12 --- /dev/null +++ b/lib/public/Files/Storage/IProcessingCallbackStorage.php @@ -0,0 +1,46 @@ + + * + * @author Julius Härtl + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCP\Files\Storage; + +use OCP\Files\GenericFileException; + +/** + * Interface that adds the ability to register processing callbacks for storage operation + * + * @since 23.0.0 + */ +interface IProcessingCallbackStorage extends IStorage { + /** + * Register a callback for a processing storage operation + * + * @param string $method + * @param callable $callback being called regularly during the storage operation + * @return void + * @throws GenericFileException + * @since 23.0.0 + */ + public function processingCallback(string $method, callable $callback): void; +} From 1b2cb18a47e8ef53329d4c34986a97b5253bf1de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 24 May 2022 12:54:46 +0200 Subject: [PATCH 10/15] Expose parts through webdav upload folder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../composer/composer/autoload_classmap.php | 1 + .../dav/composer/composer/autoload_static.php | 1 + apps/dav/lib/Upload/ChunkingV2Plugin.php | 30 +++-- apps/dav/lib/Upload/PartFile.php | 112 ++++++++++++++++++ apps/dav/lib/Upload/UploadFolder.php | 19 +++ 5 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 apps/dav/lib/Upload/PartFile.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index ea79b9a519775..a2436c29a8a4a 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -315,6 +315,7 @@ 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => $baseDir . '/../lib/Upload/ChunkingV2Plugin.php', 'OCA\\DAV\\Upload\\CleanupService' => $baseDir . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => $baseDir . '/../lib/Upload/FutureFile.php', + 'OCA\\DAV\\Upload\\PartFile' => $baseDir . '/../lib/Upload/PartFile.php', 'OCA\\DAV\\Upload\\RootCollection' => $baseDir . '/../lib/Upload/RootCollection.php', 'OCA\\DAV\\Upload\\UploadFile' => $baseDir . '/../lib/Upload/UploadFile.php', 'OCA\\DAV\\Upload\\UploadFolder' => $baseDir . '/../lib/Upload/UploadFolder.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index b91ffee6c6fdd..767d1dda28171 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -330,6 +330,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Upload\\ChunkingV2Plugin' => __DIR__ . '/..' . '/../lib/Upload/ChunkingV2Plugin.php', 'OCA\\DAV\\Upload\\CleanupService' => __DIR__ . '/..' . '/../lib/Upload/CleanupService.php', 'OCA\\DAV\\Upload\\FutureFile' => __DIR__ . '/..' . '/../lib/Upload/FutureFile.php', + 'OCA\\DAV\\Upload\\PartFile' => __DIR__ . '/..' . '/../lib/Upload/PartFile.php', 'OCA\\DAV\\Upload\\RootCollection' => __DIR__ . '/..' . '/../lib/Upload/RootCollection.php', 'OCA\\DAV\\Upload\\UploadFile' => __DIR__ . '/..' . '/../lib/Upload/UploadFile.php', 'OCA\\DAV\\Upload\\UploadFolder' => __DIR__ . '/..' . '/../lib/Upload/UploadFolder.php', diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 629f18a8af26f..77db7d6487d8c 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -55,8 +55,10 @@ class ChunkingV2Plugin extends ServerPlugin { private const TEMP_TARGET = '.target'; - private const OBJECT_UPLOAD_TARGET = '{http://nextcloud.org/ns}upload-target'; - private const OBJECT_UPLOAD_CHUNKTOKEN = '{http://nextcloud.org/ns}upload-chunktoken'; + public const OBJECT_UPLOAD_CACHE_KEY = 'chunkedv2'; + public const OBJECT_UPLOAD_TARGET_ID = '{http://nextcloud.org/ns}upload-target-id'; + public const OBJECT_UPLOAD_TARGET = '{http://nextcloud.org/ns}upload-target'; + public const OBJECT_UPLOAD_CHUNKTOKEN = '{http://nextcloud.org/ns}upload-chunktoken'; private const DESTINATION_HEADER = 'X-Chunking-Destination'; @@ -71,6 +73,8 @@ public function initialize(Server $server) { $server->on('beforeMove', [$this, 'beforeMove'], 90); $this->server = $server; + + $this->cache = \OC::$server->getMemCacheFactory()->createDistributed(self::OBJECT_UPLOAD_CACHE_KEY); } /** @@ -108,8 +112,8 @@ public function beforeMkcol(RequestInterface $request, ResponseInterface $respon $uploadId = $storage->beginChunkedFile($targetFile->getInternalPath()); - // DAV properties on the UploadFolder are used in order to properly cleanup stale chunked file writes and to persist the target path - $this->server->updateProperties($request->getPath(), [ + $this->cache->set($this->uploadFolder->getName(), [ + self::OBJECT_UPLOAD_TARGET_ID => $targetFile->getId(), self::OBJECT_UPLOAD_CHUNKTOKEN => $uploadId, self::OBJECT_UPLOAD_TARGET => $targetPath, ]); @@ -131,9 +135,9 @@ public function beforePut(RequestInterface $request, ResponseInterface $response return true; } - $properties = $this->server->getProperties(dirname($request->getPath()) . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); - $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; - $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + $properties = $this->getUploadSession(); + $targetPath = $properties[self::OBJECT_UPLOAD_TARGET] ?? null; + $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN] ?? null; if (empty($targetPath) || empty($uploadId)) { throw new PreconditionFailed('Missing metadata for chunked upload'); } @@ -183,9 +187,9 @@ public function beforeMove($sourcePath, $destination): bool { } catch (StorageInvalidException | BadRequest $e) { return true; } - $properties = $this->server->getProperties(dirname($sourcePath) . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); - $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; - $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; + $properties = $this->getUploadSession(); + $targetPath = $properties[self::OBJECT_UPLOAD_TARGET] ?? null; + $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN] ?? null; // FIXME: check if $destination === TARGET if (empty($targetPath) || empty($uploadId)) { @@ -287,7 +291,7 @@ public function beforeDelete(RequestInterface $request, ResponseInterface $respo return true; } - $properties = $this->server->getProperties($request->getPath() . '/', [ self::OBJECT_UPLOAD_CHUNKTOKEN, self::OBJECT_UPLOAD_TARGET ]); + $properties = $this->getUploadSession(); $targetPath = $properties[self::OBJECT_UPLOAD_TARGET]; $uploadId = $properties[self::OBJECT_UPLOAD_CHUNKTOKEN]; if (!$targetPath || !$uploadId) { @@ -327,4 +331,8 @@ protected function sanitizeMtime($mtimeFromRequest) { return (int)$mtimeFromRequest; } + + public function getUploadSession(): array { + return $this->cache->get($this->uploadFolder->getName()) ?? []; + } } diff --git a/apps/dav/lib/Upload/PartFile.php b/apps/dav/lib/Upload/PartFile.php new file mode 100644 index 0000000000000..a64f1945487f1 --- /dev/null +++ b/apps/dav/lib/Upload/PartFile.php @@ -0,0 +1,112 @@ + + * @author Lukas Reschke + * @author Thomas Müller + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\DAV\Upload; + +use OCA\DAV\Connector\Sabre\Directory; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\IFile; + +/** + * This class represents an Upload part which is not present on the storage itself + * 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; + } + + /** + * @inheritdoc + */ + public function put($data) { + throw new Forbidden('Permission denied to put into this file'); + } + + /** + * @inheritdoc + */ + public function get() { + throw new Forbidden('Permission denied to get this file'); + } + + public function getPath() { + return $this->root->getFileInfo()->getInternalPath() . '/' . $this->partInfo['PartNumber']; + } + + /** + * @inheritdoc + */ + public function getContentType() { + return 'application/octet-stream'; + } + + /** + * @inheritdoc + */ + public function getETag() { + return $this->partInfo['ETag']; + } + + /** + * @inheritdoc + */ + public function getSize() { + return $this->partInfo['Size']; + } + + /** + * @inheritdoc + */ + public function delete() { + $this->root->delete(); + } + + /** + * @inheritdoc + */ + public function getName() { + return $this->partInfo['PartNumber']; + } + + /** + * @inheritdoc + */ + public function setName($name) { + throw new Forbidden('Permission denied to rename this file'); + } + + /** + * @inheritdoc + */ + public function getLastModified() { + return $this->partInfo['LastModified']; + } +} diff --git a/apps/dav/lib/Upload/UploadFolder.php b/apps/dav/lib/Upload/UploadFolder.php index e6c9525940e72..6c2a5b8923901 100644 --- a/apps/dav/lib/Upload/UploadFolder.php +++ b/apps/dav/lib/Upload/UploadFolder.php @@ -24,7 +24,9 @@ */ namespace OCA\DAV\Upload; +use OC\Files\ObjectStore\ObjectStoreStorage; use OCA\DAV\Connector\Sabre\Directory; +use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload; use OCP\Files\Storage\IStorage; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; @@ -70,6 +72,23 @@ public function getChildren() { $children[] = new UploadFile($child); } + if ($this->storage->instanceOfStorage(ObjectStoreStorage::class)) { + /** @var ObjectStoreStorage $storage */ + $objectStore = $this->storage->getObjectStore(); + if ($objectStore instanceof IObjectStoreMultiPartUpload) { + $cache = \OC::$server->getMemCacheFactory()->createDistributed(ChunkingV2Plugin::OBJECT_UPLOAD_CACHE_KEY); + $uploadSession = $cache->get($this->getName()); + if ($uploadSession) { + $uploadId = $uploadSession[ChunkingV2Plugin::OBJECT_UPLOAD_CHUNKTOKEN]; + $id = $uploadSession[ChunkingV2Plugin::OBJECT_UPLOAD_TARGET_ID]; + $parts = $objectStore->getMultipartUploads($this->storage->getURN($id), $uploadId); + foreach ($parts as $part) { + $children[] = new PartFile($this->node, $part); + } + } + } + } + return $children; } From 8d4a2d676a10b56423f37f6f69982bb95552ad09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 24 May 2022 12:55:13 +0200 Subject: [PATCH 11/15] Fix encoding of paths in destitation header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Upload/ChunkingV2Plugin.php | 2 ++ apps/files/js/file-upload.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/dav/lib/Upload/ChunkingV2Plugin.php b/apps/dav/lib/Upload/ChunkingV2Plugin.php index 77db7d6487d8c..5f6528bc572bc 100644 --- a/apps/dav/lib/Upload/ChunkingV2Plugin.php +++ b/apps/dav/lib/Upload/ChunkingV2Plugin.php @@ -108,6 +108,8 @@ public function beforeMkcol(RequestInterface $request, ResponseInterface $respon return true; } + $targetPath = $this->server->calculateUri($targetPath); + $targetFile = $this->getTargetFile($targetPath, true); $uploadId = $storage->beginChunkedFile($targetFile->getInternalPath()); diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index ced5fa86940d6..0a3a46a2ea172 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -272,7 +272,7 @@ OC.FileUpload.prototype = { var headers = {}; if (OC.getCapabilities().dav.chunking === '2.0') { headers = { - 'X-Chunking-Destination': this.getTargetDestination() + 'X-Chunking-Destination': this.uploader.davClient._buildUrl(this.getTargetDestination()) }; } @@ -318,7 +318,7 @@ OC.FileUpload.prototype = { headers['OC-Total-Length'] = size; } if (OC.getCapabilities().dav.chunking === '2.0') { - headers['X-Chunking-Destination'] = this.getTargetDestination(); + headers['X-Chunking-Destination'] = this.uploader.davClient._buildUrl(this.getTargetDestination()); } return this.uploader.davClient.move( @@ -1343,7 +1343,7 @@ OC.Uploader.prototype = _.extend({ if (OC.getCapabilities().dav.chunking === '2.0') { // Calculate chunk index for usage with s3 chunkId = Math.ceil((data.chunkSize+Number(chunkId)) / upload.uploader.fileUploadParam.maxChunkSize); - data.headers['X-Chunking-Destination'] = upload.getTargetDestination(); + data.headers['X-Chunking-Destination'] = self.davClient._buildUrl(upload.getTargetDestination()); } data.url = OC.getRootPath() + From 85d4f3e317aacd3ecc51aae20b79010bb5cadc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 10 Nov 2022 08:52:37 +0100 Subject: [PATCH 12/15] Bump bundles for 25.0.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- dist/core-files_client.js | 4 ++-- dist/core-files_client.js.map | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dist/core-files_client.js b/dist/core-files_client.js index bd41dac2f65b7..e2439ab0bcdd3 100644 --- a/dist/core-files_client.js +++ b/dist/core-files_client.js @@ -1,3 +1,3 @@ /*! For license information please see core-files_client.js.LICENSE.txt */ -!function(){"use strict";var e,t={7913:function(e,t,r){var s=r(95573),n=r.n(s),i=r(25108);!function(e,t){var r=function t(r){this._root=r.root,"/"===this._root.charAt(this._root.length-1)&&(this._root=this._root.substr(0,this._root.length-1));var s=t.PROTOCOL_HTTP+"://";r.useHTTPS&&(s=t.PROTOCOL_HTTPS+"://"),s+=r.host+this._root,this._host=r.host,this._defaultHeaders=r.defaultHeaders||{"X-Requested-With":"XMLHttpRequest",requesttoken:e.requestToken},this._baseUrl=s;var n={baseUrl:this._baseUrl,xmlNamespaces:{"DAV:":"d","http://owncloud.org/ns":"oc","http://nextcloud.org/ns":"nc","http://open-collaboration-services.org/ns":"ocs"}};r.userName&&(n.userName=r.userName),r.password&&(n.password=r.password),this._client=new dav.Client(n),this._client.xhrProvider=_.bind(this._xhrProvider,this),this._fileInfoParsers=[]};r.NS_OWNCLOUD="http://owncloud.org/ns",r.NS_NEXTCLOUD="http://nextcloud.org/ns",r.NS_DAV="DAV:",r.NS_OCS="http://open-collaboration-services.org/ns",r.PROPERTY_GETLASTMODIFIED="{"+r.NS_DAV+"}getlastmodified",r.PROPERTY_GETETAG="{"+r.NS_DAV+"}getetag",r.PROPERTY_GETCONTENTTYPE="{"+r.NS_DAV+"}getcontenttype",r.PROPERTY_RESOURCETYPE="{"+r.NS_DAV+"}resourcetype",r.PROPERTY_INTERNAL_FILEID="{"+r.NS_OWNCLOUD+"}fileid",r.PROPERTY_PERMISSIONS="{"+r.NS_OWNCLOUD+"}permissions",r.PROPERTY_SIZE="{"+r.NS_OWNCLOUD+"}size",r.PROPERTY_GETCONTENTLENGTH="{"+r.NS_DAV+"}getcontentlength",r.PROPERTY_ISENCRYPTED="{"+r.NS_DAV+"}is-encrypted",r.PROPERTY_SHARE_PERMISSIONS="{"+r.NS_OCS+"}share-permissions",r.PROPERTY_SHARE_ATTRIBUTES="{"+r.NS_NEXTCLOUD+"}share-attributes",r.PROPERTY_QUOTA_AVAILABLE_BYTES="{"+r.NS_DAV+"}quota-available-bytes",r.PROTOCOL_HTTP="http",r.PROTOCOL_HTTPS="https",r._PROPFIND_PROPERTIES=[[r.NS_DAV,"getlastmodified"],[r.NS_DAV,"getetag"],[r.NS_DAV,"getcontenttype"],[r.NS_DAV,"resourcetype"],[r.NS_OWNCLOUD,"fileid"],[r.NS_OWNCLOUD,"permissions"],[r.NS_OWNCLOUD,"size"],[r.NS_DAV,"getcontentlength"],[r.NS_DAV,"quota-available-bytes"],[r.NS_NEXTCLOUD,"has-preview"],[r.NS_NEXTCLOUD,"mount-type"],[r.NS_NEXTCLOUD,"is-encrypted"],[r.NS_OCS,"share-permissions"],[r.NS_NEXTCLOUD,"share-attributes"]],r.prototype={_root:null,_client:null,_fileInfoParsers:[],_xhrProvider:function(){var t=this._defaultHeaders,r=new XMLHttpRequest,s=r.open;return r.open=function(){var e=s.apply(this,arguments);return _.each(t,(function(e,t){r.setRequestHeader(t,e)})),e},e.registerXHRForErrorProcessing(r),r},_buildUrl:function(){var e=this._buildPath.apply(this,arguments);return"/"===e.charAt([e.length-1])&&(e=e.substr(0,e.length-1)),"/"===e.charAt(0)&&(e=e.substr(1)),this._baseUrl+"/"+e},_buildPath:function(){var t,r=e.joinPaths.apply(this,arguments),s=r.split("/");for(t=0;t=200&&e<=299},_getSabreException:function(e){var t={},r=e.xhr.responseXML;if(null===r)return t;var s=r.getElementsByTagNameNS("http://sabredav.org/ns","message"),n=r.getElementsByTagNameNS("http://sabredav.org/ns","exception");return s.length&&(t.message=s[0].textContent),n.length&&(t.exception=n[0].textContent),t},getPropfindProperties:function(){return this._propfindProperties||(this._propfindProperties=_.map(r._PROPFIND_PROPERTIES,(function(e){return"{"+e[0]+"}"+e[1]}))),this._propfindProperties},getFolderContents:function(e,t){e||(e=""),t=t||{};var r,s=this,n=$.Deferred(),i=n.promise();return r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,this._client.propFind(this._buildUrl(e),r,1).then((function(e){if(s._isSuccessStatus(e.status)){var r=s._parseResult(e.body);t&&t.includeParent||r.shift(),n.resolve(e.status,r)}else e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e)})),i},getFilteredFiles:function(e,t){t=t||{};var r,s=this,i=$.Deferred(),o=i.promise();if(r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,!e||!e.systemTagIds&&_.isUndefined(e.favorite)&&!e.circlesIds)throw"Missing filter argument";var a,u="\n",_.each(r,(function(e){var t=s._client.parseClarkNotation(e);u+=" <"+s._client.xmlNamespaces[t.namespace]+":"+t.name+" />\n"})),u+=" \n",u+=" \n",_.each(e.systemTagIds,(function(e){u+=" "+n()(e)+"\n"})),_.each(e.circlesIds,(function(e){u+=" "+n()(e)+"\n"})),e.favorite&&(u+=" "+(e.favorite?"1":"0")+"\n"),u+=" \n",u+="\n",this._client.request("REPORT",this._buildUrl(),{},u).then((function(e){if(s._isSuccessStatus(e.status)){var t=s._parseResult(e.body);i.resolve(e.status,t)}else e=_.extend(e,s._getSabreException(e)),i.reject(e.status,e)})),o},getFileInfo:function(e,t){e||(e=""),t=t||{};var r,s=this,n=$.Deferred(),i=n.promise();return r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,this._client.propFind(this._buildUrl(e),r,0).then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status,s._parseResult([e.body])[0]):(e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e))})),i},getFileContents:function(e){if(!e)throw'Missing argument "path"';var t=this,r=$.Deferred(),s=r.promise();return this._client.request("GET",this._buildUrl(e)).then((function(e){t._isSuccessStatus(e.status)?r.resolve(e.status,e.body):(e=_.extend(e,t._getSabreException(e)),r.reject(e.status,e))})),s},putFileContents:function(e,t,r){if(!e)throw'Missing argument "path"';var s=this,n=$.Deferred(),i=n.promise(),o={},a="text/plain;charset=utf-8";return(r=r||{}).contentType&&(a=r.contentType),o["Content-Type"]=a,(_.isUndefined(r.overwrite)||r.overwrite)&&(o["If-None-Match"]="*"),this._client.request("PUT",this._buildUrl(e),o,t||"").then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status):(e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e))})),i},_simpleCall:function(e,t){if(!t)throw'Missing argument "path"';var r=this,s=$.Deferred(),n=s.promise();return this._client.request(e,this._buildUrl(t)).then((function(e){r._isSuccessStatus(e.status)?s.resolve(e.status):(e=_.extend(e,r._getSabreException(e)),s.reject(e.status,e))})),n},createDirectory:function(e){return this._simpleCall("MKCOL",e)},remove:function(e){return this._simpleCall("DELETE",e)},move:function(e,t,r,s){if(!e)throw'Missing argument "path"';if(!t)throw'Missing argument "destinationPath"';var n=this,i=$.Deferred(),o=i.promise();return s=_.extend({},s,{Destination:this._buildUrl(t)}),r||(s.Overwrite="F"),this._client.request("MOVE",this._buildUrl(e),s).then((function(e){n._isSuccessStatus(e.status)?i.resolve(e.status):(e=_.extend(e,n._getSabreException(e)),i.reject(e.status,e))})),o},copy:function(e,t,r){if(!e)throw'Missing argument "path"';if(!t)throw'Missing argument "destinationPath"';var s=this,n=$.Deferred(),i=n.promise(),o={Destination:this._buildUrl(t)};return r||(o.Overwrite="F"),this._client.request("COPY",this._buildUrl(e),o).then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status):n.reject(e.status)})),i},addFileInfoParser:function(e){this._fileInfoParsers.push(e)},getClient:function(){return this._client},getUserName:function(){return this._client.userName},getPassword:function(){return this._client.password},getBaseUrl:function(){return this._client.baseUrl},getHost:function(){return this._host}},e.Files||(e.Files={}),e.Files.getClient=function(){if(e.Files._defaultClient)return e.Files._defaultClient;var t=new e.Files.Client({host:e.getHost(),port:e.getPort(),root:e.linkToRemoteBase("dav")+"/files/"+e.getCurrentUser().uid,useHTTPS:"https"===e.getProtocol()});return e.Files._defaultClient=t,t},e.Files.Client=r}(OC,OC.Files.FileInfo)}},r={};function s(e){var n=r[e];if(void 0!==n)return n.exports;var i=r[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,s),i.loaded=!0,i.exports}s.m=t,s.amdD=function(){throw new Error("define cannot be used indirect")},s.amdO={},e=[],s.O=function(t,r,n,i){if(!r){var o=1/0;for(l=0;l=i)&&Object.keys(s.O).every((function(e){return s.O[e](r[u])}))?r.splice(u--,1):(a=!1,i0&&e[l-1][2]>i;l--)e[l]=e[l-1];e[l]=[r,n,i]},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,{a:t}),t},s.d=function(e,t){for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},s.j=5578,function(){s.b=document.baseURI||self.location.href;var e={5578:0};s.O.j=function(t){return 0===e[t]};var t=function(t,r){var n,i,o=r[0],a=r[1],u=r[2],c=0;if(o.some((function(t){return 0!==e[t]}))){for(n in a)s.o(a,n)&&(s.m[n]=a[n]);if(u)var l=u(s)}for(t&&t(r);c=200&&e<=299},_getSabreException:function(e){var t={},r=e.xhr.responseXML;if(null===r)return t;var s=r.getElementsByTagNameNS("http://sabredav.org/ns","message"),n=r.getElementsByTagNameNS("http://sabredav.org/ns","exception");return s.length&&(t.message=s[0].textContent),n.length&&(t.exception=n[0].textContent),t},getPropfindProperties:function(){return this._propfindProperties||(this._propfindProperties=_.map(r._PROPFIND_PROPERTIES,(function(e){return"{"+e[0]+"}"+e[1]}))),this._propfindProperties},getFolderContents:function(e,t){e||(e=""),t=t||{};var r,s=this,n=$.Deferred(),i=n.promise();return r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,this._client.propFind(this._buildUrl(e),r,1).then((function(e){if(s._isSuccessStatus(e.status)){var r=s._parseResult(e.body);t&&t.includeParent||r.shift(),n.resolve(e.status,r)}else e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e)})),i},getFilteredFiles:function(e,t){t=t||{};var r,s=this,i=$.Deferred(),o=i.promise();if(r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,!e||!e.systemTagIds&&_.isUndefined(e.favorite)&&!e.circlesIds)throw"Missing filter argument";var a,u="\n",_.each(r,(function(e){var t=s._client.parseClarkNotation(e);u+=" <"+s._client.xmlNamespaces[t.namespace]+":"+t.name+" />\n"})),u+=" \n",u+=" \n",_.each(e.systemTagIds,(function(e){u+=" "+n()(e)+"\n"})),_.each(e.circlesIds,(function(e){u+=" "+n()(e)+"\n"})),e.favorite&&(u+=" "+(e.favorite?"1":"0")+"\n"),u+=" \n",u+="\n",this._client.request("REPORT",this._buildUrl(),{},u).then((function(e){if(s._isSuccessStatus(e.status)){var t=s._parseResult(e.body);i.resolve(e.status,t)}else e=_.extend(e,s._getSabreException(e)),i.reject(e.status,e)})),o},getFileInfo:function(e,t){e||(e=""),t=t||{};var r,s=this,n=$.Deferred(),i=n.promise();return r=_.isUndefined(t.properties)?this.getPropfindProperties():t.properties,this._client.propFind(this._buildUrl(e),r,0).then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status,s._parseResult([e.body])[0]):(e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e))})),i},getFileContents:function(e){if(!e)throw'Missing argument "path"';var t=this,r=$.Deferred(),s=r.promise();return this._client.request("GET",this._buildUrl(e)).then((function(e){t._isSuccessStatus(e.status)?r.resolve(e.status,e.body):(e=_.extend(e,t._getSabreException(e)),r.reject(e.status,e))})),s},putFileContents:function(e,t,r){if(!e)throw'Missing argument "path"';var s=this,n=$.Deferred(),i=n.promise(),o={},a="text/plain;charset=utf-8";return(r=r||{}).contentType&&(a=r.contentType),o["Content-Type"]=a,(_.isUndefined(r.overwrite)||r.overwrite)&&(o["If-None-Match"]="*"),this._client.request("PUT",this._buildUrl(e),o,t||"").then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status):(e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e))})),i},_simpleCall:function(e,t,r){if(!t)throw'Missing argument "path"';var s=this,n=$.Deferred(),i=n.promise();return this._client.request(e,this._buildUrl(t),r||{}).then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status):(e=_.extend(e,s._getSabreException(e)),n.reject(e.status,e))})),i},createDirectory:function(e,t){return this._simpleCall("MKCOL",e,t)},remove:function(e){return this._simpleCall("DELETE",e)},move:function(e,t,r,s){if(!e)throw'Missing argument "path"';if(!t)throw'Missing argument "destinationPath"';var n=this,i=$.Deferred(),o=i.promise();return s=_.extend({},s,{Destination:this._buildUrl(t)}),r||(s.Overwrite="F"),this._client.request("MOVE",this._buildUrl(e),s).then((function(e){n._isSuccessStatus(e.status)?i.resolve(e.status):(e=_.extend(e,n._getSabreException(e)),i.reject(e.status,e))})),o},copy:function(e,t,r){if(!e)throw'Missing argument "path"';if(!t)throw'Missing argument "destinationPath"';var s=this,n=$.Deferred(),i=n.promise(),o={Destination:this._buildUrl(t)};return r||(o.Overwrite="F"),this._client.request("COPY",this._buildUrl(e),o).then((function(e){s._isSuccessStatus(e.status)?n.resolve(e.status):n.reject(e.status)})),i},addFileInfoParser:function(e){this._fileInfoParsers.push(e)},getClient:function(){return this._client},getUserName:function(){return this._client.userName},getPassword:function(){return this._client.password},getBaseUrl:function(){return this._client.baseUrl},getHost:function(){return this._host}},e.Files||(e.Files={}),e.Files.getClient=function(){if(e.Files._defaultClient)return e.Files._defaultClient;var t=new e.Files.Client({host:e.getHost(),port:e.getPort(),root:e.linkToRemoteBase("dav")+"/files/"+e.getCurrentUser().uid,useHTTPS:"https"===e.getProtocol()});return e.Files._defaultClient=t,t},e.Files.Client=r}(OC,OC.Files.FileInfo)}},r={};function s(e){var n=r[e];if(void 0!==n)return n.exports;var i=r[e]={id:e,loaded:!1,exports:{}};return t[e].call(i.exports,i,i.exports,s),i.loaded=!0,i.exports}s.m=t,s.amdD=function(){throw new Error("define cannot be used indirect")},s.amdO={},e=[],s.O=function(t,r,n,i){if(!r){var o=1/0;for(l=0;l=i)&&Object.keys(s.O).every((function(e){return s.O[e](r[u])}))?r.splice(u--,1):(a=!1,i0&&e[l-1][2]>i;l--)e[l]=e[l-1];e[l]=[r,n,i]},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,{a:t}),t},s.d=function(e,t){for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},s.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},s.j=5578,function(){s.b=document.baseURI||self.location.href;var e={5578:0};s.O.j=function(t){return 0===e[t]};var t=function(t,r){var n,i,o=r[0],a=r[1],u=r[2],c=0;if(o.some((function(t){return 0!==e[t]}))){for(n in a)s.o(a,n)&&(s.m[n]=a[n]);if(u)var l=u(s)}for(t&&t(r);c 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","/**\n * Copyright (c) 2015\n *\n * @author Bjoern Schiessle \n * @author John Molakvoæ \n * @author Julius Härtl \n * @author Lukas Reschke \n * @author Michael Jobst \n * @author Robin Appelman \n * @author Roeland Jago Douma \n * @author Thomas Citharel \n * @author Tomasz Grobelny \n * @author Vincent Petry \n * @author Vinicius Cubas Brand \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n/* eslint-disable */\nimport escapeHTML from 'escape-html'\n\n/* global dav */\n\n(function(OC, FileInfo) {\n\t/**\n\t * @class OC.Files.Client\n\t * @classdesc Client to access files on the server\n\t *\n\t * @param {Object} options\n\t * @param {String} options.host host name\n\t * @param {number} [options.port] port\n\t * @param {boolean} [options.useHTTPS] whether to use https\n\t * @param {String} [options.root] root path\n\t * @param {String} [options.userName] user name\n\t * @param {String} [options.password] password\n\t *\n\t * @since 8.2\n\t */\n\tvar Client = function(options) {\n\t\tthis._root = options.root\n\t\tif (this._root.charAt(this._root.length - 1) === '/') {\n\t\t\tthis._root = this._root.substr(0, this._root.length - 1)\n\t\t}\n\n\t\tlet url = Client.PROTOCOL_HTTP + '://'\n\t\tif (options.useHTTPS) {\n\t\t\turl = Client.PROTOCOL_HTTPS + '://'\n\t\t}\n\n\t\turl += options.host + this._root\n\t\tthis._host = options.host\n\t\tthis._defaultHeaders = options.defaultHeaders || {\n\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t'requesttoken': OC.requestToken,\n\t\t}\n\t\tthis._baseUrl = url\n\n\t\tconst clientOptions = {\n\t\t\tbaseUrl: this._baseUrl,\n\t\t\txmlNamespaces: {\n\t\t\t\t'DAV:': 'd',\n\t\t\t\t'http://owncloud.org/ns': 'oc',\n\t\t\t\t'http://nextcloud.org/ns': 'nc',\n\t\t\t\t'http://open-collaboration-services.org/ns': 'ocs',\n\t\t\t},\n\t\t}\n\t\tif (options.userName) {\n\t\t\tclientOptions.userName = options.userName\n\t\t}\n\t\tif (options.password) {\n\t\t\tclientOptions.password = options.password\n\t\t}\n\t\tthis._client = new dav.Client(clientOptions)\n\t\tthis._client.xhrProvider = _.bind(this._xhrProvider, this)\n\t\tthis._fileInfoParsers = []\n\t}\n\n\tClient.NS_OWNCLOUD = 'http://owncloud.org/ns'\n\tClient.NS_NEXTCLOUD = 'http://nextcloud.org/ns'\n\tClient.NS_DAV = 'DAV:'\n\tClient.NS_OCS = 'http://open-collaboration-services.org/ns'\n\n\tClient.PROPERTY_GETLASTMODIFIED\t= '{' + Client.NS_DAV + '}getlastmodified'\n\tClient.PROPERTY_GETETAG\t= '{' + Client.NS_DAV + '}getetag'\n\tClient.PROPERTY_GETCONTENTTYPE\t= '{' + Client.NS_DAV + '}getcontenttype'\n\tClient.PROPERTY_RESOURCETYPE\t= '{' + Client.NS_DAV + '}resourcetype'\n\tClient.PROPERTY_INTERNAL_FILEID\t= '{' + Client.NS_OWNCLOUD + '}fileid'\n\tClient.PROPERTY_PERMISSIONS\t= '{' + Client.NS_OWNCLOUD + '}permissions'\n\tClient.PROPERTY_SIZE\t= '{' + Client.NS_OWNCLOUD + '}size'\n\tClient.PROPERTY_GETCONTENTLENGTH\t= '{' + Client.NS_DAV + '}getcontentlength'\n\tClient.PROPERTY_ISENCRYPTED\t= '{' + Client.NS_DAV + '}is-encrypted'\n\tClient.PROPERTY_SHARE_PERMISSIONS\t= '{' + Client.NS_OCS + '}share-permissions'\n\tClient.PROPERTY_SHARE_ATTRIBUTES\t= '{' + Client.NS_NEXTCLOUD + '}share-attributes'\n\tClient.PROPERTY_QUOTA_AVAILABLE_BYTES\t= '{' + Client.NS_DAV + '}quota-available-bytes'\n\n\tClient.PROTOCOL_HTTP\t= 'http'\n\tClient.PROTOCOL_HTTPS\t= 'https'\n\n\tClient._PROPFIND_PROPERTIES = [\n\t\t/**\n\t\t * Modified time\n\t\t */\n\t\t[Client.NS_DAV, 'getlastmodified'],\n\t\t/**\n\t\t * Etag\n\t\t */\n\t\t[Client.NS_DAV, 'getetag'],\n\t\t/**\n\t\t * Mime type\n\t\t */\n\t\t[Client.NS_DAV, 'getcontenttype'],\n\t\t/**\n\t\t * Resource type \"collection\" for folders, empty otherwise\n\t\t */\n\t\t[Client.NS_DAV, 'resourcetype'],\n\t\t/**\n\t\t * File id\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'fileid'],\n\t\t/**\n\t\t * Letter-coded permissions\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'permissions'],\n\t\t// [Client.NS_OWNCLOUD, 'downloadURL'],\n\t\t/**\n\t\t * Folder sizes\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'size'],\n\t\t/**\n\t\t * File sizes\n\t\t */\n\t\t[Client.NS_DAV, 'getcontentlength'],\n\t\t[Client.NS_DAV, 'quota-available-bytes'],\n\t\t/**\n\t\t * Preview availability\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'has-preview'],\n\t\t/**\n\t\t * Mount type\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'mount-type'],\n\t\t/**\n\t\t * Encryption state\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'is-encrypted'],\n\t\t/**\n\t\t * Share permissions\n\t\t */\n\t\t[Client.NS_OCS, 'share-permissions'],\n\t\t/**\n\t\t * Share attributes\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'share-attributes'],\n\t]\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tClient.prototype = {\n\n\t\t/**\n\t\t * Root path of the Webdav endpoint\n\t\t *\n\t\t * @type string\n\t\t */\n\t\t_root: null,\n\n\t\t/**\n\t\t * Client from the library\n\t\t *\n\t\t * @type dav.Client\n\t\t */\n\t\t_client: null,\n\n\t\t/**\n\t\t * Array of file info parsing functions.\n\t\t *\n\t\t * @type Array\n\t\t */\n\t\t_fileInfoParsers: [],\n\n\t\t/**\n\t\t * Returns the configured XHR provider for davclient\n\t\t * @returns {XMLHttpRequest}\n\t\t */\n\t\t_xhrProvider: function() {\n\t\t\tconst headers = this._defaultHeaders\n\t\t\tconst xhr = new XMLHttpRequest()\n\t\t\tconst oldOpen = xhr.open\n\t\t\t// override open() method to add headers\n\t\t\txhr.open = function() {\n\t\t\t\tconst result = oldOpen.apply(this, arguments)\n\t\t\t\t_.each(headers, function(value, key) {\n\t\t\t\t\txhr.setRequestHeader(key, value)\n\t\t\t\t})\n\t\t\t\treturn result\n\t\t\t}\n\n\t\t\tOC.registerXHRForErrorProcessing(xhr)\n\t\t\treturn xhr\n\t\t},\n\n\t\t/**\n\t\t * Prepends the base url to the given path sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} base url + joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildUrl: function() {\n\t\t\tlet path = this._buildPath.apply(this, arguments)\n\t\t\tif (path.charAt([path.length - 1]) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\t\t\tif (path.charAt(0) === '/') {\n\t\t\t\tpath = path.substr(1)\n\t\t\t}\n\t\t\treturn this._baseUrl + '/' + path\n\t\t},\n\n\t\t/**\n\t\t * Append the path to the root and also encode path\n\t\t * sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildPath: function() {\n\t\t\tlet path = OC.joinPaths.apply(this, arguments)\n\t\t\tconst sections = path.split('/')\n\t\t\tlet i\n\t\t\tfor (i = 0; i < sections.length; i++) {\n\t\t\t\tsections[i] = encodeURIComponent(sections[i])\n\t\t\t}\n\t\t\tpath = sections.join('/')\n\t\t\treturn path\n\t\t},\n\n\t\t/**\n\t\t * Parse headers string into a map\n\t\t *\n\t\t * @param {string} headersString headers list as string\n\t\t *\n\t\t * @returns {Object.} map of header name to header contents\n\t\t */\n\t\t_parseHeaders: function(headersString) {\n\t\t\tconst headerRows = headersString.split('\\n')\n\t\t\tconst headers = {}\n\t\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\t\tconst sepPos = headerRows[i].indexOf(':')\n\t\t\t\tif (sepPos < 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst headerName = headerRows[i].substr(0, sepPos)\n\t\t\t\tconst headerValue = headerRows[i].substr(sepPos + 2)\n\n\t\t\t\tif (!headers[headerName]) {\n\t\t\t\t\t// make it an array\n\t\t\t\t\theaders[headerName] = []\n\t\t\t\t}\n\n\t\t\t\theaders[headerName].push(headerValue)\n\t\t\t}\n\t\t\treturn headers\n\t\t},\n\n\t\t/**\n\t\t * Parses the etag response which is in double quotes.\n\t\t *\n\t\t * @param {string} etag etag value in double quotes\n\t\t *\n\t\t * @returns {string} etag without double quotes\n\t\t */\n\t\t_parseEtag: function(etag) {\n\t\t\tif (etag.charAt(0) === '\"') {\n\t\t\t\treturn etag.split('\"')[1]\n\t\t\t}\n\t\t\treturn etag\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav result\n\t\t *\n\t\t * @param {Object} response XML object\n\t\t *\n\t\t * @returns {Array.} array of file info\n\t\t */\n\t\t_parseFileInfo: function(response) {\n\t\t\tlet path = decodeURIComponent(response.href)\n\t\t\tif (path.substr(0, this._root.length) === this._root) {\n\t\t\t\tpath = path.substr(this._root.length)\n\t\t\t}\n\n\t\t\tif (path.charAt(path.length - 1) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\n\t\t\tif (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst props = response.propStat[0].properties\n\n\t\t\tconst data = {\n\t\t\t\tid: props[Client.PROPERTY_INTERNAL_FILEID],\n\t\t\t\tpath: OC.dirname(path) || '/',\n\t\t\t\tname: OC.basename(path),\n\t\t\t\tmtime: (new Date(props[Client.PROPERTY_GETLASTMODIFIED])).getTime(),\n\t\t\t}\n\n\t\t\tconst etagProp = props[Client.PROPERTY_GETETAG]\n\t\t\tif (!_.isUndefined(etagProp)) {\n\t\t\t\tdata.etag = this._parseEtag(etagProp)\n\t\t\t}\n\n\t\t\tlet sizeProp = props[Client.PROPERTY_GETCONTENTLENGTH]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tsizeProp = props[Client.PROPERTY_SIZE]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tconst hasPreviewProp = props['{' + Client.NS_NEXTCLOUD + '}has-preview']\n\t\t\tif (!_.isUndefined(hasPreviewProp)) {\n\t\t\t\tdata.hasPreview = hasPreviewProp === 'true'\n\t\t\t} else {\n\t\t\t\tdata.hasPreview = true\n\t\t\t}\n\n\t\t\tconst isEncryptedProp = props['{' + Client.NS_NEXTCLOUD + '}is-encrypted']\n\t\t\tif (!_.isUndefined(isEncryptedProp)) {\n\t\t\t\tdata.isEncrypted = isEncryptedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isEncrypted = false\n\t\t\t}\n\n\t\t\tconst isFavouritedProp = props['{' + Client.NS_OWNCLOUD + '}favorite']\n\t\t\tif (!_.isUndefined(isFavouritedProp)) {\n\t\t\t\tdata.isFavourited = isFavouritedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isFavourited = false\n\t\t\t}\n\n\t\t\tconst contentType = props[Client.PROPERTY_GETCONTENTTYPE]\n\t\t\tif (!_.isUndefined(contentType)) {\n\t\t\t\tdata.mimetype = contentType\n\t\t\t}\n\n\t\t\tconst resType = props[Client.PROPERTY_RESOURCETYPE]\n\t\t\tif (!data.mimetype && resType) {\n\t\t\t\tconst xmlvalue = resType[0]\n\t\t\t\tif (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {\n\t\t\t\t\tdata.mimetype = 'httpd/unix-directory'\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdata.permissions = OC.PERMISSION_NONE\n\t\t\tconst permissionProp = props[Client.PROPERTY_PERMISSIONS]\n\t\t\tif (!_.isUndefined(permissionProp)) {\n\t\t\t\tconst permString = permissionProp || ''\n\t\t\t\tdata.mountType = null\n\t\t\t\tfor (let i = 0; i < permString.length; i++) {\n\t\t\t\t\tconst c = permString.charAt(i)\n\t\t\t\t\tswitch (c) {\n\t\t\t\t\t// FIXME: twisted permissions\n\t\t\t\t\tcase 'C':\n\t\t\t\t\tcase 'K':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_CREATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'G':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_READ\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'W':\n\t\t\t\t\tcase 'N':\n\t\t\t\t\tcase 'V':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_UPDATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'D':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_DELETE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'R':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_SHARE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'M':\n\t\t\t\t\t\tif (!data.mountType) {\n\t\t\t\t\t\t\t// TODO: how to identify external-root ?\n\t\t\t\t\t\t\tdata.mountType = 'external'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'S':\n\t\t\t\t\t\t// TODO: how to identify shared-root ?\n\t\t\t\t\t\tdata.mountType = 'shared'\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst sharePermissionsProp = props[Client.PROPERTY_SHARE_PERMISSIONS]\n\t\t\tif (!_.isUndefined(sharePermissionsProp)) {\n\t\t\t\tdata.sharePermissions = parseInt(sharePermissionsProp)\n\t\t\t}\n\n\t\t\tconst shareAttributesProp = props[Client.PROPERTY_SHARE_ATTRIBUTES]\n\t\t\tif (!_.isUndefined(shareAttributesProp)) {\n\t\t\t\ttry {\n\t\t\t\t\tdata.shareAttributes = JSON.parse(shareAttributesProp)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.warn('Could not parse share attributes returned by server: \"' + shareAttributesProp + '\"')\n\t\t\t\t\tdata.shareAttributes = [];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata.shareAttributes = [];\n\t\t\t}\n\n\t\t\tconst mounTypeProp = props['{' + Client.NS_NEXTCLOUD + '}mount-type']\n\t\t\tif (!_.isUndefined(mounTypeProp)) {\n\t\t\t\tdata.mountType = mounTypeProp\n\t\t\t}\n\n\t\t\tconst quotaAvailableBytes = props['{' + Client.NS_DAV + '}quota-available-bytes']\n\t\t\tif (!_.isUndefined(quotaAvailableBytes)) {\n\t\t\t\tdata.quotaAvailableBytes = quotaAvailableBytes\n\t\t\t}\n\n\t\t\t// extend the parsed data using the custom parsers\n\t\t\t_.each(this._fileInfoParsers, function(parserFunction) {\n\t\t\t\t_.extend(data, parserFunction(response, data) || {})\n\t\t\t})\n\n\t\t\treturn new FileInfo(data)\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav multistatus\n\t\t *\n\t\t * @param {Array} responses\n\t\t */\n\t\t_parseResult: function(responses) {\n\t\t\tconst self = this\n\t\t\treturn _.map(responses, function(response) {\n\t\t\t\treturn self._parseFileInfo(response)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Returns whether the given status code means success\n\t\t *\n\t\t * @param {number} status status code\n\t\t *\n\t\t * @returns true if status code is between 200 and 299 included\n\t\t */\n\t\t_isSuccessStatus: function(status) {\n\t\t\treturn status >= 200 && status <= 299\n\t\t},\n\n\t\t/**\n\t\t * Parse the Sabre exception out of the given response, if any\n\t\t *\n\t\t * @param {Object} response object\n\t\t * @returns {Object} array of parsed message and exception (only the first one)\n\t\t */\n\t\t_getSabreException: function(response) {\n\t\t\tconst result = {}\n\t\t\tconst xml = response.xhr.responseXML\n\t\t\tif (xml === null) {\n\t\t\t\treturn result\n\t\t\t}\n\t\t\tconst messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message')\n\t\t\tconst exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception')\n\t\t\tif (messages.length) {\n\t\t\t\tresult.message = messages[0].textContent\n\t\t\t}\n\t\t\tif (exceptions.length) {\n\t\t\t\tresult.exception = exceptions[0].textContent\n\t\t\t}\n\t\t\treturn result\n\t\t},\n\n\t\t/**\n\t\t * Returns the default PROPFIND properties to use during a call.\n\t\t *\n\t\t * @returns {Array.} array of properties\n\t\t */\n\t\tgetPropfindProperties: function() {\n\t\t\tif (!this._propfindProperties) {\n\t\t\t\tthis._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {\n\t\t\t\t\treturn '{' + propDef[0] + '}' + propDef[1]\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn this._propfindProperties\n\t\t},\n\n\t\t/**\n\t\t * Lists the contents of a directory\n\t\t *\n\t\t * @param {String} path path to retrieve\n\t\t * @param {Object} [options] options\n\t\t * @param {boolean} [options.includeParent=false] set to true to keep\n\t\t * the parent folder in the result list\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFolderContents: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t1\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tif (!options || !options.includeParent) {\n\t\t\t\t\t\t// remove root dir, the first entry\n\t\t\t\t\t\tresults.shift()\n\t\t\t\t\t}\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Fetches a flat list of files filtered by a given filter criteria.\n\t\t * (currently system tags and circles are supported)\n\t\t *\n\t\t * @param {Object} filter filter criteria\n\t\t * @param {Object} [filter.systemTagIds] list of system tag ids to filter by\n\t\t * @param {boolean} [filter.favorite] set it to filter by favorites\n\t\t * @param {Object} [options] options\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFilteredFiles: function(filter, options) {\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tif (!filter\n\t\t\t\t|| (!filter.systemTagIds && _.isUndefined(filter.favorite) && !filter.circlesIds)) {\n\t\t\t\tthrow 'Missing filter argument'\n\t\t\t}\n\n\t\t\t// root element with namespaces\n\t\t\tlet body = '\\n'\n\t\t\t_.each(properties, function(prop) {\n\t\t\t\tconst property = self._client.parseClarkNotation(prop)\n\t\t\t\tbody += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\\n'\n\t\t\t})\n\n\t\t\tbody += ' \\n'\n\n\t\t\t// rules block\n\t\t\tbody +=\t' \\n'\n\t\t\t_.each(filter.systemTagIds, function(systemTagIds) {\n\t\t\t\tbody += ' ' + escapeHTML(systemTagIds) + '\\n'\n\t\t\t})\n\t\t\t_.each(filter.circlesIds, function(circlesIds) {\n\t\t\t\tbody += ' ' + escapeHTML(circlesIds) + '\\n'\n\t\t\t})\n\t\t\tif (filter.favorite) {\n\t\t\t\tbody += ' ' + (filter.favorite ? '1' : '0') + '\\n'\n\t\t\t}\n\t\t\tbody += ' \\n'\n\n\t\t\t// end of root\n\t\t\tbody += '\\n'\n\n\t\t\tthis._client.request(\n\t\t\t\t'REPORT',\n\t\t\t\tthis._buildUrl(),\n\t\t\t\t{},\n\t\t\t\tbody\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the file info of a given path.\n\t\t *\n\t\t * @param {String} path path\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFileInfo: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\t// TODO: headers\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t0\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, self._parseResult([result.body])[0])\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the contents of the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tgetFileContents: function(path) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\t'GET',\n\t\t\t\tthis._buildUrl(path)\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, result.body)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Puts the given data into the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t * @param {String} body file body\n\t\t * @param {Object} [options]\n\t\t * @param {String} [options.contentType='text/plain'] content type\n\t\t * @param {boolean} [options.overwrite=true] whether to overwrite an existing file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tputFileContents: function(path, body, options) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\toptions = options || {}\n\t\t\tconst headers = {}\n\t\t\tlet contentType = 'text/plain;charset=utf-8'\n\t\t\tif (options.contentType) {\n\t\t\t\tcontentType = options.contentType\n\t\t\t}\n\n\t\t\theaders['Content-Type'] = contentType\n\n\t\t\tif (_.isUndefined(options.overwrite) || options.overwrite) {\n\t\t\t\t// will trigger 412 precondition failed if a file already exists\n\t\t\t\theaders['If-None-Match'] = '*'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'PUT',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders,\n\t\t\t\tbody || ''\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t_simpleCall: function(method, path) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\tmethod,\n\t\t\t\tthis._buildUrl(path)\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Creates a directory\n\t\t *\n\t\t * @param {String} path path to create\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tcreateDirectory: function(path) {\n\t\t\treturn this._simpleCall('MKCOL', path)\n\t\t},\n\n\t\t/**\n\t\t * Deletes a file or directory\n\t\t *\n\t\t * @param {String} path path to delete\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tremove: function(path) {\n\t\t\treturn this._simpleCall('DELETE', path)\n\t\t},\n\n\t\t/**\n\t\t * Moves path to another path\n\t\t *\n\t\t * @param {String} path path to move\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t * @param {Object} [headers=null] additional headers\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tmove: function(path, destinationPath, allowOverwrite, headers) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\theaders = _.extend({}, headers, {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t})\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'MOVE',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Copies path to another path\n\t\t *\n\t\t * @param {String} path path to copy\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tcopy: function(path, destinationPath, allowOverwrite) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tconst headers = {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t}\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'COPY',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(response) {\n\t\t\t\t\tif (self._isSuccessStatus(response.status)) {\n\t\t\t\t\t\tdeferred.resolve(response.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdeferred.reject(response.status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Add a file info parser function\n\t\t *\n\t\t * @param {OC.Files.Client~parseFileInfo} parserFunction\n\t\t */\n\t\taddFileInfoParser: function(parserFunction) {\n\t\t\tthis._fileInfoParsers.push(parserFunction)\n\t\t},\n\n\t\t/**\n\t\t * Returns the dav.Client instance used internally\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {dav.Client}\n\t\t */\n\t\tgetClient: function() {\n\t\t\treturn this._client\n\t\t},\n\n\t\t/**\n\t\t * Returns the user name\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} userName\n\t\t */\n\t\tgetUserName: function() {\n\t\t\treturn this._client.userName\n\t\t},\n\n\t\t/**\n\t\t * Returns the password\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} password\n\t\t */\n\t\tgetPassword: function() {\n\t\t\treturn this._client.password\n\t\t},\n\n\t\t/**\n\t\t * Returns the base URL\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetBaseUrl: function() {\n\t\t\treturn this._client.baseUrl\n\t\t},\n\n\t\t/**\n\t\t * Returns the host\n\t\t *\n\t\t * @since 13.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetHost: function() {\n\t\t\treturn this._host\n\t\t},\n\t}\n\n\t/**\n\t * File info parser function\n\t *\n\t * This function receives a list of Webdav properties as input and\n\t * should return a hash array of parsed properties, if applicable.\n\t *\n\t * @callback OC.Files.Client~parseFileInfo\n\t * @param {Object} XML Webdav properties\n * @return {Array} array of parsed property values\n\t */\n\n\tif (!OC.Files) {\n\t\t/**\n\t\t * @namespace OC.Files\n\t\t *\n\t\t * @since 8.2\n\t\t */\n\t\tOC.Files = {}\n\t}\n\n\t/**\n\t * Returns the default instance of the files client\n\t *\n\t * @returns {OC.Files.Client} default client\n\t *\n\t * @since 8.2\n\t */\n\tOC.Files.getClient = function() {\n\t\tif (OC.Files._defaultClient) {\n\t\t\treturn OC.Files._defaultClient\n\t\t}\n\n\t\tconst client = new OC.Files.Client({\n\t\t\thost: OC.getHost(),\n\t\t\tport: OC.getPort(),\n\t\t\troot: OC.linkToRemoteBase('dav') + '/files/' + OC.getCurrentUser().uid,\n\t\t\tuseHTTPS: OC.getProtocol() === 'https',\n\t\t})\n\t\tOC.Files._defaultClient = client\n\t\treturn client\n\t}\n\n\tOC.Files.Client = Client\n})(OC, OC.Files.FileInfo)\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.amdD = function () {\n\tthrow new Error('define cannot be used indirect');\n};","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = function(module) {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 5578;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t5578: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [7874], function() { return __webpack_require__(7913); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","OC","FileInfo","Client","options","this","_root","root","charAt","length","substr","url","PROTOCOL_HTTP","useHTTPS","PROTOCOL_HTTPS","host","_host","_defaultHeaders","defaultHeaders","requestToken","_baseUrl","clientOptions","baseUrl","xmlNamespaces","userName","password","_client","dav","xhrProvider","_","bind","_xhrProvider","_fileInfoParsers","NS_OWNCLOUD","NS_NEXTCLOUD","NS_DAV","NS_OCS","PROPERTY_GETLASTMODIFIED","PROPERTY_GETETAG","PROPERTY_GETCONTENTTYPE","PROPERTY_RESOURCETYPE","PROPERTY_INTERNAL_FILEID","PROPERTY_PERMISSIONS","PROPERTY_SIZE","PROPERTY_GETCONTENTLENGTH","PROPERTY_ISENCRYPTED","PROPERTY_SHARE_PERMISSIONS","PROPERTY_SHARE_ATTRIBUTES","PROPERTY_QUOTA_AVAILABLE_BYTES","_PROPFIND_PROPERTIES","prototype","headers","xhr","XMLHttpRequest","oldOpen","open","result","apply","arguments","each","value","key","setRequestHeader","registerXHRForErrorProcessing","_buildUrl","path","_buildPath","i","joinPaths","sections","split","encodeURIComponent","join","_parseHeaders","headersString","headerRows","sepPos","indexOf","headerName","headerValue","push","_parseEtag","etag","_parseFileInfo","response","decodeURIComponent","href","propStat","status","props","properties","data","id","dirname","name","basename","mtime","Date","getTime","etagProp","isUndefined","sizeProp","size","parseInt","hasPreviewProp","hasPreview","isEncryptedProp","isEncrypted","isFavouritedProp","isFavourited","contentType","mimetype","resType","xmlvalue","namespaceURI","nodeName","permissions","PERMISSION_NONE","permissionProp","permString","mountType","PERMISSION_CREATE","PERMISSION_READ","PERMISSION_UPDATE","PERMISSION_DELETE","PERMISSION_SHARE","sharePermissionsProp","sharePermissions","shareAttributesProp","shareAttributes","JSON","parse","e","console","warn","mounTypeProp","quotaAvailableBytes","parserFunction","extend","_parseResult","responses","self","map","_isSuccessStatus","_getSabreException","xml","responseXML","messages","getElementsByTagNameNS","exceptions","message","textContent","exception","getPropfindProperties","_propfindProperties","propDef","getFolderContents","$","Deferred","promise","propFind","then","results","body","includeParent","shift","resolve","reject","getFilteredFiles","filter","systemTagIds","favorite","circlesIds","namespace","prop","property","parseClarkNotation","escapeHTML","request","getFileInfo","getFileContents","putFileContents","overwrite","_simpleCall","method","createDirectory","remove","move","destinationPath","allowOverwrite","Overwrite","copy","addFileInfoParser","getClient","getUserName","getPassword","getBaseUrl","getHost","Files","_defaultClient","client","port","getPort","linkToRemoteBase","getCurrentUser","uid","getProtocol","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","loaded","__webpack_modules__","call","m","amdD","Error","amdO","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","j","Object","keys","every","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","g","globalThis","Function","window","obj","hasOwnProperty","Symbol","toStringTag","nmd","paths","children","b","document","baseURI","location","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","forEach","nc","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"core-files_client.js?v=186efcff8d4cc23321bb","mappings":"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","sources":["webpack:///nextcloud/webpack/runtime/chunk loaded","webpack:///nextcloud/core/src/files/client.js","webpack:///nextcloud/webpack/bootstrap","webpack:///nextcloud/webpack/runtime/amd define","webpack:///nextcloud/webpack/runtime/amd options","webpack:///nextcloud/webpack/runtime/compat get default export","webpack:///nextcloud/webpack/runtime/define property getters","webpack:///nextcloud/webpack/runtime/global","webpack:///nextcloud/webpack/runtime/hasOwnProperty shorthand","webpack:///nextcloud/webpack/runtime/make namespace object","webpack:///nextcloud/webpack/runtime/node module decorator","webpack:///nextcloud/webpack/runtime/runtimeId","webpack:///nextcloud/webpack/runtime/jsonp chunk loading","webpack:///nextcloud/webpack/runtime/nonce","webpack:///nextcloud/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","/**\n * Copyright (c) 2015\n *\n * @author Bjoern Schiessle \n * @author John Molakvoæ \n * @author Julius Härtl \n * @author Lukas Reschke \n * @author Michael Jobst \n * @author Robin Appelman \n * @author Roeland Jago Douma \n * @author Thomas Citharel \n * @author Tomasz Grobelny \n * @author Vincent Petry \n * @author Vinicius Cubas Brand \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n/* eslint-disable */\nimport escapeHTML from 'escape-html'\n\n/* global dav */\n\n(function(OC, FileInfo) {\n\t/**\n\t * @class OC.Files.Client\n\t * @classdesc Client to access files on the server\n\t *\n\t * @param {Object} options\n\t * @param {String} options.host host name\n\t * @param {number} [options.port] port\n\t * @param {boolean} [options.useHTTPS] whether to use https\n\t * @param {String} [options.root] root path\n\t * @param {String} [options.userName] user name\n\t * @param {String} [options.password] password\n\t *\n\t * @since 8.2\n\t */\n\tvar Client = function(options) {\n\t\tthis._root = options.root\n\t\tif (this._root.charAt(this._root.length - 1) === '/') {\n\t\t\tthis._root = this._root.substr(0, this._root.length - 1)\n\t\t}\n\n\t\tlet url = Client.PROTOCOL_HTTP + '://'\n\t\tif (options.useHTTPS) {\n\t\t\turl = Client.PROTOCOL_HTTPS + '://'\n\t\t}\n\n\t\turl += options.host + this._root\n\t\tthis._host = options.host\n\t\tthis._defaultHeaders = options.defaultHeaders || {\n\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t'requesttoken': OC.requestToken,\n\t\t}\n\t\tthis._baseUrl = url\n\n\t\tconst clientOptions = {\n\t\t\tbaseUrl: this._baseUrl,\n\t\t\txmlNamespaces: {\n\t\t\t\t'DAV:': 'd',\n\t\t\t\t'http://owncloud.org/ns': 'oc',\n\t\t\t\t'http://nextcloud.org/ns': 'nc',\n\t\t\t\t'http://open-collaboration-services.org/ns': 'ocs',\n\t\t\t},\n\t\t}\n\t\tif (options.userName) {\n\t\t\tclientOptions.userName = options.userName\n\t\t}\n\t\tif (options.password) {\n\t\t\tclientOptions.password = options.password\n\t\t}\n\t\tthis._client = new dav.Client(clientOptions)\n\t\tthis._client.xhrProvider = _.bind(this._xhrProvider, this)\n\t\tthis._fileInfoParsers = []\n\t}\n\n\tClient.NS_OWNCLOUD = 'http://owncloud.org/ns'\n\tClient.NS_NEXTCLOUD = 'http://nextcloud.org/ns'\n\tClient.NS_DAV = 'DAV:'\n\tClient.NS_OCS = 'http://open-collaboration-services.org/ns'\n\n\tClient.PROPERTY_GETLASTMODIFIED\t= '{' + Client.NS_DAV + '}getlastmodified'\n\tClient.PROPERTY_GETETAG\t= '{' + Client.NS_DAV + '}getetag'\n\tClient.PROPERTY_GETCONTENTTYPE\t= '{' + Client.NS_DAV + '}getcontenttype'\n\tClient.PROPERTY_RESOURCETYPE\t= '{' + Client.NS_DAV + '}resourcetype'\n\tClient.PROPERTY_INTERNAL_FILEID\t= '{' + Client.NS_OWNCLOUD + '}fileid'\n\tClient.PROPERTY_PERMISSIONS\t= '{' + Client.NS_OWNCLOUD + '}permissions'\n\tClient.PROPERTY_SIZE\t= '{' + Client.NS_OWNCLOUD + '}size'\n\tClient.PROPERTY_GETCONTENTLENGTH\t= '{' + Client.NS_DAV + '}getcontentlength'\n\tClient.PROPERTY_ISENCRYPTED\t= '{' + Client.NS_DAV + '}is-encrypted'\n\tClient.PROPERTY_SHARE_PERMISSIONS\t= '{' + Client.NS_OCS + '}share-permissions'\n\tClient.PROPERTY_SHARE_ATTRIBUTES\t= '{' + Client.NS_NEXTCLOUD + '}share-attributes'\n\tClient.PROPERTY_QUOTA_AVAILABLE_BYTES\t= '{' + Client.NS_DAV + '}quota-available-bytes'\n\n\tClient.PROTOCOL_HTTP\t= 'http'\n\tClient.PROTOCOL_HTTPS\t= 'https'\n\n\tClient._PROPFIND_PROPERTIES = [\n\t\t/**\n\t\t * Modified time\n\t\t */\n\t\t[Client.NS_DAV, 'getlastmodified'],\n\t\t/**\n\t\t * Etag\n\t\t */\n\t\t[Client.NS_DAV, 'getetag'],\n\t\t/**\n\t\t * Mime type\n\t\t */\n\t\t[Client.NS_DAV, 'getcontenttype'],\n\t\t/**\n\t\t * Resource type \"collection\" for folders, empty otherwise\n\t\t */\n\t\t[Client.NS_DAV, 'resourcetype'],\n\t\t/**\n\t\t * File id\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'fileid'],\n\t\t/**\n\t\t * Letter-coded permissions\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'permissions'],\n\t\t// [Client.NS_OWNCLOUD, 'downloadURL'],\n\t\t/**\n\t\t * Folder sizes\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'size'],\n\t\t/**\n\t\t * File sizes\n\t\t */\n\t\t[Client.NS_DAV, 'getcontentlength'],\n\t\t[Client.NS_DAV, 'quota-available-bytes'],\n\t\t/**\n\t\t * Preview availability\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'has-preview'],\n\t\t/**\n\t\t * Mount type\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'mount-type'],\n\t\t/**\n\t\t * Encryption state\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'is-encrypted'],\n\t\t/**\n\t\t * Share permissions\n\t\t */\n\t\t[Client.NS_OCS, 'share-permissions'],\n\t\t/**\n\t\t * Share attributes\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'share-attributes'],\n\t]\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tClient.prototype = {\n\n\t\t/**\n\t\t * Root path of the Webdav endpoint\n\t\t *\n\t\t * @type string\n\t\t */\n\t\t_root: null,\n\n\t\t/**\n\t\t * Client from the library\n\t\t *\n\t\t * @type dav.Client\n\t\t */\n\t\t_client: null,\n\n\t\t/**\n\t\t * Array of file info parsing functions.\n\t\t *\n\t\t * @type Array\n\t\t */\n\t\t_fileInfoParsers: [],\n\n\t\t/**\n\t\t * Returns the configured XHR provider for davclient\n\t\t * @returns {XMLHttpRequest}\n\t\t */\n\t\t_xhrProvider: function() {\n\t\t\tconst headers = this._defaultHeaders\n\t\t\tconst xhr = new XMLHttpRequest()\n\t\t\tconst oldOpen = xhr.open\n\t\t\t// override open() method to add headers\n\t\t\txhr.open = function() {\n\t\t\t\tconst result = oldOpen.apply(this, arguments)\n\t\t\t\t_.each(headers, function(value, key) {\n\t\t\t\t\txhr.setRequestHeader(key, value)\n\t\t\t\t})\n\t\t\t\treturn result\n\t\t\t}\n\n\t\t\tOC.registerXHRForErrorProcessing(xhr)\n\t\t\treturn xhr\n\t\t},\n\n\t\t/**\n\t\t * Prepends the base url to the given path sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} base url + joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildUrl: function() {\n\t\t\tlet path = this._buildPath.apply(this, arguments)\n\t\t\tif (path.charAt([path.length - 1]) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\t\t\tif (path.charAt(0) === '/') {\n\t\t\t\tpath = path.substr(1)\n\t\t\t}\n\t\t\treturn this._baseUrl + '/' + path\n\t\t},\n\n\t\t/**\n\t\t * Append the path to the root and also encode path\n\t\t * sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildPath: function() {\n\t\t\tlet path = OC.joinPaths.apply(this, arguments)\n\t\t\tconst sections = path.split('/')\n\t\t\tlet i\n\t\t\tfor (i = 0; i < sections.length; i++) {\n\t\t\t\tsections[i] = encodeURIComponent(sections[i])\n\t\t\t}\n\t\t\tpath = sections.join('/')\n\t\t\treturn path\n\t\t},\n\n\t\t/**\n\t\t * Parse headers string into a map\n\t\t *\n\t\t * @param {string} headersString headers list as string\n\t\t *\n\t\t * @returns {Object.} map of header name to header contents\n\t\t */\n\t\t_parseHeaders: function(headersString) {\n\t\t\tconst headerRows = headersString.split('\\n')\n\t\t\tconst headers = {}\n\t\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\t\tconst sepPos = headerRows[i].indexOf(':')\n\t\t\t\tif (sepPos < 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst headerName = headerRows[i].substr(0, sepPos)\n\t\t\t\tconst headerValue = headerRows[i].substr(sepPos + 2)\n\n\t\t\t\tif (!headers[headerName]) {\n\t\t\t\t\t// make it an array\n\t\t\t\t\theaders[headerName] = []\n\t\t\t\t}\n\n\t\t\t\theaders[headerName].push(headerValue)\n\t\t\t}\n\t\t\treturn headers\n\t\t},\n\n\t\t/**\n\t\t * Parses the etag response which is in double quotes.\n\t\t *\n\t\t * @param {string} etag etag value in double quotes\n\t\t *\n\t\t * @returns {string} etag without double quotes\n\t\t */\n\t\t_parseEtag: function(etag) {\n\t\t\tif (etag.charAt(0) === '\"') {\n\t\t\t\treturn etag.split('\"')[1]\n\t\t\t}\n\t\t\treturn etag\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav result\n\t\t *\n\t\t * @param {Object} response XML object\n\t\t *\n\t\t * @returns {Array.} array of file info\n\t\t */\n\t\t_parseFileInfo: function(response) {\n\t\t\tlet path = decodeURIComponent(response.href)\n\t\t\tif (path.substr(0, this._root.length) === this._root) {\n\t\t\t\tpath = path.substr(this._root.length)\n\t\t\t}\n\n\t\t\tif (path.charAt(path.length - 1) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\n\t\t\tif (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst props = response.propStat[0].properties\n\n\t\t\tconst data = {\n\t\t\t\tid: props[Client.PROPERTY_INTERNAL_FILEID],\n\t\t\t\tpath: OC.dirname(path) || '/',\n\t\t\t\tname: OC.basename(path),\n\t\t\t\tmtime: (new Date(props[Client.PROPERTY_GETLASTMODIFIED])).getTime(),\n\t\t\t}\n\n\t\t\tconst etagProp = props[Client.PROPERTY_GETETAG]\n\t\t\tif (!_.isUndefined(etagProp)) {\n\t\t\t\tdata.etag = this._parseEtag(etagProp)\n\t\t\t}\n\n\t\t\tlet sizeProp = props[Client.PROPERTY_GETCONTENTLENGTH]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tsizeProp = props[Client.PROPERTY_SIZE]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tconst hasPreviewProp = props['{' + Client.NS_NEXTCLOUD + '}has-preview']\n\t\t\tif (!_.isUndefined(hasPreviewProp)) {\n\t\t\t\tdata.hasPreview = hasPreviewProp === 'true'\n\t\t\t} else {\n\t\t\t\tdata.hasPreview = true\n\t\t\t}\n\n\t\t\tconst isEncryptedProp = props['{' + Client.NS_NEXTCLOUD + '}is-encrypted']\n\t\t\tif (!_.isUndefined(isEncryptedProp)) {\n\t\t\t\tdata.isEncrypted = isEncryptedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isEncrypted = false\n\t\t\t}\n\n\t\t\tconst isFavouritedProp = props['{' + Client.NS_OWNCLOUD + '}favorite']\n\t\t\tif (!_.isUndefined(isFavouritedProp)) {\n\t\t\t\tdata.isFavourited = isFavouritedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isFavourited = false\n\t\t\t}\n\n\t\t\tconst contentType = props[Client.PROPERTY_GETCONTENTTYPE]\n\t\t\tif (!_.isUndefined(contentType)) {\n\t\t\t\tdata.mimetype = contentType\n\t\t\t}\n\n\t\t\tconst resType = props[Client.PROPERTY_RESOURCETYPE]\n\t\t\tif (!data.mimetype && resType) {\n\t\t\t\tconst xmlvalue = resType[0]\n\t\t\t\tif (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {\n\t\t\t\t\tdata.mimetype = 'httpd/unix-directory'\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdata.permissions = OC.PERMISSION_NONE\n\t\t\tconst permissionProp = props[Client.PROPERTY_PERMISSIONS]\n\t\t\tif (!_.isUndefined(permissionProp)) {\n\t\t\t\tconst permString = permissionProp || ''\n\t\t\t\tdata.mountType = null\n\t\t\t\tfor (let i = 0; i < permString.length; i++) {\n\t\t\t\t\tconst c = permString.charAt(i)\n\t\t\t\t\tswitch (c) {\n\t\t\t\t\t// FIXME: twisted permissions\n\t\t\t\t\tcase 'C':\n\t\t\t\t\tcase 'K':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_CREATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'G':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_READ\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'W':\n\t\t\t\t\tcase 'N':\n\t\t\t\t\tcase 'V':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_UPDATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'D':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_DELETE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'R':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_SHARE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'M':\n\t\t\t\t\t\tif (!data.mountType) {\n\t\t\t\t\t\t\t// TODO: how to identify external-root ?\n\t\t\t\t\t\t\tdata.mountType = 'external'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'S':\n\t\t\t\t\t\t// TODO: how to identify shared-root ?\n\t\t\t\t\t\tdata.mountType = 'shared'\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst sharePermissionsProp = props[Client.PROPERTY_SHARE_PERMISSIONS]\n\t\t\tif (!_.isUndefined(sharePermissionsProp)) {\n\t\t\t\tdata.sharePermissions = parseInt(sharePermissionsProp)\n\t\t\t}\n\n\t\t\tconst shareAttributesProp = props[Client.PROPERTY_SHARE_ATTRIBUTES]\n\t\t\tif (!_.isUndefined(shareAttributesProp)) {\n\t\t\t\ttry {\n\t\t\t\t\tdata.shareAttributes = JSON.parse(shareAttributesProp)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.warn('Could not parse share attributes returned by server: \"' + shareAttributesProp + '\"')\n\t\t\t\t\tdata.shareAttributes = [];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata.shareAttributes = [];\n\t\t\t}\n\n\t\t\tconst mounTypeProp = props['{' + Client.NS_NEXTCLOUD + '}mount-type']\n\t\t\tif (!_.isUndefined(mounTypeProp)) {\n\t\t\t\tdata.mountType = mounTypeProp\n\t\t\t}\n\n\t\t\tconst quotaAvailableBytes = props['{' + Client.NS_DAV + '}quota-available-bytes']\n\t\t\tif (!_.isUndefined(quotaAvailableBytes)) {\n\t\t\t\tdata.quotaAvailableBytes = quotaAvailableBytes\n\t\t\t}\n\n\t\t\t// extend the parsed data using the custom parsers\n\t\t\t_.each(this._fileInfoParsers, function(parserFunction) {\n\t\t\t\t_.extend(data, parserFunction(response, data) || {})\n\t\t\t})\n\n\t\t\treturn new FileInfo(data)\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav multistatus\n\t\t *\n\t\t * @param {Array} responses\n\t\t */\n\t\t_parseResult: function(responses) {\n\t\t\tconst self = this\n\t\t\treturn _.map(responses, function(response) {\n\t\t\t\treturn self._parseFileInfo(response)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Returns whether the given status code means success\n\t\t *\n\t\t * @param {number} status status code\n\t\t *\n\t\t * @returns true if status code is between 200 and 299 included\n\t\t */\n\t\t_isSuccessStatus: function(status) {\n\t\t\treturn status >= 200 && status <= 299\n\t\t},\n\n\t\t/**\n\t\t * Parse the Sabre exception out of the given response, if any\n\t\t *\n\t\t * @param {Object} response object\n\t\t * @returns {Object} array of parsed message and exception (only the first one)\n\t\t */\n\t\t_getSabreException: function(response) {\n\t\t\tconst result = {}\n\t\t\tconst xml = response.xhr.responseXML\n\t\t\tif (xml === null) {\n\t\t\t\treturn result\n\t\t\t}\n\t\t\tconst messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message')\n\t\t\tconst exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception')\n\t\t\tif (messages.length) {\n\t\t\t\tresult.message = messages[0].textContent\n\t\t\t}\n\t\t\tif (exceptions.length) {\n\t\t\t\tresult.exception = exceptions[0].textContent\n\t\t\t}\n\t\t\treturn result\n\t\t},\n\n\t\t/**\n\t\t * Returns the default PROPFIND properties to use during a call.\n\t\t *\n\t\t * @returns {Array.} array of properties\n\t\t */\n\t\tgetPropfindProperties: function() {\n\t\t\tif (!this._propfindProperties) {\n\t\t\t\tthis._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {\n\t\t\t\t\treturn '{' + propDef[0] + '}' + propDef[1]\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn this._propfindProperties\n\t\t},\n\n\t\t/**\n\t\t * Lists the contents of a directory\n\t\t *\n\t\t * @param {String} path path to retrieve\n\t\t * @param {Object} [options] options\n\t\t * @param {boolean} [options.includeParent=false] set to true to keep\n\t\t * the parent folder in the result list\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFolderContents: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t1\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tif (!options || !options.includeParent) {\n\t\t\t\t\t\t// remove root dir, the first entry\n\t\t\t\t\t\tresults.shift()\n\t\t\t\t\t}\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Fetches a flat list of files filtered by a given filter criteria.\n\t\t * (currently system tags and circles are supported)\n\t\t *\n\t\t * @param {Object} filter filter criteria\n\t\t * @param {Object} [filter.systemTagIds] list of system tag ids to filter by\n\t\t * @param {boolean} [filter.favorite] set it to filter by favorites\n\t\t * @param {Object} [options] options\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFilteredFiles: function(filter, options) {\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tif (!filter\n\t\t\t\t|| (!filter.systemTagIds && _.isUndefined(filter.favorite) && !filter.circlesIds)) {\n\t\t\t\tthrow 'Missing filter argument'\n\t\t\t}\n\n\t\t\t// root element with namespaces\n\t\t\tlet body = '\\n'\n\t\t\t_.each(properties, function(prop) {\n\t\t\t\tconst property = self._client.parseClarkNotation(prop)\n\t\t\t\tbody += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\\n'\n\t\t\t})\n\n\t\t\tbody += ' \\n'\n\n\t\t\t// rules block\n\t\t\tbody +=\t' \\n'\n\t\t\t_.each(filter.systemTagIds, function(systemTagIds) {\n\t\t\t\tbody += ' ' + escapeHTML(systemTagIds) + '\\n'\n\t\t\t})\n\t\t\t_.each(filter.circlesIds, function(circlesIds) {\n\t\t\t\tbody += ' ' + escapeHTML(circlesIds) + '\\n'\n\t\t\t})\n\t\t\tif (filter.favorite) {\n\t\t\t\tbody += ' ' + (filter.favorite ? '1' : '0') + '\\n'\n\t\t\t}\n\t\t\tbody += ' \\n'\n\n\t\t\t// end of root\n\t\t\tbody += '\\n'\n\n\t\t\tthis._client.request(\n\t\t\t\t'REPORT',\n\t\t\t\tthis._buildUrl(),\n\t\t\t\t{},\n\t\t\t\tbody\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the file info of a given path.\n\t\t *\n\t\t * @param {String} path path\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFileInfo: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\t// TODO: headers\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t0\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, self._parseResult([result.body])[0])\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the contents of the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tgetFileContents: function(path) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\t'GET',\n\t\t\t\tthis._buildUrl(path)\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, result.body)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Puts the given data into the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t * @param {String} body file body\n\t\t * @param {Object} [options]\n\t\t * @param {String} [options.contentType='text/plain'] content type\n\t\t * @param {boolean} [options.overwrite=true] whether to overwrite an existing file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tputFileContents: function(path, body, options) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\toptions = options || {}\n\t\t\tconst headers = {}\n\t\t\tlet contentType = 'text/plain;charset=utf-8'\n\t\t\tif (options.contentType) {\n\t\t\t\tcontentType = options.contentType\n\t\t\t}\n\n\t\t\theaders['Content-Type'] = contentType\n\n\t\t\tif (_.isUndefined(options.overwrite) || options.overwrite) {\n\t\t\t\t// will trigger 412 precondition failed if a file already exists\n\t\t\t\theaders['If-None-Match'] = '*'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'PUT',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders,\n\t\t\t\tbody || ''\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t_simpleCall: function(method, path) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\tmethod,\n\t\t\t\tthis._buildUrl(path)\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Creates a directory\n\t\t *\n\t\t * @param {String} path path to create\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tcreateDirectory: function(path) {\n\t\t\treturn this._simpleCall('MKCOL', path)\n\t\t},\n\n\t\t/**\n\t\t * Deletes a file or directory\n\t\t *\n\t\t * @param {String} path path to delete\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tremove: function(path) {\n\t\t\treturn this._simpleCall('DELETE', path)\n\t\t},\n\n\t\t/**\n\t\t * Moves path to another path\n\t\t *\n\t\t * @param {String} path path to move\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t * @param {Object} [headers=null] additional headers\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tmove: function(path, destinationPath, allowOverwrite, headers) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\theaders = _.extend({}, headers, {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t})\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'MOVE',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Copies path to another path\n\t\t *\n\t\t * @param {String} path path to copy\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tcopy: function(path, destinationPath, allowOverwrite) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tconst headers = {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t}\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'COPY',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(response) {\n\t\t\t\t\tif (self._isSuccessStatus(response.status)) {\n\t\t\t\t\t\tdeferred.resolve(response.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdeferred.reject(response.status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Add a file info parser function\n\t\t *\n\t\t * @param {OC.Files.Client~parseFileInfo} parserFunction\n\t\t */\n\t\taddFileInfoParser: function(parserFunction) {\n\t\t\tthis._fileInfoParsers.push(parserFunction)\n\t\t},\n\n\t\t/**\n\t\t * Returns the dav.Client instance used internally\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {dav.Client}\n\t\t */\n\t\tgetClient: function() {\n\t\t\treturn this._client\n\t\t},\n\n\t\t/**\n\t\t * Returns the user name\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} userName\n\t\t */\n\t\tgetUserName: function() {\n\t\t\treturn this._client.userName\n\t\t},\n\n\t\t/**\n\t\t * Returns the password\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} password\n\t\t */\n\t\tgetPassword: function() {\n\t\t\treturn this._client.password\n\t\t},\n\n\t\t/**\n\t\t * Returns the base URL\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetBaseUrl: function() {\n\t\t\treturn this._client.baseUrl\n\t\t},\n\n\t\t/**\n\t\t * Returns the host\n\t\t *\n\t\t * @since 13.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetHost: function() {\n\t\t\treturn this._host\n\t\t},\n\t}\n\n\t/**\n\t * File info parser function\n\t *\n\t * This function receives a list of Webdav properties as input and\n\t * should return a hash array of parsed properties, if applicable.\n\t *\n\t * @callback OC.Files.Client~parseFileInfo\n\t * @param {Object} XML Webdav properties\n * @return {Array} array of parsed property values\n\t */\n\n\tif (!OC.Files) {\n\t\t/**\n\t\t * @namespace OC.Files\n\t\t *\n\t\t * @since 8.2\n\t\t */\n\t\tOC.Files = {}\n\t}\n\n\t/**\n\t * Returns the default instance of the files client\n\t *\n\t * @returns {OC.Files.Client} default client\n\t *\n\t * @since 8.2\n\t */\n\tOC.Files.getClient = function() {\n\t\tif (OC.Files._defaultClient) {\n\t\t\treturn OC.Files._defaultClient\n\t\t}\n\n\t\tconst client = new OC.Files.Client({\n\t\t\thost: OC.getHost(),\n\t\t\tport: OC.getPort(),\n\t\t\troot: OC.linkToRemoteBase('dav') + '/files/' + OC.getCurrentUser().uid,\n\t\t\tuseHTTPS: OC.getProtocol() === 'https',\n\t\t})\n\t\tOC.Files._defaultClient = client\n\t\treturn client\n\t}\n\n\tOC.Files.Client = Client\n})(OC, OC.Files.FileInfo)\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.amdD = function () {\n\tthrow new Error('define cannot be used indirect');\n};","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = function(module) {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 5578;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t5578: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [7874], function() { return __webpack_require__(7913); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","OC","FileInfo","Client","options","this","_root","root","charAt","length","substr","url","PROTOCOL_HTTP","useHTTPS","PROTOCOL_HTTPS","host","_host","_defaultHeaders","defaultHeaders","requestToken","_baseUrl","clientOptions","baseUrl","xmlNamespaces","userName","password","_client","dav","xhrProvider","_","bind","_xhrProvider","_fileInfoParsers","NS_OWNCLOUD","NS_NEXTCLOUD","NS_DAV","NS_OCS","PROPERTY_GETLASTMODIFIED","PROPERTY_GETETAG","PROPERTY_GETCONTENTTYPE","PROPERTY_RESOURCETYPE","PROPERTY_INTERNAL_FILEID","PROPERTY_PERMISSIONS","PROPERTY_SIZE","PROPERTY_GETCONTENTLENGTH","PROPERTY_ISENCRYPTED","PROPERTY_SHARE_PERMISSIONS","PROPERTY_SHARE_ATTRIBUTES","PROPERTY_QUOTA_AVAILABLE_BYTES","_PROPFIND_PROPERTIES","prototype","headers","xhr","XMLHttpRequest","oldOpen","open","result","apply","arguments","each","value","key","setRequestHeader","registerXHRForErrorProcessing","_buildUrl","path","_buildPath","i","joinPaths","sections","split","encodeURIComponent","join","_parseHeaders","headersString","headerRows","sepPos","indexOf","headerName","headerValue","push","_parseEtag","etag","_parseFileInfo","response","decodeURIComponent","href","propStat","status","props","properties","data","id","dirname","name","basename","mtime","Date","getTime","etagProp","isUndefined","sizeProp","size","parseInt","hasPreviewProp","hasPreview","isEncryptedProp","isEncrypted","isFavouritedProp","isFavourited","contentType","mimetype","resType","xmlvalue","namespaceURI","nodeName","permissions","PERMISSION_NONE","permissionProp","permString","mountType","PERMISSION_CREATE","PERMISSION_READ","PERMISSION_UPDATE","PERMISSION_DELETE","PERMISSION_SHARE","sharePermissionsProp","sharePermissions","shareAttributesProp","shareAttributes","JSON","parse","e","console","warn","mounTypeProp","quotaAvailableBytes","parserFunction","extend","_parseResult","responses","self","map","_isSuccessStatus","_getSabreException","xml","responseXML","messages","getElementsByTagNameNS","exceptions","message","textContent","exception","getPropfindProperties","_propfindProperties","propDef","getFolderContents","$","Deferred","promise","propFind","then","results","body","includeParent","shift","resolve","reject","getFilteredFiles","filter","systemTagIds","favorite","circlesIds","namespace","prop","property","parseClarkNotation","escapeHTML","request","getFileInfo","getFileContents","putFileContents","overwrite","_simpleCall","method","createDirectory","remove","move","destinationPath","allowOverwrite","Overwrite","copy","addFileInfoParser","getClient","getUserName","getPassword","getBaseUrl","getHost","Files","_defaultClient","client","port","getPort","linkToRemoteBase","getCurrentUser","uid","getProtocol","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","loaded","__webpack_modules__","call","m","amdD","Error","amdO","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","j","Object","keys","every","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","g","globalThis","Function","window","obj","hasOwnProperty","Symbol","toStringTag","nmd","paths","children","b","document","baseURI","location","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","forEach","nc","__webpack_exports__"],"sourceRoot":""} +{"version":3,"file":"core-files_client.js?v=cd3aad52b20f4abb45d6","mappings":";6BAAIA,8DCqCJ,SAAUC,EAAIC,GAeb,IAAIC,EAAS,SAATA,EAAkBC,GACrBC,KAAKC,MAAQF,EAAQG,KAC4B,MAA7CF,KAAKC,MAAME,OAAOH,KAAKC,MAAMG,OAAS,KACzCJ,KAAKC,MAAQD,KAAKC,MAAMI,OAAO,EAAGL,KAAKC,MAAMG,OAAS,IAGvD,IAAIE,EAAMR,EAAOS,cAAgB,MAC7BR,EAAQS,WACXF,EAAMR,EAAOW,eAAiB,OAG/BH,GAAOP,EAAQW,KAAOV,KAAKC,MAC3BD,KAAKW,MAAQZ,EAAQW,KACrBV,KAAKY,gBAAkBb,EAAQc,gBAAkB,CAChD,mBAAoB,iBACpB,aAAgBjB,EAAGkB,cAEpBd,KAAKe,SAAWT,EAEhB,IAAMU,EAAgB,CACrBC,QAASjB,KAAKe,SACdG,cAAe,CACd,OAAQ,IACR,yBAA0B,KAC1B,0BAA2B,KAC3B,4CAA6C,QAG3CnB,EAAQoB,WACXH,EAAcG,SAAWpB,EAAQoB,UAE9BpB,EAAQqB,WACXJ,EAAcI,SAAWrB,EAAQqB,UAElCpB,KAAKqB,QAAU,IAAIC,IAAIxB,OAAOkB,GAC9BhB,KAAKqB,QAAQE,YAAcC,EAAEC,KAAKzB,KAAK0B,aAAc1B,MACrDA,KAAK2B,iBAAmB,EACxB,EAED7B,EAAO8B,YAAc,yBACrB9B,EAAO+B,aAAe,0BACtB/B,EAAOgC,OAAS,OAChBhC,EAAOiC,OAAS,4CAEhBjC,EAAOkC,yBAA2B,IAAMlC,EAAOgC,OAAS,mBACxDhC,EAAOmC,iBAAmB,IAAMnC,EAAOgC,OAAS,WAChDhC,EAAOoC,wBAA0B,IAAMpC,EAAOgC,OAAS,kBACvDhC,EAAOqC,sBAAwB,IAAMrC,EAAOgC,OAAS,gBACrDhC,EAAOsC,yBAA2B,IAAMtC,EAAO8B,YAAc,UAC7D9B,EAAOuC,qBAAuB,IAAMvC,EAAO8B,YAAc,eACzD9B,EAAOwC,cAAgB,IAAMxC,EAAO8B,YAAc,QAClD9B,EAAOyC,0BAA4B,IAAMzC,EAAOgC,OAAS,oBACzDhC,EAAO0C,qBAAuB,IAAM1C,EAAOgC,OAAS,gBACpDhC,EAAO2C,2BAA6B,IAAM3C,EAAOiC,OAAS,qBAC1DjC,EAAO4C,0BAA4B,IAAM5C,EAAO+B,aAAe,oBAC/D/B,EAAO6C,+BAAiC,IAAM7C,EAAOgC,OAAS,yBAE9DhC,EAAOS,cAAgB,OACvBT,EAAOW,eAAiB,QAExBX,EAAO8C,qBAAuB,CAI7B,CAAC9C,EAAOgC,OAAQ,mBAIhB,CAAChC,EAAOgC,OAAQ,WAIhB,CAAChC,EAAOgC,OAAQ,kBAIhB,CAAChC,EAAOgC,OAAQ,gBAIhB,CAAChC,EAAO8B,YAAa,UAIrB,CAAC9B,EAAO8B,YAAa,eAKrB,CAAC9B,EAAO8B,YAAa,QAIrB,CAAC9B,EAAOgC,OAAQ,oBAChB,CAAChC,EAAOgC,OAAQ,yBAIhB,CAAChC,EAAO+B,aAAc,eAItB,CAAC/B,EAAO+B,aAAc,cAItB,CAAC/B,EAAO+B,aAAc,gBAItB,CAAC/B,EAAOiC,OAAQ,qBAIhB,CAACjC,EAAO+B,aAAc,qBAMvB/B,EAAO+C,UAAY,CAOlB5C,MAAO,KAOPoB,QAAS,KAOTM,iBAAkB,GAMlBD,aAAc,WACb,IAAMoB,EAAU9C,KAAKY,gBACfmC,EAAM,IAAIC,eACVC,EAAUF,EAAIG,KAWpB,OATAH,EAAIG,KAAO,WACV,IAAMC,EAASF,EAAQG,MAAMpD,KAAMqD,WAInC,OAHA7B,EAAE8B,KAAKR,GAAS,SAASS,EAAOC,GAC/BT,EAAIU,iBAAiBD,EAAKD,EAC1B,IACMJ,CACP,EAEDvD,EAAG8D,8BAA8BX,GAC1BA,CACP,EAUDY,UAAW,WACV,IAAIC,EAAO5D,KAAK6D,WAAWT,MAAMpD,KAAMqD,WAOvC,MANuC,MAAnCO,EAAKzD,OAAO,CAACyD,EAAKxD,OAAS,MAC9BwD,EAAOA,EAAKvD,OAAO,EAAGuD,EAAKxD,OAAS,IAEd,MAAnBwD,EAAKzD,OAAO,KACfyD,EAAOA,EAAKvD,OAAO,IAEbL,KAAKe,SAAW,IAAM6C,CAC7B,EAWDC,WAAY,WACX,IAEIC,EAFAF,EAAOhE,EAAGmE,UAAUX,MAAMpD,KAAMqD,WAC9BW,EAAWJ,EAAKK,MAAM,KAE5B,IAAKH,EAAI,EAAGA,EAAIE,EAAS5D,OAAQ0D,IAChCE,EAASF,GAAKI,mBAAmBF,EAASF,IAG3C,OADOE,EAASG,KAAK,IAErB,EASDC,cAAe,SAASC,GAGvB,IAFA,IAAMC,EAAaD,EAAcJ,MAAM,MACjCnB,EAAU,CAAC,EACRgB,EAAI,EAAGA,EAAIQ,EAAWlE,OAAQ0D,IAAK,CAC3C,IAAMS,EAASD,EAAWR,GAAGU,QAAQ,KACrC,KAAID,EAAS,GAAb,CAIA,IAAME,EAAaH,EAAWR,GAAGzD,OAAO,EAAGkE,GACrCG,EAAcJ,EAAWR,GAAGzD,OAAOkE,EAAS,GAE7CzB,EAAQ2B,KAEZ3B,EAAQ2B,GAAc,IAGvB3B,EAAQ2B,GAAYE,KAAKD,EAVxB,CAWD,CACD,OAAO5B,CACP,EASD8B,WAAY,SAASC,GACpB,MAAuB,MAAnBA,EAAK1E,OAAO,GACR0E,EAAKZ,MAAM,KAAK,GAEjBY,CACP,EASDC,eAAgB,SAASC,GACxB,IAAInB,EAAOoB,mBAAmBD,EAASE,MASvC,GARIrB,EAAKvD,OAAO,EAAGL,KAAKC,MAAMG,UAAYJ,KAAKC,QAC9C2D,EAAOA,EAAKvD,OAAOL,KAAKC,MAAMG,SAGM,MAAjCwD,EAAKzD,OAAOyD,EAAKxD,OAAS,KAC7BwD,EAAOA,EAAKvD,OAAO,EAAGuD,EAAKxD,OAAS,IAGJ,IAA7B2E,EAASG,SAAS9E,QAAgD,oBAAhC2E,EAASG,SAAS,GAAGC,OAC1D,OAAO,KAGR,IAAMC,EAAQL,EAASG,SAAS,GAAGG,WAE7BC,EAAO,CACZC,GAAIH,EAAMtF,EAAOsC,0BACjBwB,KAAMhE,EAAG4F,QAAQ5B,IAAS,IAC1B6B,KAAM7F,EAAG8F,SAAS9B,GAClB+B,MAAQ,IAAIC,KAAKR,EAAMtF,EAAOkC,2BAA4B6D,WAGrDC,EAAWV,EAAMtF,EAAOmC,kBACzBT,EAAEuE,YAAYD,KAClBR,EAAKT,KAAO7E,KAAK4E,WAAWkB,IAG7B,IAAIE,EAAWZ,EAAMtF,EAAOyC,2BACvBf,EAAEuE,YAAYC,KAClBV,EAAKW,KAAOC,SAASF,EAAU,KAGhCA,EAAWZ,EAAMtF,EAAOwC,eACnBd,EAAEuE,YAAYC,KAClBV,EAAKW,KAAOC,SAASF,EAAU,KAGhC,IAAMG,EAAiBf,EAAM,IAAMtF,EAAO+B,aAAe,gBACpDL,EAAEuE,YAAYI,GAGlBb,EAAKc,YAAa,EAFlBd,EAAKc,WAAgC,SAAnBD,EAKnB,IAAME,EAAkBjB,EAAM,IAAMtF,EAAO+B,aAAe,iBACrDL,EAAEuE,YAAYM,GAGlBf,EAAKgB,aAAc,EAFnBhB,EAAKgB,YAAkC,MAApBD,EAKpB,IAAME,EAAmBnB,EAAM,IAAMtF,EAAO8B,YAAc,aACrDJ,EAAEuE,YAAYQ,GAGlBjB,EAAKkB,cAAe,EAFpBlB,EAAKkB,aAAoC,MAArBD,EAKrB,IAAME,EAAcrB,EAAMtF,EAAOoC,yBAC5BV,EAAEuE,YAAYU,KAClBnB,EAAKoB,SAAWD,GAGjB,IAAME,EAAUvB,EAAMtF,EAAOqC,uBAC7B,IAAKmD,EAAKoB,UAAYC,EAAS,CAC9B,IAAMC,EAAWD,EAAQ,GACrBC,EAASC,eAAiB/G,EAAOgC,QAA8C,eAApC8E,EAASE,SAAS7C,MAAM,KAAK,KAC3EqB,EAAKoB,SAAW,uBAEjB,CAEDpB,EAAKyB,YAAcnH,EAAGoH,gBACtB,IAAMC,EAAiB7B,EAAMtF,EAAOuC,sBACpC,IAAKb,EAAEuE,YAAYkB,GAAiB,CACnC,IAAMC,EAAaD,GAAkB,GACrC3B,EAAK6B,UAAY,KACjB,IAAK,IAAIrD,EAAI,EAAGA,EAAIoD,EAAW9G,OAAQ0D,IAEtC,OADUoD,EAAW/G,OAAO2D,IAG5B,IAAK,IACL,IAAK,IACJwB,EAAKyB,aAAenH,EAAGwH,kBACvB,MACD,IAAK,IACJ9B,EAAKyB,aAAenH,EAAGyH,gBACvB,MACD,IAAK,IACL,IAAK,IACL,IAAK,IACJ/B,EAAKyB,aAAenH,EAAG0H,kBACvB,MACD,IAAK,IACJhC,EAAKyB,aAAenH,EAAG2H,kBACvB,MACD,IAAK,IACJjC,EAAKyB,aAAenH,EAAG4H,iBACvB,MACD,IAAK,IACClC,EAAK6B,YAET7B,EAAK6B,UAAY,YAElB,MACD,IAAK,IAEJ7B,EAAK6B,UAAY,SAInB,CAED,IAAMM,EAAuBrC,EAAMtF,EAAO2C,4BACrCjB,EAAEuE,YAAY0B,KAClBnC,EAAKoC,iBAAmBxB,SAASuB,IAGlC,IAAME,EAAsBvC,EAAMtF,EAAO4C,2BACzC,GAAKlB,EAAEuE,YAAY4B,GAQlBrC,EAAKsC,gBAAkB,QAPvB,IACCtC,EAAKsC,gBAAkBC,KAAKC,MAAMH,EAIlC,CAHC,MAAOI,GACRC,EAAQC,KAAK,yDAA2DN,EAAsB,KAC9FrC,EAAKsC,gBAAkB,EACvB,CAKF,IAAMM,EAAe9C,EAAM,IAAMtF,EAAO+B,aAAe,eAClDL,EAAEuE,YAAYmC,KAClB5C,EAAK6B,UAAYe,GAGlB,IAAMC,EAAsB/C,EAAM,IAAMtF,EAAOgC,OAAS,0BAUxD,OATKN,EAAEuE,YAAYoC,KAClB7C,EAAK6C,oBAAsBA,GAI5B3G,EAAE8B,KAAKtD,KAAK2B,kBAAkB,SAASyG,GACtC5G,EAAE6G,OAAO/C,EAAM8C,EAAerD,EAAUO,IAAS,CAAC,EAClD,IAEM,IAAIzF,EAASyF,EACpB,EAODgD,aAAc,SAASC,GACtB,IAAMC,EAAOxI,KACb,OAAOwB,EAAEiH,IAAIF,GAAW,SAASxD,GAChC,OAAOyD,EAAK1D,eAAeC,EAC3B,GACD,EASD2D,iBAAkB,SAASvD,GAC1B,OAAOA,GAAU,KAAOA,GAAU,GAClC,EAQDwD,mBAAoB,SAAS5D,GAC5B,IAAM5B,EAAS,CAAC,EACVyF,EAAM7D,EAAShC,IAAI8F,YACzB,GAAY,OAARD,EACH,OAAOzF,EAER,IAAM2F,EAAWF,EAAIG,uBAAuB,yBAA0B,WAChEC,EAAaJ,EAAIG,uBAAuB,yBAA0B,aAOxE,OANID,EAAS1I,SACZ+C,EAAO8F,QAAUH,EAAS,GAAGI,aAE1BF,EAAW5I,SACd+C,EAAOgG,UAAYH,EAAW,GAAGE,aAE3B/F,CACP,EAODiG,sBAAuB,WAMtB,OALKpJ,KAAKqJ,sBACTrJ,KAAKqJ,oBAAsB7H,EAAEiH,IAAI3I,EAAO8C,sBAAsB,SAAS0G,GACtE,MAAO,IAAMA,EAAQ,GAAK,IAAMA,EAAQ,EACxC,KAEKtJ,KAAKqJ,mBACZ,EAaDE,kBAAmB,SAAS3F,EAAM7D,GAC5B6D,IACJA,EAAO,IAER7D,EAAUA,GAAW,CAAC,EACtB,IAGIsF,EAHEmD,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAyBzB,OAtBCrE,EADG7D,EAAEuE,YAAYhG,EAAQsF,YACZrF,KAAKoJ,wBAELrJ,EAAQsF,WAGtBrF,KAAKqB,QAAQsI,SACZ3J,KAAK2D,UAAUC,GACfyB,EACA,GACCuE,MAAK,SAASzG,GACf,GAAIqF,EAAKE,iBAAiBvF,EAAOgC,QAAS,CACzC,IAAM0E,EAAUrB,EAAKF,aAAanF,EAAO2G,MACpC/J,GAAYA,EAAQgK,eAExBF,EAAQG,QAETrK,EAASsK,QAAQ9G,EAAOgC,OAAQ0E,EAChC,MACA1G,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,EAEhC,IACMuG,CACP,EAcDS,iBAAkB,SAASC,EAAQrK,GAClCA,EAAUA,GAAW,CAAC,EACtB,IAGIsF,EAHEmD,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAQzB,GALCrE,EADG7D,EAAEuE,YAAYhG,EAAQsF,YACZrF,KAAKoJ,wBAELrJ,EAAQsF,YAGjB+E,IACCA,EAAOC,cAAgB7I,EAAEuE,YAAYqE,EAAOE,YAAcF,EAAOG,WACtE,KAAM,0BAIP,IACIC,EADAV,EAAO,oBAEX,IAAKU,KAAaxK,KAAKqB,QAAQH,cAC9B4I,GAAQ,UAAY9J,KAAKqB,QAAQH,cAAcsJ,GAAa,KAAOA,EAAY,IA2ChF,OAzCAV,GAAQ,MAGRA,GAAQ,QAAU9J,KAAKqB,QAAQH,cAAc,QAAU,WACvDM,EAAE8B,KAAK+B,GAAY,SAASoF,GAC3B,IAAMC,EAAWlC,EAAKnH,QAAQsJ,mBAAmBF,GACjDX,GAAQ,YAActB,EAAKnH,QAAQH,cAAcwJ,EAASF,WAAa,IAAME,EAASjF,KAAO,OAC7F,IAEDqE,GAAQ,SAAW9J,KAAKqB,QAAQH,cAAc,QAAU,WAGxD4I,GAAQ,0BACRtI,EAAE8B,KAAK8G,EAAOC,cAAc,SAASA,GACpCP,GAAQ,yBAA2Bc,IAAWP,GAAgB,mBAC9D,IACD7I,EAAE8B,KAAK8G,EAAOG,YAAY,SAASA,GAClCT,GAAQ,sBAAwBc,IAAWL,GAAc,gBACzD,IACGH,EAAOE,WACVR,GAAQ,yBAA2BM,EAAOE,SAAW,IAAM,KAAO,oBAEnER,GAAQ,2BAGRA,GAAQ,uBAER9J,KAAKqB,QAAQwJ,QACZ,SACA7K,KAAK2D,YACL,CAAC,EACDmG,GACCF,MAAK,SAASzG,GACf,GAAIqF,EAAKE,iBAAiBvF,EAAOgC,QAAS,CACzC,IAAM0E,EAAUrB,EAAKF,aAAanF,EAAO2G,MACzCnK,EAASsK,QAAQ9G,EAAOgC,OAAQ0E,EAChC,MACA1G,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,EAEhC,IACMuG,CACP,EAUDoB,YAAa,SAASlH,EAAM7D,GACtB6D,IACJA,EAAO,IAER7D,EAAUA,GAAW,CAAC,EACtB,IAGIsF,EAHEmD,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAuBzB,OApBCrE,EADG7D,EAAEuE,YAAYhG,EAAQsF,YACZrF,KAAKoJ,wBAELrJ,EAAQsF,WAItBrF,KAAKqB,QAAQsI,SACZ3J,KAAK2D,UAAUC,GACfyB,EACA,GACCuE,MACD,SAASzG,GACJqF,EAAKE,iBAAiBvF,EAAOgC,QAChCxF,EAASsK,QAAQ9G,EAAOgC,OAAQqD,EAAKF,aAAa,CAACnF,EAAO2G,OAAO,KAEjE3G,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,GAEhC,IAEKuG,CACP,EASDqB,gBAAiB,SAASnH,GACzB,IAAKA,EACJ,KAAM,0BAEP,IAAM4E,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAezB,OAbA1J,KAAKqB,QAAQwJ,QACZ,MACA7K,KAAK2D,UAAUC,IACdgG,MACD,SAASzG,GACJqF,EAAKE,iBAAiBvF,EAAOgC,QAChCxF,EAASsK,QAAQ9G,EAAOgC,OAAQhC,EAAO2G,OAEvC3G,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,GAEhC,IAEKuG,CACP,EAaDsB,gBAAiB,SAASpH,EAAMkG,EAAM/J,GACrC,IAAK6D,EACJ,KAAM,0BAEP,IAAM4E,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAEnB5G,EAAU,CAAC,EACb2D,EAAc,2BA2BlB,OA7BA1G,EAAUA,GAAW,CAAC,GAGV0G,cACXA,EAAc1G,EAAQ0G,aAGvB3D,EAAQ,gBAAkB2D,GAEtBjF,EAAEuE,YAAYhG,EAAQkL,YAAclL,EAAQkL,aAE/CnI,EAAQ,iBAAmB,KAG5B9C,KAAKqB,QAAQwJ,QACZ,MACA7K,KAAK2D,UAAUC,GACfd,EACAgH,GAAQ,IACPF,MACD,SAASzG,GACJqF,EAAKE,iBAAiBvF,EAAOgC,QAChCxF,EAASsK,QAAQ9G,EAAOgC,SAExBhC,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,GAEhC,IAEKuG,CACP,EAEDwB,YAAa,SAASC,EAAQvH,EAAMd,GACnC,IAAKc,EACJ,KAAM,0BAGP,IAAM4E,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAgBzB,OAdA1J,KAAKqB,QAAQwJ,QACZM,EACAnL,KAAK2D,UAAUC,GACfd,GAAoB,CAAC,GACpB8G,MACD,SAASzG,GACJqF,EAAKE,iBAAiBvF,EAAOgC,QAChCxF,EAASsK,QAAQ9G,EAAOgC,SAExBhC,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,GAEhC,IAEKuG,CACP,EASD0B,gBAAiB,SAASxH,EAAMd,GAC/B,OAAO9C,KAAKkL,YAAY,QAAStH,EAAMd,EACvC,EASDuI,OAAQ,SAASzH,GAChB,OAAO5D,KAAKkL,YAAY,SAAUtH,EAClC,EAaD0H,KAAM,SAAS1H,EAAM2H,EAAiBC,EAAgB1I,GACrD,IAAKc,EACJ,KAAM,0BAEP,IAAK2H,EACJ,KAAM,qCAGP,IAAM/C,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UAuBzB,OAtBA5G,EAAUtB,EAAE6G,OAAO,CAAC,EAAGvF,EAAS,CAC/B,YAAe9C,KAAK2D,UAAU4H,KAG1BC,IACJ1I,EAAQ2I,UAAY,KAGrBzL,KAAKqB,QAAQwJ,QACZ,OACA7K,KAAK2D,UAAUC,GACfd,GACC8G,MACD,SAASzG,GACJqF,EAAKE,iBAAiBvF,EAAOgC,QAChCxF,EAASsK,QAAQ9G,EAAOgC,SAExBhC,EAAS3B,EAAE6G,OAAOlF,EAAQqF,EAAKG,mBAAmBxF,IAClDxD,EAASuK,OAAO/G,EAAOgC,OAAQhC,GAEhC,IAEKuG,CACP,EAYDgC,KAAM,SAAS9H,EAAM2H,EAAiBC,GACrC,IAAK5H,EACJ,KAAM,0BAEP,IAAK2H,EACJ,KAAM,qCAGP,IAAM/C,EAAOxI,KACPL,EAAW6J,EAAEC,WACbC,EAAU/J,EAAS+J,UACnB5G,EAAU,CACf,YAAe9C,KAAK2D,UAAU4H,IAoB/B,OAjBKC,IACJ1I,EAAQ2I,UAAY,KAGrBzL,KAAKqB,QAAQwJ,QACZ,OACA7K,KAAK2D,UAAUC,GACfd,GACC8G,MACD,SAAS7E,GACJyD,EAAKE,iBAAiB3D,EAASI,QAClCxF,EAASsK,QAAQlF,EAASI,QAE1BxF,EAASuK,OAAOnF,EAASI,OAE1B,IAEKuE,CACP,EAODiC,kBAAmB,SAASvD,GAC3BpI,KAAK2B,iBAAiBgD,KAAKyD,EAC3B,EAQDwD,UAAW,WACV,OAAO5L,KAAKqB,OACZ,EAQDwK,YAAa,WACZ,OAAO7L,KAAKqB,QAAQF,QACpB,EAQD2K,YAAa,WACZ,OAAO9L,KAAKqB,QAAQD,QACpB,EAQD2K,WAAY,WACX,OAAO/L,KAAKqB,QAAQJ,OACpB,EAQD+K,QAAS,WACR,OAAOhM,KAAKW,KACZ,GAcGf,EAAGqM,QAMPrM,EAAGqM,MAAQ,CAAC,GAUbrM,EAAGqM,MAAML,UAAY,WACpB,GAAIhM,EAAGqM,MAAMC,eACZ,OAAOtM,EAAGqM,MAAMC,eAGjB,IAAMC,EAAS,IAAIvM,EAAGqM,MAAMnM,OAAO,CAClCY,KAAMd,EAAGoM,UACTI,KAAMxM,EAAGyM,UACTnM,KAAMN,EAAG0M,iBAAiB,OAAS,UAAY1M,EAAG2M,iBAAiBC,IACnEhM,SAA+B,UAArBZ,EAAG6M,gBAGd,OADA7M,EAAGqM,MAAMC,eAAiBC,EACnBA,CACP,EAEDvM,EAAGqM,MAAMnM,OAASA,CAr8BnB,EAs8BGF,GAAIA,GAAGqM,MAAMpM,YC1+BZ6M,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CACjDrH,GAAIqH,EACJK,QAAQ,EACRF,QAAS,CAAC,GAUX,OANAG,EAAoBN,GAAUO,KAAKH,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAG3EK,EAAOC,QAAS,EAGTD,EAAOD,OACf,CAGAJ,EAAoBS,EAAIF,EC5BxBP,EAAoBU,KAAO,WAC1B,MAAM,IAAIC,MAAM,iCACjB,ECFAX,EAAoBY,KAAO,CAAC,EJAxB5N,EAAW,GACfgN,EAAoBa,EAAI,SAASrK,EAAQsK,EAAUC,EAAIC,GACtD,IAAGF,EAAH,CAMA,IAAIG,EAAeC,IACnB,IAAS/J,EAAI,EAAGA,EAAInE,EAASS,OAAQ0D,IAAK,CACrC2J,EAAW9N,EAASmE,GAAG,GACvB4J,EAAK/N,EAASmE,GAAG,GACjB6J,EAAWhO,EAASmE,GAAG,GAE3B,IAJA,IAGIgK,GAAY,EACPC,EAAI,EAAGA,EAAIN,EAASrN,OAAQ2N,MACpB,EAAXJ,GAAsBC,GAAgBD,IAAaK,OAAOC,KAAKtB,EAAoBa,GAAGU,OAAM,SAAS1K,GAAO,OAAOmJ,EAAoBa,EAAEhK,GAAKiK,EAASM,GAAK,IAChKN,EAASU,OAAOJ,IAAK,IAErBD,GAAY,EACTH,EAAWC,IAAcA,EAAeD,IAG7C,GAAGG,EAAW,CACbnO,EAASwO,OAAOrK,IAAK,GACrB,IAAIsK,EAAIV,SACEZ,IAANsB,IAAiBjL,EAASiL,EAC/B,CACD,CACA,OAAOjL,CArBP,CAJCwK,EAAWA,GAAY,EACvB,IAAI,IAAI7J,EAAInE,EAASS,OAAQ0D,EAAI,GAAKnE,EAASmE,EAAI,GAAG,GAAK6J,EAAU7J,IAAKnE,EAASmE,GAAKnE,EAASmE,EAAI,GACrGnE,EAASmE,GAAK,CAAC2J,EAAUC,EAAIC,EAwB/B,EK5BAhB,EAAoB0B,EAAI,SAASrB,GAChC,IAAIsB,EAAStB,GAAUA,EAAOuB,WAC7B,WAAa,OAAOvB,EAAgB,OAAG,EACvC,WAAa,OAAOA,CAAQ,EAE7B,OADAL,EAAoB6B,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CACR,ECNA3B,EAAoB6B,EAAI,SAASzB,EAAS2B,GACzC,IAAI,IAAIlL,KAAOkL,EACX/B,EAAoBgC,EAAED,EAAYlL,KAASmJ,EAAoBgC,EAAE5B,EAASvJ,IAC5EwK,OAAOY,eAAe7B,EAASvJ,EAAK,CAAEqL,YAAY,EAAMC,IAAKJ,EAAWlL,IAG3E,ECPAmJ,EAAoBoC,EAAI,WACvB,GAA0B,iBAAfC,WAAyB,OAAOA,WAC3C,IACC,OAAOhP,MAAQ,IAAIiP,SAAS,cAAb,EAGhB,CAFE,MAAOlH,GACR,GAAsB,iBAAXmH,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxBvC,EAAoBgC,EAAI,SAASQ,EAAK1E,GAAQ,OAAOuD,OAAOnL,UAAUuM,eAAejC,KAAKgC,EAAK1E,EAAO,ECCtGkC,EAAoByB,EAAI,SAASrB,GACX,oBAAXsC,QAA0BA,OAAOC,aAC1CtB,OAAOY,eAAe7B,EAASsC,OAAOC,YAAa,CAAE/L,MAAO,WAE7DyK,OAAOY,eAAe7B,EAAS,aAAc,CAAExJ,OAAO,GACvD,ECNAoJ,EAAoB4C,IAAM,SAASvC,GAGlC,OAFAA,EAAOwC,MAAQ,GACVxC,EAAOyC,WAAUzC,EAAOyC,SAAW,IACjCzC,CACR,ECJAL,EAAoBoB,EAAI,gBCAxBpB,EAAoB+C,EAAIC,SAASC,SAAWpH,KAAKqH,SAAS5K,KAK1D,IAAI6K,EAAkB,CACrB,KAAM,GAaPnD,EAAoBa,EAAEO,EAAI,SAASgC,GAAW,OAAoC,IAA7BD,EAAgBC,EAAgB,EAGrF,IAAIC,EAAuB,SAASC,EAA4B3K,GAC/D,IAKIsH,EAAUmD,EALVtC,EAAWnI,EAAK,GAChB4K,EAAc5K,EAAK,GACnB6K,EAAU7K,EAAK,GAGIxB,EAAI,EAC3B,GAAG2J,EAAS2C,MAAK,SAAS7K,GAAM,OAA+B,IAAxBuK,EAAgBvK,EAAW,IAAI,CACrE,IAAIqH,KAAYsD,EACZvD,EAAoBgC,EAAEuB,EAAatD,KACrCD,EAAoBS,EAAER,GAAYsD,EAAYtD,IAGhD,GAAGuD,EAAS,IAAIhN,EAASgN,EAAQxD,EAClC,CAEA,IADGsD,GAA4BA,EAA2B3K,GACrDxB,EAAI2J,EAASrN,OAAQ0D,IACzBiM,EAAUtC,EAAS3J,GAChB6I,EAAoBgC,EAAEmB,EAAiBC,IAAYD,EAAgBC,IACrED,EAAgBC,GAAS,KAE1BD,EAAgBC,GAAW,EAE5B,OAAOpD,EAAoBa,EAAErK,EAC9B,EAEIkN,EAAqB7H,KAA4B,sBAAIA,KAA4B,uBAAK,GAC1F6H,EAAmBC,QAAQN,EAAqBvO,KAAK,KAAM,IAC3D4O,EAAmB1L,KAAOqL,EAAqBvO,KAAK,KAAM4O,EAAmB1L,KAAKlD,KAAK4O,OClDvF1D,EAAoB4D,QAAKzD,ECGzB,IAAI0D,EAAsB7D,EAAoBa,OAAEV,EAAW,CAAC,OAAO,WAAa,OAAOH,EAAoB,KAAO,IAClH6D,EAAsB7D,EAAoBa,EAAEgD","sources":["webpack:///nextcloud/webpack/runtime/chunk loaded","webpack:///nextcloud/core/src/files/client.js","webpack:///nextcloud/webpack/bootstrap","webpack:///nextcloud/webpack/runtime/amd define","webpack:///nextcloud/webpack/runtime/amd options","webpack:///nextcloud/webpack/runtime/compat get default export","webpack:///nextcloud/webpack/runtime/define property getters","webpack:///nextcloud/webpack/runtime/global","webpack:///nextcloud/webpack/runtime/hasOwnProperty shorthand","webpack:///nextcloud/webpack/runtime/make namespace object","webpack:///nextcloud/webpack/runtime/node module decorator","webpack:///nextcloud/webpack/runtime/runtimeId","webpack:///nextcloud/webpack/runtime/jsonp chunk loading","webpack:///nextcloud/webpack/runtime/nonce","webpack:///nextcloud/webpack/startup"],"sourcesContent":["var deferred = [];\n__webpack_require__.O = function(result, chunkIds, fn, priority) {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar chunkIds = deferred[i][0];\n\t\tvar fn = deferred[i][1];\n\t\tvar priority = deferred[i][2];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every(function(key) { return __webpack_require__.O[key](chunkIds[j]); })) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","/**\n * Copyright (c) 2015\n *\n * @author Bjoern Schiessle \n * @author John Molakvoæ \n * @author Julius Härtl \n * @author Lukas Reschke \n * @author Michael Jobst \n * @author Robin Appelman \n * @author Roeland Jago Douma \n * @author Thomas Citharel \n * @author Tomasz Grobelny \n * @author Vincent Petry \n * @author Vinicius Cubas Brand \n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n *\n */\n\n/* eslint-disable */\nimport escapeHTML from 'escape-html'\n\n/* global dav */\n\n(function(OC, FileInfo) {\n\t/**\n\t * @class OC.Files.Client\n\t * @classdesc Client to access files on the server\n\t *\n\t * @param {Object} options\n\t * @param {String} options.host host name\n\t * @param {number} [options.port] port\n\t * @param {boolean} [options.useHTTPS] whether to use https\n\t * @param {String} [options.root] root path\n\t * @param {String} [options.userName] user name\n\t * @param {String} [options.password] password\n\t *\n\t * @since 8.2\n\t */\n\tvar Client = function(options) {\n\t\tthis._root = options.root\n\t\tif (this._root.charAt(this._root.length - 1) === '/') {\n\t\t\tthis._root = this._root.substr(0, this._root.length - 1)\n\t\t}\n\n\t\tlet url = Client.PROTOCOL_HTTP + '://'\n\t\tif (options.useHTTPS) {\n\t\t\turl = Client.PROTOCOL_HTTPS + '://'\n\t\t}\n\n\t\turl += options.host + this._root\n\t\tthis._host = options.host\n\t\tthis._defaultHeaders = options.defaultHeaders || {\n\t\t\t'X-Requested-With': 'XMLHttpRequest',\n\t\t\t'requesttoken': OC.requestToken,\n\t\t}\n\t\tthis._baseUrl = url\n\n\t\tconst clientOptions = {\n\t\t\tbaseUrl: this._baseUrl,\n\t\t\txmlNamespaces: {\n\t\t\t\t'DAV:': 'd',\n\t\t\t\t'http://owncloud.org/ns': 'oc',\n\t\t\t\t'http://nextcloud.org/ns': 'nc',\n\t\t\t\t'http://open-collaboration-services.org/ns': 'ocs',\n\t\t\t},\n\t\t}\n\t\tif (options.userName) {\n\t\t\tclientOptions.userName = options.userName\n\t\t}\n\t\tif (options.password) {\n\t\t\tclientOptions.password = options.password\n\t\t}\n\t\tthis._client = new dav.Client(clientOptions)\n\t\tthis._client.xhrProvider = _.bind(this._xhrProvider, this)\n\t\tthis._fileInfoParsers = []\n\t}\n\n\tClient.NS_OWNCLOUD = 'http://owncloud.org/ns'\n\tClient.NS_NEXTCLOUD = 'http://nextcloud.org/ns'\n\tClient.NS_DAV = 'DAV:'\n\tClient.NS_OCS = 'http://open-collaboration-services.org/ns'\n\n\tClient.PROPERTY_GETLASTMODIFIED\t= '{' + Client.NS_DAV + '}getlastmodified'\n\tClient.PROPERTY_GETETAG\t= '{' + Client.NS_DAV + '}getetag'\n\tClient.PROPERTY_GETCONTENTTYPE\t= '{' + Client.NS_DAV + '}getcontenttype'\n\tClient.PROPERTY_RESOURCETYPE\t= '{' + Client.NS_DAV + '}resourcetype'\n\tClient.PROPERTY_INTERNAL_FILEID\t= '{' + Client.NS_OWNCLOUD + '}fileid'\n\tClient.PROPERTY_PERMISSIONS\t= '{' + Client.NS_OWNCLOUD + '}permissions'\n\tClient.PROPERTY_SIZE\t= '{' + Client.NS_OWNCLOUD + '}size'\n\tClient.PROPERTY_GETCONTENTLENGTH\t= '{' + Client.NS_DAV + '}getcontentlength'\n\tClient.PROPERTY_ISENCRYPTED\t= '{' + Client.NS_DAV + '}is-encrypted'\n\tClient.PROPERTY_SHARE_PERMISSIONS\t= '{' + Client.NS_OCS + '}share-permissions'\n\tClient.PROPERTY_SHARE_ATTRIBUTES\t= '{' + Client.NS_NEXTCLOUD + '}share-attributes'\n\tClient.PROPERTY_QUOTA_AVAILABLE_BYTES\t= '{' + Client.NS_DAV + '}quota-available-bytes'\n\n\tClient.PROTOCOL_HTTP\t= 'http'\n\tClient.PROTOCOL_HTTPS\t= 'https'\n\n\tClient._PROPFIND_PROPERTIES = [\n\t\t/**\n\t\t * Modified time\n\t\t */\n\t\t[Client.NS_DAV, 'getlastmodified'],\n\t\t/**\n\t\t * Etag\n\t\t */\n\t\t[Client.NS_DAV, 'getetag'],\n\t\t/**\n\t\t * Mime type\n\t\t */\n\t\t[Client.NS_DAV, 'getcontenttype'],\n\t\t/**\n\t\t * Resource type \"collection\" for folders, empty otherwise\n\t\t */\n\t\t[Client.NS_DAV, 'resourcetype'],\n\t\t/**\n\t\t * File id\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'fileid'],\n\t\t/**\n\t\t * Letter-coded permissions\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'permissions'],\n\t\t// [Client.NS_OWNCLOUD, 'downloadURL'],\n\t\t/**\n\t\t * Folder sizes\n\t\t */\n\t\t[Client.NS_OWNCLOUD, 'size'],\n\t\t/**\n\t\t * File sizes\n\t\t */\n\t\t[Client.NS_DAV, 'getcontentlength'],\n\t\t[Client.NS_DAV, 'quota-available-bytes'],\n\t\t/**\n\t\t * Preview availability\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'has-preview'],\n\t\t/**\n\t\t * Mount type\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'mount-type'],\n\t\t/**\n\t\t * Encryption state\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'is-encrypted'],\n\t\t/**\n\t\t * Share permissions\n\t\t */\n\t\t[Client.NS_OCS, 'share-permissions'],\n\t\t/**\n\t\t * Share attributes\n\t\t */\n\t\t[Client.NS_NEXTCLOUD, 'share-attributes'],\n\t]\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tClient.prototype = {\n\n\t\t/**\n\t\t * Root path of the Webdav endpoint\n\t\t *\n\t\t * @type string\n\t\t */\n\t\t_root: null,\n\n\t\t/**\n\t\t * Client from the library\n\t\t *\n\t\t * @type dav.Client\n\t\t */\n\t\t_client: null,\n\n\t\t/**\n\t\t * Array of file info parsing functions.\n\t\t *\n\t\t * @type Array\n\t\t */\n\t\t_fileInfoParsers: [],\n\n\t\t/**\n\t\t * Returns the configured XHR provider for davclient\n\t\t * @returns {XMLHttpRequest}\n\t\t */\n\t\t_xhrProvider: function() {\n\t\t\tconst headers = this._defaultHeaders\n\t\t\tconst xhr = new XMLHttpRequest()\n\t\t\tconst oldOpen = xhr.open\n\t\t\t// override open() method to add headers\n\t\t\txhr.open = function() {\n\t\t\t\tconst result = oldOpen.apply(this, arguments)\n\t\t\t\t_.each(headers, function(value, key) {\n\t\t\t\t\txhr.setRequestHeader(key, value)\n\t\t\t\t})\n\t\t\t\treturn result\n\t\t\t}\n\n\t\t\tOC.registerXHRForErrorProcessing(xhr)\n\t\t\treturn xhr\n\t\t},\n\n\t\t/**\n\t\t * Prepends the base url to the given path sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} base url + joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildUrl: function() {\n\t\t\tlet path = this._buildPath.apply(this, arguments)\n\t\t\tif (path.charAt([path.length - 1]) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\t\t\tif (path.charAt(0) === '/') {\n\t\t\t\tpath = path.substr(1)\n\t\t\t}\n\t\t\treturn this._baseUrl + '/' + path\n\t\t},\n\n\t\t/**\n\t\t * Append the path to the root and also encode path\n\t\t * sections\n\t\t *\n\t\t * @param {...String} path sections\n\t\t *\n\t\t * @returns {String} joined path, any leading or trailing slash\n\t\t * will be kept\n\t\t */\n\t\t_buildPath: function() {\n\t\t\tlet path = OC.joinPaths.apply(this, arguments)\n\t\t\tconst sections = path.split('/')\n\t\t\tlet i\n\t\t\tfor (i = 0; i < sections.length; i++) {\n\t\t\t\tsections[i] = encodeURIComponent(sections[i])\n\t\t\t}\n\t\t\tpath = sections.join('/')\n\t\t\treturn path\n\t\t},\n\n\t\t/**\n\t\t * Parse headers string into a map\n\t\t *\n\t\t * @param {string} headersString headers list as string\n\t\t *\n\t\t * @returns {Object.} map of header name to header contents\n\t\t */\n\t\t_parseHeaders: function(headersString) {\n\t\t\tconst headerRows = headersString.split('\\n')\n\t\t\tconst headers = {}\n\t\t\tfor (let i = 0; i < headerRows.length; i++) {\n\t\t\t\tconst sepPos = headerRows[i].indexOf(':')\n\t\t\t\tif (sepPos < 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tconst headerName = headerRows[i].substr(0, sepPos)\n\t\t\t\tconst headerValue = headerRows[i].substr(sepPos + 2)\n\n\t\t\t\tif (!headers[headerName]) {\n\t\t\t\t\t// make it an array\n\t\t\t\t\theaders[headerName] = []\n\t\t\t\t}\n\n\t\t\t\theaders[headerName].push(headerValue)\n\t\t\t}\n\t\t\treturn headers\n\t\t},\n\n\t\t/**\n\t\t * Parses the etag response which is in double quotes.\n\t\t *\n\t\t * @param {string} etag etag value in double quotes\n\t\t *\n\t\t * @returns {string} etag without double quotes\n\t\t */\n\t\t_parseEtag: function(etag) {\n\t\t\tif (etag.charAt(0) === '\"') {\n\t\t\t\treturn etag.split('\"')[1]\n\t\t\t}\n\t\t\treturn etag\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav result\n\t\t *\n\t\t * @param {Object} response XML object\n\t\t *\n\t\t * @returns {Array.} array of file info\n\t\t */\n\t\t_parseFileInfo: function(response) {\n\t\t\tlet path = decodeURIComponent(response.href)\n\t\t\tif (path.substr(0, this._root.length) === this._root) {\n\t\t\t\tpath = path.substr(this._root.length)\n\t\t\t}\n\n\t\t\tif (path.charAt(path.length - 1) === '/') {\n\t\t\t\tpath = path.substr(0, path.length - 1)\n\t\t\t}\n\n\t\t\tif (response.propStat.length === 0 || response.propStat[0].status !== 'HTTP/1.1 200 OK') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst props = response.propStat[0].properties\n\n\t\t\tconst data = {\n\t\t\t\tid: props[Client.PROPERTY_INTERNAL_FILEID],\n\t\t\t\tpath: OC.dirname(path) || '/',\n\t\t\t\tname: OC.basename(path),\n\t\t\t\tmtime: (new Date(props[Client.PROPERTY_GETLASTMODIFIED])).getTime(),\n\t\t\t}\n\n\t\t\tconst etagProp = props[Client.PROPERTY_GETETAG]\n\t\t\tif (!_.isUndefined(etagProp)) {\n\t\t\t\tdata.etag = this._parseEtag(etagProp)\n\t\t\t}\n\n\t\t\tlet sizeProp = props[Client.PROPERTY_GETCONTENTLENGTH]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tsizeProp = props[Client.PROPERTY_SIZE]\n\t\t\tif (!_.isUndefined(sizeProp)) {\n\t\t\t\tdata.size = parseInt(sizeProp, 10)\n\t\t\t}\n\n\t\t\tconst hasPreviewProp = props['{' + Client.NS_NEXTCLOUD + '}has-preview']\n\t\t\tif (!_.isUndefined(hasPreviewProp)) {\n\t\t\t\tdata.hasPreview = hasPreviewProp === 'true'\n\t\t\t} else {\n\t\t\t\tdata.hasPreview = true\n\t\t\t}\n\n\t\t\tconst isEncryptedProp = props['{' + Client.NS_NEXTCLOUD + '}is-encrypted']\n\t\t\tif (!_.isUndefined(isEncryptedProp)) {\n\t\t\t\tdata.isEncrypted = isEncryptedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isEncrypted = false\n\t\t\t}\n\n\t\t\tconst isFavouritedProp = props['{' + Client.NS_OWNCLOUD + '}favorite']\n\t\t\tif (!_.isUndefined(isFavouritedProp)) {\n\t\t\t\tdata.isFavourited = isFavouritedProp === '1'\n\t\t\t} else {\n\t\t\t\tdata.isFavourited = false\n\t\t\t}\n\n\t\t\tconst contentType = props[Client.PROPERTY_GETCONTENTTYPE]\n\t\t\tif (!_.isUndefined(contentType)) {\n\t\t\t\tdata.mimetype = contentType\n\t\t\t}\n\n\t\t\tconst resType = props[Client.PROPERTY_RESOURCETYPE]\n\t\t\tif (!data.mimetype && resType) {\n\t\t\t\tconst xmlvalue = resType[0]\n\t\t\t\tif (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {\n\t\t\t\t\tdata.mimetype = 'httpd/unix-directory'\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdata.permissions = OC.PERMISSION_NONE\n\t\t\tconst permissionProp = props[Client.PROPERTY_PERMISSIONS]\n\t\t\tif (!_.isUndefined(permissionProp)) {\n\t\t\t\tconst permString = permissionProp || ''\n\t\t\t\tdata.mountType = null\n\t\t\t\tfor (let i = 0; i < permString.length; i++) {\n\t\t\t\t\tconst c = permString.charAt(i)\n\t\t\t\t\tswitch (c) {\n\t\t\t\t\t// FIXME: twisted permissions\n\t\t\t\t\tcase 'C':\n\t\t\t\t\tcase 'K':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_CREATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'G':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_READ\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'W':\n\t\t\t\t\tcase 'N':\n\t\t\t\t\tcase 'V':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_UPDATE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'D':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_DELETE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'R':\n\t\t\t\t\t\tdata.permissions |= OC.PERMISSION_SHARE\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'M':\n\t\t\t\t\t\tif (!data.mountType) {\n\t\t\t\t\t\t\t// TODO: how to identify external-root ?\n\t\t\t\t\t\t\tdata.mountType = 'external'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'S':\n\t\t\t\t\t\t// TODO: how to identify shared-root ?\n\t\t\t\t\t\tdata.mountType = 'shared'\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst sharePermissionsProp = props[Client.PROPERTY_SHARE_PERMISSIONS]\n\t\t\tif (!_.isUndefined(sharePermissionsProp)) {\n\t\t\t\tdata.sharePermissions = parseInt(sharePermissionsProp)\n\t\t\t}\n\n\t\t\tconst shareAttributesProp = props[Client.PROPERTY_SHARE_ATTRIBUTES]\n\t\t\tif (!_.isUndefined(shareAttributesProp)) {\n\t\t\t\ttry {\n\t\t\t\t\tdata.shareAttributes = JSON.parse(shareAttributesProp)\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconsole.warn('Could not parse share attributes returned by server: \"' + shareAttributesProp + '\"')\n\t\t\t\t\tdata.shareAttributes = [];\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdata.shareAttributes = [];\n\t\t\t}\n\n\t\t\tconst mounTypeProp = props['{' + Client.NS_NEXTCLOUD + '}mount-type']\n\t\t\tif (!_.isUndefined(mounTypeProp)) {\n\t\t\t\tdata.mountType = mounTypeProp\n\t\t\t}\n\n\t\t\tconst quotaAvailableBytes = props['{' + Client.NS_DAV + '}quota-available-bytes']\n\t\t\tif (!_.isUndefined(quotaAvailableBytes)) {\n\t\t\t\tdata.quotaAvailableBytes = quotaAvailableBytes\n\t\t\t}\n\n\t\t\t// extend the parsed data using the custom parsers\n\t\t\t_.each(this._fileInfoParsers, function(parserFunction) {\n\t\t\t\t_.extend(data, parserFunction(response, data) || {})\n\t\t\t})\n\n\t\t\treturn new FileInfo(data)\n\t\t},\n\n\t\t/**\n\t\t * Parse Webdav multistatus\n\t\t *\n\t\t * @param {Array} responses\n\t\t */\n\t\t_parseResult: function(responses) {\n\t\t\tconst self = this\n\t\t\treturn _.map(responses, function(response) {\n\t\t\t\treturn self._parseFileInfo(response)\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Returns whether the given status code means success\n\t\t *\n\t\t * @param {number} status status code\n\t\t *\n\t\t * @returns true if status code is between 200 and 299 included\n\t\t */\n\t\t_isSuccessStatus: function(status) {\n\t\t\treturn status >= 200 && status <= 299\n\t\t},\n\n\t\t/**\n\t\t * Parse the Sabre exception out of the given response, if any\n\t\t *\n\t\t * @param {Object} response object\n\t\t * @returns {Object} array of parsed message and exception (only the first one)\n\t\t */\n\t\t_getSabreException: function(response) {\n\t\t\tconst result = {}\n\t\t\tconst xml = response.xhr.responseXML\n\t\t\tif (xml === null) {\n\t\t\t\treturn result\n\t\t\t}\n\t\t\tconst messages = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'message')\n\t\t\tconst exceptions = xml.getElementsByTagNameNS('http://sabredav.org/ns', 'exception')\n\t\t\tif (messages.length) {\n\t\t\t\tresult.message = messages[0].textContent\n\t\t\t}\n\t\t\tif (exceptions.length) {\n\t\t\t\tresult.exception = exceptions[0].textContent\n\t\t\t}\n\t\t\treturn result\n\t\t},\n\n\t\t/**\n\t\t * Returns the default PROPFIND properties to use during a call.\n\t\t *\n\t\t * @returns {Array.} array of properties\n\t\t */\n\t\tgetPropfindProperties: function() {\n\t\t\tif (!this._propfindProperties) {\n\t\t\t\tthis._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {\n\t\t\t\t\treturn '{' + propDef[0] + '}' + propDef[1]\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn this._propfindProperties\n\t\t},\n\n\t\t/**\n\t\t * Lists the contents of a directory\n\t\t *\n\t\t * @param {String} path path to retrieve\n\t\t * @param {Object} [options] options\n\t\t * @param {boolean} [options.includeParent=false] set to true to keep\n\t\t * the parent folder in the result list\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFolderContents: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t1\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tif (!options || !options.includeParent) {\n\t\t\t\t\t\t// remove root dir, the first entry\n\t\t\t\t\t\tresults.shift()\n\t\t\t\t\t}\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Fetches a flat list of files filtered by a given filter criteria.\n\t\t * (currently system tags and circles are supported)\n\t\t *\n\t\t * @param {Object} filter filter criteria\n\t\t * @param {Object} [filter.systemTagIds] list of system tag ids to filter by\n\t\t * @param {boolean} [filter.favorite] set it to filter by favorites\n\t\t * @param {Object} [options] options\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFilteredFiles: function(filter, options) {\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\tif (!filter\n\t\t\t\t|| (!filter.systemTagIds && _.isUndefined(filter.favorite) && !filter.circlesIds)) {\n\t\t\t\tthrow 'Missing filter argument'\n\t\t\t}\n\n\t\t\t// root element with namespaces\n\t\t\tlet body = '\\n'\n\t\t\t_.each(properties, function(prop) {\n\t\t\t\tconst property = self._client.parseClarkNotation(prop)\n\t\t\t\tbody += ' <' + self._client.xmlNamespaces[property.namespace] + ':' + property.name + ' />\\n'\n\t\t\t})\n\n\t\t\tbody += ' \\n'\n\n\t\t\t// rules block\n\t\t\tbody +=\t' \\n'\n\t\t\t_.each(filter.systemTagIds, function(systemTagIds) {\n\t\t\t\tbody += ' ' + escapeHTML(systemTagIds) + '\\n'\n\t\t\t})\n\t\t\t_.each(filter.circlesIds, function(circlesIds) {\n\t\t\t\tbody += ' ' + escapeHTML(circlesIds) + '\\n'\n\t\t\t})\n\t\t\tif (filter.favorite) {\n\t\t\t\tbody += ' ' + (filter.favorite ? '1' : '0') + '\\n'\n\t\t\t}\n\t\t\tbody += ' \\n'\n\n\t\t\t// end of root\n\t\t\tbody += '\\n'\n\n\t\t\tthis._client.request(\n\t\t\t\t'REPORT',\n\t\t\t\tthis._buildUrl(),\n\t\t\t\t{},\n\t\t\t\tbody\n\t\t\t).then(function(result) {\n\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\tconst results = self._parseResult(result.body)\n\t\t\t\t\tdeferred.resolve(result.status, results)\n\t\t\t\t} else {\n\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the file info of a given path.\n\t\t *\n\t\t * @param {String} path path\n\t\t * @param {Array} [options.properties] list of Webdav properties to retrieve\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tgetFileInfo: function(path, options) {\n\t\t\tif (!path) {\n\t\t\t\tpath = ''\n\t\t\t}\n\t\t\toptions = options || {}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tlet properties\n\t\t\tif (_.isUndefined(options.properties)) {\n\t\t\t\tproperties = this.getPropfindProperties()\n\t\t\t} else {\n\t\t\t\tproperties = options.properties\n\t\t\t}\n\n\t\t\t// TODO: headers\n\t\t\tthis._client.propFind(\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\tproperties,\n\t\t\t\t0\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, self._parseResult([result.body])[0])\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Returns the contents of the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tgetFileContents: function(path) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\t'GET',\n\t\t\t\tthis._buildUrl(path)\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status, result.body)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Puts the given data into the given file.\n\t\t *\n\t\t * @param {String} path path to file\n\t\t * @param {String} body file body\n\t\t * @param {Object} [options]\n\t\t * @param {String} [options.contentType='text/plain'] content type\n\t\t * @param {boolean} [options.overwrite=true] whether to overwrite an existing file\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tputFileContents: function(path, body, options) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\toptions = options || {}\n\t\t\tconst headers = {}\n\t\t\tlet contentType = 'text/plain;charset=utf-8'\n\t\t\tif (options.contentType) {\n\t\t\t\tcontentType = options.contentType\n\t\t\t}\n\n\t\t\theaders['Content-Type'] = contentType\n\n\t\t\tif (_.isUndefined(options.overwrite) || options.overwrite) {\n\t\t\t\t// will trigger 412 precondition failed if a file already exists\n\t\t\t\theaders['If-None-Match'] = '*'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'PUT',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders,\n\t\t\t\tbody || ''\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t_simpleCall: function(method, path, headers) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\n\t\t\tthis._client.request(\n\t\t\t\tmethod,\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders ? headers : {}\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Creates a directory\n\t\t *\n\t\t * @param {String} path path to create\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tcreateDirectory: function(path, headers) {\n\t\t\treturn this._simpleCall('MKCOL', path, headers)\n\t\t},\n\n\t\t/**\n\t\t * Deletes a file or directory\n\t\t *\n\t\t * @param {String} path path to delete\n\t\t *\n\t\t * @returns {Promise}\n\t\t */\n\t\tremove: function(path) {\n\t\t\treturn this._simpleCall('DELETE', path)\n\t\t},\n\n\t\t/**\n\t\t * Moves path to another path\n\t\t *\n\t\t * @param {String} path path to move\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t * @param {Object} [headers=null] additional headers\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tmove: function(path, destinationPath, allowOverwrite, headers) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\theaders = _.extend({}, headers, {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t})\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'MOVE',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(result) {\n\t\t\t\t\tif (self._isSuccessStatus(result.status)) {\n\t\t\t\t\t\tdeferred.resolve(result.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresult = _.extend(result, self._getSabreException(result))\n\t\t\t\t\t\tdeferred.reject(result.status, result)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Copies path to another path\n\t\t *\n\t\t * @param {String} path path to copy\n\t\t * @param {String} destinationPath destination path\n\t\t * @param {boolean} [allowOverwrite=false] true to allow overwriting,\n\t\t * false otherwise\n\t\t *\n\t\t * @returns {Promise} promise\n\t\t */\n\t\tcopy: function(path, destinationPath, allowOverwrite) {\n\t\t\tif (!path) {\n\t\t\t\tthrow 'Missing argument \"path\"'\n\t\t\t}\n\t\t\tif (!destinationPath) {\n\t\t\t\tthrow 'Missing argument \"destinationPath\"'\n\t\t\t}\n\n\t\t\tconst self = this\n\t\t\tconst deferred = $.Deferred()\n\t\t\tconst promise = deferred.promise()\n\t\t\tconst headers = {\n\t\t\t\t'Destination': this._buildUrl(destinationPath),\n\t\t\t}\n\n\t\t\tif (!allowOverwrite) {\n\t\t\t\theaders.Overwrite = 'F'\n\t\t\t}\n\n\t\t\tthis._client.request(\n\t\t\t\t'COPY',\n\t\t\t\tthis._buildUrl(path),\n\t\t\t\theaders\n\t\t\t).then(\n\t\t\t\tfunction(response) {\n\t\t\t\t\tif (self._isSuccessStatus(response.status)) {\n\t\t\t\t\t\tdeferred.resolve(response.status)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdeferred.reject(response.status)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t)\n\t\t\treturn promise\n\t\t},\n\n\t\t/**\n\t\t * Add a file info parser function\n\t\t *\n\t\t * @param {OC.Files.Client~parseFileInfo} parserFunction\n\t\t */\n\t\taddFileInfoParser: function(parserFunction) {\n\t\t\tthis._fileInfoParsers.push(parserFunction)\n\t\t},\n\n\t\t/**\n\t\t * Returns the dav.Client instance used internally\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {dav.Client}\n\t\t */\n\t\tgetClient: function() {\n\t\t\treturn this._client\n\t\t},\n\n\t\t/**\n\t\t * Returns the user name\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} userName\n\t\t */\n\t\tgetUserName: function() {\n\t\t\treturn this._client.userName\n\t\t},\n\n\t\t/**\n\t\t * Returns the password\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} password\n\t\t */\n\t\tgetPassword: function() {\n\t\t\treturn this._client.password\n\t\t},\n\n\t\t/**\n\t\t * Returns the base URL\n\t\t *\n\t\t * @since 11.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetBaseUrl: function() {\n\t\t\treturn this._client.baseUrl\n\t\t},\n\n\t\t/**\n\t\t * Returns the host\n\t\t *\n\t\t * @since 13.0.0\n\t\t * @returns {String} base URL\n\t\t */\n\t\tgetHost: function() {\n\t\t\treturn this._host\n\t\t},\n\t}\n\n\t/**\n\t * File info parser function\n\t *\n\t * This function receives a list of Webdav properties as input and\n\t * should return a hash array of parsed properties, if applicable.\n\t *\n\t * @callback OC.Files.Client~parseFileInfo\n\t * @param {Object} XML Webdav properties\n * @return {Array} array of parsed property values\n\t */\n\n\tif (!OC.Files) {\n\t\t/**\n\t\t * @namespace OC.Files\n\t\t *\n\t\t * @since 8.2\n\t\t */\n\t\tOC.Files = {}\n\t}\n\n\t/**\n\t * Returns the default instance of the files client\n\t *\n\t * @returns {OC.Files.Client} default client\n\t *\n\t * @since 8.2\n\t */\n\tOC.Files.getClient = function() {\n\t\tif (OC.Files._defaultClient) {\n\t\t\treturn OC.Files._defaultClient\n\t\t}\n\n\t\tconst client = new OC.Files.Client({\n\t\t\thost: OC.getHost(),\n\t\t\tport: OC.getPort(),\n\t\t\troot: OC.linkToRemoteBase('dav') + '/files/' + OC.getCurrentUser().uid,\n\t\t\tuseHTTPS: OC.getProtocol() === 'https',\n\t\t})\n\t\tOC.Files._defaultClient = client\n\t\treturn client\n\t}\n\n\tOC.Files.Client = Client\n})(OC, OC.Files.FileInfo)\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\tloaded: false,\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Flag the module as loaded\n\tmodule.loaded = true;\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","__webpack_require__.amdD = function () {\n\tthrow new Error('define cannot be used indirect');\n};","__webpack_require__.amdO = {};","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","__webpack_require__.nmd = function(module) {\n\tmodule.paths = [];\n\tif (!module.children) module.children = [];\n\treturn module;\n};","__webpack_require__.j = 5578;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t5578: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = function(chunkId) { return installedChunks[chunkId] === 0; };\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = function(parentChunkLoadingFunction, data) {\n\tvar chunkIds = data[0];\n\tvar moreModules = data[1];\n\tvar runtime = data[2];\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some(function(id) { return installedChunks[id] !== 0; })) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkId] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunknextcloud\"] = self[\"webpackChunknextcloud\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));","__webpack_require__.nc = undefined;","// startup\n// Load entry module and return exports\n// This entry module depends on other loaded chunks and execution need to be delayed\nvar __webpack_exports__ = __webpack_require__.O(undefined, [7874], function() { return __webpack_require__(7913); })\n__webpack_exports__ = __webpack_require__.O(__webpack_exports__);\n"],"names":["deferred","OC","FileInfo","Client","options","this","_root","root","charAt","length","substr","url","PROTOCOL_HTTP","useHTTPS","PROTOCOL_HTTPS","host","_host","_defaultHeaders","defaultHeaders","requestToken","_baseUrl","clientOptions","baseUrl","xmlNamespaces","userName","password","_client","dav","xhrProvider","_","bind","_xhrProvider","_fileInfoParsers","NS_OWNCLOUD","NS_NEXTCLOUD","NS_DAV","NS_OCS","PROPERTY_GETLASTMODIFIED","PROPERTY_GETETAG","PROPERTY_GETCONTENTTYPE","PROPERTY_RESOURCETYPE","PROPERTY_INTERNAL_FILEID","PROPERTY_PERMISSIONS","PROPERTY_SIZE","PROPERTY_GETCONTENTLENGTH","PROPERTY_ISENCRYPTED","PROPERTY_SHARE_PERMISSIONS","PROPERTY_SHARE_ATTRIBUTES","PROPERTY_QUOTA_AVAILABLE_BYTES","_PROPFIND_PROPERTIES","prototype","headers","xhr","XMLHttpRequest","oldOpen","open","result","apply","arguments","each","value","key","setRequestHeader","registerXHRForErrorProcessing","_buildUrl","path","_buildPath","i","joinPaths","sections","split","encodeURIComponent","join","_parseHeaders","headersString","headerRows","sepPos","indexOf","headerName","headerValue","push","_parseEtag","etag","_parseFileInfo","response","decodeURIComponent","href","propStat","status","props","properties","data","id","dirname","name","basename","mtime","Date","getTime","etagProp","isUndefined","sizeProp","size","parseInt","hasPreviewProp","hasPreview","isEncryptedProp","isEncrypted","isFavouritedProp","isFavourited","contentType","mimetype","resType","xmlvalue","namespaceURI","nodeName","permissions","PERMISSION_NONE","permissionProp","permString","mountType","PERMISSION_CREATE","PERMISSION_READ","PERMISSION_UPDATE","PERMISSION_DELETE","PERMISSION_SHARE","sharePermissionsProp","sharePermissions","shareAttributesProp","shareAttributes","JSON","parse","e","console","warn","mounTypeProp","quotaAvailableBytes","parserFunction","extend","_parseResult","responses","self","map","_isSuccessStatus","_getSabreException","xml","responseXML","messages","getElementsByTagNameNS","exceptions","message","textContent","exception","getPropfindProperties","_propfindProperties","propDef","getFolderContents","$","Deferred","promise","propFind","then","results","body","includeParent","shift","resolve","reject","getFilteredFiles","filter","systemTagIds","favorite","circlesIds","namespace","prop","property","parseClarkNotation","escapeHTML","request","getFileInfo","getFileContents","putFileContents","overwrite","_simpleCall","method","createDirectory","remove","move","destinationPath","allowOverwrite","Overwrite","copy","addFileInfoParser","getClient","getUserName","getPassword","getBaseUrl","getHost","Files","_defaultClient","client","port","getPort","linkToRemoteBase","getCurrentUser","uid","getProtocol","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","loaded","__webpack_modules__","call","m","amdD","Error","amdO","O","chunkIds","fn","priority","notFulfilled","Infinity","fulfilled","j","Object","keys","every","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","g","globalThis","Function","window","obj","hasOwnProperty","Symbol","toStringTag","nmd","paths","children","b","document","baseURI","location","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","moreModules","runtime","some","chunkLoadingGlobal","forEach","nc","__webpack_exports__"],"sourceRoot":""} \ No newline at end of file From fcf6d1aebf974238e836e63f4d1f4afcc0579829 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Thu, 20 Jul 2023 18:49:31 +0200 Subject: [PATCH 13/15] Use correct ICache class --- lib/private/Files/ObjectStore/ObjectStoreStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/Files/ObjectStore/ObjectStoreStorage.php b/lib/private/Files/ObjectStore/ObjectStoreStorage.php index b5b8e8a65f4e3..d6ca3cc39f900 100644 --- a/lib/private/Files/ObjectStore/ObjectStoreStorage.php +++ b/lib/private/Files/ObjectStore/ObjectStoreStorage.php @@ -47,7 +47,6 @@ use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IProcessingCallbackStorage; use OCP\Files\Storage\IStorage; -use OCP\ICache; class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite, IProcessingCallbackStorage { use CopyDirectory; @@ -100,6 +99,7 @@ public function __construct($params) { public function mkdir($path) { $path = $this->normalizePath($path); + if ($this->file_exists($path)) { return false; } From 83520d5345dbe7f046b751e04b267a718a195f13 Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Thu, 20 Jul 2023 18:55:25 +0200 Subject: [PATCH 14/15] Clean coding style --- lib/private/Files/View.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index ec4c3df78f500..ea5d1a131d4c2 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -64,7 +64,6 @@ use OCP\Files\Storage\IChunkedFileWrite; use OCP\Files\Storage\IStorage; use OCP\Files\StorageInvalidException; -use OCP\ILogger; use OCP\IUser; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; @@ -838,14 +837,14 @@ public function rename($path1, $path2) { } else { $result = false; } - // moving a file/folder within the same mount point + // moving a file/folder within the same mount point } elseif ($storage1 === $storage2) { if ($storage1) { $result = $storage1->rename($internalPath1, $internalPath2); } else { $result = false; } - // moving a file/folder between storages (from $storage1 to $storage2) + // moving a file/folder between storages (from $storage1 to $storage2) } else { $result = $storage2->moveFromStorage($storage1, $internalPath1, $internalPath2); } @@ -1064,7 +1063,6 @@ public function toTmpFile($path) { public function fromTmpFile($tmpFile, $path) { $this->assertPathLength($path); if (Filesystem::isValidPath($path)) { - // Get directory that the file is going into $filePath = dirname($path); @@ -1820,7 +1818,6 @@ private function assertPathLength($path) { * @return boolean */ private function targetIsNotShared(IStorage $targetStorage, string $targetInternalPath) { - // note: cannot use the view because the target is already locked $fileId = (int)$targetStorage->getCache()->getId($targetInternalPath); if ($fileId === -1) { From d90e5e4ce0e11bbb75f044ea5305ba0c93cc2f9c Mon Sep 17 00:00:00 2001 From: "Bernd.Rederlechner@t-systems.com" Date: Fri, 18 Aug 2023 11:31:04 +0200 Subject: [PATCH 15/15] Inject lacking CustomPropertiesService --- apps/dav/lib/DAV/CustomPropertiesBackend.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/dav/lib/DAV/CustomPropertiesBackend.php b/apps/dav/lib/DAV/CustomPropertiesBackend.php index fa03dd2730eb7..0e34e089b4095 100644 --- a/apps/dav/lib/DAV/CustomPropertiesBackend.php +++ b/apps/dav/lib/DAV/CustomPropertiesBackend.php @@ -142,6 +142,7 @@ class CustomPropertiesBackend implements BackendInterface { public function __construct( Tree $tree, IDBConnection $connection, + CustomPropertiesService $customPropertiesService, IUser $user ) { $this->tree = $tree;