From 5d4efb4d5fd8e4389856df5d94c3b92c7019e603 Mon Sep 17 00:00:00 2001 From: Anna Larch Date: Tue, 7 Feb 2023 15:55:55 +0100 Subject: [PATCH 1/5] Do not set up filesystem on every call Also remove old Oc_FileChunking logis that produced GC- collectable chunks Signed-off-by: Anna Larch --- apps/dav/lib/Connector/Sabre/Directory.php | 75 +++--- apps/dav/lib/Connector/Sabre/File.php | 135 ----------- apps/dav/lib/Connector/Sabre/FilesPlugin.php | 9 - apps/dav/lib/Connector/Sabre/LockPlugin.php | 4 +- apps/dav/lib/Connector/Sabre/ObjectTree.php | 38 --- apps/dav/lib/Connector/Sabre/QuotaPlugin.php | 17 -- .../tests/unit/Connector/Sabre/FileTest.php | 221 ------------------ .../unit/Connector/Sabre/ObjectTreeTest.php | 60 ++--- .../unit/Connector/Sabre/QuotaPluginTest.php | 91 +------- .../Sabre/RequestTest/RequestTestCase.php | 2 - .../Sabre/RequestTest/UploadTest.php | 110 --------- apps/files/appinfo/info.xml | 9 +- apps/files/composer/composer/ClassLoader.php | 12 +- .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/files/composer/composer/installed.php | 4 +- .../lib/BackgroundJob/FileChunkCleanupJob.php | 62 +++++ lib/base.php | 17 -- lib/composer/composer/autoload_classmap.php | 1 - lib/composer/composer/autoload_static.php | 1 - lib/private/Cache/File.php | 24 +- lib/private/Server.php | 1 + lib/private/legacy/OC_FileChunking.php | 184 --------------- tests/lib/FileChunkingTest.php | 72 ------ 24 files changed, 144 insertions(+), 1007 deletions(-) create mode 100644 apps/files/lib/BackgroundJob/FileChunkCleanupJob.php delete mode 100644 lib/private/legacy/OC_FileChunking.php delete mode 100644 tests/lib/FileChunkingTest.php diff --git a/apps/dav/lib/Connector/Sabre/Directory.php b/apps/dav/lib/Connector/Sabre/Directory.php index f4b1ee62190f3..4459daea869a9 100644 --- a/apps/dav/lib/Connector/Sabre/Directory.php +++ b/apps/dav/lib/Connector/Sabre/Directory.php @@ -48,6 +48,7 @@ use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Psr\Log\LoggerInterface; +use Sabre\DAV\Exception; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Locked; use Sabre\DAV\Exception\NotFound; @@ -102,33 +103,19 @@ public function __construct(View $view, FileInfo $info, ?CachingTree $tree = nul * @param string $name Name of the file * @param resource|string $data Initial payload * @return null|string - * @throws Exception\EntityTooLarge * @throws Exception\UnsupportedMediaType * @throws FileLocked * @throws InvalidPath - * @throws \Sabre\DAV\Exception - * @throws \Sabre\DAV\Exception\BadRequest - * @throws \Sabre\DAV\Exception\Forbidden - * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @throws Exception + * @throws BadRequest + * @throws Exception\Forbidden + * @throws ServiceUnavailable */ public function createFile($name, $data = null) { try { - // for chunked upload also updating a existing file is a "createFile" - // because we create all the chunks before re-assemble them to the existing file. - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - - // exit if we can't create a new file and we don't updatable existing file - $chunkInfo = \OC_FileChunking::decodeName($name); - if (!$this->fileView->isCreatable($this->path) && - !$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name']) - ) { - throw new \Sabre\DAV\Exception\Forbidden(); - } - } else { - // For non-chunked upload it is enough to check if we can create a new file - if (!$this->fileView->isCreatable($this->path)) { - throw new \Sabre\DAV\Exception\Forbidden(); - } + // For non-chunked upload it is enough to check if we can create a new file + if (!$this->fileView->isCreatable($this->path)) { + throw new Exception\Forbidden(); } $this->fileView->verifyPath($this->path, $name); @@ -153,8 +140,8 @@ public function createFile($name, $data = null) { $this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE); $node->releaseLock(ILockingProvider::LOCK_SHARED); return $result; - } catch (\OCP\Files\StorageNotAvailableException $e) { - throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e); + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage(), false, $ex); } catch (ForbiddenException $ex) { @@ -170,22 +157,22 @@ public function createFile($name, $data = null) { * @param string $name * @throws FileLocked * @throws InvalidPath - * @throws \Sabre\DAV\Exception\Forbidden - * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @throws Exception\Forbidden + * @throws ServiceUnavailable */ public function createDirectory($name) { try { if (!$this->info->isCreatable()) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } $this->fileView->verifyPath($this->path, $name); $newPath = $this->path . '/' . $name; if (!$this->fileView->mkdir($newPath)) { - throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath); + throw new Exception\Forbidden('Could not create directory ' . $newPath); } - } catch (\OCP\Files\StorageNotAvailableException $e) { - throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $ex) { @@ -203,7 +190,7 @@ public function createDirectory($name) { * @return \Sabre\DAV\INode * @throws InvalidPath * @throws \Sabre\DAV\Exception\NotFound - * @throws \Sabre\DAV\Exception\ServiceUnavailable + * @throws ServiceUnavailable */ public function getChild($name, $info = null) { if (!$this->info->isReadable()) { @@ -216,12 +203,12 @@ public function getChild($name, $info = null) { try { $this->fileView->verifyPath($this->path, $name); $info = $this->fileView->getFileInfo($path); - } catch (\OCP\Files\StorageNotAvailableException $e) { - throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $e) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } } @@ -298,17 +285,17 @@ public function childExists($name) { * * @return void * @throws FileLocked - * @throws \Sabre\DAV\Exception\Forbidden + * @throws Exception\Forbidden */ public function delete() { if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } try { if (!$this->fileView->rmdir($this->path)) { // assume it wasn't possible to remove due to permission issue - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); @@ -343,7 +330,7 @@ public function getQuotaInfo() { } catch (\OCP\Files\NotFoundException $e) { $logger->warning("error while getting quota into", ['exception' => $e]); return [0, 0]; - } catch (\OCP\Files\StorageNotAvailableException $e) { + } catch (StorageNotAvailableException $e) { $logger->warning("error while getting quota into", ['exception' => $e]); return [0, 0]; } catch (NotPermittedException $e) { @@ -375,7 +362,7 @@ public function getQuotaInfo() { * @throws ServiceUnavailable * @throws Forbidden * @throws FileLocked - * @throws \Sabre\DAV\Exception\Forbidden + * @throws Exception\Forbidden */ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { if (!$sourceNode instanceof Node) { @@ -399,7 +386,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { // at getNodeForPath we also check the path for isForbiddenFileOrDir // with that we have covered both source and destination if ($sourceNode instanceof Directory && $targetNodeExists) { - throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists'); + throw new Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists'); } [$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath()); @@ -420,11 +407,11 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { if ($targetNodeExists || $sameFolder) { // note that renaming a share mount point is always allowed if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } } else { if (!$this->fileView->isCreatable($destinationDir)) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } } @@ -432,7 +419,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { // moving to a different folder, source will be gone, like a deletion // note that moving a share mount point is always allowed if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } } @@ -445,7 +432,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { $renameOkay = $this->fileView->rename($sourcePath, $destinationPath); if (!$renameOkay) { - throw new \Sabre\DAV\Exception\Forbidden(''); + throw new Exception\Forbidden(''); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable($e->getMessage()); @@ -465,7 +452,7 @@ public function copyInto($targetName, $sourcePath, INode $sourceNode) { $sourcePath = $sourceNode->getPath(); if (!$this->fileView->isCreatable($this->getPath())) { - throw new \Sabre\DAV\Exception\Forbidden(); + throw new Exception\Forbidden(); } try { diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index b0f17417d2195..075026142ecc6 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -148,15 +148,6 @@ public function put($data) { // verify path of the target $this->verifyPath(); - // chunked handling - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - try { - return $this->createFileChunked($data); - } catch (\Exception $e) { - $this->convertToSabreException($e); - } - } - /** @var Storage $partStorage */ [$partStorage] = $this->fileView->resolvePath($this->path); $needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1); @@ -577,132 +568,6 @@ public function getDirectDownload() { return $storage->getDirectDownload($internalPath); } - /** - * @param resource $data - * @return null|string - * @throws Exception - * @throws BadRequest - * @throws NotImplemented - * @throws ServiceUnavailable - */ - private function createFileChunked($data) { - [$path, $name] = \Sabre\Uri\split($this->path); - - $info = \OC_FileChunking::decodeName($name); - if (empty($info)) { - throw new NotImplemented($this->l10n->t('Invalid chunk name')); - } - - $chunk_handler = new \OC_FileChunking($info); - $bytesWritten = $chunk_handler->store($info['index'], $data); - - //detect aborted upload - if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { - if (isset($_SERVER['CONTENT_LENGTH'])) { - $expected = (int)$_SERVER['CONTENT_LENGTH']; - if ($bytesWritten !== $expected) { - $chunk_handler->remove($info['index']); - throw new BadRequest( - $this->l10n->t( - 'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.', - [ - $this->l10n->n('%n byte', '%n bytes', $expected), - $this->l10n->n('%n byte', '%n bytes', $bytesWritten), - ], - ) - ); - } - } - } - - if ($chunk_handler->isComplete()) { - /** @var Storage $storage */ - [$storage,] = $this->fileView->resolvePath($path); - $needsPartFile = $storage->needsPartFile(); - $partFile = null; - - $targetPath = $path . '/' . $info['name']; - /** @var \OC\Files\Storage\Storage $targetStorage */ - [$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath); - - $exists = $this->fileView->file_exists($targetPath); - - try { - $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED); - - $this->emitPreHooks($exists, $targetPath); - $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE); - /** @var \OC\Files\Storage\Storage $targetStorage */ - [$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath); - - if ($needsPartFile) { - // we first assembly the target file as a part file - $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part'; - /** @var \OC\Files\Storage\Storage $targetStorage */ - [$partStorage, $partInternalPath] = $this->fileView->resolvePath($partFile); - - - $chunk_handler->file_assemble($partStorage, $partInternalPath); - - // here is the final atomic rename - $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath); - $fileExists = $targetStorage->file_exists($targetInternalPath); - if ($renameOkay === false || $fileExists === false) { - \OC::$server->get(LoggerInterface::class)->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']); - // only delete if an error occurred and the target file was already created - if ($fileExists) { - // set to null to avoid double-deletion when handling exception - // stray part file - $partFile = null; - $targetStorage->unlink($targetInternalPath); - } - $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); - throw new Exception($this->l10n->t('Could not rename part file assembled from chunks')); - } - } else { - // assemble directly into the final file - $chunk_handler->file_assemble($targetStorage, $targetInternalPath); - } - - // allow sync clients to send the mtime along in a header - if (isset($this->request->server['HTTP_X_OC_MTIME'])) { - $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']); - if ($targetStorage->touch($targetInternalPath, $mtime)) { - $this->header('X-OC-MTime: accepted'); - } - } - - // since we skipped the view we need to scan and emit the hooks ourselves - $targetStorage->getUpdater()->update($targetInternalPath); - - $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); - - $this->emitPostHooks($exists, $targetPath); - - // FIXME: should call refreshInfo but can't because $this->path is not the of the final file - $info = $this->fileView->getFileInfo($targetPath); - - if (isset($this->request->server['HTTP_OC_CHECKSUM'])) { - $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']); - $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]); - } elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') { - $this->fileView->putFileInfo($this->path, ['checksum' => '']); - } - - $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED); - - return $info->getEtag(); - } catch (\Exception $e) { - if ($partFile !== null) { - $targetStorage->unlink($targetInternalPath); - } - $this->convertToSabreException($e); - } - } - - return null; - } - /** * Convert the given exception to a SabreException instance * diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index a6c9b8b4ebe4e..4a5c071848cc1 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -577,15 +577,6 @@ public function handleUpdateProperties($path, PropPatch $propPatch) { * @throws \Sabre\DAV\Exception\BadRequest */ public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { - // chunked upload handling - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - [$path, $name] = \Sabre\Uri\split($filePath); - $info = \OC_FileChunking::decodeName($name); - if (!empty($info)) { - $filePath = $path . '/' . $info['name']; - } - } - // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder if (!$this->server->tree->nodeExists($filePath)) { return; diff --git a/apps/dav/lib/Connector/Sabre/LockPlugin.php b/apps/dav/lib/Connector/Sabre/LockPlugin.php index 6305b0ec1380a..1f3c5211986e6 100644 --- a/apps/dav/lib/Connector/Sabre/LockPlugin.php +++ b/apps/dav/lib/Connector/Sabre/LockPlugin.php @@ -61,7 +61,7 @@ public function initialize(\Sabre\DAV\Server $server) { public function getLock(RequestInterface $request) { // we can't listen on 'beforeMethod:PUT' due to order of operations with setting up the tree // so instead we limit ourselves to the PUT method manually - if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { + if ($request->getMethod() !== 'PUT') { return; } try { @@ -84,7 +84,7 @@ public function releaseLock(RequestInterface $request) { if ($this->isLocked === false) { return; } - if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { + if ($request->getMethod() !== 'PUT') { return; } try { diff --git a/apps/dav/lib/Connector/Sabre/ObjectTree.php b/apps/dav/lib/Connector/Sabre/ObjectTree.php index c129371e37632..8a147d7396fca 100644 --- a/apps/dav/lib/Connector/Sabre/ObjectTree.php +++ b/apps/dav/lib/Connector/Sabre/ObjectTree.php @@ -67,35 +67,6 @@ public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\File $this->mountManager = $mountManager; } - /** - * If the given path is a chunked file name, converts it - * to the real file name. Only applies if the OC-CHUNKED header - * is present. - * - * @param string $path chunk file path to convert - * - * @return string path to real file - */ - private function resolveChunkFile($path) { - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - // resolve to real file name to find the proper node - [$dir, $name] = \Sabre\Uri\split($path); - if ($dir === '/' || $dir === '.') { - $dir = ''; - } - - $info = \OC_FileChunking::decodeName($name); - // only replace path if it was really the chunked file - if (isset($info['transferid'])) { - // getNodePath is called for multiple nodes within a chunk - // upload call - $path = $dir . '/' . $info['name']; - $path = ltrim($path, '/'); - } - } - return $path; - } - /** * Returns the INode object for the requested path * @@ -147,9 +118,6 @@ public function getNodeForPath($path) { $info = null; } } else { - // resolve chunk file name to real name, if applicable - $path = $this->resolveChunkFile($path); - // read from cache try { $info = $this->fileView->getFileInfo($path); @@ -159,12 +127,6 @@ public function getNodeForPath($path) { } } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available', 0, $e); - } catch (StorageInvalidException $e) { - throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); - } catch (LockedException $e) { - throw new \Sabre\DAV\Exception\Locked(); - } catch (ForbiddenException $e) { - throw new \Sabre\DAV\Exception\Forbidden(); } } diff --git a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php index ddf4b2773e0b1..2b233d0043734 100644 --- a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php +++ b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php @@ -193,31 +193,14 @@ public function checkQuota($path, $length = null) { $parentPath = ''; } $req = $this->server->httpRequest; - if ($req->getHeader('OC-Chunked')) { - $info = \OC_FileChunking::decodeName($newName); - $chunkHandler = $this->getFileChunking($info); - // subtract the already uploaded size to see whether - // there is still enough space for the remaining chunks - $length -= $chunkHandler->getCurrentSize(); - // use target file name for free space check in case of shared files - $path = rtrim($parentPath, '/') . '/' . $info['name']; - } $freeSpace = $this->getFreeSpace($path); if ($freeSpace >= 0 && $length > $freeSpace) { - if (isset($chunkHandler)) { - $chunkHandler->cleanup(); - } throw new InsufficientStorage("Insufficient space in $path, $length required, $freeSpace available"); } } return true; } - public function getFileChunking($info) { - // FIXME: need a factory for better mocking support - return new \OC_FileChunking($info); - } - public function getLength() { $req = $this->server->httpRequest; $length = $req->getHeader('X-Expected-Entity-Length'); diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 8d72fb13b7875..fd75508398f7d 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -73,7 +73,6 @@ class FileTest extends TestCase { protected function setUp(): void { parent::setUp(); - unset($_SERVER['HTTP_OC_CHUNKED']); unset($_SERVER['CONTENT_LENGTH']); unset($_SERVER['REQUEST_METHOD']); @@ -91,7 +90,6 @@ protected function setUp(): void { protected function tearDown(): void { $userManager = \OC::$server->getUserManager(); $userManager->get($this->user)->delete(); - unset($_SERVER['HTTP_OC_CHUNKED']); parent::tearDown(); } @@ -234,81 +232,6 @@ function ($path) use ($storage) { $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); } - /** - * Test putting a file using chunking - * - * @dataProvider fopenFailuresProvider - */ - public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false): void { - // setup - $storage = $this->getMockBuilder(Local::class) - ->setMethods(['fopen']) - ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) - ->getMock(); - \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); - $view = $this->getMockBuilder(View::class) - ->setMethods(['getRelativePath', 'resolvePath']) - ->getMock(); - $view->expects($this->atLeastOnce()) - ->method('resolvePath') - ->willReturnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - ); - - if ($thrownException !== null) { - $storage->expects($this->once()) - ->method('fopen') - ->will($this->throwException($thrownException)); - } else { - $storage->expects($this->once()) - ->method('fopen') - ->willReturn(false); - } - - $view->expects($this->any()) - ->method('getRelativePath') - ->willReturnArgument(0); - - $_SERVER['HTTP_OC_CHUNKED'] = true; - - $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); - - // put first chunk - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $this->assertNull($file->put('test data one')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - - $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); - - // action - $caughtException = null; - try { - // last chunk - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $file->put('test data two'); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - } catch (\Exception $e) { - $caughtException = $e; - } - - $this->assertInstanceOf($expectedException, $caughtException); - if ($checkPreviousClass) { - $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); - } - - $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); - } - /** * Simulate putting a file to the given path. * @@ -437,41 +360,6 @@ public function testPutSingleFileLegalMtime($requestMtime, $resultMtime): void { } } - /** - * Test putting a file with string Mtime using chunking - * @dataProvider legalMtimeProvider - */ - public function testChunkedPutLegalMtime($requestMtime, $resultMtime): void { - $request = new Request([ - 'server' => [ - 'HTTP_X_OC_MTIME' => $requestMtime, - ] - ], $this->requestId, $this->config, null); - - $_SERVER['HTTP_OC_CHUNKED'] = true; - $file = 'foo.txt'; - - if ($resultMtime === null) { - $this->expectException(\Sabre\DAV\Exception::class); - } - - $this->doPut($file.'-chunking-12345-2-0', null, $request); - $this->doPut($file.'-chunking-12345-2-1', null, $request); - - if ($resultMtime !== null) { - $this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']); - } - } - - /** - * Test putting a file using chunking - */ - public function testChunkedPut(): void { - $_SERVER['HTTP_OC_CHUNKED'] = true; - $this->assertNull($this->doPut('/test.txt-chunking-12345-2-0')); - $this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1')); - } - /** * Test that putting a file triggers create hooks */ @@ -574,75 +462,6 @@ public function testPutSingleFileTriggersHooksDifferentRoot(): void { ); } - /** - * Test that putting a file with chunks triggers create hooks - */ - public function testPutChunkedFileTriggersHooks(): void { - HookHelper::setUpHooks(); - - $_SERVER['HTTP_OC_CHUNKED'] = true; - $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0')); - $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1')); - - $this->assertCount(4, HookHelper::$hookCalls); - $this->assertHookCall( - HookHelper::$hookCalls[0], - Filesystem::signal_create, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[1], - Filesystem::signal_write, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[2], - Filesystem::signal_post_create, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[3], - Filesystem::signal_post_write, - '/foo.txt' - ); - } - - /** - * Test that putting a chunked file triggers update hooks - */ - public function testPutOverwriteChunkedFileTriggersHooks(): void { - $view = \OC\Files\Filesystem::getView(); - $view->file_put_contents('/foo.txt', 'some content that will be replaced'); - - HookHelper::setUpHooks(); - - $_SERVER['HTTP_OC_CHUNKED'] = true; - $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0')); - $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1')); - - $this->assertCount(4, HookHelper::$hookCalls); - $this->assertHookCall( - HookHelper::$hookCalls[0], - Filesystem::signal_update, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[1], - Filesystem::signal_write, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[2], - Filesystem::signal_post_update, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[3], - Filesystem::signal_post_write, - '/foo.txt' - ); - } - public static function cancellingHook($params): void { self::$hookCalls[] = [ 'signal' => Filesystem::signal_post_create, @@ -755,46 +574,6 @@ public function testSimplePutFailsMoveFromStorage(): void { $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); } - /** - * Test exception during final rename in chunk upload mode - */ - public function testChunkedPutFailsFinalRename(): void { - $view = new \OC\Files\View('/' . $this->user . '/files'); - - // simulate situation where the target file is locked - $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); - - $_SERVER['HTTP_OC_CHUNKED'] = true; - - $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $this->assertNull($file->put('test data one')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - - $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); - - // action - $thrown = false; - try { - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $file->put($this->getStream('test data')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $thrown = true; - } - - $this->assertTrue($thrown); - $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); - } - /** * Test put file with invalid chars */ diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php index d219888ef155a..86de3d8133451 100644 --- a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php @@ -148,13 +148,8 @@ public function testGetNodeForPath( $inputFileName, $fileInfoQueryPath, $outputFileName, - $type, - $enableChunkingHeader + $type ): void { - if ($enableChunkingHeader) { - $_SERVER['HTTP_OC_CHUNKED'] = true; - } - $rootNode = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() ->getMock(); @@ -191,8 +186,6 @@ public function testGetNodeForPath( } else { $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory); } - - unset($_SERVER['HTTP_OC_CHUNKED']); } public function nodeForPathProvider() { @@ -202,94 +195,67 @@ public function nodeForPathProvider() { 'regularfile.txt', 'regularfile.txt', 'regularfile.txt', - 'file', - false + 'file' ], // regular directory [ 'regulardir', 'regulardir', 'regulardir', - 'dir', - false + 'dir' ], // regular file with chunking [ 'regularfile.txt', 'regularfile.txt', 'regularfile.txt', - 'file', - true + 'file' ], // regular directory with chunking [ 'regulardir', 'regulardir', 'regulardir', - 'dir', - true - ], - // file with chunky file name - [ - 'regularfile.txt-chunking-123566789-10-1', - 'regularfile.txt', - 'regularfile.txt', - 'file', - true + 'dir' ], // regular file in subdir [ 'subdir/regularfile.txt', 'subdir/regularfile.txt', 'regularfile.txt', - 'file', - false + 'file' ], // regular directory in subdir [ 'subdir/regulardir', 'subdir/regulardir', 'regulardir', - 'dir', - false - ], - // file with chunky file name in subdir - [ - 'subdir/regularfile.txt-chunking-123566789-10-1', - 'subdir/regularfile.txt', - 'regularfile.txt', - 'file', - true + 'dir' ], ]; } public function testGetNodeForPathInvalidPath(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); - $path = '/foo\bar'; - - $storage = new Temporary([]); - + $rootNode = $this->getMockBuilder(Directory::class) + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->createMock(IMountManager::class); $view = $this->getMockBuilder(View::class) ->setMethods(['resolvePath']) ->getMock(); + + $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); $view->expects($this->once()) ->method('resolvePath') ->willReturnCallback(function ($path) use ($storage) { return [$storage, ltrim($path, '/')]; }); - $rootNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); - $mountManager = $this->createMock(IMountManager::class); - $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); $tree->init($rootNode, $view, $mountManager); - $tree->getNodeForPath($path); } diff --git a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php index 4a9ca159bbd8c..75f799ada7196 100644 --- a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php @@ -52,10 +52,7 @@ class QuotaPluginTest extends TestCase { private function init($quota, $checkedPath = ''): void { $view = $this->buildFileViewMock($quota, $checkedPath); $this->server = new \Sabre\DAV\Server(); - $this->plugin = $this->getMockBuilder(QuotaPlugin::class) - ->setConstructorArgs([$view]) - ->setMethods(['getFileChunking']) - ->getMock(); + $this->plugin = new QuotaPlugin($view); $this->plugin->initialize($this->server); } @@ -64,8 +61,6 @@ private function init($quota, $checkedPath = ''): void { */ public function testLength($expected, $headers): void { $this->init(0); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $length = $this->plugin->getLength(); $this->assertEquals($expected, $length); @@ -76,8 +71,6 @@ public function testLength($expected, $headers): void { */ public function testCheckQuota($quota, $headers): void { $this->init($quota); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota(''); @@ -91,8 +84,6 @@ public function testCheckExceededQuota($quota, $headers): void { $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); $this->init($quota); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $this->plugin->checkQuota(''); @@ -103,8 +94,6 @@ public function testCheckExceededQuota($quota, $headers): void { */ public function testCheckQuotaOnPath($quota, $headers): void { $this->init($quota, 'sub/test.txt'); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota('/sub/test.txt'); @@ -154,84 +143,6 @@ public function lengthProvider() { ]; } - public function quotaChunkedOkProvider() { - return [ - [1024, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], - [1024, 0, ['CONTENT-LENGTH' => '512']], - [1024, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], - // with existing chunks (allowed size = total length - chunk total size) - [400, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']], - [400, 128, ['CONTENT-LENGTH' => '512']], - [400, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']], - // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 - [-2, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], - [-2, 0, ['CONTENT-LENGTH' => '512']], - [-2, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], - [-2, 128, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], - [-2, 128, ['CONTENT-LENGTH' => '512']], - [-2, 128, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], - ]; - } - - /** - * @dataProvider quotaChunkedOkProvider - */ - public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers): void { - $this->init($quota, 'sub/test.txt'); - - $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) - ->disableOriginalConstructor() - ->getMock(); - $mockChunking->expects($this->once()) - ->method('getCurrentSize') - ->willReturn($chunkTotalSize); - - $this->plugin->expects($this->once()) - ->method('getFileChunking') - ->willReturn($mockChunking); - - $headers['OC-CHUNKED'] = 1; - $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); - $result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); - $this->assertTrue($result); - } - - public function quotaChunkedFailProvider() { - return [ - [400, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], - [400, 0, ['CONTENT-LENGTH' => '512']], - [400, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']], - // with existing chunks (allowed size = total length - chunk total size) - [380, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']], - [380, 128, ['CONTENT-LENGTH' => '512']], - [380, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']], - ]; - } - - /** - * @dataProvider quotaChunkedFailProvider - */ - public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers): void { - $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); - - $this->init($quota, 'sub/test.txt'); - - $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) - ->disableOriginalConstructor() - ->getMock(); - $mockChunking->expects($this->once()) - ->method('getCurrentSize') - ->willReturn($chunkTotalSize); - - $this->plugin->expects($this->once()) - ->method('getFileChunking') - ->willReturn($mockChunking); - - $headers['OC-CHUNKED'] = 1; - $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); - $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); - } - private function buildFileViewMock($quota, $checkedPath) { // mock filesysten $view = $this->getMockBuilder(View::class) diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php index f6aa79eb6c4b6..564d8c5938c2b 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php @@ -57,8 +57,6 @@ protected function getStream($string) { protected function setUp(): void { parent::setUp(); - unset($_SERVER['HTTP_OC_CHUNKED']); - $this->serverFactory = new ServerFactory( \OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php index 16953d9b59893..f8d5ad40b4937 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php @@ -92,114 +92,4 @@ public function testUploadOverWriteWriteLocked(): void { $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); } - public function testChunkedUpload(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(201, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertTrue($view->file_exists('foo.txt')); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOverWrite(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $view->file_put_contents('foo.txt', 'bar'); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertEquals('bar', $view->file_get_contents('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOutOfOrder(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(201, $response->getStatus()); - $this->assertTrue($view->file_exists('foo.txt')); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOutOfOrderReadLocked(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - - $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); - - try { - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $this->fail('Didn\'t expect locked error for the first chunk on read lock'); - return; - } - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - // last chunk should trigger the locked error since it tries to assemble - $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); - } - - public function testChunkedUploadOutOfOrderWriteLocked(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - - $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); - - try { - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $this->fail('Didn\'t expect locked error for the first chunk on write lock'); // maybe forbid this in the future for write locks only? - return; - } - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - // last chunk should trigger the locked error since it tries to assemble - $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); - } } diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml index 1d06259074b16..e69af548d9aac 100644 --- a/apps/files/appinfo/info.xml +++ b/apps/files/appinfo/info.xml @@ -5,7 +5,7 @@ Files File Management File Management - 1.21.1 + 1.21.2 agpl Robin Appelman Vincent Petry @@ -22,11 +22,12 @@ - OCA\Files\BackgroundJob\ScanFiles - OCA\Files\BackgroundJob\DeleteOrphanedItems - OCA\Files\BackgroundJob\CleanupFileLocks OCA\Files\BackgroundJob\CleanupDirectEditingTokens + OCA\Files\BackgroundJob\CleanupFileLocks OCA\Files\BackgroundJob\DeleteExpiredOpenLocalEditor + OCA\Files\BackgroundJob\DeleteOrphanedItems + OCA\Files\BackgroundJob\FileChunkCleanupJob + OCA\Files\BackgroundJob\ScanFiles diff --git a/apps/files/composer/composer/ClassLoader.php b/apps/files/composer/composer/ClassLoader.php index fd56bd7d8405f..a72151c77c8eb 100644 --- a/apps/files/composer/composer/ClassLoader.php +++ b/apps/files/composer/composer/ClassLoader.php @@ -429,7 +429,8 @@ public function unregister() public function loadClass($class) { if ($file = $this->findFile($class)) { - (self::$includeFile)($file); + $includeFile = self::$includeFile; + $includeFile($file); return true; } @@ -560,7 +561,10 @@ private function findFileWithExtension($class, $ext) return false; } - private static function initializeIncludeClosure(): void + /** + * @return void + */ + private static function initializeIncludeClosure() { if (self::$includeFile !== null) { return; @@ -574,8 +578,8 @@ private static function initializeIncludeClosure(): void * @param string $file * @return void */ - self::$includeFile = static function($file) { + self::$includeFile = \Closure::bind(static function($file) { include $file; - }; + }, null, null); } } diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index ef3480081e0c4..642e5a1d87eba 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -22,6 +22,7 @@ 'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => $baseDir . '/../lib/BackgroundJob/CleanupFileLocks.php', 'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => $baseDir . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php', 'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php', + 'OCA\\Files\\BackgroundJob\\FileChunkCleanupJob' => $baseDir . '/../lib/BackgroundJob/FileChunkCleanupJob.php', 'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php', 'OCA\\Files\\BackgroundJob\\TransferOwnership' => $baseDir . '/../lib/BackgroundJob/TransferOwnership.php', 'OCA\\Files\\Capabilities' => $baseDir . '/../lib/Capabilities.php', diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index 4f7872e39dff1..7287614d34565 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -37,6 +37,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupFileLocks.php', 'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php', 'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php', + 'OCA\\Files\\BackgroundJob\\FileChunkCleanupJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/FileChunkCleanupJob.php', 'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php', 'OCA\\Files\\BackgroundJob\\TransferOwnership' => __DIR__ . '/..' . '/../lib/BackgroundJob/TransferOwnership.php', 'OCA\\Files\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', diff --git a/apps/files/composer/composer/installed.php b/apps/files/composer/composer/installed.php index a1f6a8636b4d4..a07f17770d442 100644 --- a/apps/files/composer/composer/installed.php +++ b/apps/files/composer/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'd51429a47232bbf46a2be832ecfa711f102da802', + 'reference' => '3e452cfe8d80995d1657c617f887a9ee422e6ab1', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => 'd51429a47232bbf46a2be832ecfa711f102da802', + 'reference' => '3e452cfe8d80995d1657c617f887a9ee422e6ab1', 'type' => 'library', 'install_path' => __DIR__ . '/../', 'aliases' => array(), diff --git a/apps/files/lib/BackgroundJob/FileChunkCleanupJob.php b/apps/files/lib/BackgroundJob/FileChunkCleanupJob.php new file mode 100644 index 0000000000000..1c1f6f3bc99f2 --- /dev/null +++ b/apps/files/lib/BackgroundJob/FileChunkCleanupJob.php @@ -0,0 +1,62 @@ + + * + * @author Anna Larch + * + * @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\Files\BackgroundJob; + +use OC\Cache\File; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\Job; +use OCP\BackgroundJob\TimedJob; +use OCP\Files\IRootFolder; +use OCP\IUser; +use OCP\IUserManager; +use Psr\Log\LoggerInterface; + +class FileChunkCleanupJob extends TimedJob { + private IUserManager $userManager; + private IRootFolder $rootFolder; + private LoggerInterface $logger; + + public function __construct(IUserManager $userManager, IRootFolder $rootFolder, LoggerInterface $logger, ITimeFactory $timeFactory) { + parent::__construct($timeFactory); + $this->setInterval(3600*24); + $this->setTimeSensitivity(Job::TIME_INSENSITIVE); + $this->userManager = $userManager; + $this->rootFolder = $rootFolder; + $this->logger = $logger; + } + + /** + * This job cleans up all backups except the latest 3 from the updaters backup directory + */ + public function run($argument): void { + $this->userManager->callForSeenUsers(function (IUser $user): void { + $this->logger->debug('Running chunk cleanup job for user '. $user->getUID()); + $fileCache = new File(); + $fileCache->setUpStorage($user->getUID()); + $fileCache->gc(); + $this->logger->debug('Finished running chunk cleanup job for user '. $user->getUID()); + }); + } +} diff --git a/lib/base.php b/lib/base.php index b5c5845b5a0ca..452d6ecc05d0d 100644 --- a/lib/base.php +++ b/lib/base.php @@ -855,23 +855,6 @@ public static function registerCleanupHooks(\OC\SystemConfig $systemConfig): voi $throttler = Server::get(\OC\Security\Bruteforce\Throttler::class); $throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]); } - - try { - $cache = new \OC\Cache\File(); - $cache->gc(); - } catch (\OC\ServerNotAvailableException $e) { - // not a GC exception, pass it on - throw $e; - } catch (\OC\ForbiddenException $e) { - // filesystem blocked for this request, ignore - } catch (\Exception $e) { - // a GC exception should not prevent users from using OC, - // so log the exception - Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [ - 'app' => 'core', - 'exception' => $e, - ]); - } }); } } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 62f66dca67b2a..8a32b3bd2625c 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1620,7 +1620,6 @@ 'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php', 'OC_Defaults' => $baseDir . '/lib/private/legacy/OC_Defaults.php', 'OC_EventSource' => $baseDir . '/lib/private/legacy/OC_EventSource.php', - 'OC_FileChunking' => $baseDir . '/lib/private/legacy/OC_FileChunking.php', 'OC_Files' => $baseDir . '/lib/private/legacy/OC_Files.php', 'OC_Helper' => $baseDir . '/lib/private/legacy/OC_Helper.php', 'OC_Hook' => $baseDir . '/lib/private/legacy/OC_Hook.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 33d63a26e3e70..09a4c92a08fbe 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1653,7 +1653,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php', 'OC_Defaults' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Defaults.php', 'OC_EventSource' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_EventSource.php', - 'OC_FileChunking' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_FileChunking.php', 'OC_Files' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Files.php', 'OC_Helper' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Helper.php', 'OC_Hook' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Hook.php', diff --git a/lib/private/Cache/File.php b/lib/private/Cache/File.php index 1f63e462bb58e..56200bda6faf9 100644 --- a/lib/private/Cache/File.php +++ b/lib/private/Cache/File.php @@ -35,10 +35,26 @@ use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; +/** + * @deprecated 26.0.0 + */ class File implements ICache { /** @var View */ protected $storage; + /** + * Set the cache storage for a user + */ + public function setUpStorage(string $userId) { + Filesystem::initMountPoints($userId); + $rootView = new View(); + if (!$rootView->file_exists('/' . $userId . '/cache')) { + $rootView->mkdir('/' . $userId . '/cache'); + } + $this->storage = new View('/' . $userId . '/cache'); + return $this->storage; + } + /** * Returns the cache storage for the logged in user * @@ -51,14 +67,8 @@ protected function getStorage() { return $this->storage; } if (\OC::$server->getUserSession()->isLoggedIn()) { - $rootView = new View(); $user = \OC::$server->getUserSession()->getUser(); - Filesystem::initMountPoints($user->getUID()); - if (!$rootView->file_exists('/' . $user->getUID() . '/cache')) { - $rootView->mkdir('/' . $user->getUID() . '/cache'); - } - $this->storage = new View('/' . $user->getUID() . '/cache'); - return $this->storage; + return $this->setUpStorage($user->getUID()); } else { \OC::$server->get(LoggerInterface::class)->error('Can\'t get cache storage, user not logged in', ['app' => 'core']); throw new \OC\ForbiddenException('Can\t get cache storage, user not logged in'); diff --git a/lib/private/Server.php b/lib/private/Server.php index 35f6368645727..6ef805a4d85b2 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -708,6 +708,7 @@ public function __construct($webRoot, \OC\Config $config) { $this->registerService(ICache::class, function ($c) { return new Cache\File(); }); + /** @deprecated 19.0.0 */ $this->registerDeprecatedAlias('UserCache', ICache::class); diff --git a/lib/private/legacy/OC_FileChunking.php b/lib/private/legacy/OC_FileChunking.php deleted file mode 100644 index e3782cabb4a72..0000000000000 --- a/lib/private/legacy/OC_FileChunking.php +++ /dev/null @@ -1,184 +0,0 @@ - - * @author Christoph Wurst - * @author Felix Moeller - * @author Jörn Friedrich Dreyer - * @author Morris Jobke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @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 - * - */ -class OC_FileChunking { - protected $info; - protected $cache; - - /** - * TTL of chunks - * - * @var int - */ - protected $ttl; - - public static function decodeName($name) { - preg_match('/(?P.*)-chunking-(?P\d+)-(?P\d+)-(?P\d+)/', $name, $matches); - return $matches; - } - - /** - * @param string[] $info - */ - public function __construct($info) { - $this->info = $info; - $this->ttl = \OC::$server->getConfig()->getSystemValue('cache_chunk_gc_ttl', 86400); - } - - public function getPrefix() { - $name = $this->info['name']; - $transferid = $this->info['transferid']; - - return $name.'-chunking-'.$transferid.'-'; - } - - protected function getCache() { - if (!isset($this->cache)) { - $this->cache = new \OC\Cache\File(); - } - return $this->cache; - } - - /** - * Stores the given $data under the given $key - the number of stored bytes is returned - * - * @param string $index - * @param resource $data - * @return int - */ - public function store($index, $data) { - $cache = $this->getCache(); - $name = $this->getPrefix().$index; - $cache->set($name, $data, $this->ttl); - - return $cache->size($name); - } - - public function isComplete() { - $prefix = $this->getPrefix(); - $cache = $this->getCache(); - $chunkcount = (int)$this->info['chunkcount']; - - for ($i = ($chunkcount - 1); $i >= 0; $i--) { - if (!$cache->hasKey($prefix.$i)) { - return false; - } - } - - return true; - } - - /** - * Assembles the chunks into the file specified by the path. - * Chunks are deleted afterwards. - * - * @param resource $f target path - * - * @return integer assembled file size - * - * @throws \OC\InsufficientStorageException when file could not be fully - * assembled due to lack of free space - */ - public function assemble($f) { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $count = 0; - for ($i = 0; $i < $this->info['chunkcount']; $i++) { - $chunk = $cache->get($prefix.$i); - // remove after reading to directly save space - $cache->remove($prefix.$i); - $count += fwrite($f, $chunk); - // let php release the memory to work around memory exhausted error with php 5.6 - $chunk = null; - } - - return $count; - } - - /** - * Returns the size of the chunks already present - * @return integer size in bytes - */ - public function getCurrentSize() { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $total = 0; - for ($i = 0; $i < $this->info['chunkcount']; $i++) { - $total += $cache->size($prefix.$i); - } - return $total; - } - - /** - * Removes all chunks which belong to this transmission - */ - public function cleanup() { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - for ($i = 0; $i < $this->info['chunkcount']; $i++) { - $cache->remove($prefix.$i); - } - } - - /** - * Removes one specific chunk - * @param string $index - */ - public function remove($index) { - $cache = $this->getCache(); - $prefix = $this->getPrefix(); - $cache->remove($prefix.$index); - } - - /** - * Assembles the chunks into the file specified by the path. - * Also triggers the relevant hooks and proxies. - * - * @param \OC\Files\Storage\Storage $storage storage - * @param string $path target path relative to the storage - * @return bool true on success or false if file could not be created - * - * @throws \OC\ServerNotAvailableException - */ - public function file_assemble($storage, $path) { - // use file_put_contents as method because that best matches what this function does - if (\OC\Files\Filesystem::isValidPath($path)) { - $target = $storage->fopen($path, 'w'); - if ($target) { - $count = $this->assemble($target); - fclose($target); - return $count > 0; - } else { - return false; - } - } - return false; - } -} diff --git a/tests/lib/FileChunkingTest.php b/tests/lib/FileChunkingTest.php deleted file mode 100644 index 23f50a5b6f7c2..0000000000000 --- a/tests/lib/FileChunkingTest.php +++ /dev/null @@ -1,72 +0,0 @@ - - * - * @copyright Copyright (c) 2016, ownCloud, Inc. - * @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 Test; - -use OCP\ICache; - -class FileChunkingTest extends \Test\TestCase { - public function dataIsComplete() { - return [ - [1, [], false], - [1, [0], true], - [2, [], false], - [2, [0], false], - [2, [1], false], - [2, [0,1], true], - [10, [], false], - [10, [0,1,2,3,4,5,6,7,8], false], - [10, [1,2,3,4,5,6,7,8,9], false], - [10, [0,1,2,3,5,6,7,8,9], false], - [10, [0,1,2,3,4,5,6,7,8,9], true], - ]; - } - - /** - * @dataProvider dataIsComplete - * @param $total - * @param array $present - * @param $expected - */ - public function testIsComplete($total, array $present, $expected) { - $fileChunking = $this->getMockBuilder(\OC_FileChunking::class) - ->setMethods(['getCache']) - ->setConstructorArgs([[ - 'name' => 'file', - 'transferid' => '42', - 'chunkcount' => $total, - ]]) - ->getMock(); - - $cache = $this->createMock(ICache::class); - - $cache->expects($this->atLeastOnce()) - ->method('hasKey') - ->willReturnCallback(function ($key) use ($present) { - $data = explode('-', $key); - return in_array($data[3], $present); - }); - - $fileChunking->method('getCache')->willReturn($cache); - - $this->assertEquals($expected, $fileChunking->isComplete()); - } -} From 20058eb9dea035ea541ff351d9a1e8a0d777a7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Thu, 9 Feb 2023 20:44:07 +0100 Subject: [PATCH 2/5] tests: Fix test isolation on object storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/files_sharing/tests/ApiTest.php | 6 ++++++ apps/files_sharing/tests/TestCase.php | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index d7661297e9e86..d9b61f837eb5f 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -73,6 +73,7 @@ protected function setUp(): void { \OC::$server->getConfig()->setAppValue('core', 'shareapi_exclude_groups', 'no'); \OC::$server->getConfig()->setAppValue('core', 'shareapi_expire_after_n_days', '7'); + \OC::$server->getConfig()->setAppValue('core', 'shareapi_enforce_links_password', 'no'); $this->folder = self::TEST_FOLDER_NAME; $this->subfolder = '/subfolder_share_api_test'; @@ -80,6 +81,11 @@ protected function setUp(): void { $this->filename = '/share-api-test.txt'; + // Initialize view again as we delete all filecache/mount entries in tearDown + // Otherwise those tests fail on object storage as the filecache is missing the user home + $this->view = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files'); + $this->view2 = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files'); + // save file with content $this->view->file_put_contents($this->filename, $this->data); $this->view->mkdir($this->folder); diff --git a/apps/files_sharing/tests/TestCase.php b/apps/files_sharing/tests/TestCase.php index 2bd83d6c9c28f..abdb6a2d60f39 100644 --- a/apps/files_sharing/tests/TestCase.php +++ b/apps/files_sharing/tests/TestCase.php @@ -33,6 +33,7 @@ namespace OCA\Files_Sharing\Tests; use OC\Files\Filesystem; +use OC\Files\View; use OCA\Files_Sharing\AppInfo\Application; use OCA\Files_Sharing\External\MountProvider as ExternalMountProvider; use OCA\Files_Sharing\MountProvider; @@ -211,7 +212,12 @@ protected static function loginHelper($user, $create = false, $password = false) \OC::$server->getUserSession()->setUser(null); \OC\Files\Filesystem::tearDown(); \OC::$server->getUserSession()->login($user, $password); - \OC::$server->getUserFolder($user); + // We need to get the directory listing to trigger the lazy user folder + // to create the files directory. Since the filecache might get cleared + // in the cache, any follow up test case may fail as with object storage + // the filecache represents the file structure + Filesystem::initMountPoints($user); + \OC::$server->getUserFolder($user)->getDirectoryListing(); \OC_Util::setupFS($user); } From 3493099617511b00190e64acc0b588b50f04ec4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 10 Feb 2023 11:53:18 +0100 Subject: [PATCH 3/5] tests: Remove legacy checksum test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../features/bootstrap/ChecksumsContext.php | 27 ------------------- build/integration/features/checksums.feature | 16 ----------- 2 files changed, 43 deletions(-) diff --git a/build/integration/features/bootstrap/ChecksumsContext.php b/build/integration/features/bootstrap/ChecksumsContext.php index ae44fcb15034c..f45bbf5e94f6a 100644 --- a/build/integration/features/bootstrap/ChecksumsContext.php +++ b/build/integration/features/bootstrap/ChecksumsContext.php @@ -231,31 +231,4 @@ public function theOcChecksumHeaderShouldNotBeThere() { throw new \Exception("Expected no checksum header but got ".$this->response->getHeader('OC-Checksum')[0]); } } - - /** - * @Given user :user uploads chunk file :num of :total with :data to :destination with checksum :checksum - * @param string $user - * @param int $num - * @param int $total - * @param string $data - * @param string $destination - * @param string $checksum - */ - public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination, $checksum) { - $num -= 1; - $this->response = $this->client->put( - $this->baseUrl . '/remote.php/webdav' . $destination . '-chunking-42-'.$total.'-'.$num, - [ - 'auth' => [ - $user, - $this->getPasswordForUser($user) - ], - 'body' => $data, - 'headers' => [ - 'OC-Checksum' => $checksum, - 'OC-Chunked' => '1', - ] - ] - ); - } } diff --git a/build/integration/features/checksums.feature b/build/integration/features/checksums.feature index d391e93afe8da..fe6fb5053157f 100644 --- a/build/integration/features/checksums.feature +++ b/build/integration/features/checksums.feature @@ -58,19 +58,3 @@ Feature: checksums When user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt" And user "user0" downloads the file "/myChecksumFile.txt" Then The OC-Checksum header should not be there - - Scenario: Uploading a chunked file with checksum should return the checksum in the propfind - Given user "user0" exists - And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - When user "user0" request the checksum of "/myChecksumFile.txt" via propfind - Then The webdav checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88" - - Scenario: Uploading a chunked file with checksum should return the checksum in the download header - Given user "user0" exists - And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88" - When user "user0" downloads the file "/myChecksumFile.txt" - Then The header checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88" From ec356504eaadba5411c30e6a06abd8eb177b6b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 13 Feb 2023 08:23:19 +0100 Subject: [PATCH 4/5] tests: Remove legacy chunking tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .../features/webdav-related.feature | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index 21e195af1159d..446a4ee5a497c 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -277,33 +277,6 @@ Feature: webdav-related When Sending a "PROPFIND" to "/remote.php/webdav/welcome.txt" with requesttoken Then the HTTP status code should be "207" - Scenario: Upload chunked file asc - Given user "user0" exists - And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt" - When As an "user0" - And Downloading file "/myChunkedFile.txt" - Then Downloaded content should be "AAAAABBBBBCCCCC" - - Scenario: Upload chunked file desc - Given user "user0" exists - And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt" - When As an "user0" - And Downloading file "/myChunkedFile.txt" - Then Downloaded content should be "AAAAABBBBBCCCCC" - - Scenario: Upload chunked file random - Given user "user0" exists - And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt" - And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt" - When As an "user0" - And Downloading file "/myChunkedFile.txt" - Then Downloaded content should be "AAAAABBBBBCCCCC" - Scenario: A file that is not shared does not have a share-types property Given user "user0" exists And user "user0" created a folder "/test" From 3bdf3322ec208152dc3393b0076f076b8e9aaff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 17 Feb 2023 19:21:05 +0100 Subject: [PATCH 5/5] fix: Use passed LazyFolder when building sabre nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dav/lib/Connector/Sabre/Node.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index ee159cef1d607..b4855eaf341a8 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -38,6 +38,7 @@ use OC\Files\Mount\MoveableMount; use OC\Files\Node\File; use OC\Files\Node\Folder; +use OC\Files\Node\LazyFolder; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCP\Files\DavUtil; @@ -88,7 +89,7 @@ public function __construct(View $view, FileInfo $info, IManager $shareManager = } else { $this->shareManager = \OC::$server->getShareManager(); } - if ($info instanceof Folder || $info instanceof File) { + if ($info instanceof Folder || $info instanceof File || $info instanceof LazyFolder) { $this->node = $info; } else { $root = \OC::$server->get(IRootFolder::class);