From fc6e07705a0915614c76a9ac4f0e9834c1bc4644 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Thu, 16 Sep 2021 19:09:38 +0200 Subject: [PATCH 1/3] Add BulkUpload DAV plugin Signed-off-by: Louis Chemineau --- .../composer/composer/autoload_classmap.php | 3 + .../dav/composer/composer/autoload_static.php | 3 + apps/dav/lib/BundleUpload/BundledFile.php | 214 ++++++ apps/dav/lib/BundleUpload/BundlingPlugin.php | 456 +++++++++++ .../BundleUpload/MultipartContentsParser.php | 497 ++++++++++++ apps/dav/lib/Capabilities.php | 2 + apps/dav/lib/Connector/Sabre/File.php | 8 +- apps/dav/lib/Server.php | 6 + apps/dav/tests/temporary/bundling_profile.sh | 149 ++++ apps/dav/tests/temporary/bundling_tests.sh | 70 ++ apps/dav/tests/temporary/put_test.sh | 12 + apps/dav/tests/temporary/screenshot.png | Bin 0 -> 183411 bytes .../dav/tests/unit/Files/BundlePluginTest.php | 711 ++++++++++++++++++ apps/dav/tests/unit/Files/BundledFileTest.php | 220 ++++++ .../Files/MultipartContentsParserTest.php | 416 ++++++++++ 15 files changed, 2763 insertions(+), 4 deletions(-) create mode 100644 apps/dav/lib/BundleUpload/BundledFile.php create mode 100644 apps/dav/lib/BundleUpload/BundlingPlugin.php create mode 100644 apps/dav/lib/BundleUpload/MultipartContentsParser.php create mode 100755 apps/dav/tests/temporary/bundling_profile.sh create mode 100755 apps/dav/tests/temporary/bundling_tests.sh create mode 100755 apps/dav/tests/temporary/put_test.sh create mode 100644 apps/dav/tests/temporary/screenshot.png create mode 100644 apps/dav/tests/unit/Files/BundlePluginTest.php create mode 100644 apps/dav/tests/unit/Files/BundledFileTest.php create mode 100644 apps/dav/tests/unit/Files/MultipartContentsParserTest.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 62c41a0828d..e7e29d85d89 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -22,6 +22,9 @@ return array( 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => $baseDir . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\UploadCleanup' => $baseDir . '/../lib/BackgroundJob/UploadCleanup.php', + 'OCA\\DAV\\BundleUpload\\BundledFile' => $baseDir . '/../lib/BundleUpload/BundledFile.php', + 'OCA\\DAV\\BundleUpload\\BundlingPlugin' => $baseDir . '/../lib/BundleUpload/BundlingPlugin.php', + 'OCA\\DAV\\BundleUpload\\MultipartContentsParser' => $baseDir . '/../lib/BundleUpload/MultipartContentsParser.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => $baseDir . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Filter/Todo.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 5d5f57eb51b..ee23085eee5 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -37,6 +37,9 @@ class ComposerStaticInitDAV 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\UploadCleanup' => __DIR__ . '/..' . '/../lib/BackgroundJob/UploadCleanup.php', + 'OCA\\DAV\\BundleUpload\\BundledFile' => __DIR__ . '/..' . '/../lib/BundleUpload/BundledFile.php', + 'OCA\\DAV\\BundleUpload\\BundlingPlugin' => __DIR__ . '/..' . '/../lib/BundleUpload/BundlingPlugin.php', + 'OCA\\DAV\\BundleUpload\\MultipartContentsParser' => __DIR__ . '/..' . '/../lib/BundleUpload/MultipartContentsParser.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Todo.php', diff --git a/apps/dav/lib/BundleUpload/BundledFile.php b/apps/dav/lib/BundleUpload/BundledFile.php new file mode 100644 index 00000000000..db9b5bbd3fe --- /dev/null +++ b/apps/dav/lib/BundleUpload/BundledFile.php @@ -0,0 +1,214 @@ + + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\BundleUpload; + +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCP\Files\StorageNotAvailableException; +use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; +use Sabre\DAV\Exception; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\ServiceUnavailable; +use OCA\DAV\Connector\Sabre\File; +use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge; +use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException; +use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; +use OCP\Files\ForbiddenException; +use Sabre\DAV\Exception\BadRequest; + +class BundledFile extends File { + + + /** + * This class is a wrapper around the bundled request body and provides access to its contents + * + * @var \OCA\DAV\BundleUpload\MultipartContentsParser + * + */ + private $contentHandler; + + public function __construct($view, $info, $contentHandler){ + $this->contentHandler = $contentHandler; + parent::__construct($view, $info); + } + /** + * Updates the data + * + * The $data['data] argument is a readable stream resource. + * The other $data key-values should be header fields in form of string + * + * After a successful put operation, you may choose to return an ETag. The + * ETag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param array $data + * + * @throws Forbidden + * @throws UnsupportedMediaType + * @throws BadRequest + * @throws Exception + * @throws EntityTooLarge + * @throws ServiceUnavailable + * @throws FileLocked + * @return array $properties + */ + public function putFile($data) { + $properties = array(); + + if (!isset($data['oc-total-length'])) { + //this should not happen, since upper layer takes care of that + //Thus, return Forbidden as sign of code inconsistency + throw new Forbidden('File requires oc-total-length header to be read'); + } + + try { + $exists = $this->fileView->file_exists($this->path); + if ($this->info && $exists) { + $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); + throw new Forbidden('Bundling not supported for already existing files'); + } + } catch (StorageNotAvailableException $e) { + $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); + throw new ServiceUnavailable("StorageNotAvailableException raised"); + } + + // verify path of the target + $this->verifyPath(); + + $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand(); + + // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) + /** @var \OC\Files\Storage\Storage $partStorage */ + list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); + /** @var \OC\Files\Storage\Storage $storage */ + list($storage, $internalPath) = $this->fileView->resolvePath($this->path); + try { + $target = $partStorage->fopen($internalPartPath, 'wb'); + if ($target === false || $target === null) { + \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR); + // because we have no clue about the cause we can only throw back a 500/Internal Server Error + $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); + throw new Exception('Could not write file contents'); + } + + $result = $this->contentHandler->streamReadToStream($target, $data['oc-total-length']); + + if ($result === false) { + throw new Exception('Error while copying file to target location (expected filesize: ' . $data['oc-total-length'] . ' )'); + } + + } catch (\Exception $e) { + $partStorage->unlink($internalPartPath); + $this->convertToSabreException($e); + } + + try { + $view = \OC\Files\Filesystem::getView(); + if ($view) { + $run = $this->emitPreHooks($exists); + } else { + $run = true; + } + + try { + $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); + } catch (LockedException $e) { + $partStorage->unlink($internalPartPath); + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + try { + if ($run) { + $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); + $fileExists = $storage->file_exists($internalPath); + } + if (!$run || $renameOkay === false || $fileExists === false) { + \OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR); + throw new Exception('Could not rename part file to final file'); + } + } catch (ForbiddenException $ex) { + throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); + } catch (\Exception $e) { + $partStorage->unlink($internalPartPath); + $this->convertToSabreException($e); + } + + // since we skipped the view we need to scan and emit the hooks ourselves + $storage->getUpdater()->update($internalPath); + + try { + $this->changeLock(ILockingProvider::LOCK_SHARED); + } catch (LockedException $e) { + throw new FileLocked($e->getMessage(), $e->getCode(), $e); + } + + if ($view) { + $this->emitPostHooks($exists); + } + + // allow sync clients to send the mtime along in a header + if (isset($data['oc-mtime'])) { + if ($this->fileView->touch($this->path, $data['oc-mtime'])) { + $properties['{DAV:}oc-mtime'] = 'accepted'; + } + } + + $this->refreshInfo(); + + if (isset($data['oc-checksum'])) { + $checksum = trim($data['oc-checksum']); + $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]); + $this->refreshInfo(); + } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') { + $this->fileView->putFileInfo($this->path, ['checksum' => '']); + $this->refreshInfo(); + } + + } catch (StorageNotAvailableException $e) { + throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); + } + + $etag = $this->getEtag(); + $properties['{DAV:}etag'] = $etag; + $properties['{DAV:}oc-etag'] = $etag; + $properties['{DAV:}oc-fileid'] = $this->getFileId(); + return $properties; + } + + /* + * @param resource $data + * + * @throws Forbidden + */ + public function put($data) { + throw new Forbidden('PUT method not supported for bundling'); + } +} \ No newline at end of file diff --git a/apps/dav/lib/BundleUpload/BundlingPlugin.php b/apps/dav/lib/BundleUpload/BundlingPlugin.php new file mode 100644 index 00000000000..b3c7a007ac2 --- /dev/null +++ b/apps/dav/lib/BundleUpload/BundlingPlugin.php @@ -0,0 +1,456 @@ + + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\BundleUpload; + +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use OC\Files\View; +use Sabre\HTTP\URLUtil; +use OCP\Lock\ILockingProvider; +use OC\Files\FileInfo; +use Sabre\DAV\Exception\BadRequest; +use OCA\DAV\Connector\Sabre\Exception\Forbidden; +use OCP\Files\Folder; +use OCP\AppFramework\Http\JSONResponse; +use Psr\Log\LoggerInterface; + +/** + * This plugin is responsible for interconnecting three components of the OC server: + * - RequestInterface object handler for request incoming from the client + * - MultipartContentsParser responsible for reading the contents of the request body + * - BundledFile responsible for storage of the file associated with request in the OC server + * + * Bundling plugin is responsible for receiving, validation and processing of the multipart/related request containing files. + * + */ +class BundlingPlugin extends ServerPlugin { + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\HTTP\RequestInterface + */ + private $request; + + /** + * @var \Sabre\HTTP\ResponseInterface + */ + private $response; + + /** + * @var \OCA\DAV\FilesBundle + */ + private $contentHandler = null; + + /** + * @var String + */ + private $userFilesHome = null; + + /** + * @var View + */ + private $fileView; + + /** + * @var Array + */ + // private $cacheValidParents = null; + + /** @var IFolder */ + private $userFolder; + + /** @var LoggerInterface */ + private $logger; + + /** + * Plugin constructor + */ + public function __construct(View $view, Folder $userFolder) { + $this->fileView = $view; + $this->userFolder = $userFolder; + } + + /** + * This initializes the plugin. + * + * This function is called by \Sabre\DAV\Server, after + * addPlugin is called. + * + * This method should set up the requires event subscriptions. + * + * @param \Sabre\DAV\Server $server + * @return void + */ + public function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + $this->logger = $this->server->getLogger(); + + $server->on('method:POST', array($this, 'handleBundle')); + } + + /** + * We intercept this to handle method:POST on a dav resource and process the bundled files multipart HTTP request. + * + * @throws /Sabre\DAV\Exception\BadRequest + * @throws /Sabre\DAV\Exception\Forbidden + */ + public function handleBundle(RequestInterface $request, ResponseInterface $response) { + // Limit bundle upload to the /bundle endpoint + if ($request->getPath() !== "files/bundle") { + return true; + } + + $multiPartParser = new MultipartContentsParser($request); + $writtenFiles = []; + + // $multiPartParser->eof() + while (!$multiPartParser->lastBoundary()) { + try { + [$headers, $content] = $multiPartParser->readNextPart(); + + if ((int)$headers['content-length'] !== strlen($content)) { + throw new BadRequest("Content read with different size than declared. Got " . $headers['content-length'] . ", expected" . strlen($content)); + } + + $node = $this->userFolder->newFile($headers['x-file-path'], $content); + $writtenFiles[$headers['x-file-path']] = $node->getSize(); + + if ((int)$headers['content-length'] !== $node->getSize()) { + throw new BadRequest("Written file length is different than declared length. Got " . $headers['content-length'] . ", expected" . $node->getSize()); + } + + // TODO - check md5 hash + // $context = hash_init('md5'); + // hash_update_stream($context, $stream); + // echo hash_final($context); + // if ($header['x-file-md5'] !== hash_final($context)) { + // } + } catch (\Exception $e) { + throw $e; + $this->logger->error($e->getMessage(), ['path' => $header['x-file-path']]); + } + } + + $response->setStatus(200); + $response->setBody(new JSONResponse([ + $writtenFiles + ])); + + return false; + + // $this->contentHandler = $this->getContentHandler($this->request); + + // $multipleRequestsData = $this->parseBundleMetadata(); + + //Process bundle and send a multi-status response + // $result = $this->processBundle($multipleRequestsData); + + // return $result; + } + + public function handleBundleWithMetadata(RequestInterface $request, ResponseInterface $response) { + // Limit bundle upload to the /bundle endpoint + if ($request->getPath() !== "files/bundle") { + return true; + } + + $multiPartParser = new MultipartContentsParser($request); + + [$metadataHeaders, $rawMetadata] = $multiPartParser->getMetadata(); + + if ($metadataHeaders['content-type'] !== "text/xml; charset=utf-8") { + throw new BadRequest("Incorrect Content-Type for metadata."); + } + + if ((int)$metadataHeaders['content-length'] !== strlen($rawMetadata)) { + throw new BadRequest("Content read with different size than declared."); + } + + $metadata = $this->parseMetadata($rawMetadata); + + $writtenFiles = []; + + foreach ($metadata as $fileMetadata) { + try { + [$headers, $content] = $multiPartParser->readNextPart((int)$fileMetadata['oc-total-length']); + + if ($fileMetadata['oc-id'] !== $headers['content-id']) { + throw new BadRequest("Content-ID do not match oc-id. Check the order of your metadata."); + } + + if (isset($file[$fileMetadata['oc-id']])) { + throw new BadRequest("Content-ID appear twice. Check the order of your metadata."); + } + + if ((int)$fileMetadata['oc-total-length'] !== strlen($content)) { + throw new BadRequest("Content read with different size than declared."); + } + + $node = $this->userFolder->newFile($fileMetadata['oc-path'], $content); + $writtenFiles[$fileMetadata['oc-id']] = $node->getSize(); + + // TODO - check md5 hash + // $context = hash_init('md5'); + // hash_update_stream($context, $stream); + // echo hash_final($context); + if ($fileMetadata['oc-md5'] !== hash_final($context)) { + + } + } catch (\Exception $e) { + throw $e; + $this->logger->error($e->getMessage(), ['path' => $fileMetadata['oc-path']]); + } + } + + $response->setStatus(200); + $response->setBody(new JSONResponse([ + $writtenFiles + ])); + + return false; + + // $this->contentHandler = $this->getContentHandler($this->request); + + // $multipleRequestsData = $this->parseBundleMetadata(); + + //Process bundle and send a multi-status response + // $result = $this->processBundle($multipleRequestsData); + + // return $result; + } + + private function parseMetadata(string $rawMetadata) { + $xml = simplexml_load_string($rawMetadata); + if ($xml === false) { + $error = libxml_get_errors(); + throw new \Exception('Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request', $error); + } + + libxml_clear_errors(); + + $xml->registerXPathNamespace('d','urn:DAV'); + + $metadataXml = $xml->xpath('/d:multipart/d:part/d:prop'); + + if($metadataXml === false){ + throw new \Exception('Fail to access d:multipart/d:part/d:prop elements'); + } + + return array_map(function($xmlObject) { return get_object_vars($xmlObject->children('d', TRUE));}, $metadataXml); + } + + /** + * Parses multipart contents and send appropriate response + * + * @throws \Sabre\DAV\Exception\Forbidden + * + * @return array $multipleRequestsData + */ + private function parseBundleMetadata() { + $multipleRequestsData = array(); + try { + // Verify metadata part headers + $bundleMetadata = null; + try{ + $bundleMetadata = $this->contentHandler->getPartHeaders($this->boundary); + } + catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + $contentParts = explode(';', $bundleMetadata['content-type']); + if (count($contentParts) != 2) { + throw new \Exception('Incorrect Content-type format. Charset might be missing'); + } + $contentType = trim($contentParts[0]); + $expectedContentType = 'text/xml'; + if ($contentType != $expectedContentType) { + throw new BadRequest(sprintf( + 'Content-Type must be %s', + $expectedContentType + )); + } + if (!isset($bundleMetadata['content-length'])) { + throw new \Exception('Bundle metadata header does not contain Content-Length. Unable to parse whole bundle request'); + } + + // Read metadata part headers + $bundleMetadataBody = $this->contentHandler->streamReadToString($bundleMetadata['content-length']); + + $bundleMetadataBody = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$bundleMetadataBody); + + //Try to load xml + $xml = simplexml_load_string($bundleMetadataBody); + if (false === $xml) { + $mlerror = libxml_get_errors(); + throw new \Exception('Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request'); + } + $xml->registerXPathNamespace('d','urn:DAV'); + unset($bundleMetadataBody); + + if(1 != count($xml->xpath('/d:multipart'))){ + throw new \Exception('Bundle metadata does not contain d:multipart children elements'); + } + + $fileMetadataObjectXML = $xml->xpath('/d:multipart/d:part/d:prop'); + + if(0 == count($fileMetadataObjectXML)){ + throw new \Exception('Bundle metadata does not contain d:multipart/d:part/d:prop children elements'); + } + + foreach ($fileMetadataObjectXML as $prop) { + $fileMetadata = get_object_vars($prop->children('d', TRUE)); + + // if any of the field is not contained, + // bthe try-catch clausule will raise Undefined index exception + $contentID = intval($fileMetadata['oc-id']); + if(array_key_exists($contentID, $multipleRequestsData)){ + throw new \Exception('One or more files have the same Content-ID '.$contentID.'. Unable to parse whole bundle request'); + } + $multipleRequestsData[$contentID]['oc-path'] = $fileMetadata['oc-path']; + $multipleRequestsData[$contentID]['oc-mtime'] = $fileMetadata['oc-mtime']; + $multipleRequestsData[$contentID]['oc-total-length'] = intval($fileMetadata['oc-total-length']); + $multipleRequestsData[$contentID]['response'] = null; + } + } catch (\Exception $e) { + libxml_clear_errors(); + throw new Forbidden($e->getMessage()); + } + return $multipleRequestsData; + } + + /** + * Process multipart contents and send appropriate response + * + * @param RequestInterface $request + * + * @return boolean + */ + private function processBundle($multipleRequestsData) { + $bundleResponseProperties = array(); + + while(!$this->contentHandler->getEndDelimiterReached()) { + // Verify metadata part headers + $fileContentHeader = null; + + //If something fails at this point, just continue, $multipleRequestsData[$contentID]['response'] will be null for this content + try{ + $fileContentHeader = $this->contentHandler->getPartHeaders($this->boundary); + if(is_null($fileContentHeader) || !isset($fileContentHeader['content-id']) || !array_key_exists(intval($fileContentHeader['content-id']), $multipleRequestsData)){ + continue; + } + } + catch (\Exception $e) { + continue; + } + + $fileID = intval($fileContentHeader['content-id']); + $fileMetadata = $multipleRequestsData[$fileID]; + + $filePath = $fileMetadata['oc-path']; + + list($folderPath, $fileName) = \OC\URLUtil::splitPath($filePath); + + try { + //get absolute path of the file + $absoluteFilePath = $this->fileView->getAbsolutePath($folderPath) . '/' . $fileName; + $info = new FileInfo($absoluteFilePath, null, null, array(), null); + $node = new BundledFile($this->fileView, $info, $this->contentHandler); + $node->acquireLock(ILockingProvider::LOCK_SHARED); + $properties = $node->putFile($fileMetadata); + $multipleRequestsData[$fileID]['response'] = $this->handleFileMultiStatus($filePath, $properties); + } catch (\Exception $exc) { + //TODO: This should not be BadRequest! This should be any exception - how to do it carefully? + $exc = new BadRequest($exc->getMessage()); + $multipleRequestsData[$fileID]['response'] = $this->handleFileMultiStatusError($filePath, $exc); + continue; + } + + //TODO: do we need to unlock file if putFile failed? In this version we dont (does continue) + //release lock as in dav/lib/Connector/Sabre/LockPlugin.php + $node->releaseLock(ILockingProvider::LOCK_SHARED); + $this->server->tree->markDirty($filePath); + } + + foreach($multipleRequestsData as $requestData) { + $response = $requestData['response']; + if (is_null($response)){ + $exc = new BadRequest('File parsing error'); + $response = $this->handleFileMultiStatusError($requestData['oc-path'], $exc); + } + $bundleResponseProperties[] = $response; + } + + //multi-status response announced + $this->response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->response->setStatus(207); + $body = $this->server->generateMultiStatus($bundleResponseProperties); + $this->response->setBody($body); + + return false; + } + + /** + * Adds to multi-status response exception class string and exception message for specific file + * + * @return array $entry + */ + private function handleFileMultiStatusError($ocPath, $exc){ + $status = $exc->getHTTPCode(); + $entry['href'] = $this->userFilesHome; + $entry[$status]['{DAV:}error']['{http://sabredav.org/ns}exception'] = get_class($exc); + $entry[$status]['{DAV:}error']['{http://sabredav.org/ns}message'] = $exc->getMessage(); + $entry[$status]['{DAV:}oc-path'] = $ocPath; + return $entry; + } + + /** + * Adds to multi-status response properties for specific file + * + * @return array $entry + */ + private function handleFileMultiStatus($ocPath, $properties){ + $entry['href'] = $this->userFilesHome; + $entry[200] = $properties; + $entry[200]['{DAV:}oc-path'] = $ocPath; + return $entry; + } + + /** + * Get content handler + * + * @param RequestInterface $request + * @return \OCA\DAV\BundleUpload\MultipartContentsParser + */ + // private function getContentHandler(RequestInterface $request) { + // if ($this->contentHandler === null) { + // return new MultipartContentsParser($request); + // } + // return $this->contentHandler; + // } +} \ No newline at end of file diff --git a/apps/dav/lib/BundleUpload/MultipartContentsParser.php b/apps/dav/lib/BundleUpload/MultipartContentsParser.php new file mode 100644 index 00000000000..93b24539c49 --- /dev/null +++ b/apps/dav/lib/BundleUpload/MultipartContentsParser.php @@ -0,0 +1,497 @@ + + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\BundleUpload; + +use Exception; +use Sabre\HTTP\RequestInterface; +use Sabre\DAV\Exception\BadRequest; + +/** + * This class is used to parse multipart/related HTTP message according to RFC http://www.rfc-archive.org/getrfc.php?rfc=2387 + * This class requires a message to contain Content-length parameters, which is used in high performance reading of file contents. + */ + +class MultipartContentsParser { + /** + * @var \Sabre\HTTP\RequestInterface + */ + // private $request; + + /** @var resource */ + private $stream = null; + + /** @var string */ + private $boundary = ""; + private $lastBoundary = ""; + + /** + * @var Bool + */ + // private $endDelimiterReached = false; + + /** + * Constructor. + */ + public function __construct(RequestInterface $request) { + $this->stream = $request->getBody(); + if (gettype($this->stream) !== 'resource') { + throw new BadRequest('Wrong body type'); + } + + $this->boundary = '--'.$this->getBoundary($request->getHeader('Content-Type'))."\r\n"; + $this->lastBoundary = '--'.$this->getBoundary($request->getHeader('Content-Type'))."--\r\n"; + } + + /** + * Parse the boundary from a Content-Type header + * + * @throws \Sabre\DAV\Exception\BadRequest + */ + private function getBoundary(string $contentType) { + // Making sure the end node exists + //TODO: add support for user creation if that is first sync. Currently user has to be created. + // $this->userFilesHome = $this->request->getPath(); + // $userFilesHomeNode = $this->server->tree->getNodeForPath($this->userFilesHome); + // if (!($userFilesHomeNode instanceof FilesHome)){ + // throw new Forbidden('URL endpoint has to be instance of \OCA\DAV\Files\FilesHome'); + // } + + // $headers = array('Content-Type'); + // foreach ($headers as $header) { + // $value = $this->request->getHeader($header); + // if ($value === null) { + // throw new Forbidden(sprintf('%s header is needed', $header)); + // } elseif (!is_int($value) && empty($value)) { + // throw new Forbidden(sprintf('%s header must not be empty', $header)); + // } + // } + + // Validate content-type + // Ex: Content-Type: "multipart/related; boundary=boundary_bf38b9b4b10a303a28ed075624db3978" + [$mimeType, $boundary] = explode(';', $contentType); + + if (trim($mimeType) !== 'multipart/related') { + throw new BadRequest('Content-Type must be multipart/related'); + } + + // Validate boundary + [$key, $value] = explode('=', $boundary); + if (trim($key) !== 'boundary') { + throw new BadRequest('Boundary is invalid'); + } + + $value=trim($value); + + // Remove potential quotes around boundary value + if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') { + $value = substr($value, 1, -1); + } + + return $value; + } + + /** + * Get a line. + * + * If false is return, it's the end of file. + * + * @throws \Sabre\DAV\Exception\BadRequest + */ + // public function gets() { + // $content = $this->getContent(); + // if (!is_resource($content)) { + // throw new BadRequest('Unable to get request content'); + // } + + // return fgets($content); + // } + + /** + */ + // public function getCursor() { + // return ftell($this->getContent()); + // } + + /** + */ + // public function getEndDelimiterReached() { + // return $this->endDelimiterReached; + // } + + /** + * Return if end of file. + */ + public function eof() { + return feof($this->stream); + } + + /** + * Seeks to offset of some file contentLength from the current cursor position in the + * multipartContent. + * + * Return true on success and false on failure + */ + // public function multipartContentSeekToContentLength(int $contentLength) { + // return (fseek($this->getContent(), $contentLength, SEEK_CUR) === 0 ? true : false); + // } + + /** + * Get request content. + * + * @throws \Sabre\DAV\Exception\BadRequest + * + * @return resource + */ + // public function getContent() { + // if ($this->stream === null) { + // // Pass body by reference, so other objects can have global access + // $content = $this->request->getBody(); + + // if (!$this->stream) { + // throw new BadRequest('Unable to get request content'); + // } + + // if (gettype($this->stream) !== 'resource') { + // throw new BadRequest('Wrong body type'); + // } + + // $this->stream = $content; + // } + + // return $this->stream; + // } + + // public function getBoundary(string $boundary) { + // return "\r\n--$boundary\r\n"; + // } + + public function checkBoundary(string $boundary, string $line) { + if ($line !== $boundary) { + throw new Exception("Invalid boundary, is '$line', should be '$this->boundary'."); + } + + return true; + } + + public function lastBoundary() { + $content = fread($this->stream, strlen($this->lastBoundary)); + $result = fseek($this->stream, -strlen($this->lastBoundary), SEEK_CUR); + + if ($result === -1) { + throw new Exception("Unknown error while seeking content"); + } + + return $content === $this->lastBoundary; + } + + /** + * Return the next part of the request. + * + * @throws Exception + */ + public function readNextPart(int $length = 0) { + $this->checkBoundary($this->boundary, fread($this->stream, strlen($this->boundary))); + + $headers = $this->readPartHeaders(); + + if ($length === 0 && isset($headers["content-length"])) { + $length = $headers["content-length"]; + } + + if ($length === 0) { + throw new Exception("Part cannot be of length 0."); + } + + $content = $this->readPartContent2($length); + + return [$headers, $content]; + } + + /** + * Return the next part of the request. + * + * @throws Exception + */ + public function readNextStream() { + $this->checkBoundary($this->boundary, fread($this->stream, strlen($this->boundary))); + + $headers = $this->readPartHeaders(); + + return [$headers, $this->stream]; + } + + /** + * Return the headers of a part of the request. + * + * @throws \Sabre\DAV\Exception\BadRequest + * @throws Exception + */ + public function readPartHeaders() { + $headers = []; + $blankLineCount = 0; + + while($blankLineCount < 1) { + $line = fgets($this->stream); + + if ($line === false) { + throw new Exception('An error appears while reading headers of a part'); + } + + if ($line === "\r\n") { + break; + } + + try { + [$key, $value] = explode(':', $line, 2); + $headers[strtolower(trim($key))] = trim($value); + } catch (Exception $e) { + throw new BadRequest('An error appears while parsing headers of a part', $e); + } + } + + return $headers; + } + + /** + * Return the content of the current part of the stream. + * + * @throws \Sabre\DAV\Exception\BadRequest + * @throws Exception + */ + public function readPartContent() { + $line = ''; + $content = ''; + + do { + $content .= $line; + + if (feof($this->stream)) { + throw new BadRequest("Unexpected EOF while reading stream."); + } + + $line = fgets($this->stream); + + if ($line === false) { + throw new Exception("Fail to read part's content."); + } + } while ($line !== $this->boundary); + + // We need to be before $boundary for the next parsing. + $result = fseek($this->stream, -strlen($this->boundary), SEEK_CUR); + + if ($result === -1) { + throw new Exception("Fail to seek upstream."); + } + + // Remove the extra new line "\r\n" that is not part of the content + return substr($content, 0, -2); + } + + public function readPartContent2(int $length) { + // Read stream until file's $length, EOF or $boundary is reached + $content = stream_get_line($this->stream, $length); + + if ($content === false) { + throw new Exception("Fail to read part's content."); + } + + if (feof($this->stream)) { + throw new Exception("Unexpected EOF while reading stream."); + } + + stream_get_contents($this->stream, 2); + + return $content; + } + + public function getContentPosition() { + return ftell($this->stream); + } + + public function getMetadata() { + fseek($this->stream, 0); + return $this->readNextPart(); + } + + public function getContent(int $pos, int $length) { + $previousPos = ftell($this->stream); + + $content = stream_get_contents($this->stream, $length, $pos); + + fseek($this->stream, $previousPos); + + return $content; + } + + /** + * Get a part of request separated by boundary $boundary. + * + * If this method returns an exception, it means whole request has to be abandoned, + * Request part without correct headers might corrupt the message and parsing is impossible + * + * @throws \Exception + */ + // public function getPartHeaders(string $boundary) { + // $delimiter = '--'.$boundary."\r\n"; + // $endDelimiter = '--'.$boundary.'--'; + // $boundaryCount = 0; + // $content = ''; + // $headers = null; + + // while (!$this->eof()) { + // $line = $this->gets(); + // if ($line === false) { + // if ($boundaryCount == 0) { + // // Empty part, ignore + // break; + // } + // else{ + // throw new \Exception('An error appears while reading and parsing header of content part using fgets'); + // } + // } + + // if ($boundaryCount == 0) { + // if ($line != $delimiter) { + // if ($this->getCursor() == strlen($line)) { + // throw new \Exception('Expected boundary delimiter in content part - this is not a multipart request'); + // } + // elseif ($line == $endDelimiter || $line == $endDelimiter."\r\n") { + // $this->endDelimiterReached = true; + // break; + // } + // elseif ($line == "\r\n") { + // continue; + // } + // } else { + // continue; + // } + // // At this point we know, that first line was boundary + // $boundaryCount++; + // } + // elseif ($boundaryCount == 1 && $line == "\r\n"){ + // //header-end according to RFC + // $content .= $line; + // $headers = $this->readHeaders($content); + // break; + // } + // elseif ($line == $endDelimiter || $line == $endDelimiter."\r\n") { + // $this->endDelimiterReached = true; + // break; + // } + + // $content .= $line; + // } + + // if ($this->eof()){ + // $this->endDelimiterReached = true; + // } + + // return $headers; + // } + + /** + * Read the contents from the current file pointer to the specified length + * + * @throws \Sabre\DAV\Exception\BadRequest + */ + // public function streamReadToString(int $length) { + // if ($length<0) { + // throw new BadRequest('Method streamRead cannot read contents with negative length'); + // } + // $source = $this->getContent(); + // $bufChunkSize = 8192; + // $count = $length; + // $buf = ''; + + // while ($count!=0) { + // $bufSize = (($count - $bufChunkSize)<0) ? $count : $bufChunkSize; + // $buf .= fread($source, $bufSize); + // $count -= $bufSize; + // } + + // $bytesWritten = strlen($buf); + // if ($length != $bytesWritten){ + // throw new BadRequest('Method streamRead read '.$bytesWritten.' expected '.$length); + // } + // return $buf; + // } + + /** + * Read the contents from the current file pointer to the specified length and pass + * + * @param resource $target + * + * @throws \Sabre\DAV\Exception\BadRequest + */ + // public function streamReadToStream($target, int $length) { + // if ($length<0) { + // throw new BadRequest('Method streamRead cannot read contents with negative length'); + // } + // $source = $this->getContent(); + // $bufChunkSize = 8192; + // $count = $length; + // $returnStatus = true; + + // while ($count!=0) { + // $bufSize = (($count - $bufChunkSize)<0) ? $count : $bufChunkSize; + // $buf = fread($source, $bufSize); + // $bytesWritten = fwrite($target, $buf); + + // // note: strlen is expensive so only use it when necessary, + // // on the last block + // if ($bytesWritten === false + // || ($bytesWritten < $bufSize) + // ) { + // // write error, could be disk full ? + // $returnStatus = false; + // break; + // } + // $count -= $bufSize; + // } + + // return $returnStatus; + // } + + + /** + * Get headers from content + */ + // public function readHeaders($content) { + // $headers = null; + // $headerLimitation = strpos($content, "\r\n\r\n"); + // if ($headerLimitation === false) { + // return null; + // } + // $headersContent = substr($content, 0, $headerLimitation); + // $headersContent = trim($headersContent); + // foreach (explode("\r\n", $headersContent) as $header) { + // $parts = explode(':', $header, 2); + // if (count($parts) != 2) { + // //has incorrect header, abort + // return null; + // } + // $headers[strtolower(trim($parts[0]))] = trim($parts[1]); + // } + + // return $headers; + // } +} \ No newline at end of file diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index 5d4e3c05077..17db2346c68 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud GmbH * * @author Thomas Müller + * @author Louis Chemineau * * @license AGPL-3.0 * @@ -28,6 +29,7 @@ class Capabilities implements ICapability { return [ 'dav' => [ 'chunking' => '1.0', + 'bundleupload' => '1.0', ] ]; } diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 5ff5f831eb5..ec33b44fe4b 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -356,7 +356,7 @@ class File extends Node implements IFile { return '"' . $this->info->getEtag() . '"'; } - private function getPartFileBasePath($path) { + protected function getPartFileBasePath($path) { $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true); if ($partFileInStorage) { return $path; @@ -368,7 +368,7 @@ class File extends Node implements IFile { /** * @param string $path */ - private function emitPreHooks($exists, $path = null) { + protected function emitPreHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } @@ -396,7 +396,7 @@ class File extends Node implements IFile { /** * @param string $path */ - private function emitPostHooks($exists, $path = null) { + protected function emitPostHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } @@ -633,7 +633,7 @@ class File extends Node implements IFile { * * @throws \Sabre\DAV\Exception */ - private function convertToSabreException(\Exception $e) { + protected function convertToSabreException(\Exception $e) { if ($e instanceof \Sabre\DAV\Exception) { throw $e; } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index e9634f670d3..74ae94a6d51 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -62,10 +62,12 @@ use OCA\DAV\DAV\PublicAuth; use OCA\DAV\Events\SabrePluginAuthInitEvent; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\LazySearchBackend; +use OCA\DAV\BundleUpload\BundlingPlugin; use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\Upload\ChunkingPlugin; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\IRootFolder; use OCP\IRequest; use OCP\SabrePluginEvent; use Sabre\CardDAV\VCFExportPlugin; @@ -294,6 +296,10 @@ class Server { \OC::$server->getShareManager(), $view )); + $rootFolder = \OC::$server->query(IRootFolder::class); + $this->server->addPlugin( + new BundlingPlugin($view, $userFolder) + ); } $this->server->addPlugin(new \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin( \OC::$server->getConfig(), diff --git a/apps/dav/tests/temporary/bundling_profile.sh b/apps/dav/tests/temporary/bundling_profile.sh new file mode 100755 index 00000000000..a86e5ba5be4 --- /dev/null +++ b/apps/dav/tests/temporary/bundling_profile.sh @@ -0,0 +1,149 @@ +#!/bin/bash + +script_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +user='admin' +pass='admin' +server='localhost/owncloud' +upload="/tmp/upload.txt" + + +testfile2="$script_path/zombie.jpg" +size2=$(du -sb $testfile2 | awk '{ print $1 }') +md52=$(md5sum $testfile2 | awk '{ print $1 }') + +header="\n +\n + \n + \n + /test/zombie1.jpg\n + 1476393386\n + 0\n + $size2\n + \n + \n + \n + \n + /test/zombie2.jpg\n + 1476393386\n + 1\n + $size2\n + \n + \n + \n + \n + /test/zombie3.jpg\n + 1476393386\n + 2\n + $size2\n + \n + \n + \n + \n + /test/zombie4.jpg\n + 1476393386\n + 3\n + $size2\n + \n + \n + \n + \n + /test/zombie5.jpg\n + 1476393386\n + 4\n + $size2\n + \n + \n + \n + \n + /test/zombie6.jpg\n + 1476393386\n + 5\n + $size2\n + \n + \n + \n + \n + /test/zombie7.jpg\n + 1476393386\n + 6\n + $size2\n + \n + \n + \n + \n + /test/zombie8.jpg\n + 1476393386\n + 7\n + $size2\n + \n + \n + \n + \n + /test/zombie9.jpg\n + 1476393386\n + 8\n + $size2\n + \n + \n + \n + \n + /test/zombie10.jpg\n + 1476393386\n + 9\n + $size2\n + \n + \n +" +headersize=$(echo -en $header | wc -c) + +mdupload=$(md5sum $upload | awk '{ print $1 }') +boundrary="boundary_$mdupload" + +#CONTENTS +echo -en "--$boundrary\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $headersize\r\n\r\n" > $upload +echo -en $header >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 0\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 1\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 2\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 3\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 4\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 5\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 6\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 7\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 8\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +echo -en "\r\n--$boundrary\r\nContent-ID: 9\r\n\r\n" >> $upload +cat $testfile2 >> $upload + +#END BOUNDRARY +echo -en "\r\n--$boundrary--\r\n" >> $upload + +#POST +#curl -X DELETE -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" "http://$server/remote.php/webdav/config.cfg" + +blackfire --samples 1 curl -X POST -H "Content-Type: multipart/related; boundary=$boundrary" --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ + --data-binary "@$upload" \ + "http://$user:$pass@$server/remote.php/dav/files/$user" + + + + diff --git a/apps/dav/tests/temporary/bundling_tests.sh b/apps/dav/tests/temporary/bundling_tests.sh new file mode 100755 index 00000000000..3aa1eac3469 --- /dev/null +++ b/apps/dav/tests/temporary/bundling_tests.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -eu + +scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +user='admin' +pass='password' +server='nextcloud.test' +upload="/tmp/upload.txt" + + +testFile1="$scriptPath/put_test.sh" +size1=$(du -sb "$testFile1" | awk '{ print $1 }') +# md51=$(md5sum "$testFile1" | awk '{ print $1 }') +id1="0" + +testFile2="$scriptPath/screenshot.png" +size2=$(du -sb "$testFile2" | awk '{ print $1 }') +# md52=$(md5sum "$testFile2" | awk '{ print $1 }') +id2="1" + +header="\n +\n + \n + \n + /put_test.sh\n + 1476393777\n + $id1\n + $size1\n + \n + \n + \n + \n + /zombie.jpg\n + 1476393386\n + $id2\n + $size2\n + \n + \n +" +headerSize=$(echo -en "$header" | wc -c) + +mdUpload=$(md5sum $upload | awk '{ print $1 }') +boundary="boundary_$mdUpload" + +#CONTENTS +echo -en "--$boundary\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $headerSize\r\n\r\n" > $upload +echo -en "$header" >> $upload + +cat "$upload" +echo -en "\r\n--$boundary\r\nContent-ID: $id1\r\n\r\n" >> $upload +cat "$testFile1" >> $upload + +echo -en "\r\n--$boundary\r\nContent-ID: $id2\r\n\r\n" >> $upload +cat "$testFile2" >> $upload + +#END boundary +echo -en "\r\n--$boundary--\r\n" >> $upload + +#POST +#curl -X DELETE -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" "http://$server/remote.php/webdav/config.cfg" + +curl -X POST -k -H "Content-Type: multipart/related; boundary=$boundary" --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ + --data-binary "@$upload" \ + "https://$user:$pass@$server/remote.php/dav/files/bundle" + + + + diff --git a/apps/dav/tests/temporary/put_test.sh b/apps/dav/tests/temporary/put_test.sh new file mode 100755 index 00000000000..c3b64dee448 --- /dev/null +++ b/apps/dav/tests/temporary/put_test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +script_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +user='admin' +pass='admin' +server='localhost/owncloud' + +testfile2="$script_path/zombie.jpg" + +blackfire --samples 1 curl -X PUT -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" --data-binary @"$testfile2" "http://$server/remote.php/webdav/test/zombie.jpg" +#curl -X PUT -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" --data-binary @"$testfile2" "http://$server/remote.php/webdav/test/zombie.jpg" diff --git a/apps/dav/tests/temporary/screenshot.png b/apps/dav/tests/temporary/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e776531287841da5382a9157d1ac95d0920939 GIT binary patch literal 183411 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�$$0umxBUhPuG;rYb{a{~+81&J z$^_^SMm3EVOr%wRmL)Pxy-~E*Jsr<-GO`=H$9ect@3~_R?Y*NN#xm%+HcDr5`b0Fp zt#Cru2oPK5PapneCIKm5WY`@7=P{8Mdy%aZ7(Bs+qJlq{A&8h0Y0GJa1Q-eMYVQV5 zvWk*MT~Y@B3~>Y@#9XjPcnT^Xv~gl+U;u7HCQ~N`=asOfk?vzB!h}3Mz(|l8j`RYA ziK`&o9u~CQ0Kh zd_i?DzQZ&Q5s7T+XCRfW$=YjWl)9%?m9F|DtG{d-R6o7|tB33!GtnL?MW?1dfQ2$I zT8ZvhJ!d=97g|tM`a`kTSzWyyk_Ih9%z@RJIG{Xlpb3cq!`W)tNP!XOqwidmihbZ= zE+H%hfg^KJLc6nJddZ=%$H)8(IqB(3ndB;;V-C|_!4zD;h@GqK9k8L+u%Sx8yij=;3j)cy!-u@Ydly8sIlJ?*>jhLpm9oia{ooV_q_oX8|W zOQcqUg&Bfpsj>$6p`haeha>IHiN8ex-WoRzhQip#BUW4^t;nGn7mmaukwBsIbSI9} zWJm&wfgH=8G%V;dz##|1n?@NPh9JUar3Tr4pL0K%HSkE}a!T!>&Y4-f@hJa!FNFMX z3AgWYA;+8=uCYC5OCnYr`c{p0RAYTRs0~tTPlp5qwBFs8-PvtF@I-X9zGbzjqq)TI z!-)i<5eFFMa0=k|WBXK)0;<08HS#$9y7I8Vj~f!PH7Crn^9%Xvn>mzjeI1urUzLw( z8nt94*!)Rt-QzZBt*u=+pp~R~sz;6NQp}%EymM5RjOYM4DY4&Q`g+#G#KLk}7n*er zbfv^;I-C=akBVh%Q;?FnmW_O=)A;P}+M}?TP7e!gPbqnd@>PAe2iAZnl0oFST!X)z zxhZdOUXrzv(d+6#ph8uJu61KAexsic)60ZsoU(oIFBH+lB7PaJ$^fnbfAtrub$($J zydCZAb+u z?X&1(As-+5a#Xu&m!CjFOGMhN}>ShGtO3P;P4yk4)Y{BwgCA#ZwlZ~rKhHAWKaGot2$YK>{A5Nsv5G^aZ!lT5zL#F?Ms;FtmeVV?3G1_51e zpDW~TjtGceWxO+ATDRW%t8LpO+N+A&9#vcfp&!YO-z22z0la5V6{D*XWjLVST7&v9 z^OB8(iQ&ze`%fX>1M=#_2H$-Bc$bp4HUY$_DDoQVfbM)pEEhN)a0QR`cH!m$ufeF2 z9p|6-WJ`Y9m*gf<;cia8gJ}M_bN9#nM6VVQIH4zxjSuMN1GHJf)xpY}Wq0avp+5rV zCpO&*g;GMgoWwbYPFYTpms=3>(5!BA0%T+<4$f& zQUt6T2x45uMKrNtvR@*hO7$Ktj-Q4wwViYq9ROpQJ8!dEI!#&)Gfp#+&Oa@-fP! zuh0-=KWPUOdasGu>aer!L)3c+zX!a5Qz`lsyT72Iz=sPP5eg8`Cvx@{_Y(RB^v8G$ zbyB`rqeqIe1R;0rZo-kBi0~HThwvh7AIW~2qiIEj(U~#&)Q-f20UYaTvmr47#45d7-nkh_!}dI#ZC!*vhauE zhhhDR*Of>#5W1o#KX1XBsNUfFq&3!H{n;sG!VM7n??x<_oT6ciyAx_JOK?L#fS3K; zOY9Q&Lqzr4?`{AB`(IK3{+|f~|G)d~CNcw~b>QxlRfh z@%HX>SH;y770GA4;v466hvVlS(G*3pSC(#wTNA{rp4;B`hL)uU3|*@w#8f_OQ}#-j-4ynD4w(x#tuM0dc@wSs^1a z&AmcT3ItvE;0NIk=<6n(?4Elr-@Qz+pznJ@&Q4vQ!~1#}en;efp#AQynw=Ell3Ik1 z4_DvNf9@H-5V_5A7sw_=t2<0zTqFE~=<**5%VmT}c>Q-qOAxIr-DTMmC_xZedYA=G z$LkVPs0UbOvDgoDwR2r-Epw+y>b$_tnn_r0drpM1TyLLWjVQS}V$*a;!kxW(b(yYW z)`s|lHUa(%uOlViPU~I%a~9_-qT%RlVqako56LlWWP)w^6-NoN=Z}eXl>HqWp-g$2Q8*JJv z`*9!s9&Ur6S?cSHe+}Cty?#@kMCvfA<2vBH8A5pr5i*a)LIeA6&J&TK9`qULS;R@t zVJrt2j8~(CG~jJs5A9Qqdf<3@4LWS5NN9dTuKfeJ{`Ar0H!|~Lh`8~hyG5pj; znwMuKUcY|rkjPxP{Wl&svYXaBrb%Lr{`Y3AKLn93ey0q&+xGoMH_?`lvbHEG_<#U= z4b@^e>#2U4x;ax9#N;jyr&)rR^-Q3!+weF`HEpHSjfzdfwp~PzoZo-^cpp*3-SpfA z`jj?;P}nckMlM&+R^IjDp`P24W-+C!&S?S=8PA!pzA-`zQVuQmAb;iR!EZyJPpglzq5-!QrRba`l% zo0BffOXNdjX5Oi)*AG1zG`^}pn30H#u2Q&$f4KHPU``&pS&tpOug*=|thtM1+L+%u zq&Rgl@pg@)025h`3|+IR;%H~$4!Ubfp5RN=@WgcWb2y#NQ{7G!9!8)N4f?AAZ3+18 zzA$`CQAr65ny`--os5YoY9Lh&y}D))<|eHjGR7a)>a{mHj=g$A&q*?!5xwe>-FRV#zL!W(+BNdpxhpir&;3(U>ng&&SFA{VK`yY)kTz#MCpYTRAc=qY1qX z?o1?IJ!8cVth&#wF}j3EwBf@Cid}A(+fkQ67^dqu`?zj6zu%MXgJweQZrkEHm7<$c zx$4~!*?Mme1==QT%`F&*veOCTQMjHaymz@uTethP<<$@${kP1psNtwW zC;2eNWn_Tx6alVS)jKkT?G4E)!?|qN-yS?NDi=3KD>G(V)M1sT!s&kY6i0(*nrpR# z{Fd4$oA?;x_PR=EzTdvb5VgkNw9@b2kH#eo0y7&J0Ea*$>3VT*FCM9uRsmwt(BMUm z`T-7y&!G!pWBUuwmAtcb8=iL=+xMWgSyR(*A$0!wmcsJ*XpK5^dD)XUEcO;pTp-Aw&zkJ8Pk$|QFe1zk9~^nS*Ma+`O|Lx1c;>N}pr-wf$exS6Y!LIzeE z)z*qPpLk;)W59tS_v#ntep-D!uf8Zb#kje;|K?|8-M&UEt2QDv})5? z=t*B2WF5md%m3ckSkiyumrBt<-12m*cT8=n?yl~<+}Vo3b8cI)<~*hT>@61pd+4}Y zbZ=moEO6E z3&wfKHdM&uv&ifkutBEch0kj2KkpF-pV^?FdgI9N$+t8G>FDZ!zSZ>PcDI(-tp#%sH};hJ<#9zJibZOl#1<4<%OM; za~Dkh3RXIJ=gDT7Kp%-I&>HtzJ*kyvPBVK;Udv@(lQ#D{zUbWwul1h$;)HtLvEzYH zT(}n#hdX3hL`h|;yuVcn?{UcDxLXnS`zIFtw`JyDv_99>P;zeMN2PAx;)BJB-o4r* zlrc@}6YX^xP18od)OGQ3U)pyiXGHf>XPG69Or8_lI?Rl>%huYRD{ULA*-lwi(Hvx-P)k0v`k9=YrEBz_jk<4u z$E9we+JPLV`NZcAat`5LEberz&^ruVuU>#HJdSfUdFIM10IFeOy`>8c7r(~N^B|Q4(F7!+$hpSB zhaeU8A-`?YaY$+m-~xRMiVaIQoVo_MI(L<-j%lAwcuM@@P+g@XGzFSKmxzWGW;?>--S6x~oFl62}l%NemGe7kA(6-(-&0Q`IvIe|-oO7Y-)KgQ*JBotZwioRw{gfHY=iga4Sy8tHe*I2`PceV6F|;EzzIeCG z<61v6+K=qZrK$P?BPk9F0d+m$xcZqh9BlcX)AKFKl%lPp`-URNlSY@EG(Fz1>{=s^ zyQgL`>LzM_Nc79T6^r}iR$=zvdmR-?>DO5E@`rkx1EszZ5q0pG+{igV0yDMm`tGZW zqs9abkS8h@3QVpZ5M{o?7O&pU zqy8~-;x_FmEurE)+$m)*Z79s|2wzZQ( zi}GmFN{Q1q#|64oqphX!)k@!(IsMnal-!&?XnZmZW8#_g4I*=PVr=+uS?r+;JP}T8iCEr`*UFRG(tm<*~`z2QgFuOT+^=^ zZ~Ud{ws+a6cfa~{quQ3ULt9%g$>R(jo=Dx$s@aUgi=p+}}wA+sAgD396lse*UuS zL?0gqk13hi{8_%?x9@&!-wb+W>{0w6rg%2uY^i50YZ{OFaUs-?)6ls`Aj0y67RASV zZeyh!-wusdkB z`bfT>`Al#1LO{vTz+k8+rA8Q=*Acgq9Zt*vnu)(+n>?r^TEDi_S6|7}NVpPg`Q*OW z*NhY~Q=x6<_kK1cu*YSq@6u`;BDwwXMVf&iBTvfxkQ~0gGlDV4ERAch6hhwRzYxoO zM)tlOALtH<9CthQF)gxdC`p}jtx8lOK;vb-p#jC= zkoRTlM;}(F*GkiYI21lhSQK@mYYKdQQ<61<`)&MuqxhHNBntdF-}F{u-L0bH2l;8;TAP*?_LQy9NyMIrEg#Ef`SpX_(<~hYN-uGDS$o-7So@2fY&(EEq6rBy( z}QWLex7ZV$>)4)X4u3t%-l1KZbofS9R0wrRDGetGa+*q z*2fbA7gRRe4;M@Rb!x~x;d$1zo(`*R=5OVv&+>6{eXZHgF`eMTxg0ehbWk7&OZIK_ z-6@i;Z8o=lHW=q+4go+ovftwl01N~I0NSDE1kxI+x4Wwwv#)yXVURVD&W_9&No6GI z3iTWo^Q)XRlsdk5&H>^*tf)o7-cOEiiXe%QXN_08>B*vds)V1gSI5q%6ww&+RR{1rkgDi2(Vgr>d{HrJGjdS0LdmIV zmSXd(i$}_v6f=u_kFo6YVfN?OM~^Y(lJOibSrNCSdQMKhc(?_QP*Sja<1)Uy=@^o=e4MXdKR* zMgT%-onxst*jHBGAKIF?$U`sRRK9(XPK=2kN$p%)`;2eFy{fDJE9?qq(%4D1_7QK7 z{$Vg$r<`qmLv8FyXhakBH!;g-ruW_yN@AaMsb(B0hRC)EIc2fQ>YR~FlNr5x4_EU| zTBg0~i>kx|5*^yVin1H14$cR#7oNL(WS4sUUfY+|n`0ch&4wRe0vWTi&!@S+b6s^? zT_8PuWJuMQ(Im!&C$@a`>}ypgr)T^4y6is?*1HaljAXxw!@Z02U*NwmoTc~m!ZY^# z?&P*{EB+922Gm2&y`e4ipz#sV-BTB6z)5xelf^`0$V+=h_4rp>JO|{p({znE##Ho= zMMi5MPn6AWrXhON^CVf9eq@yZV$P$Nr`~zuxNryAZ;Iig(3!!&AN)_;|KkTh!jx2+ zt;5C;5d!6;P4@o!=dHAJz^))TctLL%+=^bi;gHy7iE2Vg8lU?Py=wPCC-~1Of&OUc z6~)V8gB)veGcuK$N0JC|F)=~ro1Oi+^_h&9;jTi}%DJq+!5ooOf39K{nJC_~TU+aF zYBpmm-cNL(b@YB?kND~>L;64eCx3On@s-!o>w3}OP$@sBqflzFtX*7X>Fa~(hx)lg zUS{Nc-)5cPbQeFqi~fc-nZF;Y`DzL2YNy9^46>RVvd1L|UsHC-7?W)nl@6?<9D3<= zDfKt8@=>iJ=ZDDhGWoiycfN1W2{E2I#d1y3w{1AVCf~`qSmy|HSUJrM^@4rKWls0{ z-M9R<2Z)@|@x8ro^&4^d{J<5D79-xb?j`qg#=eCr4_=a%H|6ADF*YD|?zGQGa`ro(ozb?^HNBXYZ(ydr#c zC7tQ7xRQROePb`TNg7g;Q~Y7hjkDN(xFYqlXMC&wsKbND;e--^rONnI-<#<=JgiIk zc6@h~b~QtD;jZ_6o>6znOpQF}e7#^*QhEVedI1*ujfFeEocT@rH$BI)J@*$sm{F8V zzZ7}fiRGWKnQnai+SQn7Ew!^_4tM4XX%*z|xEY#Rk6Fs+X9lzS#B1f|@|DG|mCZ{n z)rGEjZ#;JCbeQ=x>gglB_#)@ellgq__%-Ick=+;LZuUHVuWGfBoX4ME`Nk-H;f9lv zDKGtsw$+pu$zSU|BLy!cUKXym_tZ&Oi?Mcf{@n8$+%{B`l%^6o@gpGaP)bF0Tx=3W zO)`^3Gi}TybA&c&!$i=VcNfhoRmVN|3vZ1YNF*3HZ%<=i8cY1({Tr|+da*}}MOfH; zET1h|kdsmMe7+L=ef>UWJns7~8h`YhDLt+zYfAX5cW5B@#4K5M;ntv9)n-rUU(dmIKq~*($1iQ} z!!MoHDFICpE*W33*DT^c&IW(;)!Qq{>15MW_*0hzH zZgAL7V*E7 z&+`dU@zFzXWej?S?xf|VQ|eRvDIN6h-c037cjng@y>mxdnsaojCG>E9QflgGj54zT zuR%awdbZx5x31>*87vJC={}r{rTMY&C1~|Cac(Me#EgCNasOXk>oUzDg$xP5_j-NG zPT|Zg=aMcNkA00Rj@J?uZ0c7W1NT$SukEyG5$vdA{ax6Y*FDpr#1U-qz&e_b{ON6{ zJa0booQs!!8*1Mik@z2Ld zoEcgU61q1Us})j5SGGF8FQl&6Qe|qg=~QJ}qT}|Unt$GBtTs5fZaLT$XSU=(1yV6u;G%ovOb4|d6ccj{AeCm=*P@aVHPu10FN<8>S&f~+}%m!b- zH?FnU`?WSsW_>%_sm&cjJl!n$&!uz~!-_pleeLD4E9e}5VdixH^W;Jgm)-iz7u&%o z>mzEvcU-tjuKwI|{FAF5?KG2~hOVV1$wr3jF3QaA6W;d!e9*KqUH+-)7@b#_1S+Ma zSFZcEIEqirJR6Oxl37UiH2ddWbh!`K-*~P*F>DjHkZ-%Ne*g3L*0qQ?s(;q-XLHPC z1B=wJtNWxz1NX+b2O6cUhIYL+HOdac}Q&zQpKuikf2=f0jLpG`!wtVicr}AF_93F8`KC zAaV-m*zXf2a8tJGkxIUxk?MnsWsTJCg1kl)Blv-niII}?9MPb@B%3tZh4??mU`d;c zDIvN2cxLCNcemSLFz|BV%^&mbE_JA%E?&PkiS8y*QKO1jP+)?&*XA=L>Q)`<#|D?wkX9@-m;Uz*&}Eh)uv?wp`gFGqr*VUX{uBg`M>W$piLj5;OSxbm2&5pjk2>#NsO zrJ#vTl1&z;%?SkJI5-t$vfN9+UG}MRg@bcI>33Ag! z$^H3SWWM_9D+w-BkBA#y?>KXC*Zi2@9FKbKai5u*yPaV=HvcYS;9m+)mlnS}>OPaY zJ?eR%<>Z4$XM-JLxwQNIBY*6kWr%kh9VNs#kBUt07o_+6luJ|3-;m$oTjo3XnKtW; z{?zt!HhlRWzT&x|by3YP?4ZYf&(BUD;!=YXZ1+1Vo#5uU-o8KmzI1kSoT%c; z)?|o{*5UHbPb5=@9#6y1M-eNk#4{Weaeedj8P5?##v7+o2UXopriS0f^c1P)H{=-A z{ylZ)A%`pMPgTT4u8C~(e>lH`uPBMY!R7B$d0n2VM{4RS&3wmS!88pv}Bf0_9!be>v^C3uIs*^=eh6a z{^#+!Ue~W);XKdp_j?@2=kxx&$G*v|o(VR3!jXutWWKuO_7YKs)Qkl&xuV*rJmPN) ziQjbUw>W%BR=fFGzs8B|+K?$VYT2{3^FQ8T+V{tLm)h+-;)oAdJ|E>VK4wC$Y_#X} z#*WC1y={iybL7(P9op+Vr|F;7T?aYn@0Z5~J7#9`$Maf`d9@=1tt9ry9UtQ`tNd`e zh2eCd9j#ANlbP8zr4gqF#7jX13U|lOoB#Y|hG@bas-PX*(!MMug$%mK1Q|DUR3$#^ zXXSmqK^>C+<$>eIPTlH$Vg92>F_lL6tttfth2OKBoOd5TYM1EoM#DZG!ZtDv0}Bfo zNCFHa+JCYC`eaoel=O5)%26av2Yd;22%V#9ExAnI@{p6%~VkbR=rxhk= zAP$f|1;7NZ8tJ>C>FFxusU_t0THD%+0qG9Zf!SKt1>KW)*Xf#%=V9IhR(@FY6Rq?B zFylZGBP8Rrx1TRCM$Ntka|(Ei1u<~^f~k$5co|LHDPKs{RY)1&OvlaLT42jNyS%)- zxF`w%g0QdyywL6JczAd)jQP;o8l5Na?kVM3_6_u4ydzMcAH>GC9xhp*2>$pL%3(O~ z+<)?fZU6pkhqDH1;uVVyL1AHJ&H;=XkxQGtufK0i4t(o>u94nqp({sf7?Vm@ox1OG zROz56gZ^a^?_DQWLu$1HaS=*xwSVpx@b~}rbKcNVR#%q^^t`bjKWA6Eg2g^dkGiTIIWZ0RWYbBOLuNU}<5J;eD>*rUIb%?l}*XI7)AFc*`YDOtl zX@>en+mZ0}Y-v8W#nj0AE$b>t@-sei85;TAUkT;i#JhVRlr<8Ex0u#}WDZHc>j{LA z;#C}0$QX5mSy@@}2qBGPvOWE-;!cE}R3jEGF4>M1(mxPNz!5aA@>_*odH7&v`FTf2 zus>z?>=)BC-UUE}jqM4f76>pK!32K()N9BjA|p?O=mB;i^!dK4^A}dT_JrPg_>e)Z zwQnOI&YS)H{Swz8l4)#IUhUdiBHupSUUX4A0h$Z_+=C7g^w%DJSuYQ{Sxm2aLGpw* z>HA07JZ7OSu51QpZYD{eS@8ZUaiBBbG|F9+j#XCI`x5&{8)QDf6v|Sm>LgM2ZNU#EM$Pomsmq1W!h>OSi zC>j}c1IB*yMh+Bnjd|ob!m$7Y>sEiW>xv*0Knh#;?OQ^#P0|TZ^{R(3%td}2Svsal z2HE>F9tsf=e8{M@=z&QZP%j28SP8`P*|O!?IhTrw=;RpZnu{s6IWb5S7P`Xw8igSK;Uk#n%O=Lm#f z2NS=sD=Z28)`7Ugvrt`IdjLk);KY}etwPQWaU_q$kIpDuB-*1RxK{9dz|A})T8B6y ztN;j!QxVmbn~O*L8h#INg$MjQ3fyS+h>3|I(ii?ailoQ(-iB*QP;juiC-3zIc2&7+v1w_$L zQ21d~@Ae1(Rn*{sem>MIY{!lrO$z2Ew%GPy%T+T0n^Qtck(3EJ2>Zt&c-pQN|M>Ay z1iIZ3mIBupfqvK4R|Y)&u-vzA-x;vFea9Ollg-b|YHTxTf>R5EC1~UU3)XT@5$Vn5 zR6R{E9gHlbmKSXO6gHrHDLX^zZGehzm9k90UH>(*17Ru`@1|a2`c!#xyiH+YGUyez z`~F}}hW%#EIx16VuidJ7qDaCUbDs3PjzK?jXwcpJT*liSRN>|izB$C-BU4kaz2>aL z-bFzjnvI}yYHXmVJjSDHq-;E356dkUVg@EG^|~u!a8#6(N+|BS&f_zj3fN>xhl38+ zYIs|JzdxK16T1GYrSH3J5D{g_fPdyCT9TT29-6M?+bP0wA*j)hG;Lv!1h*6Lr+0MiV`2F@F%d=$zI%9B z7|o-LxRGA0-#&eM;lJTS!^C+L`ieVP>JV~abAq=xHuP7J96?Z^xIdSm--l55imU7R zmm+S+XbX(NPr|Vd=cx2Mlwi)m6}4^xoRXLa4@h&~BMAaqm&68m))=Y>a^K3#v{zMy zPlxtE)wKx^AFz?_?I&`Pp~@o3*`R}%u!ETjybCh}Y~1j~IpXL!J=vL!H_>ycwzE_8 z&>^MuKM=r4!(k+$DqyQhSbJH1c@9L6n=PybaT& zE$L@rp?BDAyGKZ_%J12;2ce>{kFYI*k_WMQ+DTHpoa~|u6G@^}sJ@prLPa*CoARr)62k{+HVDADYoRt+D;3xA+pU$qXJNu6Bx(Hc) z9HJDo-ZnIhjEzMzgY6dlGP~vddqmbeK`uI?(BN$icNqY3U`x#+kObD+Q2E~+>%eBP z(&=PvEl-taT#!;y!oXpG?1#%^T9RTBcF(AT+52*17b;bzJ761BwKjb+@DpM#aP{wg z?Ct70Y#-}8rCft;2|9A*$Pu)LP{ByV`%o!#@KKVI!pwG}Eft|xJ|N3uQ`y5R6-sUL z(izG^UiopD98oh0>ZX)3^n@VhN$bxnvXWZ$6%`b=e|*7a)P2M=CDQX@@|Vv&*Go#C zUJM#P%OT$#6~@af%pzG%zT{irLvpq^g;r@**p0^L+(v%>Hi`D_HwpQ)cV})Oub7M% z>97JOx=`4_3ATO$hv#pZx<}xPRonu-^YGS5AH;GXFVw*F0dP(%V{G_{oE*bpNjy*s zr__}W!ro!t4l%pLp6KQW^18>w#kWS|U7Cvbp2r$NO~Mr^?KOM7x+981fsod5;6Nq` z)a6caKbx6hV`MA^IS)m|k0Cs_Y}XcO#7bzNGQt~*!9o?T^3`kYZHn4C)jnsOSnFi296M4;CKPUt_-Df z=g!RzorMQVQWE?Zuec-Q_xz;j{g)%~a>xu1&(7xCgzIija)` z$^L=7BYL62pudqu2*J--sRr-mPK9s;>cAS|>dziI$FR2e?t7RCz>O2PrXT7e``?W;v`2t$F;Nbk2`h54lLLQ`*j!dqo-A_=xc6h4o-ye@1 zcVg}o>eGH`dG|k_EP45|8VN;EuKZ=RhIR%MWv5uz^32i?bApdr^3SfA~%tFghQiw>>?|U-IvaN>WSHE4(}{4U3!g_ zqnxzw8z(YFy}Z1jXPwK~H^?>Qq_;zFz-QlpFNoAgZ{{`$?=YJ_dSIJ(mw3nJy@d$t zMC6vC#t&Bs#d*0~yM7H+TPzeMua~~hvUERjV%RX=_`$j%`U z!c8_`uV1)uOY{AOTnp-72M#4=G))TUtMC*&1t!M8QnxS$rx7hP0an;UivoO87@k^7F@E>3oS zK9{r`RnkA0cCH!pT#(I=jFem+ z`v9x`z$lp333FXSmeXM1c!|I8@2R%b5Q)w|Yin;C8wXn+YmP!eze>4>K9Ik%7%5p2 z9M82HY>SQ$un$DuW)w8n9NmX(^0!`Rnu_<4A4pT|{rvldy}!0}ncMUm%_%-;WZhdHA~o)&5B3h&y*mi;C#H zIFKEN*d)Rp1zB^|HDCBbJ+eNpsi}#`I<(e!d}px7BDq`pbPf+HRd{8~&Eo1(uKSMSlaY zdgMm@GYp2|Z95}tno&97c!4H(45mM$qhe5mw+W|LA2I58C;y>j&%JqMu)Ql-&n0z3 zCOLNNzzeBs!#a{|&v#cY7-bFQ??DqjhJyXgAd3-Lbi?Bhwn^;D^f11~H$%c*w?)M)VuuWA zf_~3FAyPS&%&`Wxfj<-x2-=xg_xZCsQZ9RP;Fyh_;xw(amE5;>C z4 zU*pf;Ed%FFv&|p$DtKcY$EMKaAj7H}p@IB&tE!|Y7^FJ7x|9_aK@b)f6T51EbViMs4TvOe1$gH~oXZ`njxRb6ti6!{p z2JT2GMNya$t3LPlCL*6IQsz^kG5=-|SKuf3Eodc4g|$mnQ3k^P=-zm*9(?8t0Z?o@ zpV85gWxNP0`m~j4_BFy*|IMeYVtKe{q`Z+d!?_OD`qZq<5-cC^M68MiVhPb$Y@`2L z%K1~$q9D#a>(L1Rg^FL^yxzfgwgf(s=bOngZLJSfZ{O8YgAi6HAWq1NIP85((d5P(61MM(>gwdjqDFo|1v&itHn6Exll&dn2m%`( zaXSx6*=ism3Tj1s-MDdsm_kq7C$&+C-9j9lY4iLETX+feG(AgAy>(H13Wdv$dO}qA z7df*kzsDW?Lbu|MTh?5E^-6*zg?!J^`cLwbO+6X~X_0YIbao@?NatYYYXSMtcN|bj z?wBTzi;8*yM>M#Y9yPBpeq?!H`Vwc932TZk0kVPd05Ko;-K>IwV~_vP!CbM~!{P)I zxsEy(%?|jlMf%AfUa}wL`rj^O@81h~rjGjTpsI!jU8#Q(yfJcKk@qhB`7L~)JluTk zP{a7jjH=fTzp&9c17qO&hK?kQmWM~N#EqiiCev;Wf`Qkj z#+55eP^K2BKwSC-&>R@Nb;o0A>28=YaVU;%L#RCVaw)kwYI%ea#P+k>KhWb%5}g>H z7^to;Tdk%_tsP9@3@j`vQW(HB=iUrz(n5eD85{rRA6mJmFV$#BD;CwgX4KmLUMuOk zD2%f0S!g=R6%3ssj7$WW9`5D2*sF5m0ez}|$Vg_k)CZ;XJIQybYtbo&$ye}?l6e9 zYWncujsagorM_D4{XT>F>`SZ$XG|A9y&UIQ3CeU;PkP#}qWgN?dXw>#TtJdy^u=WV zu8f~$=d%179DEuaTF##t3dr1lhi~BQPWp$_wTOUa)^UPf8i$YIfUI#Mr?224feH2Ff0$Hpi*PI` zDAXbL^#(OFf^%`s;RH8Ia{TPsx3si`v_@^SKUYNMc$Ejt^q*o)_2$j_@SxqN{?fVPX2I2mdrd+cYikGLLms0mrgpC~mR2bZ z>ZJT3O{7`?1swm@jKwhvy~ckkWSX|p$F^qzID_K1`3=$w9B@bi}CV-JpBb!LzA zd%kem$w`Fref!-VULu(XmtxDc`L=n#u@T1Z;#M4m^J==MKmjq!iX?+ynm{xz=@LDD zoa^-~(!fA12Rpl{)OYR|@%d0FH)xxvtGD1vtvaa^T#bH?_#+M7aY6(pL^!=T!>`W3 z{lHsTKco_O$?e@pW@$tCt=+p*%ygSiF`VL+LPjn;VBKuqA>CmH`!w8@W=0O$k2Upc zXsD|L8l#p6vcfP%=Y=FK)r?JK0}Mv93u1k}I8r+_%znPR0lYtaO-)R!uC4icd41or zWX$F1>KY2QJR%79>S@ZU9^519Ukv1=<8=`q{k$f;s;#Cs6H}aDs&RHKRd!!cmi62l zoT2@MzBfKA%L!Wef?O9TiZ#W}=wny9{+MK4KBK$i>hSr+76+%-=T<68M=Tf&0^L(s zDzUPh>tU=LY0(b1X5eV9moL}lU(d=q;_%LYcjv3x=4Q3}Y#S7m^;H((hL#s-Sy*mA z$wp1_KJlbc@}BFYFms19ymo5aIWsdXtCHg47Xwz;u5Dlw@e+=v{7x;8+W1Iu{1_ax z$wP(?<~0)^JAS;5jP`uU6Sx||ySxq2*N}WkAp0eV0)XLzD+PtipW%=Xq<|~XPUPz= zw;@Dn@c+3D0|(YGA6($0>TYDv*!U$jNdd-j#<53EXikvN1; z5X@6$wlnG{AsADy{_9ufncSz&QNQ#Nm&d;%6|Ue(j&f_+oL7+a#&78(Nr`XG2z zQu1-Y7Y;tYhy>z*A|tw{-P+%~`X0gemoAp5aOXAz2vd5wdze9gnh4<8-{ zaMq`@#TLY9(P@R2R*b3iIY)P7{PNx7 z`H)8EK=4y|e-u`63|i3#|8(MGb~{7lo}DE~FRdSPTD~LFDCc?g6GoYwb)yEuAJTU* z?Jh@!8}FwxhqF*2u_mez&S3at5(lWM*=zgb97ER-MLL;IGw)GaHu_Rsj8VwPx90Q0)~Ap0|GvGcjSb2b*ki_>$2EKafCoW0p|vXQ%D z?%kWi#AeU~y}_#Z86#>uMTiKeRfC~O$WPs)QE)YnYJLx^F2o?&&?K<|krJNNXDYOr zD2Fq;TEMLr*=xuTRWs4kzNU81gcV^FH#4fRqH8CY7Z%okQP$Qb^OB}`S!L1t*n(*E)G}6uk~uVoa(bEV#_iK-y#ZEU%tst zi4^+@-9TjGZ~&qx2p9fekpgMxON`%yPRYZmM38+*aEZ z>Ql8WL^uFLtU@#1<0>Pjg#HmXN)cnXF9W@L%Rjc1l=Cu9EWdgof9Q2bHXlRI=;gZ< z$6L1+^HHJE?+91FN|+5wJvpSfHy0rY=xv$Oku?a_WPLP%%|{hsm^%PGSKU~|b+Kt% zZ56CO=)&?2D}!*94|1ooG%=kO6wDW_TDi%rRNvZ4s0KP1c7Zl9U;-19IbxASK*Clznfe6kC^6~) zXi@9?ZvK$lLAa{te-rEgi}F)$SJHDlz~WUwsJUf?IfC!Q-DZP7Z7_ zU$cI_y-gB);smAR>Z>aizeHY`QhItUJs&Wl+Xy83u+Vo0pC1#(`vJ&c(588unv#N@ z-VyPzsco3Bi^4<(y%B~+5D8NqIPsuI5POs_ zG>!3Z1jQ(h!#>Oa6co5bL>4e9sz9BPZIq8zH8&>*iM3XimdF}$l|c0z@-46EIK=ye z;@#1aTK>(OH;_dmTn^5P0N3$n{J%smio%ng0_iss4Plg4V=3g};Q*9_zu%C)cDz zJtczH%@fxMc%VUN3MN|U`#WkRR7>*^Z~}u}2OAr-s71)tLimr!sZ$%c(=hpgp;~Wm zyym&Vp`i;N+n7i)M-Y>EH!SQ6K-F7supHi^nn?gu4Zs|M>9)3`LqiG)sd*OF79I#pR#wr@EF=IfBR0ughS zNWc)grH9|9^oaj@msZfdoj8E42p#{tRp@(LAd}GVs$d+ZqB4f$6|hgP;FD`;M4bE1`kEot5@GDkZSuG z*Wv-H#O=kJKWy*l_^rbGGV*F1nYg!Mi33qy znS0AtAXAAr%Ez`ec$|ZBnWK6-x!N(COIjx&+6boeIz=8K$1}&dGF<=DfQ@uL6|C>FQ0^)j` z^aFs7!2b@jbnb~f4NjYTKz>i+neQ)kiU;@>sr(y&{=&j=^JUDQX~ejN5sv&MI(leo z>ZD~2(Po(~JT1|?*ElcXf?#0|QD4Rg*lXwQ$;*OkPATP)VbAsd&rJh091tPQ1Q{dIKr#t)4;M(I!Fxc#r2#WO|b{9#=B(4lEa z!AT^vF?j`^mo(xMR;6W9s~9uY$IWW2f zwue;;pNY@sZteD4?z@V(5)hb@Sz%)cZ21qsmhPOh3XU2ui9IOfV+g#!UVJd*=uKq2 zpzr*N6R~<5!pxzKz~p#dWeS=S%T>DG4FY& z7Lyl`)FOKN2T@TqMLM_o0|nQ97JbC}J6PR1x#R4xB*+fHkp;{vn&|dYiZ`J01%%Jv zaM;88b6Xn?=8W)fWW4~602d$MC7ceNNh2VKU|WqT-+QxD>!!zhdB1zG-KwvS!m6ZM z^!ukQZWi+RUULIo99})^K9(KKKw0+fQxiQ{xBt;B`;f0`jI^eiK&78-Ud z@uv^#CQ{Sa78AE9PZ2Ze8I+W#a46YH&K>JsU(RYdS-h4nmwTJ!T)X_{ z6%P-)#j!rd$dErI73zN7!CL9*&z>zz_r1k(7D{O2!`ujI=)M88) zHp)m|i~SyX=D@T|OPAnEw|A=lVD$q$tx;*{_Lm|39l0r3rw3VQt`s!bSsTU9^6chi2f#At_#iV8~~ zv?R>AYfOW$hU0le6lIJg%<6iAg$Bhnl$CdhEk&TXy?Zxyyu6-XS-E4fl7uUn)A=nd zXfTqCynP$+B&-Y(@jxSwjjP^Ef}dZr*o4QkcToDBr4TBV_RpVZLk`kpM+|dMdK_~I z9OKs7PU-Q0DcB&_&gLZ?eu`IZk8BzNqZmJEFcPSuOY{6CxH#z3azt7h{C8v1OwZ6n zJ`oBKQbN)1-%cP`UMIN`9)9Bc%*@?~bYk(R;6aD#+EC-!bPZ{S2v}1Xv{1`qDuHS2 zyjjFFy;V8+$H6_T%N@mY*C>5F6t=&>0%oF&)Ti5|bUEFr7v;lq#PUz)FZkz^YlVi= z5ndxh=HMoR3c6)AXh`;M8`+ttg>{nl;nPtWc}{*;y9>nE8K#79-o7Q_=)lAS1&Uz=fh()pj3l8K0ax(JYjn;N z?GYkwxd(0tR?P~57x`*q9LNq~Xy0CcNtcqt`Dod@I8n8il%BXQ4xW8n59H`G6;z?PmqHa#tk`3UWu{NmyVjs|H| zQ9bqR34A<-!a~NQnX)0ICGr~jLu(y5MRzIEc(OGKy?owBa0PDOx|OMd_`xgF-!hMQ z^#ww*!>T#g$5kUPXk5jH!}R1DKjCsXe^g zXvo7t6J%_@&D4FOyu$j7Ybj6Bh4<&uQr#Z5Uus(x-y~`Jc?VNN`;5>N_X2(aM?+vy znHn#6{ST2zxMB?{kDyk9#4)TGIC8^ue~T}qt_*8fM<;4toFj@piR@3~@aq6Z`hxO> zD~tSBrpn$18FbUPWd%7T8kWCmA!QFl<^i0nFc6%Ynwsf*y$m+goBQH;g%vO~27jr? z#g)hAJ2BxSHDE&k_UCy1{BcCzpytKJWR5&zL^%8iIqYHbGJJG5B90Cx2M0IKXE%$S zjyzT>cBHeWtdpOm9IUO`+G08ID+vZPq z;HSsy7)oOe-?b-qDRw&W4ytf6KFzSRv->_VCAn#BR{O?z328+Wll}|LxcMN7M8TE^zD$4>uu9 zNN^0Z2V$SY95fBQ>Jg5iT=eJQ1afJ)^t^zHyTXTgf~lJ9@^zKt9KT%(8(J2}(z>6- z`cF%GywT~7jm)Xqb;^J%!_{1@aCdR>knl`EfM5j6@Rtrf_Q|q@o8SV_A$$ zxw#MXf^iZPB9~-jWMCVfm$EHfsnOI0eLw_PpwEKNYw_otj|lt7y#OR3fj(w%lnSq( z+|^W-A>T)JzjeevK#*@vtxf)Fci8rylRb{DNxR7JnaZ~?Gy6`AA3Vhu+3KfbY^r^S zqFt~4?!uuHw^9t50iY1W^4P4@ai6>$SUW&)&|7uVZo8fm8Xlg~W=HxIoQr=Ro+cWr zEQ-5%dd8;JlV9I+?O}WG>AC*NJiVZ#Q%6O`!c=2oKIM5=sgGE%tm(d_+!OxQ8MK#c~MRl>eZ9vFE0Ai)rjD2l7rgQ0%x)lKS_+^l|Au94o3 zcXvs8PVcqs);G;AtE{Ztz9{<1D9}-GfBbk{s9d^CcCO6Uv+-}yDdmh})$6LEj2xLU zDegD`$p0I@DiJ+to3^DALH$ZicUH=9<#$vkgZs0*pld?hTb5dz$^DIN73U>Ce=Q7Q zOnjbs_IJ5byw>sdi%pzCoqTJo9oybEms2{etoEgI@Uu#%DDjA5?II;_2kk=_ z60&f0rW@~V$3{jnO~{FKBwqZLBs9fpQP!;7ofXWrWHs!T%F6o^KUbsqWX{>5$ZFI! z&-R*!sJ+N(N-Y%?eczMmw=~*2K0R$Kp71yjcQ{eGT`^<((<@XV?TrOUgkhoig zclNY-;(JU-r%Z0Kc-f#ua#k zj}L?f+9>(@&FpadTd9I=gF&i`S~EhT*FXl zKQlAx;w#`uV0n@93j`5i{tg}~Xv1)31dB#RU!N|zSyMyfB+^^JzE3CSYIdo7r22IQ ze?iYr`V^%QfwVO-@jmHPZ+CZhOAGnH1{!Xp9w( zX1>n_{Jo&4XuqVpjjC!B;s>1jE1LB|=h|sufcOm`pFbG1sJQ`l<%nEn!@R(a666gS zz$0IM8uxsW7Uea35Q9uha&k14w;^AMmWIa4a<>9j;LY7#TEKD^cYnGTmPANLd+ot|ceCoN@nidJ@h`t(S`Ev1H$i|TAmPLN_c-sq5PF@2 zg!PS$p}|2=3jr!XtB{*K2=)xvqFfFC7vD`wDifyQC;(DlFz?>I8;~J{9xnYA@S}c- z4+jQDm`M!~0JM6zy#Q@MD}%E?o|u7hPrm5p`hA$sgI&f^Li9h-`iA@e#p$d4kP>Pt z=sUE3-@XY{Ar~(qz4sluy5?s4jtpH~Hq;Ca`!M^1^ZC=HMd-(a?Jg@zq=ok#0Hf1-tK?_T4vL8_5t8NLA|G@uE4JVjNTm?QxTO=a;!xPE z+L(h{g&{9?DS*c8fgo$81F&$UBV?#IH8l+n+qemwWrybj+9&Lx!#@cm5iKogp9LO9 zP_bOGwW5AVB*#Wy1pZ)x01pp(v0aiB^5Ym{lw&G@u`M3PaOyZLaLinTye*oRyIQm7 z;BpJzg6#AfE(nsOJpoPO#tn^7VHk!N2ieQ|@fmY-RyMW(+*z5eb$tR=ZFpv83#eK2 z32>M9?cOCH`j>xp zl|2rm?x{1y?ha><5P$5kKQ-9FUnT!}V&B93#+vYOpEo~TQ&Lk`59G(k#Z>`KOF-?E zgj~Hx47)H^F^5GkR5(pDT?rsS1AN)Zu!n-8uJjaUq)4{qpu13uU95TR@}OoI2WpdAbrCng?PmY_O?LkHa`MwO5iObkNHiC{}QK|1V>lNH2Chl8ABKwDjJzOnq?R87k5G2CZ0qJuUs9-6*7GJH6FeIQDDZL68eqK1aFF2@hP9kkBl zSorxx|8r53)@c;4NZ!Evw zE63ltl*HV$R2un3tm4xUbxKxIHgPMgKU$ZjAv13$00*1XvOB=Hido<$%T|S!<=qVAzaP3n*4#X(p;)44L#@A|Qx=2v)cbXfZ#GvTc8q$yx0lt3 zh{z(<6y*#Gbh5z4C#>yFi5KKYwGy?(MMe8wxhbR!s$LEL)9-k0;lgxK;{Lecav%`I`8j`Z zCGRI`H4frgwdtQ3898L{miA+KIFQcT`I+h=QBj7OnLE@z0OXa1{Xq0^z4@0#q4Jqd z&1b5!Y|hie-s-B+=4 z#&o^ZJcXuOD>EooB%YQ?NvZM^*bw64Oe@it{Dk#AKuu#C1bVu}kEtm>*EK4|f@}9( z=5WYi=>B5?bKgUrTRK!Yk`MCdujHy*7JUIN?Cr$$>qIo&2r-jL@cOfmDSJVo>ZN(& z{s8nB6E3q$ON@`k;5vKQo|cFiZ-$6IoMmxa*t({TVnVyxvtLbW0+K|8^W*RQ?BHxV z5?*Ny<$5-K)}zRtK)(qV%+ArU+qVsL=5Q2#4-5)w0DUCrLND?eDq`BLUD#I%N6X@tQ4CZnK;z%L|!cO3OA2?yWb2E#F7m zLHbT%F)S-D?@X{U;VrVS-Vc@VrWaXcDso>nzXv=(=@%&}Y0vAc`A}jlen%|;)#%N3 zj7?IO8J`jIo{=_{VE)RLMob5oKzGL z*`B_qOOIL}jbF<>Mts!qB-Llx>|~t`J2`?{o|fWcwhMZ~(VQ^Q#iMz6m(e2}-zZ^2 zz)i4IrFHv{A=qSH-G2_j)@WY!zP#N4F$F@9UIc7y8s(eMt=HQYftUb;Vfqug)8pf$ z6coKcK2YR)-?@0<0#tk2OGN;QFxlJ0DBwpmv|0ZeS4NyUPVX^g*uPQ>KLsjsa&=YJ zjSFlbo@RHA;L0LG5zsaoZv*=YNm)yfLi;+3*frMYs( z@15vY<;pzO;|Hx6w<#-FJH$qO2)p=s*hNJptdT-> ztg8NpQ-?O`cg_5&4-50ws1m^k!2ZZN4>$vrGSA7Arqd0Ep|bzBa#>ADnhc*eq%S+! zerv+0dko75&jLg+c<$D#-6ayRBGA&1=^wY@C&1cpjw#US>mk7?BZICpud zRaense9jR2+~mXrM<5PYgQE!C#+MmO%Ki+88)=t#xDD`&rsDs$xcG-7a&77nCcNA) z?*7ZFhN$9z&c(@jJ(CV~NnM_QFb90!^;(*zma|hreJcBDw*5sr3BNX3UG^vqsp8)t zD8k=me-Zt~d + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\Files; + +use OC\Files\FileInfo; +use OC\Files\Storage\Local; +use Sabre\HTTP\RequestInterface; +use Test\TestCase; +use OC\Files\View; +use OCP\Files\Storage; +use Sabre\DAV\Exception; +use OC\Files\Filesystem; +use OCP\Files\StorageNotAvailableException; + +/** + * Class BundlingPlugin + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Files + */ +class BundlingPluginTest extends TestCase { + + /** + * @var string + */ + private $user; + + /** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */ + private $view; + + /** @var \OC\Files\FileInfo | \PHPUnit_Framework_MockObject_MockObject */ + private $info; + + /** + * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject + */ + private $server; + + /** + * @var FilesPlugin + */ + private $plugin; + + /** + * @var \Sabre\HTTP\RequestInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $request; + /** + * @var \Sabre\HTTP\ResponseInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $response; + + /** + * @var MultipartContentsParser | \PHPUnit_Framework_MockObject_MockObject + */ + private $contentHandler; + + const BOUNDRARY = 'test_boundrary'; + + public function setUp() { + parent::setUp(); +// $this->server = new \Sabre\DAV\Server(); + + $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + ->setConstructorArgs(array()) + ->setMethods(array('emit')) + ->getMock(); + + $this->server->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + // setup + $storage = $this->getMockBuilder(Local::class) + ->setMethods(["fopen","moveFromStorage","file_exists"]) + ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) + ->getMock(); + $storage->method('fopen') + ->will($this->returnCallback( + function ($path,$mode) { + $bodyStream = fopen('php://temp', 'r+'); + return $bodyStream; + } + )); + $storage->method('moveFromStorage') + ->will($this->returnValue(true)); + $storage->method('file_exists') + ->will($this->returnValue(true)); + + \OC_Hook::clear(); + + $this->user = $this->getUniqueID('user_'); + $userManager = \OC::$server->getUserManager(); + $userManager->createUser($this->user, 'pass'); + + $this->loginAsUser($this->user); + + Filesystem::mount($storage, [], $this->user . '/'); + + $this->view = $this->getMockBuilder(View::class) + ->setMethods(['resolvePath', 'touch', 'file_exists', 'getFileInfo']) + ->setConstructorArgs([]) + ->getMock(); + + $this->view->method('touch') + ->will($this->returnValue(true)); + + $this->view + ->method('resolvePath') + ->will($this->returnCallback( + function ($path) use ($storage) { + return [$storage, $path]; + } + )); + + $this->view + ->method('getFileInfo') + ->will($this->returnCallback( + function ($path) { + $props = array(); + $props['checksum'] = null; + $props['etag'] = $path; + $props['fileid'] = $path; + $info = new FileInfo($path, null, null, $props, null); + return $info; + } + )); + + $this->info = $this->createMock('OC\Files\FileInfo', [], [], '', false); + + $this->request = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->response = new \Sabre\HTTP\Response(); + + $this->plugin = new BundlingPlugin( + $this->view + ); + + $this->plugin->initialize($this->server); + } + + /*TESTS*/ + + /** + * This test checks that if url endpoint is wrong, plugin with return exception + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage URL endpoint has to be instance of \OCA\DAV\Files\FilesHome + */ + public function testHandleBundleNotHomeCollection() { + + $this->request + ->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('notFilesHome.xml')); + + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + + $this->server->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('notFilesHome.xml') + ->will($this->returnValue($node)); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate NULL request header + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Content-Type header is needed + */ + public function testHandleBundleNoHeader() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue(null)); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate empty request header + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Content-Type header must not be empty + */ + public function testHandleBundleEmptyHeader() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("")); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate content-type header without boundrary specification request header + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Improper Content-type format. Boundary may be missing + */ + public function testHandleBundleNoBoundraryHeader() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("multipart/related")); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate content-type header with wrong boundrary specification request header + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Boundary is not set + */ + public function testHandleBundleWrongBoundraryHeader() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("multipart/related;thisIsNotBoundrary")); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate content-type header with wrong boundrary specification request header + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Content-Type must be multipart/related + */ + public function testHandleBundleWrongContentTypeHeader() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("multipart/mixed; boundary=".self::BOUNDRARY)); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Simulate content-type header with alternative correct boundrary specification request header + * + * Request with user out of quota + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage beforeWriteBundle preconditions failed + */ + public function testHandleAlternativeBoundraryPlusBundleOutOfQuota() { + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("multipart/related; boundary=\"".self::BOUNDRARY."\"")); + + $this->server + ->expects($this->once()) + ->method('emit') + ->will($this->returnValue(false)); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Request without request body + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Unable to get request content + */ + public function testHandleBundleWithNullBody() { + $this->setupServerTillHeader(); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Test empty request body. This will pass getPartHeader, but exception will be raised after we ready headers + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing + */ + public function testHandleBundleWithEmptyBody() { + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody(""); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Test wrong request body + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Expected boundary delimiter in content part - this is not a multipart request + */ + public function testHandleBundleWithWrongBody() { + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody("WrongBody"); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Test wrong request body, with metadata header containing no charset + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing + */ + public function testHandleMetadataNoCharsetType(){ + $bodyContent = 'I am wrong metadata not in utf-8'; + $headers['content-length'] = strlen($bodyContent); + $headers['content-type'] = 'text/xml'; + + //this part will have some arbitrary, correct headers + $bodyFull = "--".self::BOUNDRARY + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\n\r\n" + ."$bodyContent\r\n--".self::BOUNDRARY."--"; + + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Test wrong request body, with metadata header containing wrong content-type + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Content-Type must be text/xml + */ + public function testHandleMetadataWrongContentType(){ + $bodyContent = 'I am wrong metadata content type'; + $headers['content-type'] = 'text/plain; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = "--".self::BOUNDRARY + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\n\r\n" + ."$bodyContent\r\n--".self::BOUNDRARY."--"; + + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Test wrong request body, with metadata header containing wrong content-type + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Bundle metadata header does not contain Content-Length. Unable to parse whole bundle request + */ + public function testHandleMetadataNoContentLength(){ + $bodyContent = 'I am wrong metadata content type'; + //$headers['content-length'] = strlen($bodyContent); + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = "--".self::BOUNDRARY + ."\r\nContent-Type: ".$headers['content-type'] + //."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent\r\n--".self::BOUNDRARY."--"; + + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Try to parse body which is not xml + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request + */ + public function testHandleWrongMetadataNoXML(){ + $bodyContent = "I am not xml"; + + $this->setupServerTillMetadata($bodyContent); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Try to parse body which has xml d:multipart element which + * has not been declared + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Bundle metadata does not contain d:multipart children elements + */ + public function testHandleWrongMetadataWrongXMLdElement(){ + $bodyContent = ""; + + $this->setupServerTillMetadata($bodyContent); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * This test checks that exception is raised for + * parsed XML which contains empty(without d:part elements) d:multipart section in metadata XML + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Bundle metadata does not contain d:multipart/d:part/d:prop children elements + */ + public function testHandleEmptyMultipartMetadataSection(){ + $bodyContent = ""; + + $this->setupServerTillMetadata($bodyContent); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Metadata contains part properties not containing obligatory field will raise exception + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage Undefined index: oc-id + */ + public function testHandleWrongMetadataNoPartID(){ + $bodyContent = " + + + + + + "; + + $this->setupServerTillMetadata($bodyContent); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * In the request, insert two files with the same Content-ID + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage One or more files have the same Content-ID 1. Unable to parse whole bundle request + */ + public function testHandleWrongMetadataMultipleIDs(){ + $bodyContent = " + + + + /test/zombie1.jpg\n + 1476393386\n + 1\n + 6\n + + + + + /test/zombie2.jpg\n + 1476393386\n + 1\n + 6\n + + + "; + + $this->setupServerTillMetadata($bodyContent); + + $this->plugin->handleBundle($this->request, $this->response); + } + + /** + * Specify metadata part without corresponding binary content + * + */ + public function testHandleWithoutBinaryContent(){ + $bodyContent = " + + + + /test/zombie1.jpg\n + 1476393386\n + 1\n + 6\n + + + "; + + $this->setupServerTillMetadata($bodyContent); + $this->plugin->handleBundle($this->request, $this->response); + $return = $this->response->getBody(); + $this->assertTrue(false != $return); + $xml = simplexml_load_string($return); + $this->assertTrue(false != $xml); + $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + + $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); + + $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertTrue(false != $fileMetadataObjectXML); + $this->assertEquals(1, count($fileMetadataObjectXML)); + $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); + + $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); + $this->assertTrue(false != $fileMetadataObjectXML); + $this->assertEquals(1, count($fileMetadataObjectXML)); + $this->assertEquals("File parsing error", (string) $fileMetadataObjectXML[0]); + } + + /** + * This test will simulate success and failure in putFile class. + * + */ + public function testHandlePutFile(){ + $this->setupServerTillData(); + + $this->view + ->method('file_exists') + ->will($this->onConsecutiveCalls(true, false, $this->throwException(new StorageNotAvailableException()))); + + $this->plugin->handleBundle($this->request, $this->response); + + $return = $this->response->getBody(); + $this->assertTrue(false != $return); + $xml = simplexml_load_string($return); + $this->assertTrue(false != $xml); + $xml->registerXPathNamespace('d','urn:DAV'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + + $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); + + $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertTrue(false != $fileMetadataObjectXML); + $this->assertEquals(3, count($fileMetadataObjectXML)); + $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); + $this->assertEquals("HTTP/1.1 200 OK", (string) $fileMetadataObjectXML[1]); + $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[2]); + + $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); + $this->assertTrue(false != $fileMetadataObjectXML); + $this->assertEquals(2, count($fileMetadataObjectXML)); + $this->assertEquals("Bundling not supported for already existing files", (string) $fileMetadataObjectXML[0]); + $this->assertEquals("StorageNotAvailableException raised", (string) $fileMetadataObjectXML[1]); + } + + /*UTILITIES*/ + + private function setupServerTillData(){ + $bodyContent = " + + + + /test/zombie1.jpg\n + 1476393386\n + 0\n + 7\n + + + + + /test/zombie2.jpg\n + 1476393386\n + 1\n + 7\n + + + + + zombie3.jpg\n + 1476393232\n + 2\n + 7\n + + + "; + + $headers['content-length'] = strlen($bodyContent); + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = "--".self::BOUNDRARY + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent" + ."\r\n--".self::BOUNDRARY + ."\r\nContent-ID: 0" + ."\r\n\r\n" + ."zombie1" + ."\r\n--".self::BOUNDRARY + ."\r\nContent-ID: 1" + ."\r\n\r\n" + ."zombie2" + ."\r\n--".self::BOUNDRARY + ."\r\nContent-ID: 2" + ."\r\n\r\n" + ."zombie3" + ."\r\n--".self::BOUNDRARY."--"; + + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody($bodyFull); + } + + private function setupServerTillMetadata($bodyContent){ + $headers['content-length'] = strlen($bodyContent); + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = "--".self::BOUNDRARY + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent\r\n--".self::BOUNDRARY."--"; + + $this->setupServerTillHeader(); + + $this->fillMultipartContentsParserStreamWithBody($bodyFull); + } + + private function setupServerTillHeader(){ + $this->setupServerTillFilesHome(); + + $this->request + ->expects($this->atLeastOnce()) + ->method('getHeader') + ->with('Content-Type') + ->will($this->returnValue("multipart/related; boundary=".self::BOUNDRARY)); + + $this->server + ->expects($this->once()) + ->method('emit') + ->will($this->returnValue(true)); + } + + private function setupServerTillFilesHome(){ + $this->request + ->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('files/admin')); + + $node = $this->getMockBuilder('\OCA\DAV\Files\FilesHome') + ->disableOriginalConstructor() + ->getMock(); + + $this->server->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('files/admin') + ->will($this->returnValue($node)); + } + + private function fillMultipartContentsParserStreamWithBody($bodyString){ + $bodyStream = fopen('php://temp', 'r+'); + fwrite($bodyStream, $bodyString); + rewind($bodyStream); + + $this->request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + } + + public function tearDown() { + $userManager = \OC::$server->getUserManager(); + $userManager->get($this->user)->delete(); + unset($_SERVER['HTTP_OC_CHUNKED']); + + parent::tearDown(); + } +} diff --git a/apps/dav/tests/unit/Files/BundledFileTest.php b/apps/dav/tests/unit/Files/BundledFileTest.php new file mode 100644 index 00000000000..e1a65459b60 --- /dev/null +++ b/apps/dav/tests/unit/Files/BundledFileTest.php @@ -0,0 +1,220 @@ + + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\Files; + +use OCP\Lock\ILockingProvider; + +/** + * Class File + * + * @group DB + * + * @package OCA\DAV\Tests\unit\Connector\Sabre + */ +class BundledFileTest extends \Test\TestCase { + + /** + * @var string + */ + private $user; + + /* BASICS */ + + public function setUp() { + parent::setUp(); + + \OC_Hook::clear(); + + $this->user = $this->getUniqueID('user_'); + $userManager = \OC::$server->getUserManager(); + $userManager->createUser($this->user, 'pass'); + + $this->loginAsUser($this->user); + } + + /* TESTS */ + + /** + * Test basic successful bundled file PutFile + */ + public function testPutFile() { + $bodyContent = 'blabla'; + $headers['oc-total-length'] = 6; + $headers['oc-path'] = '/foo.txt'; + $headers['oc-mtime'] = '1473336321'; + $headers['response'] = null; + + //this part will have some arbitrary, correct headers + $bodyFull = "$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + $this->doPutFIle($headers, $multipartContentsParser); + } + + /** + * Test basic successful bundled file PutFile + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage File requires oc-total-length header to be read + */ + public function testPutFileNoLength() { + $bodyContent = 'blabla'; + $headers['oc-path'] = '/foo.txt'; + $headers['oc-mtime'] = '1473336321'; + $headers['response'] = null; + + //this part will have some arbitrary, correct headers + $bodyFull = "$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + $this->doPutFIle($headers, $multipartContentsParser); + } + + /** + * Test putting a single file + * + * @expectedException \Sabre\DAV\Exception\Forbidden + * @expectedExceptionMessage PUT method not supported for bundling + */ + public function testThrowIfPut() { + $fileContents = $this->getStream('test data'); + $this->doPut('/foo.txt', $fileContents); + } + + /* UTILITIES */ + + private function getMockStorage() { + $storage = $this->getMockBuilder('\OCP\Files\Storage') + ->getMock(); + $storage->expects($this->any()) + ->method('getId') + ->will($this->returnValue('home::someuser')); + return $storage; + } + + public function tearDown() { + $userManager = \OC::$server->getUserManager(); + $userManager->get($this->user)->delete(); + unset($_SERVER['HTTP_OC_CHUNKED']); + + parent::tearDown(); + } + + /** + * @param string $string + */ + private function getStream($string) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $string); + fseek($stream, 0); + return $stream; + } + + /** + * Do basic put for single bundled file + */ + private function doPutFIle($fileMetadata, $contentHandler, $view = null, $viewRoot = null) { + $path = $fileMetadata['oc-path']; + + if(is_null($view)){ + $view = \OC\Files\Filesystem::getView(); + } + if (!is_null($viewRoot)) { + $view = new \OC\Files\View($viewRoot); + } else { + $viewRoot = '/' . $this->user . '/files'; + } + + $info = new \OC\Files\FileInfo( + $viewRoot . '/' . ltrim($path, '/'), + $this->getMockStorage(), + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new BundledFile($view, $info, $contentHandler); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $result = $file->putFile($fileMetadata); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + return $result; + } + + private function fillMultipartContentsParserStreamWithBody($bodyString){ + $bodyStream = fopen('php://temp', 'r+'); + fwrite($bodyStream, $bodyString); + rewind($bodyStream); + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + return $mcp; + } + + /** + * Simulate putting a file to the given path. + * + * @param string $path path to put the file into + * @param string $viewRoot root to use for the view + * + * @return null|string of the PUT operaiton which is usually the etag + */ + private function doPut($path, $fileContents, $viewRoot = null) { + $view = \OC\Files\Filesystem::getView(); + if (!is_null($viewRoot)) { + $view = new \OC\Files\View($viewRoot); + } else { + $viewRoot = '/' . $this->user . '/files'; + } + + $info = new \OC\Files\FileInfo( + $viewRoot . '/' . ltrim($path, '/'), + $this->getMockStorage(), + null, + ['permissions' => \OCP\Constants::PERMISSION_ALL], + null + ); + + $file = new BundledFile($view, $info, null); + + // beforeMethod locks + $view->lockFile($path, ILockingProvider::LOCK_SHARED); + + $result = $file->put($fileContents); + + // afterMethod unlocks + $view->unlockFile($path, ILockingProvider::LOCK_SHARED); + + return $result; + } +} diff --git a/apps/dav/tests/unit/Files/MultipartContentsParserTest.php b/apps/dav/tests/unit/Files/MultipartContentsParserTest.php new file mode 100644 index 00000000000..6d23fe296d2 --- /dev/null +++ b/apps/dav/tests/unit/Files/MultipartContentsParserTest.php @@ -0,0 +1,416 @@ + + * @author Louis Chemineau + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @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\Tests\unit\DAV; + +use Test\TestCase; + +class MultipartContentsParserTest extends TestCase { + private $boundrary; + + protected function setUp() { + parent::setUp(); + + $this->boundrary = 'boundary'; + + } + + /*TESTS*/ + + /** + * Test basic gets() functionality, that if passed string instead of resource, it should fail + * + * @expectedException \Sabre\DAV\Exception\BadRequest + * @expectedExceptionMessage Unable to get request content + */ + public function testGetsThrowWrongContents() { + //TODO + $bodyStream = "I am not a stream, but pretend to be"; + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + + $mcp->gets(); + } + + /** + * Test function readHeaders(), so if passed empty string, it will return null + * + */ + public function testReadHeadersThrowEmptyHeader() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + $mcp->readHeaders(''); + $this->assertEquals(null, $mcp->readHeaders('')); + } + + /** + * streamRead function with incorrect parameter + * + * @expectedException \Sabre\DAV\Exception\BadRequest + * @expectedExceptionMessage Method streamRead cannot read contents with negative length + */ + public function testStreamReadToStringThrowNegativeLength() { + $bodyContent = 'blabla'; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); + //give negative length + $multipartContentsParser->streamReadToString(-1); + } + + /** + * streamRead function with incorrect parameter + * + * @expectedException \Sabre\DAV\Exception\BadRequest + * @expectedExceptionMessage Method streamRead cannot read contents with negative length + */ + public function testStreamReadToStreamThrowNegativeLength() { + $target = fopen('php://temp', 'r+'); + $bodyContent = 'blabla'; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); + //give negative length + $multipartContentsParser->streamReadToStream($target,-1); + } + + public function testStreamReadToString() { + $length = 0; + list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); + $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); + + $length = 1000; + list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); + $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); + + $length = 8192; + list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); + $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); + + $length = 20000; + list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); + $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); + } + + public function testStreamReadToStream() { + $length = 0; + $this->streamReadToStreamBuilder($length); + + $length = 1000; + $this->streamReadToStreamBuilder($length); + + $length = 8192; + $this->streamReadToStreamBuilder($length); + + $length = 20000; + $this->streamReadToStreamBuilder($length); + } + + private function streamReadToStreamBuilder($length) { + $target = fopen('php://temp', 'r+'); + list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); + $this->assertEquals(true, $multipartContentsParser->streamReadToStream($target,$length)); + rewind($target); + $this->assertEquals($bodyString, stream_get_contents($target)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage An error appears while reading and parsing header of content part using fgets + */ + public function testGetPartThrowFailfgets() { + $bodyStream = fopen('php://temp', 'r+'); + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = $this->getMockBuilder('OCA\DAV\Files\MultipartContentsParser') + ->setConstructorArgs(array($request)) + ->setMethods(array('gets')) + ->getMock(); + + $mcp->expects($this->any()) + ->method('gets') + ->will($this->onConsecutiveCalls("--boundary\r\n", "Content-ID: 0\r\n", false)); + + $mcp->getPartHeaders($this->boundrary); + } + + /** + * If one one the content parts does not contain boundrary, means that received wrong request + * + * @expectedException \Exception + * @expectedExceptionMessage Expected boundary delimiter in content part + */ + public function testGetPartThrowNoBoundraryFound() { + // Calling multipletimes getPart on parts without contents should return null,null and signal immedietaly that endDelimiter was reached + $bodyFull = "--boundary_wrong\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + $multipartContentsParser->getPartHeaders($this->boundrary); + } + + /** + * Reading from request which method getBody returns false + * + * @expectedException \Sabre\DAV\Exception\BadRequest + * @expectedExceptionMessage Unable to get request content + */ + public function testStreamReadThrowWrongBody() { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn(false); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + $mcp->getPartHeaders($this->boundrary); + } + + /** + * Reading from request which method getBody returns false + * + */ + public function testMultipartContentSeekToContentLength() { + $bodyStream = fopen('php://temp', 'r+'); + $bodyString = ''; + $length = 1000; + for ($x = 0; $x < $length; $x++) { + $bodyString .= 'k'; + } + fwrite($bodyStream, $bodyString); + rewind($bodyStream); + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + $this->assertEquals(true,$mcp->multipartContentSeekToContentLength($length)); + } + + /** + * Test cases with wrong or incomplete boundraries + * + */ + public function testGetPartHeadersWrongBoundaryCases() { + // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached + $bodyFull = "--boundary\r\n--boundary_wrong\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + + // Test empty content + $bodyFull = "--boundary\r\n"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + + // Test empty content + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody(''); + $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + + // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached + // endDelimiter should be signaled after first getPart since it will read --boundrary till it finds contents. + $bodyFull = "--boundary\r\n--boundary\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + } + + /** + * Test will check if we can correctly parse headers and content using streamReadToString + * + */ + public function testReadHeaderBodyCorrect() { + //multipart part will have some content bodyContent and some headers + $bodyContent = 'blabla'; + $headers['content-length'] = '6'; + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = '--boundary' + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + //parse it + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $bodyParsed = $multipartContentsParser->streamReadToString(6); + + //check if end delimiter is not reached, since we just read 6 bytes, and stopped at \r\n + $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); + + //check that we parsed correct headers + $this->assertEquals($bodyContent, $bodyParsed); + $this->assertEquals($headers, $headersParsed); + + //parse further to check if there is new part. There is no, so headers are null and delimiter reached + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $this->assertEquals(null,$headersParsed); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + } + + /** + * Test will check parsing incorrect headers and content using streamReadToString + * + */ + public function testReadHeaderBodyIncorrect() { + + //multipart part will have some content bodyContent and some headers + $bodyContent = 'blabla'; + $headers['content-length'] = '6'; + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will one correct and one incorrect header + $bodyFull = '--boundary' + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length" + ."\r\n\r\n" + ."$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + //parse it and expect null, since contains incorrect headers + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $this->assertEquals(null, $headersParsed); + $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); + + //parse further to check if next call with not read headers again + //this should return null again and get to end of delimiter + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $this->assertEquals(null,$headersParsed); + $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); + } + + /** + * Test will check reading error in StreamReadToString + * + * @expectedException \Sabre\DAV\Exception\BadRequest + * @expectedExceptionMessage Method streamRead read 20 expeceted 60 + */ + public function testReadBodyIncorrect() { + //multipart part will have some content bodyContent and content-length header will specify to big value + //this + $bodyContent = 'blabla'; + $headers['content-length'] = '60'; + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = '--boundary' + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + //parse headers + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $this->assertEquals($headers, $headersParsed); + + $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); + $multipartContentsParser->streamReadToString($headersParsed['content-length']); + } + + /** + * Test will check reading error in StreamReadToString return false + * + */ + public function testReadBodyStreamIncorrect() { + //multipart part will have some content bodyContent and content-length header will specify to big value + //this + $bodyContent = 'blabla'; + $headers['content-length'] = '60'; + $headers['content-type'] = 'text/xml; charset=utf-8'; + + //this part will have some arbitrary, correct headers + $bodyFull = '--boundary' + ."\r\nContent-Type: ".$headers['content-type'] + ."\r\nContent-length: ".$headers['content-length'] + ."\r\n\r\n" + ."$bodyContent\r\n--boundary--"; + $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); + + //parse headers + $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); + $this->assertEquals($headers, $headersParsed); + + $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); + $target = fopen('php://temp', 'r+'); + $bodyParsed = $multipartContentsParser->streamReadToStream($target, $headersParsed['content-length']); + $this->assertEquals(false, $bodyParsed); + } + + /*UTILITIES*/ + + private function fillMultipartContentsParserStreamWithChars($length){ + $bodyStream = fopen('php://temp', 'r+'); + $bodyString = ''; + for ($x = 0; $x < $length; $x++) { + $bodyString .= 'k'; + } + fwrite($bodyStream, $bodyString); + rewind($bodyStream); + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + return array($mcp, $bodyString); + } + + private function fillMultipartContentsParserStreamWithBody($bodyString){ + $bodyStream = fopen('php://temp', 'r+'); + fwrite($bodyStream, $bodyString); + rewind($bodyStream); + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); + return $mcp; + } +} From dd938dadefcbfa09fece30efcdaf09538f01d9e3 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Mon, 20 Sep 2021 18:29:24 +0200 Subject: [PATCH 2/3] Add benchmark tests Signed-off-by: Louis Chemineau --- apps/dav/tests/temporary/benchmark.sh | 50 +++++++ apps/dav/tests/temporary/bundle_upload.sh | 70 +++++++++ apps/dav/tests/temporary/bundling_profile.sh | 149 ------------------- apps/dav/tests/temporary/bundling_tests.sh | 70 --------- apps/dav/tests/temporary/put_test.sh | 12 -- apps/dav/tests/temporary/screenshot.png | Bin 183411 -> 0 bytes apps/dav/tests/temporary/single_upload.sh | 54 +++++++ 7 files changed, 174 insertions(+), 231 deletions(-) create mode 100755 apps/dav/tests/temporary/benchmark.sh create mode 100755 apps/dav/tests/temporary/bundle_upload.sh delete mode 100755 apps/dav/tests/temporary/bundling_profile.sh delete mode 100755 apps/dav/tests/temporary/bundling_tests.sh delete mode 100755 apps/dav/tests/temporary/put_test.sh delete mode 100644 apps/dav/tests/temporary/screenshot.png create mode 100755 apps/dav/tests/temporary/single_upload.sh diff --git a/apps/dav/tests/temporary/benchmark.sh b/apps/dav/tests/temporary/benchmark.sh new file mode 100755 index 00000000000..4a2f283e320 --- /dev/null +++ b/apps/dav/tests/temporary/benchmark.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -eu + +export KB=1000 +export MB=$((KB*1000)) + +MAX_UPLOAD_SIZE=$((512*KB)) + +export CONCURRENCY=5 +export BANDWIDTH=$((100*MB/CONCURRENCY)) + +FILE_SIZES=($((1*KB)) $((10*KB)) $((100*KB))) + +echo "Concurrency: $CONCURRENCY" +echo "Bandwidth: $BANDWIDTH" + +md_output="# Bulk upload benchmark\n" +md_output+="\n" +md_output+="- Concurrency: $CONCURRENCY\n" +md_output+="- Bandwidth: ${BANDWIDTH}B\n" +md_output+="\n" +md_output+="| Nb | Size (B) | Bundle (sec) | Single (sec) |\n" +md_output+="|---|---|---|---|\n" + +requests_count='1 2 3 4 5' + +for size in "${FILE_SIZES[@]}" +do + nb=$((MAX_UPLOAD_SIZE/size)) + + echo "- Upload of $nb tiny file of ${size}B" + echo " - Bundled" + start=$(date +%s) + echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./bundle_upload.sh "$nb" "$size" + end=$(date +%s) + bundle_exec_time=$((end-start)) + echo "${bundle_exec_time}s" + + echo " - Single" + start=$(date +%s) + echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./single_upload.sh "$nb" "$size" + end=$(date +%s) + single_exec_time=$((end-start)) + echo "${single_exec_time}s" + + md_output+="| $nb | $size | $bundle_exec_time | $single_exec_time |\n" +done + +echo -en "$md_output" \ No newline at end of file diff --git a/apps/dav/tests/temporary/bundle_upload.sh b/apps/dav/tests/temporary/bundle_upload.sh new file mode 100755 index 00000000000..9d2b9c6f200 --- /dev/null +++ b/apps/dav/tests/temporary/bundle_upload.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -eu + +KB=${KB:-100} +MB=${MB:-$((KB*1000))} + +NB=$1 +SIZE=$2 + +CONCURRENCY=${CONCURRENCY:-1} +BANDWIDTH=${BANDWIDTH:-$((100*MB/CONCURRENCY))} + +USER="admin" +PASS="password" +SERVER="nextcloud.test" +UPLOAD_PATH="/tmp/bundle_upload_request_$(openssl rand --hex 8).txt" +BOUNDARY="boundary_$(openssl rand --hex 8)" +LOCAL_FOLDER="/tmp/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" +REMOTE_FOLDER="/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" + +mkdir --parent "$LOCAL_FOLDER" + +for ((i=1; i<="$NB"; i++)) +do + file_name=$(openssl rand --hex 8) + file_local_path="$LOCAL_FOLDER/$file_name.txt" + file_remote_path="$REMOTE_FOLDER/$file_name.txt" + head -c "$SIZE" /dev/urandom > "$file_local_path" + file_mtime=$(stat -c %Y "$file_local_path") + file_hash=$(md5sum "$file_local_path" | awk '{ print $1 }') + file_size=$(du -sb "$file_local_path" | awk '{ print $1 }') + + { + echo -en "--$BOUNDARY\r\n" + # echo -en "Content-ID: $file_name\r\n" + echo -en "X-File-Path: $file_remote_path\r\n" + echo -en "X-File-Mtime: $file_mtime\r\n" + # echo -en "X-File-Id: $file_id\r\n" + echo -en "X-File-Md5: $file_hash\r\n" + echo -en "Content-Length: $file_size\r\n" + echo -en "\r\n" >> "$UPLOAD_PATH" + + cat "$file_local_path" + echo -en "\r\n" >> "$UPLOAD_PATH" + } >> "$UPLOAD_PATH" +done + +echo -en "--$BOUNDARY--\r\n" >> "$UPLOAD_PATH" + +echo "Creating folder /${BOUNDARY}_${NB}_${SIZE}" +curl \ + -X MKCOL \ + -k \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/$REMOTE_FOLDER" + +echo "Uploading $NB files with total size: $(du -sh "$UPLOAD_PATH" | cut -d ' ' -f1)" +echo "Local file is: $UPLOAD_PATH" +blackfire curl \ + -X POST \ + -k \ + --progress-bar \ + --limit-rate "${BANDWIDTH}k" \ + --cookie "XDEBUG_PROFILE=MROW4A;path=/;" \ + -H "Content-Type: multipart/related; boundary=$BOUNDARY" \ + --data-binary "@$UPLOAD_PATH" \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/bundle" + +rm -rf "${LOCAL_FOLDER:?}" +rm "$UPLOAD_PATH" diff --git a/apps/dav/tests/temporary/bundling_profile.sh b/apps/dav/tests/temporary/bundling_profile.sh deleted file mode 100755 index a86e5ba5be4..00000000000 --- a/apps/dav/tests/temporary/bundling_profile.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/bin/bash - -script_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -user='admin' -pass='admin' -server='localhost/owncloud' -upload="/tmp/upload.txt" - - -testfile2="$script_path/zombie.jpg" -size2=$(du -sb $testfile2 | awk '{ print $1 }') -md52=$(md5sum $testfile2 | awk '{ print $1 }') - -header="\n -\n - \n - \n - /test/zombie1.jpg\n - 1476393386\n - 0\n - $size2\n - \n - \n - \n - \n - /test/zombie2.jpg\n - 1476393386\n - 1\n - $size2\n - \n - \n - \n - \n - /test/zombie3.jpg\n - 1476393386\n - 2\n - $size2\n - \n - \n - \n - \n - /test/zombie4.jpg\n - 1476393386\n - 3\n - $size2\n - \n - \n - \n - \n - /test/zombie5.jpg\n - 1476393386\n - 4\n - $size2\n - \n - \n - \n - \n - /test/zombie6.jpg\n - 1476393386\n - 5\n - $size2\n - \n - \n - \n - \n - /test/zombie7.jpg\n - 1476393386\n - 6\n - $size2\n - \n - \n - \n - \n - /test/zombie8.jpg\n - 1476393386\n - 7\n - $size2\n - \n - \n - \n - \n - /test/zombie9.jpg\n - 1476393386\n - 8\n - $size2\n - \n - \n - \n - \n - /test/zombie10.jpg\n - 1476393386\n - 9\n - $size2\n - \n - \n -" -headersize=$(echo -en $header | wc -c) - -mdupload=$(md5sum $upload | awk '{ print $1 }') -boundrary="boundary_$mdupload" - -#CONTENTS -echo -en "--$boundrary\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $headersize\r\n\r\n" > $upload -echo -en $header >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 0\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 1\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 2\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 3\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 4\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 5\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 6\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 7\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 8\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -echo -en "\r\n--$boundrary\r\nContent-ID: 9\r\n\r\n" >> $upload -cat $testfile2 >> $upload - -#END BOUNDRARY -echo -en "\r\n--$boundrary--\r\n" >> $upload - -#POST -#curl -X DELETE -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" "http://$server/remote.php/webdav/config.cfg" - -blackfire --samples 1 curl -X POST -H "Content-Type: multipart/related; boundary=$boundrary" --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ - --data-binary "@$upload" \ - "http://$user:$pass@$server/remote.php/dav/files/$user" - - - - diff --git a/apps/dav/tests/temporary/bundling_tests.sh b/apps/dav/tests/temporary/bundling_tests.sh deleted file mode 100755 index 3aa1eac3469..00000000000 --- a/apps/dav/tests/temporary/bundling_tests.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash - -set -eu - -scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -user='admin' -pass='password' -server='nextcloud.test' -upload="/tmp/upload.txt" - - -testFile1="$scriptPath/put_test.sh" -size1=$(du -sb "$testFile1" | awk '{ print $1 }') -# md51=$(md5sum "$testFile1" | awk '{ print $1 }') -id1="0" - -testFile2="$scriptPath/screenshot.png" -size2=$(du -sb "$testFile2" | awk '{ print $1 }') -# md52=$(md5sum "$testFile2" | awk '{ print $1 }') -id2="1" - -header="\n -\n - \n - \n - /put_test.sh\n - 1476393777\n - $id1\n - $size1\n - \n - \n - \n - \n - /zombie.jpg\n - 1476393386\n - $id2\n - $size2\n - \n - \n -" -headerSize=$(echo -en "$header" | wc -c) - -mdUpload=$(md5sum $upload | awk '{ print $1 }') -boundary="boundary_$mdUpload" - -#CONTENTS -echo -en "--$boundary\r\nContent-Type: text/xml; charset=utf-8\r\nContent-Length: $headerSize\r\n\r\n" > $upload -echo -en "$header" >> $upload - -cat "$upload" -echo -en "\r\n--$boundary\r\nContent-ID: $id1\r\n\r\n" >> $upload -cat "$testFile1" >> $upload - -echo -en "\r\n--$boundary\r\nContent-ID: $id2\r\n\r\n" >> $upload -cat "$testFile2" >> $upload - -#END boundary -echo -en "\r\n--$boundary--\r\n" >> $upload - -#POST -#curl -X DELETE -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" "http://$server/remote.php/webdav/config.cfg" - -curl -X POST -k -H "Content-Type: multipart/related; boundary=$boundary" --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ - --data-binary "@$upload" \ - "https://$user:$pass@$server/remote.php/dav/files/bundle" - - - - diff --git a/apps/dav/tests/temporary/put_test.sh b/apps/dav/tests/temporary/put_test.sh deleted file mode 100755 index c3b64dee448..00000000000 --- a/apps/dav/tests/temporary/put_test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -script_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -user='admin' -pass='admin' -server='localhost/owncloud' - -testfile2="$script_path/zombie.jpg" - -blackfire --samples 1 curl -X PUT -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" --data-binary @"$testfile2" "http://$server/remote.php/webdav/test/zombie.jpg" -#curl -X PUT -u $user:$pass --cookie "XDEBUG_SESSION=MROW4A;path=/;" --data-binary @"$testfile2" "http://$server/remote.php/webdav/test/zombie.jpg" diff --git a/apps/dav/tests/temporary/screenshot.png b/apps/dav/tests/temporary/screenshot.png deleted file mode 100644 index c4e776531287841da5382a9157d1ac95d0920939..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 183411 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�$$0umxBUhPuG;rYb{a{~+81&J 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 "$file_local_path" + + curl \ + -X PUT \ + -k \ + --limit-rate "${BANDWIDTH}k" \ + --data-binary @"$file_local_path" "https://$USER:$PASS@$SERVER/remote.php/webdav/$file_remote_path" +} +export -f upload_file + +file_list='' +for ((i=1; i<"$NB"; i++)) +do + file_list+="$i " +done +file_list+=$NB + +echo "$file_list" | xargs -d ' ' -P "$((CONCURRENCY/5))" -I{} bash -c "upload_file {}" + +printf "\n" + +rm -rf "${LOCAL_FOLDER:?}"/* \ No newline at end of file From def983dc7ea11b9f8e449d56019f934ce89d9490 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Fri, 15 Oct 2021 11:57:39 +0200 Subject: [PATCH 3/3] Clean BulkUpload plugin Signed-off-by: Louis Chemineau --- .../composer/composer/autoload_classmap.php | 5 +- .../dav/composer/composer/autoload_static.php | 5 +- apps/dav/lib/BulkUpload/BulkUploadPlugin.php | 100 +++ .../lib/BulkUpload/MultipartRequestParser.php | 239 ++++++ apps/dav/lib/BundleUpload/BundledFile.php | 214 ------ apps/dav/lib/BundleUpload/BundlingPlugin.php | 456 ----------- .../BundleUpload/MultipartContentsParser.php | 497 ------------ apps/dav/lib/Capabilities.php | 2 +- apps/dav/lib/Connector/Sabre/File.php | 8 +- apps/dav/lib/Server.php | 8 +- .../{temporary => benchmarks}/benchmark.sh | 10 +- .../bulk_upload.sh} | 22 +- .../single_upload.sh | 13 +- apps/dav/tests/unit/CapabilitiesTest.php | 1 + .../dav/tests/unit/Files/BundlePluginTest.php | 711 ------------------ apps/dav/tests/unit/Files/BundledFileTest.php | 220 ------ .../Files/MultipartContentsParserTest.php | 416 ---------- .../unit/Files/MultipartRequestParserTest.php | 281 +++++++ .../integration/features/bootstrap/WebDav.php | 51 ++ .../features/webdav-related.feature | 11 + 20 files changed, 727 insertions(+), 2543 deletions(-) create mode 100644 apps/dav/lib/BulkUpload/BulkUploadPlugin.php create mode 100644 apps/dav/lib/BulkUpload/MultipartRequestParser.php delete mode 100644 apps/dav/lib/BundleUpload/BundledFile.php delete mode 100644 apps/dav/lib/BundleUpload/BundlingPlugin.php delete mode 100644 apps/dav/lib/BundleUpload/MultipartContentsParser.php rename apps/dav/tests/{temporary => benchmarks}/benchmark.sh (83%) rename apps/dav/tests/{temporary/bundle_upload.sh => benchmarks/bulk_upload.sh} (75%) rename apps/dav/tests/{temporary => benchmarks}/single_upload.sh (74%) delete mode 100644 apps/dav/tests/unit/Files/BundlePluginTest.php delete mode 100644 apps/dav/tests/unit/Files/BundledFileTest.php delete mode 100644 apps/dav/tests/unit/Files/MultipartContentsParserTest.php create mode 100644 apps/dav/tests/unit/Files/MultipartRequestParserTest.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index e7e29d85d89..1a536c98272 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -22,9 +22,8 @@ return array( 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => $baseDir . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\UploadCleanup' => $baseDir . '/../lib/BackgroundJob/UploadCleanup.php', - 'OCA\\DAV\\BundleUpload\\BundledFile' => $baseDir . '/../lib/BundleUpload/BundledFile.php', - 'OCA\\DAV\\BundleUpload\\BundlingPlugin' => $baseDir . '/../lib/BundleUpload/BundlingPlugin.php', - 'OCA\\DAV\\BundleUpload\\MultipartContentsParser' => $baseDir . '/../lib/BundleUpload/MultipartContentsParser.php', + 'OCA\\DAV\\BulkUpload\\BulkUploadPlugin' => $baseDir . '/../lib/BulkUpload/BulkUploadPlugin.php', + 'OCA\\DAV\\BulkUpload\\MultipartRequestParser' => $baseDir . '/../lib/BulkUpload/MultipartRequestParser.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => $baseDir . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Filter/Todo.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index ee23085eee5..b65d4477800 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -37,9 +37,8 @@ class ComposerStaticInitDAV 'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php', 'OCA\\DAV\\BackgroundJob\\UpdateCalendarResourcesRoomsBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJob.php', 'OCA\\DAV\\BackgroundJob\\UploadCleanup' => __DIR__ . '/..' . '/../lib/BackgroundJob/UploadCleanup.php', - 'OCA\\DAV\\BundleUpload\\BundledFile' => __DIR__ . '/..' . '/../lib/BundleUpload/BundledFile.php', - 'OCA\\DAV\\BundleUpload\\BundlingPlugin' => __DIR__ . '/..' . '/../lib/BundleUpload/BundlingPlugin.php', - 'OCA\\DAV\\BundleUpload\\MultipartContentsParser' => __DIR__ . '/..' . '/../lib/BundleUpload/MultipartContentsParser.php', + 'OCA\\DAV\\BulkUpload\\BulkUploadPlugin' => __DIR__ . '/..' . '/../lib/BulkUpload/BulkUploadPlugin.php', + 'OCA\\DAV\\BulkUpload\\MultipartRequestParser' => __DIR__ . '/..' . '/../lib/BulkUpload/MultipartRequestParser.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Todo.php', diff --git a/apps/dav/lib/BulkUpload/BulkUploadPlugin.php b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php new file mode 100644 index 00000000000..0766ae37a17 --- /dev/null +++ b/apps/dav/lib/BulkUpload/BulkUploadPlugin.php @@ -0,0 +1,100 @@ + + * + * @author Louis Chemineau + * + * @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\BulkUpload; + +use Psr\Log\LoggerInterface; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use OCP\Files\Folder; +use OCP\AppFramework\Http; + +class BulkUploadPlugin extends ServerPlugin { + + /** @var Folder */ + private $userFolder; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(Folder $userFolder, LoggerInterface $logger) { + $this->userFolder = $userFolder; + $this->logger = $logger; + } + + /** + * Register listener on POST requests with the httpPost method. + */ + public function initialize(Server $server): void { + $server->on('method:POST', [$this, 'httpPost'], 10); + } + + /** + * Handle POST requests on /dav/bulk + * - parsing is done with a MultipartContentsParser object + * - writing is done with the userFolder service + * + * Will respond with an object containing an ETag for every written files. + */ + public function httpPost(RequestInterface $request, ResponseInterface $response): bool { + // Limit bulk upload to the /dav/bulk endpoint + if ($request->getPath() !== "bulk") { + return true; + } + + $multiPartParser = new MultipartRequestParser($request); + $writtenFiles = []; + + while (!$multiPartParser->isAtLastBoundary()) { + try { + [$headers, $content] = $multiPartParser->parseNextPart(); + } catch (\Exception $e) { + // Return early if an error occurs during parsing. + $this->logger->error($e->getMessage()); + $response->setStatus(Http::STATUS_BAD_REQUEST); + $response->setBody(json_encode($writtenFiles)); + return false; + } + + try { + $node = $this->userFolder->newFile($headers['x-file-path'], $content); + $writtenFiles[$headers['x-file-path']] = [ + "error" => false, + "etag" => $node->getETag(), + ]; + } catch (\Exception $e) { + $this->logger->error($e->getMessage(), ['path' => $headers['x-file-path']]); + $writtenFiles[$headers['x-file-path']] = [ + "error" => true, + "message" => $e->getMessage(), + ]; + } + } + + $response->setStatus(Http::STATUS_OK); + $response->setBody(json_encode($writtenFiles)); + + return false; + } +} diff --git a/apps/dav/lib/BulkUpload/MultipartRequestParser.php b/apps/dav/lib/BulkUpload/MultipartRequestParser.php new file mode 100644 index 00000000000..7554447fc93 --- /dev/null +++ b/apps/dav/lib/BulkUpload/MultipartRequestParser.php @@ -0,0 +1,239 @@ + + * + * @author Louis Chemineau + * + * @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\BulkUpload; + +use Sabre\HTTP\RequestInterface; +use Sabre\DAV\Exception; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\LengthRequired; +use OCP\AppFramework\Http; + +class MultipartRequestParser { + + /** @var resource */ + private $stream; + + /** @var string */ + private $boundary = ""; + + /** @var string */ + private $lastBoundary = ""; + + /** + * @throws BadRequest + */ + public function __construct(RequestInterface $request) { + $stream = $request->getBody(); + $contentType = $request->getHeader('Content-Type'); + + if (!is_resource($stream)) { + throw new BadRequest('Body should be of type resource'); + } + + if ($contentType === null) { + throw new BadRequest("Content-Type can not be null"); + } + + $this->stream = $stream; + + $boundary = $this->parseBoundaryFromHeaders($contentType); + $this->boundary = '--'.$boundary."\r\n"; + $this->lastBoundary = '--'.$boundary."--\r\n"; + } + + /** + * Parse the boundary from the Content-Type header. + * Example: Content-Type: "multipart/related; boundary=boundary_bf38b9b4b10a303a28ed075624db3978" + * + * @throws BadRequest + */ + private function parseBoundaryFromHeaders(string $contentType): string { + try { + [$mimeType, $boundary] = explode(';', $contentType); + [$boundaryKey, $boundaryValue] = explode('=', $boundary); + } catch (\Exception $e) { + throw new BadRequest("Error while parsing boundary in Content-Type header.", Http::STATUS_BAD_REQUEST, $e); + } + + $boundaryValue = trim($boundaryValue); + + // Remove potential quotes around boundary value. + if (substr($boundaryValue, 0, 1) == '"' && substr($boundaryValue, -1) == '"') { + $boundaryValue = substr($boundaryValue, 1, -1); + } + + if (trim($mimeType) !== 'multipart/related') { + throw new BadRequest('Content-Type must be multipart/related'); + } + + if (trim($boundaryKey) !== 'boundary') { + throw new BadRequest('Boundary is invalid'); + } + + return $boundaryValue; + } + + /** + * Check whether the stream's cursor is sitting right before the provided string. + * + * @throws Exception + */ + private function isAt(string $expectedContent): bool { + $expectedContentLength = strlen($expectedContent); + + $content = fread($this->stream, $expectedContentLength); + if ($content === false) { + throw new Exception('An error occurred while checking content'); + } + + $seekBackResult = fseek($this->stream, -$expectedContentLength, SEEK_CUR); + if ($seekBackResult === -1) { + throw new Exception("Unknown error while seeking content", Http::STATUS_INTERNAL_SERVER_ERROR); + } + + return $expectedContent === $content; + } + + + /** + * Check whether the stream's cursor is sitting right before the boundary. + */ + private function isAtBoundary(): bool { + return $this->isAt($this->boundary); + } + + /** + * Check whether the stream's cursor is sitting right before the last boundary. + */ + public function isAtLastBoundary(): bool { + return $this->isAt($this->lastBoundary); + } + + /** + * Parse and return the next part of the multipart headers. + * + * Example: + * --boundary_azertyuiop + * Header1: value + * Header2: value + * + * Content of + * the part + * + */ + public function parseNextPart(): array { + $this->readBoundary(); + + $headers = $this->readPartHeaders(); + + $content = $this->readPartContent($headers["content-length"], $headers["x-file-md5"]); + + return [$headers, $content]; + } + + /** + * Read the boundary and check its content. + * + * @throws BadRequest + */ + private function readBoundary(): string { + if (!$this->isAtBoundary()) { + throw new BadRequest("Boundary not found where it should be."); + } + + return fread($this->stream, strlen($this->boundary)); + } + + /** + * Return the headers of a part of the multipart body. + * + * @throws Exception + * @throws BadRequest + * @throws LengthRequired + */ + private function readPartHeaders(): array { + $headers = []; + + while (($line = fgets($this->stream)) !== "\r\n") { + if ($line === false) { + throw new Exception('An error occurred while reading headers of a part'); + } + + try { + [$key, $value] = explode(':', $line, 2); + $headers[strtolower(trim($key))] = trim($value); + } catch (\Exception $e) { + throw new BadRequest('An error occurred while parsing headers of a part', Http::STATUS_BAD_REQUEST, $e); + } + } + + if (!isset($headers["content-length"])) { + throw new LengthRequired("The Content-Length header must not be null."); + } + + if (!isset($headers["x-file-md5"])) { + throw new BadRequest("The X-File-MD5 header must not be null."); + } + + return $headers; + } + + /** + * Return the content of a part of the multipart body. + * + * @throws Exception + * @throws BadRequest + */ + private function readPartContent(int $length, string $md5): string { + $computedMd5 = $this->computeMd5Hash($length); + + if ($md5 !== $computedMd5) { + throw new BadRequest("Computed md5 hash is incorrect."); + } + + $content = stream_get_line($this->stream, $length); + + if ($content === false) { + throw new Exception("Fail to read part's content."); + } + + if (feof($this->stream)) { + throw new Exception("Unexpected EOF while reading stream."); + } + + // Read '\r\n'. + stream_get_contents($this->stream, 2); + + return $content; + } + + /** + * Compute the MD5 hash of the next x bytes. + */ + private function computeMd5Hash(int $length): string { + $context = hash_init('md5'); + hash_update_stream($context, $this->stream, $length); + fseek($this->stream, -$length, SEEK_CUR); + return hash_final($context); + } +} diff --git a/apps/dav/lib/BundleUpload/BundledFile.php b/apps/dav/lib/BundleUpload/BundledFile.php deleted file mode 100644 index db9b5bbd3fe..00000000000 --- a/apps/dav/lib/BundleUpload/BundledFile.php +++ /dev/null @@ -1,214 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\BundleUpload; - -use OCA\DAV\Connector\Sabre\Exception\FileLocked; -use OCP\Files\StorageNotAvailableException; -use OCP\Lock\ILockingProvider; -use OCP\Lock\LockedException; -use Sabre\DAV\Exception; -use Sabre\DAV\Exception\Forbidden; -use Sabre\DAV\Exception\ServiceUnavailable; -use OCA\DAV\Connector\Sabre\File; -use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge; -use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException; -use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; -use OCP\Files\ForbiddenException; -use Sabre\DAV\Exception\BadRequest; - -class BundledFile extends File { - - - /** - * This class is a wrapper around the bundled request body and provides access to its contents - * - * @var \OCA\DAV\BundleUpload\MultipartContentsParser - * - */ - private $contentHandler; - - public function __construct($view, $info, $contentHandler){ - $this->contentHandler = $contentHandler; - parent::__construct($view, $info); - } - /** - * Updates the data - * - * The $data['data] argument is a readable stream resource. - * The other $data key-values should be header fields in form of string - * - * After a successful put operation, you may choose to return an ETag. The - * ETag must always be surrounded by double-quotes. These quotes must - * appear in the actual string you're returning. - * - * Clients may use the ETag from a PUT request to later on make sure that - * when they update the file, the contents haven't changed in the mean - * time. - * - * If you don't plan to store the file byte-by-byte, and you return a - * different object on a subsequent GET you are strongly recommended to not - * return an ETag, and just return null. - * - * @param array $data - * - * @throws Forbidden - * @throws UnsupportedMediaType - * @throws BadRequest - * @throws Exception - * @throws EntityTooLarge - * @throws ServiceUnavailable - * @throws FileLocked - * @return array $properties - */ - public function putFile($data) { - $properties = array(); - - if (!isset($data['oc-total-length'])) { - //this should not happen, since upper layer takes care of that - //Thus, return Forbidden as sign of code inconsistency - throw new Forbidden('File requires oc-total-length header to be read'); - } - - try { - $exists = $this->fileView->file_exists($this->path); - if ($this->info && $exists) { - $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); - throw new Forbidden('Bundling not supported for already existing files'); - } - } catch (StorageNotAvailableException $e) { - $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); - throw new ServiceUnavailable("StorageNotAvailableException raised"); - } - - // verify path of the target - $this->verifyPath(); - - $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand(); - - // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) - /** @var \OC\Files\Storage\Storage $partStorage */ - list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); - /** @var \OC\Files\Storage\Storage $storage */ - list($storage, $internalPath) = $this->fileView->resolvePath($this->path); - try { - $target = $partStorage->fopen($internalPartPath, 'wb'); - if ($target === false || $target === null) { - \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR); - // because we have no clue about the cause we can only throw back a 500/Internal Server Error - $this->contentHandler->multipartContentSeekToContentLength($data['oc-total-length']); - throw new Exception('Could not write file contents'); - } - - $result = $this->contentHandler->streamReadToStream($target, $data['oc-total-length']); - - if ($result === false) { - throw new Exception('Error while copying file to target location (expected filesize: ' . $data['oc-total-length'] . ' )'); - } - - } catch (\Exception $e) { - $partStorage->unlink($internalPartPath); - $this->convertToSabreException($e); - } - - try { - $view = \OC\Files\Filesystem::getView(); - if ($view) { - $run = $this->emitPreHooks($exists); - } else { - $run = true; - } - - try { - $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); - } catch (LockedException $e) { - $partStorage->unlink($internalPartPath); - throw new FileLocked($e->getMessage(), $e->getCode(), $e); - } - - try { - if ($run) { - $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); - $fileExists = $storage->file_exists($internalPath); - } - if (!$run || $renameOkay === false || $fileExists === false) { - \OCP\Util::writeLog('webdav', 'renaming part file to final file failed', \OCP\Util::ERROR); - throw new Exception('Could not rename part file to final file'); - } - } catch (ForbiddenException $ex) { - throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); - } catch (\Exception $e) { - $partStorage->unlink($internalPartPath); - $this->convertToSabreException($e); - } - - // since we skipped the view we need to scan and emit the hooks ourselves - $storage->getUpdater()->update($internalPath); - - try { - $this->changeLock(ILockingProvider::LOCK_SHARED); - } catch (LockedException $e) { - throw new FileLocked($e->getMessage(), $e->getCode(), $e); - } - - if ($view) { - $this->emitPostHooks($exists); - } - - // allow sync clients to send the mtime along in a header - if (isset($data['oc-mtime'])) { - if ($this->fileView->touch($this->path, $data['oc-mtime'])) { - $properties['{DAV:}oc-mtime'] = 'accepted'; - } - } - - $this->refreshInfo(); - - if (isset($data['oc-checksum'])) { - $checksum = trim($data['oc-checksum']); - $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]); - $this->refreshInfo(); - } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') { - $this->fileView->putFileInfo($this->path, ['checksum' => '']); - $this->refreshInfo(); - } - - } catch (StorageNotAvailableException $e) { - throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); - } - - $etag = $this->getEtag(); - $properties['{DAV:}etag'] = $etag; - $properties['{DAV:}oc-etag'] = $etag; - $properties['{DAV:}oc-fileid'] = $this->getFileId(); - return $properties; - } - - /* - * @param resource $data - * - * @throws Forbidden - */ - public function put($data) { - throw new Forbidden('PUT method not supported for bundling'); - } -} \ No newline at end of file diff --git a/apps/dav/lib/BundleUpload/BundlingPlugin.php b/apps/dav/lib/BundleUpload/BundlingPlugin.php deleted file mode 100644 index b3c7a007ac2..00000000000 --- a/apps/dav/lib/BundleUpload/BundlingPlugin.php +++ /dev/null @@ -1,456 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\BundleUpload; - -use Sabre\DAV\ServerPlugin; -use Sabre\HTTP\RequestInterface; -use Sabre\HTTP\ResponseInterface; -use OC\Files\View; -use Sabre\HTTP\URLUtil; -use OCP\Lock\ILockingProvider; -use OC\Files\FileInfo; -use Sabre\DAV\Exception\BadRequest; -use OCA\DAV\Connector\Sabre\Exception\Forbidden; -use OCP\Files\Folder; -use OCP\AppFramework\Http\JSONResponse; -use Psr\Log\LoggerInterface; - -/** - * This plugin is responsible for interconnecting three components of the OC server: - * - RequestInterface object handler for request incoming from the client - * - MultipartContentsParser responsible for reading the contents of the request body - * - BundledFile responsible for storage of the file associated with request in the OC server - * - * Bundling plugin is responsible for receiving, validation and processing of the multipart/related request containing files. - * - */ -class BundlingPlugin extends ServerPlugin { - /** - * Reference to main server object - * - * @var \Sabre\DAV\Server - */ - private $server; - - /** - * @var \Sabre\HTTP\RequestInterface - */ - private $request; - - /** - * @var \Sabre\HTTP\ResponseInterface - */ - private $response; - - /** - * @var \OCA\DAV\FilesBundle - */ - private $contentHandler = null; - - /** - * @var String - */ - private $userFilesHome = null; - - /** - * @var View - */ - private $fileView; - - /** - * @var Array - */ - // private $cacheValidParents = null; - - /** @var IFolder */ - private $userFolder; - - /** @var LoggerInterface */ - private $logger; - - /** - * Plugin constructor - */ - public function __construct(View $view, Folder $userFolder) { - $this->fileView = $view; - $this->userFolder = $userFolder; - } - - /** - * This initializes the plugin. - * - * This function is called by \Sabre\DAV\Server, after - * addPlugin is called. - * - * This method should set up the requires event subscriptions. - * - * @param \Sabre\DAV\Server $server - * @return void - */ - public function initialize(\Sabre\DAV\Server $server) { - $this->server = $server; - $this->logger = $this->server->getLogger(); - - $server->on('method:POST', array($this, 'handleBundle')); - } - - /** - * We intercept this to handle method:POST on a dav resource and process the bundled files multipart HTTP request. - * - * @throws /Sabre\DAV\Exception\BadRequest - * @throws /Sabre\DAV\Exception\Forbidden - */ - public function handleBundle(RequestInterface $request, ResponseInterface $response) { - // Limit bundle upload to the /bundle endpoint - if ($request->getPath() !== "files/bundle") { - return true; - } - - $multiPartParser = new MultipartContentsParser($request); - $writtenFiles = []; - - // $multiPartParser->eof() - while (!$multiPartParser->lastBoundary()) { - try { - [$headers, $content] = $multiPartParser->readNextPart(); - - if ((int)$headers['content-length'] !== strlen($content)) { - throw new BadRequest("Content read with different size than declared. Got " . $headers['content-length'] . ", expected" . strlen($content)); - } - - $node = $this->userFolder->newFile($headers['x-file-path'], $content); - $writtenFiles[$headers['x-file-path']] = $node->getSize(); - - if ((int)$headers['content-length'] !== $node->getSize()) { - throw new BadRequest("Written file length is different than declared length. Got " . $headers['content-length'] . ", expected" . $node->getSize()); - } - - // TODO - check md5 hash - // $context = hash_init('md5'); - // hash_update_stream($context, $stream); - // echo hash_final($context); - // if ($header['x-file-md5'] !== hash_final($context)) { - // } - } catch (\Exception $e) { - throw $e; - $this->logger->error($e->getMessage(), ['path' => $header['x-file-path']]); - } - } - - $response->setStatus(200); - $response->setBody(new JSONResponse([ - $writtenFiles - ])); - - return false; - - // $this->contentHandler = $this->getContentHandler($this->request); - - // $multipleRequestsData = $this->parseBundleMetadata(); - - //Process bundle and send a multi-status response - // $result = $this->processBundle($multipleRequestsData); - - // return $result; - } - - public function handleBundleWithMetadata(RequestInterface $request, ResponseInterface $response) { - // Limit bundle upload to the /bundle endpoint - if ($request->getPath() !== "files/bundle") { - return true; - } - - $multiPartParser = new MultipartContentsParser($request); - - [$metadataHeaders, $rawMetadata] = $multiPartParser->getMetadata(); - - if ($metadataHeaders['content-type'] !== "text/xml; charset=utf-8") { - throw new BadRequest("Incorrect Content-Type for metadata."); - } - - if ((int)$metadataHeaders['content-length'] !== strlen($rawMetadata)) { - throw new BadRequest("Content read with different size than declared."); - } - - $metadata = $this->parseMetadata($rawMetadata); - - $writtenFiles = []; - - foreach ($metadata as $fileMetadata) { - try { - [$headers, $content] = $multiPartParser->readNextPart((int)$fileMetadata['oc-total-length']); - - if ($fileMetadata['oc-id'] !== $headers['content-id']) { - throw new BadRequest("Content-ID do not match oc-id. Check the order of your metadata."); - } - - if (isset($file[$fileMetadata['oc-id']])) { - throw new BadRequest("Content-ID appear twice. Check the order of your metadata."); - } - - if ((int)$fileMetadata['oc-total-length'] !== strlen($content)) { - throw new BadRequest("Content read with different size than declared."); - } - - $node = $this->userFolder->newFile($fileMetadata['oc-path'], $content); - $writtenFiles[$fileMetadata['oc-id']] = $node->getSize(); - - // TODO - check md5 hash - // $context = hash_init('md5'); - // hash_update_stream($context, $stream); - // echo hash_final($context); - if ($fileMetadata['oc-md5'] !== hash_final($context)) { - - } - } catch (\Exception $e) { - throw $e; - $this->logger->error($e->getMessage(), ['path' => $fileMetadata['oc-path']]); - } - } - - $response->setStatus(200); - $response->setBody(new JSONResponse([ - $writtenFiles - ])); - - return false; - - // $this->contentHandler = $this->getContentHandler($this->request); - - // $multipleRequestsData = $this->parseBundleMetadata(); - - //Process bundle and send a multi-status response - // $result = $this->processBundle($multipleRequestsData); - - // return $result; - } - - private function parseMetadata(string $rawMetadata) { - $xml = simplexml_load_string($rawMetadata); - if ($xml === false) { - $error = libxml_get_errors(); - throw new \Exception('Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request', $error); - } - - libxml_clear_errors(); - - $xml->registerXPathNamespace('d','urn:DAV'); - - $metadataXml = $xml->xpath('/d:multipart/d:part/d:prop'); - - if($metadataXml === false){ - throw new \Exception('Fail to access d:multipart/d:part/d:prop elements'); - } - - return array_map(function($xmlObject) { return get_object_vars($xmlObject->children('d', TRUE));}, $metadataXml); - } - - /** - * Parses multipart contents and send appropriate response - * - * @throws \Sabre\DAV\Exception\Forbidden - * - * @return array $multipleRequestsData - */ - private function parseBundleMetadata() { - $multipleRequestsData = array(); - try { - // Verify metadata part headers - $bundleMetadata = null; - try{ - $bundleMetadata = $this->contentHandler->getPartHeaders($this->boundary); - } - catch (\Exception $e) { - throw new \Exception($e->getMessage()); - } - $contentParts = explode(';', $bundleMetadata['content-type']); - if (count($contentParts) != 2) { - throw new \Exception('Incorrect Content-type format. Charset might be missing'); - } - $contentType = trim($contentParts[0]); - $expectedContentType = 'text/xml'; - if ($contentType != $expectedContentType) { - throw new BadRequest(sprintf( - 'Content-Type must be %s', - $expectedContentType - )); - } - if (!isset($bundleMetadata['content-length'])) { - throw new \Exception('Bundle metadata header does not contain Content-Length. Unable to parse whole bundle request'); - } - - // Read metadata part headers - $bundleMetadataBody = $this->contentHandler->streamReadToString($bundleMetadata['content-length']); - - $bundleMetadataBody = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$bundleMetadataBody); - - //Try to load xml - $xml = simplexml_load_string($bundleMetadataBody); - if (false === $xml) { - $mlerror = libxml_get_errors(); - throw new \Exception('Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request'); - } - $xml->registerXPathNamespace('d','urn:DAV'); - unset($bundleMetadataBody); - - if(1 != count($xml->xpath('/d:multipart'))){ - throw new \Exception('Bundle metadata does not contain d:multipart children elements'); - } - - $fileMetadataObjectXML = $xml->xpath('/d:multipart/d:part/d:prop'); - - if(0 == count($fileMetadataObjectXML)){ - throw new \Exception('Bundle metadata does not contain d:multipart/d:part/d:prop children elements'); - } - - foreach ($fileMetadataObjectXML as $prop) { - $fileMetadata = get_object_vars($prop->children('d', TRUE)); - - // if any of the field is not contained, - // bthe try-catch clausule will raise Undefined index exception - $contentID = intval($fileMetadata['oc-id']); - if(array_key_exists($contentID, $multipleRequestsData)){ - throw new \Exception('One or more files have the same Content-ID '.$contentID.'. Unable to parse whole bundle request'); - } - $multipleRequestsData[$contentID]['oc-path'] = $fileMetadata['oc-path']; - $multipleRequestsData[$contentID]['oc-mtime'] = $fileMetadata['oc-mtime']; - $multipleRequestsData[$contentID]['oc-total-length'] = intval($fileMetadata['oc-total-length']); - $multipleRequestsData[$contentID]['response'] = null; - } - } catch (\Exception $e) { - libxml_clear_errors(); - throw new Forbidden($e->getMessage()); - } - return $multipleRequestsData; - } - - /** - * Process multipart contents and send appropriate response - * - * @param RequestInterface $request - * - * @return boolean - */ - private function processBundle($multipleRequestsData) { - $bundleResponseProperties = array(); - - while(!$this->contentHandler->getEndDelimiterReached()) { - // Verify metadata part headers - $fileContentHeader = null; - - //If something fails at this point, just continue, $multipleRequestsData[$contentID]['response'] will be null for this content - try{ - $fileContentHeader = $this->contentHandler->getPartHeaders($this->boundary); - if(is_null($fileContentHeader) || !isset($fileContentHeader['content-id']) || !array_key_exists(intval($fileContentHeader['content-id']), $multipleRequestsData)){ - continue; - } - } - catch (\Exception $e) { - continue; - } - - $fileID = intval($fileContentHeader['content-id']); - $fileMetadata = $multipleRequestsData[$fileID]; - - $filePath = $fileMetadata['oc-path']; - - list($folderPath, $fileName) = \OC\URLUtil::splitPath($filePath); - - try { - //get absolute path of the file - $absoluteFilePath = $this->fileView->getAbsolutePath($folderPath) . '/' . $fileName; - $info = new FileInfo($absoluteFilePath, null, null, array(), null); - $node = new BundledFile($this->fileView, $info, $this->contentHandler); - $node->acquireLock(ILockingProvider::LOCK_SHARED); - $properties = $node->putFile($fileMetadata); - $multipleRequestsData[$fileID]['response'] = $this->handleFileMultiStatus($filePath, $properties); - } catch (\Exception $exc) { - //TODO: This should not be BadRequest! This should be any exception - how to do it carefully? - $exc = new BadRequest($exc->getMessage()); - $multipleRequestsData[$fileID]['response'] = $this->handleFileMultiStatusError($filePath, $exc); - continue; - } - - //TODO: do we need to unlock file if putFile failed? In this version we dont (does continue) - //release lock as in dav/lib/Connector/Sabre/LockPlugin.php - $node->releaseLock(ILockingProvider::LOCK_SHARED); - $this->server->tree->markDirty($filePath); - } - - foreach($multipleRequestsData as $requestData) { - $response = $requestData['response']; - if (is_null($response)){ - $exc = new BadRequest('File parsing error'); - $response = $this->handleFileMultiStatusError($requestData['oc-path'], $exc); - } - $bundleResponseProperties[] = $response; - } - - //multi-status response announced - $this->response->setHeader('Content-Type', 'application/xml; charset=utf-8'); - $this->response->setStatus(207); - $body = $this->server->generateMultiStatus($bundleResponseProperties); - $this->response->setBody($body); - - return false; - } - - /** - * Adds to multi-status response exception class string and exception message for specific file - * - * @return array $entry - */ - private function handleFileMultiStatusError($ocPath, $exc){ - $status = $exc->getHTTPCode(); - $entry['href'] = $this->userFilesHome; - $entry[$status]['{DAV:}error']['{http://sabredav.org/ns}exception'] = get_class($exc); - $entry[$status]['{DAV:}error']['{http://sabredav.org/ns}message'] = $exc->getMessage(); - $entry[$status]['{DAV:}oc-path'] = $ocPath; - return $entry; - } - - /** - * Adds to multi-status response properties for specific file - * - * @return array $entry - */ - private function handleFileMultiStatus($ocPath, $properties){ - $entry['href'] = $this->userFilesHome; - $entry[200] = $properties; - $entry[200]['{DAV:}oc-path'] = $ocPath; - return $entry; - } - - /** - * Get content handler - * - * @param RequestInterface $request - * @return \OCA\DAV\BundleUpload\MultipartContentsParser - */ - // private function getContentHandler(RequestInterface $request) { - // if ($this->contentHandler === null) { - // return new MultipartContentsParser($request); - // } - // return $this->contentHandler; - // } -} \ No newline at end of file diff --git a/apps/dav/lib/BundleUpload/MultipartContentsParser.php b/apps/dav/lib/BundleUpload/MultipartContentsParser.php deleted file mode 100644 index 93b24539c49..00000000000 --- a/apps/dav/lib/BundleUpload/MultipartContentsParser.php +++ /dev/null @@ -1,497 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\BundleUpload; - -use Exception; -use Sabre\HTTP\RequestInterface; -use Sabre\DAV\Exception\BadRequest; - -/** - * This class is used to parse multipart/related HTTP message according to RFC http://www.rfc-archive.org/getrfc.php?rfc=2387 - * This class requires a message to contain Content-length parameters, which is used in high performance reading of file contents. - */ - -class MultipartContentsParser { - /** - * @var \Sabre\HTTP\RequestInterface - */ - // private $request; - - /** @var resource */ - private $stream = null; - - /** @var string */ - private $boundary = ""; - private $lastBoundary = ""; - - /** - * @var Bool - */ - // private $endDelimiterReached = false; - - /** - * Constructor. - */ - public function __construct(RequestInterface $request) { - $this->stream = $request->getBody(); - if (gettype($this->stream) !== 'resource') { - throw new BadRequest('Wrong body type'); - } - - $this->boundary = '--'.$this->getBoundary($request->getHeader('Content-Type'))."\r\n"; - $this->lastBoundary = '--'.$this->getBoundary($request->getHeader('Content-Type'))."--\r\n"; - } - - /** - * Parse the boundary from a Content-Type header - * - * @throws \Sabre\DAV\Exception\BadRequest - */ - private function getBoundary(string $contentType) { - // Making sure the end node exists - //TODO: add support for user creation if that is first sync. Currently user has to be created. - // $this->userFilesHome = $this->request->getPath(); - // $userFilesHomeNode = $this->server->tree->getNodeForPath($this->userFilesHome); - // if (!($userFilesHomeNode instanceof FilesHome)){ - // throw new Forbidden('URL endpoint has to be instance of \OCA\DAV\Files\FilesHome'); - // } - - // $headers = array('Content-Type'); - // foreach ($headers as $header) { - // $value = $this->request->getHeader($header); - // if ($value === null) { - // throw new Forbidden(sprintf('%s header is needed', $header)); - // } elseif (!is_int($value) && empty($value)) { - // throw new Forbidden(sprintf('%s header must not be empty', $header)); - // } - // } - - // Validate content-type - // Ex: Content-Type: "multipart/related; boundary=boundary_bf38b9b4b10a303a28ed075624db3978" - [$mimeType, $boundary] = explode(';', $contentType); - - if (trim($mimeType) !== 'multipart/related') { - throw new BadRequest('Content-Type must be multipart/related'); - } - - // Validate boundary - [$key, $value] = explode('=', $boundary); - if (trim($key) !== 'boundary') { - throw new BadRequest('Boundary is invalid'); - } - - $value=trim($value); - - // Remove potential quotes around boundary value - if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') { - $value = substr($value, 1, -1); - } - - return $value; - } - - /** - * Get a line. - * - * If false is return, it's the end of file. - * - * @throws \Sabre\DAV\Exception\BadRequest - */ - // public function gets() { - // $content = $this->getContent(); - // if (!is_resource($content)) { - // throw new BadRequest('Unable to get request content'); - // } - - // return fgets($content); - // } - - /** - */ - // public function getCursor() { - // return ftell($this->getContent()); - // } - - /** - */ - // public function getEndDelimiterReached() { - // return $this->endDelimiterReached; - // } - - /** - * Return if end of file. - */ - public function eof() { - return feof($this->stream); - } - - /** - * Seeks to offset of some file contentLength from the current cursor position in the - * multipartContent. - * - * Return true on success and false on failure - */ - // public function multipartContentSeekToContentLength(int $contentLength) { - // return (fseek($this->getContent(), $contentLength, SEEK_CUR) === 0 ? true : false); - // } - - /** - * Get request content. - * - * @throws \Sabre\DAV\Exception\BadRequest - * - * @return resource - */ - // public function getContent() { - // if ($this->stream === null) { - // // Pass body by reference, so other objects can have global access - // $content = $this->request->getBody(); - - // if (!$this->stream) { - // throw new BadRequest('Unable to get request content'); - // } - - // if (gettype($this->stream) !== 'resource') { - // throw new BadRequest('Wrong body type'); - // } - - // $this->stream = $content; - // } - - // return $this->stream; - // } - - // public function getBoundary(string $boundary) { - // return "\r\n--$boundary\r\n"; - // } - - public function checkBoundary(string $boundary, string $line) { - if ($line !== $boundary) { - throw new Exception("Invalid boundary, is '$line', should be '$this->boundary'."); - } - - return true; - } - - public function lastBoundary() { - $content = fread($this->stream, strlen($this->lastBoundary)); - $result = fseek($this->stream, -strlen($this->lastBoundary), SEEK_CUR); - - if ($result === -1) { - throw new Exception("Unknown error while seeking content"); - } - - return $content === $this->lastBoundary; - } - - /** - * Return the next part of the request. - * - * @throws Exception - */ - public function readNextPart(int $length = 0) { - $this->checkBoundary($this->boundary, fread($this->stream, strlen($this->boundary))); - - $headers = $this->readPartHeaders(); - - if ($length === 0 && isset($headers["content-length"])) { - $length = $headers["content-length"]; - } - - if ($length === 0) { - throw new Exception("Part cannot be of length 0."); - } - - $content = $this->readPartContent2($length); - - return [$headers, $content]; - } - - /** - * Return the next part of the request. - * - * @throws Exception - */ - public function readNextStream() { - $this->checkBoundary($this->boundary, fread($this->stream, strlen($this->boundary))); - - $headers = $this->readPartHeaders(); - - return [$headers, $this->stream]; - } - - /** - * Return the headers of a part of the request. - * - * @throws \Sabre\DAV\Exception\BadRequest - * @throws Exception - */ - public function readPartHeaders() { - $headers = []; - $blankLineCount = 0; - - while($blankLineCount < 1) { - $line = fgets($this->stream); - - if ($line === false) { - throw new Exception('An error appears while reading headers of a part'); - } - - if ($line === "\r\n") { - break; - } - - try { - [$key, $value] = explode(':', $line, 2); - $headers[strtolower(trim($key))] = trim($value); - } catch (Exception $e) { - throw new BadRequest('An error appears while parsing headers of a part', $e); - } - } - - return $headers; - } - - /** - * Return the content of the current part of the stream. - * - * @throws \Sabre\DAV\Exception\BadRequest - * @throws Exception - */ - public function readPartContent() { - $line = ''; - $content = ''; - - do { - $content .= $line; - - if (feof($this->stream)) { - throw new BadRequest("Unexpected EOF while reading stream."); - } - - $line = fgets($this->stream); - - if ($line === false) { - throw new Exception("Fail to read part's content."); - } - } while ($line !== $this->boundary); - - // We need to be before $boundary for the next parsing. - $result = fseek($this->stream, -strlen($this->boundary), SEEK_CUR); - - if ($result === -1) { - throw new Exception("Fail to seek upstream."); - } - - // Remove the extra new line "\r\n" that is not part of the content - return substr($content, 0, -2); - } - - public function readPartContent2(int $length) { - // Read stream until file's $length, EOF or $boundary is reached - $content = stream_get_line($this->stream, $length); - - if ($content === false) { - throw new Exception("Fail to read part's content."); - } - - if (feof($this->stream)) { - throw new Exception("Unexpected EOF while reading stream."); - } - - stream_get_contents($this->stream, 2); - - return $content; - } - - public function getContentPosition() { - return ftell($this->stream); - } - - public function getMetadata() { - fseek($this->stream, 0); - return $this->readNextPart(); - } - - public function getContent(int $pos, int $length) { - $previousPos = ftell($this->stream); - - $content = stream_get_contents($this->stream, $length, $pos); - - fseek($this->stream, $previousPos); - - return $content; - } - - /** - * Get a part of request separated by boundary $boundary. - * - * If this method returns an exception, it means whole request has to be abandoned, - * Request part without correct headers might corrupt the message and parsing is impossible - * - * @throws \Exception - */ - // public function getPartHeaders(string $boundary) { - // $delimiter = '--'.$boundary."\r\n"; - // $endDelimiter = '--'.$boundary.'--'; - // $boundaryCount = 0; - // $content = ''; - // $headers = null; - - // while (!$this->eof()) { - // $line = $this->gets(); - // if ($line === false) { - // if ($boundaryCount == 0) { - // // Empty part, ignore - // break; - // } - // else{ - // throw new \Exception('An error appears while reading and parsing header of content part using fgets'); - // } - // } - - // if ($boundaryCount == 0) { - // if ($line != $delimiter) { - // if ($this->getCursor() == strlen($line)) { - // throw new \Exception('Expected boundary delimiter in content part - this is not a multipart request'); - // } - // elseif ($line == $endDelimiter || $line == $endDelimiter."\r\n") { - // $this->endDelimiterReached = true; - // break; - // } - // elseif ($line == "\r\n") { - // continue; - // } - // } else { - // continue; - // } - // // At this point we know, that first line was boundary - // $boundaryCount++; - // } - // elseif ($boundaryCount == 1 && $line == "\r\n"){ - // //header-end according to RFC - // $content .= $line; - // $headers = $this->readHeaders($content); - // break; - // } - // elseif ($line == $endDelimiter || $line == $endDelimiter."\r\n") { - // $this->endDelimiterReached = true; - // break; - // } - - // $content .= $line; - // } - - // if ($this->eof()){ - // $this->endDelimiterReached = true; - // } - - // return $headers; - // } - - /** - * Read the contents from the current file pointer to the specified length - * - * @throws \Sabre\DAV\Exception\BadRequest - */ - // public function streamReadToString(int $length) { - // if ($length<0) { - // throw new BadRequest('Method streamRead cannot read contents with negative length'); - // } - // $source = $this->getContent(); - // $bufChunkSize = 8192; - // $count = $length; - // $buf = ''; - - // while ($count!=0) { - // $bufSize = (($count - $bufChunkSize)<0) ? $count : $bufChunkSize; - // $buf .= fread($source, $bufSize); - // $count -= $bufSize; - // } - - // $bytesWritten = strlen($buf); - // if ($length != $bytesWritten){ - // throw new BadRequest('Method streamRead read '.$bytesWritten.' expected '.$length); - // } - // return $buf; - // } - - /** - * Read the contents from the current file pointer to the specified length and pass - * - * @param resource $target - * - * @throws \Sabre\DAV\Exception\BadRequest - */ - // public function streamReadToStream($target, int $length) { - // if ($length<0) { - // throw new BadRequest('Method streamRead cannot read contents with negative length'); - // } - // $source = $this->getContent(); - // $bufChunkSize = 8192; - // $count = $length; - // $returnStatus = true; - - // while ($count!=0) { - // $bufSize = (($count - $bufChunkSize)<0) ? $count : $bufChunkSize; - // $buf = fread($source, $bufSize); - // $bytesWritten = fwrite($target, $buf); - - // // note: strlen is expensive so only use it when necessary, - // // on the last block - // if ($bytesWritten === false - // || ($bytesWritten < $bufSize) - // ) { - // // write error, could be disk full ? - // $returnStatus = false; - // break; - // } - // $count -= $bufSize; - // } - - // return $returnStatus; - // } - - - /** - * Get headers from content - */ - // public function readHeaders($content) { - // $headers = null; - // $headerLimitation = strpos($content, "\r\n\r\n"); - // if ($headerLimitation === false) { - // return null; - // } - // $headersContent = substr($content, 0, $headerLimitation); - // $headersContent = trim($headersContent); - // foreach (explode("\r\n", $headersContent) as $header) { - // $parts = explode(':', $header, 2); - // if (count($parts) != 2) { - // //has incorrect header, abort - // return null; - // } - // $headers[strtolower(trim($parts[0]))] = trim($parts[1]); - // } - - // return $headers; - // } -} \ No newline at end of file diff --git a/apps/dav/lib/Capabilities.php b/apps/dav/lib/Capabilities.php index 17db2346c68..ce60bccfd0b 100644 --- a/apps/dav/lib/Capabilities.php +++ b/apps/dav/lib/Capabilities.php @@ -29,7 +29,7 @@ class Capabilities implements ICapability { return [ 'dav' => [ 'chunking' => '1.0', - 'bundleupload' => '1.0', + 'bulkupload' => '1.0', ] ]; } diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index ec33b44fe4b..5ff5f831eb5 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -356,7 +356,7 @@ class File extends Node implements IFile { return '"' . $this->info->getEtag() . '"'; } - protected function getPartFileBasePath($path) { + private function getPartFileBasePath($path) { $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true); if ($partFileInStorage) { return $path; @@ -368,7 +368,7 @@ class File extends Node implements IFile { /** * @param string $path */ - protected function emitPreHooks($exists, $path = null) { + private function emitPreHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } @@ -396,7 +396,7 @@ class File extends Node implements IFile { /** * @param string $path */ - protected function emitPostHooks($exists, $path = null) { + private function emitPostHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } @@ -633,7 +633,7 @@ class File extends Node implements IFile { * * @throws \Sabre\DAV\Exception */ - protected function convertToSabreException(\Exception $e) { + private function convertToSabreException(\Exception $e) { if ($e instanceof \Sabre\DAV\Exception) { throw $e; } diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 74ae94a6d51..055c37f8472 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -34,6 +34,7 @@ */ namespace OCA\DAV; +use Psr\Log\LoggerInterface; use OCA\DAV\AppInfo\PluginManager; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CardDAV\HasPhotoPlugin; @@ -62,12 +63,11 @@ use OCA\DAV\DAV\PublicAuth; use OCA\DAV\Events\SabrePluginAuthInitEvent; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\LazySearchBackend; -use OCA\DAV\BundleUpload\BundlingPlugin; +use OCA\DAV\BulkUpload\BulkUploadPlugin; use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin; use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\Upload\ChunkingPlugin; use OCP\EventDispatcher\IEventDispatcher; -use OCP\Files\IRootFolder; use OCP\IRequest; use OCP\SabrePluginEvent; use Sabre\CardDAV\VCFExportPlugin; @@ -296,9 +296,9 @@ class Server { \OC::$server->getShareManager(), $view )); - $rootFolder = \OC::$server->query(IRootFolder::class); + $logger = \OC::$server->get(LoggerInterface::class); $this->server->addPlugin( - new BundlingPlugin($view, $userFolder) + new BulkUploadPlugin($userFolder, $logger) ); } $this->server->addPlugin(new \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin( diff --git a/apps/dav/tests/temporary/benchmark.sh b/apps/dav/tests/benchmarks/benchmark.sh similarity index 83% rename from apps/dav/tests/temporary/benchmark.sh rename to apps/dav/tests/benchmarks/benchmark.sh index 4a2f283e320..27d7c4ecbc7 100755 --- a/apps/dav/tests/temporary/benchmark.sh +++ b/apps/dav/tests/benchmarks/benchmark.sh @@ -2,6 +2,8 @@ set -eu +# benchmark.sh + export KB=1000 export MB=$((KB*1000)) @@ -32,10 +34,10 @@ do echo "- Upload of $nb tiny file of ${size}B" echo " - Bundled" start=$(date +%s) - echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./bundle_upload.sh "$nb" "$size" + echo "$requests_count" | xargs -d ' ' -P $CONCURRENCY -I{} ./bulk_upload.sh "$nb" "$size" end=$(date +%s) - bundle_exec_time=$((end-start)) - echo "${bundle_exec_time}s" + bulk_exec_time=$((end-start)) + echo "${bulk_exec_time}s" echo " - Single" start=$(date +%s) @@ -44,7 +46,7 @@ do single_exec_time=$((end-start)) echo "${single_exec_time}s" - md_output+="| $nb | $size | $bundle_exec_time | $single_exec_time |\n" + md_output+="| $nb | $size | $bulk_exec_time | $single_exec_time |\n" done echo -en "$md_output" \ No newline at end of file diff --git a/apps/dav/tests/temporary/bundle_upload.sh b/apps/dav/tests/benchmarks/bulk_upload.sh similarity index 75% rename from apps/dav/tests/temporary/bundle_upload.sh rename to apps/dav/tests/benchmarks/bulk_upload.sh index 9d2b9c6f200..862ddfe461f 100755 --- a/apps/dav/tests/temporary/bundle_upload.sh +++ b/apps/dav/tests/benchmarks/bulk_upload.sh @@ -2,6 +2,8 @@ set -eu +# bulk_upload.sh + KB=${KB:-100} MB=${MB:-$((KB*1000))} @@ -14,10 +16,10 @@ BANDWIDTH=${BANDWIDTH:-$((100*MB/CONCURRENCY))} USER="admin" PASS="password" SERVER="nextcloud.test" -UPLOAD_PATH="/tmp/bundle_upload_request_$(openssl rand --hex 8).txt" +UPLOAD_PATH="/tmp/bulk_upload_request_$(openssl rand --hex 8).txt" BOUNDARY="boundary_$(openssl rand --hex 8)" -LOCAL_FOLDER="/tmp/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" -REMOTE_FOLDER="/bundle_upload/${BOUNDARY}_${NB}_${SIZE}" +LOCAL_FOLDER="/tmp/bulk_upload/${BOUNDARY}_${NB}_${SIZE}" +REMOTE_FOLDER="/bulk_upload/${BOUNDARY}_${NB}_${SIZE}" mkdir --parent "$LOCAL_FOLDER" @@ -48,7 +50,13 @@ done echo -en "--$BOUNDARY--\r\n" >> "$UPLOAD_PATH" -echo "Creating folder /${BOUNDARY}_${NB}_${SIZE}" +echo "Creating folder /bulk_upload" +curl \ + -X MKCOL \ + -k \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/bulk_upload" > /dev/null + +echo "Creating folder $REMOTE_FOLDER" curl \ -X MKCOL \ -k \ @@ -56,15 +64,15 @@ curl \ echo "Uploading $NB files with total size: $(du -sh "$UPLOAD_PATH" | cut -d ' ' -f1)" echo "Local file is: $UPLOAD_PATH" -blackfire curl \ +curl \ -X POST \ -k \ --progress-bar \ --limit-rate "${BANDWIDTH}k" \ - --cookie "XDEBUG_PROFILE=MROW4A;path=/;" \ + --cookie "XDEBUG_PROFILE=true;path=/;" \ -H "Content-Type: multipart/related; boundary=$BOUNDARY" \ --data-binary "@$UPLOAD_PATH" \ - "https://$USER:$PASS@$SERVER/remote.php/dav/files/bundle" + "https://$USER:$PASS@$SERVER/remote.php/dav/bulk" rm -rf "${LOCAL_FOLDER:?}" rm "$UPLOAD_PATH" diff --git a/apps/dav/tests/temporary/single_upload.sh b/apps/dav/tests/benchmarks/single_upload.sh similarity index 74% rename from apps/dav/tests/temporary/single_upload.sh rename to apps/dav/tests/benchmarks/single_upload.sh index da8e414be60..ec57e66668d 100755 --- a/apps/dav/tests/temporary/single_upload.sh +++ b/apps/dav/tests/benchmarks/single_upload.sh @@ -2,6 +2,8 @@ set -eu +# single_upload.sh + export KB=${KB:-100} export MB=${MB:-$((KB*1000))} @@ -15,15 +17,20 @@ export USER="admin" export PASS="password" export SERVER="nextcloud.test" export UPLOAD_ID="single_$(openssl rand --hex 8)" -export LOCAL_FOLDER="/tmp/bundle_upload/${UPLOAD_ID}_${NB}_${SIZE}" -export REMOTE_FOLDER="/bundle_upload/${UPLOAD_ID}_${NB}_${SIZE}" +export LOCAL_FOLDER="/tmp/single_upload/${UPLOAD_ID}_${NB}_${SIZE}" +export REMOTE_FOLDER="/single_upload/${UPLOAD_ID}_${NB}_${SIZE}" mkdir --parent "$LOCAL_FOLDER" curl \ -X MKCOL \ -k \ - --cookie "XDEBUG_SESSION=MROW4A;path=/;" \ + "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/bulk_upload" > /dev/null + +curl \ + -X MKCOL \ + -k \ + --cookie "XDEBUG_SESSION=true;path=/;" \ "https://$USER:$PASS@$SERVER/remote.php/dav/files/$USER/$REMOTE_FOLDER" upload_file() { diff --git a/apps/dav/tests/unit/CapabilitiesTest.php b/apps/dav/tests/unit/CapabilitiesTest.php index 719b62115d9..399467f6ed8 100644 --- a/apps/dav/tests/unit/CapabilitiesTest.php +++ b/apps/dav/tests/unit/CapabilitiesTest.php @@ -35,6 +35,7 @@ class CapabilitiesTest extends TestCase { $expected = [ 'dav' => [ 'chunking' => '1.0', + 'bulkupload' => '1.0', ], ]; $this->assertSame($expected, $capabilities->getCapabilities()); diff --git a/apps/dav/tests/unit/Files/BundlePluginTest.php b/apps/dav/tests/unit/Files/BundlePluginTest.php deleted file mode 100644 index 92309fe5021..00000000000 --- a/apps/dav/tests/unit/Files/BundlePluginTest.php +++ /dev/null @@ -1,711 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\Files; - -use OC\Files\FileInfo; -use OC\Files\Storage\Local; -use Sabre\HTTP\RequestInterface; -use Test\TestCase; -use OC\Files\View; -use OCP\Files\Storage; -use Sabre\DAV\Exception; -use OC\Files\Filesystem; -use OCP\Files\StorageNotAvailableException; - -/** - * Class BundlingPlugin - * - * @group DB - * - * @package OCA\DAV\Tests\unit\Files - */ -class BundlingPluginTest extends TestCase { - - /** - * @var string - */ - private $user; - - /** @var \OC\Files\View | \PHPUnit_Framework_MockObject_MockObject */ - private $view; - - /** @var \OC\Files\FileInfo | \PHPUnit_Framework_MockObject_MockObject */ - private $info; - - /** - * @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject - */ - private $server; - - /** - * @var FilesPlugin - */ - private $plugin; - - /** - * @var \Sabre\HTTP\RequestInterface | \PHPUnit_Framework_MockObject_MockObject - */ - private $request; - /** - * @var \Sabre\HTTP\ResponseInterface | \PHPUnit_Framework_MockObject_MockObject - */ - private $response; - - /** - * @var MultipartContentsParser | \PHPUnit_Framework_MockObject_MockObject - */ - private $contentHandler; - - const BOUNDRARY = 'test_boundrary'; - - public function setUp() { - parent::setUp(); -// $this->server = new \Sabre\DAV\Server(); - - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') - ->setConstructorArgs(array()) - ->setMethods(array('emit')) - ->getMock(); - - $this->server->tree = $this->getMockBuilder('\Sabre\DAV\Tree') - ->disableOriginalConstructor() - ->getMock(); - - // setup - $storage = $this->getMockBuilder(Local::class) - ->setMethods(["fopen","moveFromStorage","file_exists"]) - ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) - ->getMock(); - $storage->method('fopen') - ->will($this->returnCallback( - function ($path,$mode) { - $bodyStream = fopen('php://temp', 'r+'); - return $bodyStream; - } - )); - $storage->method('moveFromStorage') - ->will($this->returnValue(true)); - $storage->method('file_exists') - ->will($this->returnValue(true)); - - \OC_Hook::clear(); - - $this->user = $this->getUniqueID('user_'); - $userManager = \OC::$server->getUserManager(); - $userManager->createUser($this->user, 'pass'); - - $this->loginAsUser($this->user); - - Filesystem::mount($storage, [], $this->user . '/'); - - $this->view = $this->getMockBuilder(View::class) - ->setMethods(['resolvePath', 'touch', 'file_exists', 'getFileInfo']) - ->setConstructorArgs([]) - ->getMock(); - - $this->view->method('touch') - ->will($this->returnValue(true)); - - $this->view - ->method('resolvePath') - ->will($this->returnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - )); - - $this->view - ->method('getFileInfo') - ->will($this->returnCallback( - function ($path) { - $props = array(); - $props['checksum'] = null; - $props['etag'] = $path; - $props['fileid'] = $path; - $info = new FileInfo($path, null, null, $props, null); - return $info; - } - )); - - $this->info = $this->createMock('OC\Files\FileInfo', [], [], '', false); - - $this->request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->response = new \Sabre\HTTP\Response(); - - $this->plugin = new BundlingPlugin( - $this->view - ); - - $this->plugin->initialize($this->server); - } - - /*TESTS*/ - - /** - * This test checks that if url endpoint is wrong, plugin with return exception - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage URL endpoint has to be instance of \OCA\DAV\Files\FilesHome - */ - public function testHandleBundleNotHomeCollection() { - - $this->request - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('notFilesHome.xml')); - - $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') - ->disableOriginalConstructor() - ->getMock(); - - $this->server->tree->expects($this->once()) - ->method('getNodeForPath') - ->with('notFilesHome.xml') - ->will($this->returnValue($node)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate NULL request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type header is needed - */ - public function testHandleBundleNoHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue(null)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate empty request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type header must not be empty - */ - public function testHandleBundleEmptyHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header without boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Improper Content-type format. Boundary may be missing - */ - public function testHandleBundleNoBoundraryHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with wrong boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Boundary is not set - */ - public function testHandleBundleWrongBoundraryHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related;thisIsNotBoundrary")); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with wrong boundrary specification request header - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type must be multipart/related - */ - public function testHandleBundleWrongContentTypeHeader() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/mixed; boundary=".self::BOUNDRARY)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Simulate content-type header with alternative correct boundrary specification request header - * - * Request with user out of quota - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage beforeWriteBundle preconditions failed - */ - public function testHandleAlternativeBoundraryPlusBundleOutOfQuota() { - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related; boundary=\"".self::BOUNDRARY."\"")); - - $this->server - ->expects($this->once()) - ->method('emit') - ->will($this->returnValue(false)); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Request without request body - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Unable to get request content - */ - public function testHandleBundleWithNullBody() { - $this->setupServerTillHeader(); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test empty request body. This will pass getPartHeader, but exception will be raised after we ready headers - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing - */ - public function testHandleBundleWithEmptyBody() { - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody(""); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Expected boundary delimiter in content part - this is not a multipart request - */ - public function testHandleBundleWithWrongBody() { - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody("WrongBody"); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing no charset - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Incorrect Content-type format. Charset might be missing - */ - public function testHandleMetadataNoCharsetType(){ - $bodyContent = 'I am wrong metadata not in utf-8'; - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing wrong content-type - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Content-Type must be text/xml - */ - public function testHandleMetadataWrongContentType(){ - $bodyContent = 'I am wrong metadata content type'; - $headers['content-type'] = 'text/plain; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Test wrong request body, with metadata header containing wrong content-type - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata header does not contain Content-Length. Unable to parse whole bundle request - */ - public function testHandleMetadataNoContentLength(){ - $bodyContent = 'I am wrong metadata content type'; - //$headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - //."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Try to parse body which is not xml - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata contains incorrect xml structure. Unable to parse whole bundle request - */ - public function testHandleWrongMetadataNoXML(){ - $bodyContent = "I am not xml"; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Try to parse body which has xml d:multipart element which - * has not been declared - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata does not contain d:multipart children elements - */ - public function testHandleWrongMetadataWrongXMLdElement(){ - $bodyContent = ""; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * This test checks that exception is raised for - * parsed XML which contains empty(without d:part elements) d:multipart section in metadata XML - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Bundle metadata does not contain d:multipart/d:part/d:prop children elements - */ - public function testHandleEmptyMultipartMetadataSection(){ - $bodyContent = ""; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Metadata contains part properties not containing obligatory field will raise exception - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage Undefined index: oc-id - */ - public function testHandleWrongMetadataNoPartID(){ - $bodyContent = " - - - - - - "; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * In the request, insert two files with the same Content-ID - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage One or more files have the same Content-ID 1. Unable to parse whole bundle request - */ - public function testHandleWrongMetadataMultipleIDs(){ - $bodyContent = " - - - - /test/zombie1.jpg\n - 1476393386\n - 1\n - 6\n - - - - - /test/zombie2.jpg\n - 1476393386\n - 1\n - 6\n - - - "; - - $this->setupServerTillMetadata($bodyContent); - - $this->plugin->handleBundle($this->request, $this->response); - } - - /** - * Specify metadata part without corresponding binary content - * - */ - public function testHandleWithoutBinaryContent(){ - $bodyContent = " - - - - /test/zombie1.jpg\n - 1476393386\n - 1\n - 6\n - - - "; - - $this->setupServerTillMetadata($bodyContent); - $this->plugin->handleBundle($this->request, $this->response); - $return = $this->response->getBody(); - $this->assertTrue(false != $return); - $xml = simplexml_load_string($return); - $this->assertTrue(false != $xml); - $xml->registerXPathNamespace('d','urn:DAV'); - $xml->registerXPathNamespace('s','http://sabredav.org/ns'); - - $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(1, count($fileMetadataObjectXML)); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(1, count($fileMetadataObjectXML)); - $this->assertEquals("File parsing error", (string) $fileMetadataObjectXML[0]); - } - - /** - * This test will simulate success and failure in putFile class. - * - */ - public function testHandlePutFile(){ - $this->setupServerTillData(); - - $this->view - ->method('file_exists') - ->will($this->onConsecutiveCalls(true, false, $this->throwException(new StorageNotAvailableException()))); - - $this->plugin->handleBundle($this->request, $this->response); - - $return = $this->response->getBody(); - $this->assertTrue(false != $return); - $xml = simplexml_load_string($return); - $this->assertTrue(false != $xml); - $xml->registerXPathNamespace('d','urn:DAV'); - $xml->registerXPathNamespace('s','http://sabredav.org/ns'); - - $this->assertEquals(1, count($xml->xpath('/d:multistatus'))); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(3, count($fileMetadataObjectXML)); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[0]); - $this->assertEquals("HTTP/1.1 200 OK", (string) $fileMetadataObjectXML[1]); - $this->assertEquals("HTTP/1.1 400 Bad Request", (string) $fileMetadataObjectXML[2]); - - $fileMetadataObjectXML = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:error/s:message'); - $this->assertTrue(false != $fileMetadataObjectXML); - $this->assertEquals(2, count($fileMetadataObjectXML)); - $this->assertEquals("Bundling not supported for already existing files", (string) $fileMetadataObjectXML[0]); - $this->assertEquals("StorageNotAvailableException raised", (string) $fileMetadataObjectXML[1]); - } - - /*UTILITIES*/ - - private function setupServerTillData(){ - $bodyContent = " - - - - /test/zombie1.jpg\n - 1476393386\n - 0\n - 7\n - - - - - /test/zombie2.jpg\n - 1476393386\n - 1\n - 7\n - - - - - zombie3.jpg\n - 1476393232\n - 2\n - 7\n - - - "; - - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 0" - ."\r\n\r\n" - ."zombie1" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 1" - ."\r\n\r\n" - ."zombie2" - ."\r\n--".self::BOUNDRARY - ."\r\nContent-ID: 2" - ."\r\n\r\n" - ."zombie3" - ."\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - } - - private function setupServerTillMetadata($bodyContent){ - $headers['content-length'] = strlen($bodyContent); - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = "--".self::BOUNDRARY - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--".self::BOUNDRARY."--"; - - $this->setupServerTillHeader(); - - $this->fillMultipartContentsParserStreamWithBody($bodyFull); - } - - private function setupServerTillHeader(){ - $this->setupServerTillFilesHome(); - - $this->request - ->expects($this->atLeastOnce()) - ->method('getHeader') - ->with('Content-Type') - ->will($this->returnValue("multipart/related; boundary=".self::BOUNDRARY)); - - $this->server - ->expects($this->once()) - ->method('emit') - ->will($this->returnValue(true)); - } - - private function setupServerTillFilesHome(){ - $this->request - ->expects($this->once()) - ->method('getPath') - ->will($this->returnValue('files/admin')); - - $node = $this->getMockBuilder('\OCA\DAV\Files\FilesHome') - ->disableOriginalConstructor() - ->getMock(); - - $this->server->tree->expects($this->once()) - ->method('getNodeForPath') - ->with('files/admin') - ->will($this->returnValue($node)); - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - - $this->request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - } - - public function tearDown() { - $userManager = \OC::$server->getUserManager(); - $userManager->get($this->user)->delete(); - unset($_SERVER['HTTP_OC_CHUNKED']); - - parent::tearDown(); - } -} diff --git a/apps/dav/tests/unit/Files/BundledFileTest.php b/apps/dav/tests/unit/Files/BundledFileTest.php deleted file mode 100644 index e1a65459b60..00000000000 --- a/apps/dav/tests/unit/Files/BundledFileTest.php +++ /dev/null @@ -1,220 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\Files; - -use OCP\Lock\ILockingProvider; - -/** - * Class File - * - * @group DB - * - * @package OCA\DAV\Tests\unit\Connector\Sabre - */ -class BundledFileTest extends \Test\TestCase { - - /** - * @var string - */ - private $user; - - /* BASICS */ - - public function setUp() { - parent::setUp(); - - \OC_Hook::clear(); - - $this->user = $this->getUniqueID('user_'); - $userManager = \OC::$server->getUserManager(); - $userManager->createUser($this->user, 'pass'); - - $this->loginAsUser($this->user); - } - - /* TESTS */ - - /** - * Test basic successful bundled file PutFile - */ - public function testPutFile() { - $bodyContent = 'blabla'; - $headers['oc-total-length'] = 6; - $headers['oc-path'] = '/foo.txt'; - $headers['oc-mtime'] = '1473336321'; - $headers['response'] = null; - - //this part will have some arbitrary, correct headers - $bodyFull = "$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->doPutFIle($headers, $multipartContentsParser); - } - - /** - * Test basic successful bundled file PutFile - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage File requires oc-total-length header to be read - */ - public function testPutFileNoLength() { - $bodyContent = 'blabla'; - $headers['oc-path'] = '/foo.txt'; - $headers['oc-mtime'] = '1473336321'; - $headers['response'] = null; - - //this part will have some arbitrary, correct headers - $bodyFull = "$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - $this->doPutFIle($headers, $multipartContentsParser); - } - - /** - * Test putting a single file - * - * @expectedException \Sabre\DAV\Exception\Forbidden - * @expectedExceptionMessage PUT method not supported for bundling - */ - public function testThrowIfPut() { - $fileContents = $this->getStream('test data'); - $this->doPut('/foo.txt', $fileContents); - } - - /* UTILITIES */ - - private function getMockStorage() { - $storage = $this->getMockBuilder('\OCP\Files\Storage') - ->getMock(); - $storage->expects($this->any()) - ->method('getId') - ->will($this->returnValue('home::someuser')); - return $storage; - } - - public function tearDown() { - $userManager = \OC::$server->getUserManager(); - $userManager->get($this->user)->delete(); - unset($_SERVER['HTTP_OC_CHUNKED']); - - parent::tearDown(); - } - - /** - * @param string $string - */ - private function getStream($string) { - $stream = fopen('php://temp', 'r+'); - fwrite($stream, $string); - fseek($stream, 0); - return $stream; - } - - /** - * Do basic put for single bundled file - */ - private function doPutFIle($fileMetadata, $contentHandler, $view = null, $viewRoot = null) { - $path = $fileMetadata['oc-path']; - - if(is_null($view)){ - $view = \OC\Files\Filesystem::getView(); - } - if (!is_null($viewRoot)) { - $view = new \OC\Files\View($viewRoot); - } else { - $viewRoot = '/' . $this->user . '/files'; - } - - $info = new \OC\Files\FileInfo( - $viewRoot . '/' . ltrim($path, '/'), - $this->getMockStorage(), - null, - ['permissions' => \OCP\Constants::PERMISSION_ALL], - null - ); - - $file = new BundledFile($view, $info, $contentHandler); - - // beforeMethod locks - $view->lockFile($path, ILockingProvider::LOCK_SHARED); - - $result = $file->putFile($fileMetadata); - - // afterMethod unlocks - $view->unlockFile($path, ILockingProvider::LOCK_SHARED); - - return $result; - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return $mcp; - } - - /** - * Simulate putting a file to the given path. - * - * @param string $path path to put the file into - * @param string $viewRoot root to use for the view - * - * @return null|string of the PUT operaiton which is usually the etag - */ - private function doPut($path, $fileContents, $viewRoot = null) { - $view = \OC\Files\Filesystem::getView(); - if (!is_null($viewRoot)) { - $view = new \OC\Files\View($viewRoot); - } else { - $viewRoot = '/' . $this->user . '/files'; - } - - $info = new \OC\Files\FileInfo( - $viewRoot . '/' . ltrim($path, '/'), - $this->getMockStorage(), - null, - ['permissions' => \OCP\Constants::PERMISSION_ALL], - null - ); - - $file = new BundledFile($view, $info, null); - - // beforeMethod locks - $view->lockFile($path, ILockingProvider::LOCK_SHARED); - - $result = $file->put($fileContents); - - // afterMethod unlocks - $view->unlockFile($path, ILockingProvider::LOCK_SHARED); - - return $result; - } -} diff --git a/apps/dav/tests/unit/Files/MultipartContentsParserTest.php b/apps/dav/tests/unit/Files/MultipartContentsParserTest.php deleted file mode 100644 index 6d23fe296d2..00000000000 --- a/apps/dav/tests/unit/Files/MultipartContentsParserTest.php +++ /dev/null @@ -1,416 +0,0 @@ - - * @author Louis Chemineau - * - * @copyright Copyright (c) 2016, ownCloud GmbH. - * @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\Tests\unit\DAV; - -use Test\TestCase; - -class MultipartContentsParserTest extends TestCase { - private $boundrary; - - protected function setUp() { - parent::setUp(); - - $this->boundrary = 'boundary'; - - } - - /*TESTS*/ - - /** - * Test basic gets() functionality, that if passed string instead of resource, it should fail - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Unable to get request content - */ - public function testGetsThrowWrongContents() { - //TODO - $bodyStream = "I am not a stream, but pretend to be"; - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - - $mcp->gets(); - } - - /** - * Test function readHeaders(), so if passed empty string, it will return null - * - */ - public function testReadHeadersThrowEmptyHeader() { - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $mcp->readHeaders(''); - $this->assertEquals(null, $mcp->readHeaders('')); - } - - /** - * streamRead function with incorrect parameter - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead cannot read contents with negative length - */ - public function testStreamReadToStringThrowNegativeLength() { - $bodyContent = 'blabla'; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); - //give negative length - $multipartContentsParser->streamReadToString(-1); - } - - /** - * streamRead function with incorrect parameter - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead cannot read contents with negative length - */ - public function testStreamReadToStreamThrowNegativeLength() { - $target = fopen('php://temp', 'r+'); - $bodyContent = 'blabla'; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyContent); - //give negative length - $multipartContentsParser->streamReadToStream($target,-1); - } - - public function testStreamReadToString() { - $length = 0; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 1000; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 8192; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - - $length = 20000; - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals($bodyString, $multipartContentsParser->streamReadToString($length)); - } - - public function testStreamReadToStream() { - $length = 0; - $this->streamReadToStreamBuilder($length); - - $length = 1000; - $this->streamReadToStreamBuilder($length); - - $length = 8192; - $this->streamReadToStreamBuilder($length); - - $length = 20000; - $this->streamReadToStreamBuilder($length); - } - - private function streamReadToStreamBuilder($length) { - $target = fopen('php://temp', 'r+'); - list($multipartContentsParser, $bodyString) = $this->fillMultipartContentsParserStreamWithChars($length); - $this->assertEquals(true, $multipartContentsParser->streamReadToStream($target,$length)); - rewind($target); - $this->assertEquals($bodyString, stream_get_contents($target)); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage An error appears while reading and parsing header of content part using fgets - */ - public function testGetPartThrowFailfgets() { - $bodyStream = fopen('php://temp', 'r+'); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = $this->getMockBuilder('OCA\DAV\Files\MultipartContentsParser') - ->setConstructorArgs(array($request)) - ->setMethods(array('gets')) - ->getMock(); - - $mcp->expects($this->any()) - ->method('gets') - ->will($this->onConsecutiveCalls("--boundary\r\n", "Content-ID: 0\r\n", false)); - - $mcp->getPartHeaders($this->boundrary); - } - - /** - * If one one the content parts does not contain boundrary, means that received wrong request - * - * @expectedException \Exception - * @expectedExceptionMessage Expected boundary delimiter in content part - */ - public function testGetPartThrowNoBoundraryFound() { - // Calling multipletimes getPart on parts without contents should return null,null and signal immedietaly that endDelimiter was reached - $bodyFull = "--boundary_wrong\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $multipartContentsParser->getPartHeaders($this->boundrary); - } - - /** - * Reading from request which method getBody returns false - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Unable to get request content - */ - public function testStreamReadThrowWrongBody() { - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn(false); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $mcp->getPartHeaders($this->boundrary); - } - - /** - * Reading from request which method getBody returns false - * - */ - public function testMultipartContentSeekToContentLength() { - $bodyStream = fopen('php://temp', 'r+'); - $bodyString = ''; - $length = 1000; - for ($x = 0; $x < $length; $x++) { - $bodyString .= 'k'; - } - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - $this->assertEquals(true,$mcp->multipartContentSeekToContentLength($length)); - } - - /** - * Test cases with wrong or incomplete boundraries - * - */ - public function testGetPartHeadersWrongBoundaryCases() { - // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached - $bodyFull = "--boundary\r\n--boundary_wrong\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Test empty content - $bodyFull = "--boundary\r\n"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Test empty content - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody(''); - $this->assertEquals(null, $multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - - // Calling multipletimes getPart on parts without contents should return null and signal immedietaly that endDelimiter was reached - // endDelimiter should be signaled after first getPart since it will read --boundrary till it finds contents. - $bodyFull = "--boundary\r\n--boundary\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - $this->assertEquals(null,$multipartContentsParser->getPartHeaders($this->boundrary)); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check if we can correctly parse headers and content using streamReadToString - * - */ - public function testReadHeaderBodyCorrect() { - //multipart part will have some content bodyContent and some headers - $bodyContent = 'blabla'; - $headers['content-length'] = '6'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse it - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $bodyParsed = $multipartContentsParser->streamReadToString(6); - - //check if end delimiter is not reached, since we just read 6 bytes, and stopped at \r\n - $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); - - //check that we parsed correct headers - $this->assertEquals($bodyContent, $bodyParsed); - $this->assertEquals($headers, $headersParsed); - - //parse further to check if there is new part. There is no, so headers are null and delimiter reached - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null,$headersParsed); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check parsing incorrect headers and content using streamReadToString - * - */ - public function testReadHeaderBodyIncorrect() { - - //multipart part will have some content bodyContent and some headers - $bodyContent = 'blabla'; - $headers['content-length'] = '6'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will one correct and one incorrect header - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length" - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse it and expect null, since contains incorrect headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null, $headersParsed); - $this->assertEquals(false,$multipartContentsParser->getEndDelimiterReached()); - - //parse further to check if next call with not read headers again - //this should return null again and get to end of delimiter - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals(null,$headersParsed); - $this->assertEquals(true,$multipartContentsParser->getEndDelimiterReached()); - } - - /** - * Test will check reading error in StreamReadToString - * - * @expectedException \Sabre\DAV\Exception\BadRequest - * @expectedExceptionMessage Method streamRead read 20 expeceted 60 - */ - public function testReadBodyIncorrect() { - //multipart part will have some content bodyContent and content-length header will specify to big value - //this - $bodyContent = 'blabla'; - $headers['content-length'] = '60'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals($headers, $headersParsed); - - $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); - $multipartContentsParser->streamReadToString($headersParsed['content-length']); - } - - /** - * Test will check reading error in StreamReadToString return false - * - */ - public function testReadBodyStreamIncorrect() { - //multipart part will have some content bodyContent and content-length header will specify to big value - //this - $bodyContent = 'blabla'; - $headers['content-length'] = '60'; - $headers['content-type'] = 'text/xml; charset=utf-8'; - - //this part will have some arbitrary, correct headers - $bodyFull = '--boundary' - ."\r\nContent-Type: ".$headers['content-type'] - ."\r\nContent-length: ".$headers['content-length'] - ."\r\n\r\n" - ."$bodyContent\r\n--boundary--"; - $multipartContentsParser = $this->fillMultipartContentsParserStreamWithBody($bodyFull); - - //parse headers - $headersParsed = $multipartContentsParser->getPartHeaders($this->boundrary); - $this->assertEquals($headers, $headersParsed); - - $this->assertEquals(true, array_key_exists('content-length',$headersParsed)); - $target = fopen('php://temp', 'r+'); - $bodyParsed = $multipartContentsParser->streamReadToStream($target, $headersParsed['content-length']); - $this->assertEquals(false, $bodyParsed); - } - - /*UTILITIES*/ - - private function fillMultipartContentsParserStreamWithChars($length){ - $bodyStream = fopen('php://temp', 'r+'); - $bodyString = ''; - for ($x = 0; $x < $length; $x++) { - $bodyString .= 'k'; - } - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return array($mcp, $bodyString); - } - - private function fillMultipartContentsParserStreamWithBody($bodyString){ - $bodyStream = fopen('php://temp', 'r+'); - fwrite($bodyStream, $bodyString); - rewind($bodyStream); - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->any()) - ->method('getBody') - ->willReturn($bodyStream); - - $mcp = new \OCA\DAV\Files\MultipartContentsParser($request); - return $mcp; - } -} diff --git a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php new file mode 100644 index 00000000000..ec9e2d0a383 --- /dev/null +++ b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php @@ -0,0 +1,281 @@ + + * + * @author Louis Chemineau + * + * @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\Tests\unit\DAV; + +use Test\TestCase; +use \OCA\DAV\BulkUpload\MultipartRequestParser; + +class MultipartRequestParserTest extends TestCase { + private function getValidBodyObject() { + return [ + [ + "headers" => [ + "Content-Length" => 7, + "X-File-MD5" => "4f2377b4d911f7ec46325fe603c3af03", + "X-File-Path" => "/coucou.txt" + ], + "content" => "Coucou\n" + ] + ]; + } + + private function getMultipartParser(array $parts, array $headers = [], string $boundary = "boundary_azertyuiop"): MultipartRequestParser { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + + $headers = array_merge(['Content-Type' => 'multipart/related; boundary='.$boundary], $headers); + $request->expects($this->any()) + ->method('getHeader') + ->willReturnCallback(function (string $key) use (&$headers) { + return $headers[$key]; + }); + + $body = ""; + foreach ($parts as $part) { + $body .= '--'.$boundary."\r\n"; + + foreach ($part['headers'] as $headerKey => $headerPart) { + $body .= $headerKey.": ".$headerPart."\r\n"; + } + + $body .= "\r\n"; + $body .= $part['content']."\r\n"; + } + + $body .= '--'.$boundary."--"; + + $stream = fopen('php://temp','r+'); + fwrite($stream, $body); + rewind($stream); + + $request->expects($this->any()) + ->method('getBody') + ->willReturn($stream); + + return new MultipartRequestParser($request); + } + + + /** + * Test validation of the request's body type + */ + public function testBodyTypeValidation() { + $bodyStream = "I am not a stream, but pretend to be"; + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->any()) + ->method('getBody') + ->willReturn($bodyStream); + + $this->expectExceptionMessage('Body should be of type resource'); + new MultipartRequestParser($request); + } + + /** + * Test with valid request. + * - valid boundary + * - valid md5 hash + * - valid content-length + * - valid file content + * - valid file path + */ + public function testValidRequest() { + $multipartParser = $this->getMultipartParser( + $this->getValidBodyObject() + ); + + [$headers, $content] = $multipartParser->parseNextPart(); + + $this->assertSame((int)$headers["content-length"], 7, "Content-Length header should be the same as provided."); + $this->assertSame($headers["x-file-md5"], "4f2377b4d911f7ec46325fe603c3af03", "X-File-MD5 header should be the same as provided."); + $this->assertSame($headers["x-file-path"], "/coucou.txt", "X-File-Path header should be the same as provided."); + + $this->assertSame($content, "Coucou\n", "Content should be the same"); + } + + /** + * Test with invalid md5 hash. + */ + public function testInvalidMd5Hash() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["X-File-MD5"] = "f2377b4d911f7ec46325fe603c3af03"; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a null md5 hash. + */ + public function testNullMd5Hash() { + $bodyObject = $this->getValidBodyObject(); + unset($bodyObject["0"]["headers"]["X-File-MD5"]); + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('The X-File-MD5 header must not be null.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a null Content-Length. + */ + public function testNullContentLength() { + $bodyObject = $this->getValidBodyObject(); + unset($bodyObject["0"]["headers"]["Content-Length"]); + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('The Content-Length header must not be null.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a lower Content-Length. + */ + public function testLowerContentLength() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["Content-Length"] = 6; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a higher Content-Length. + */ + public function testHigherContentLength() { + $bodyObject = $this->getValidBodyObject(); + $bodyObject["0"]["headers"]["Content-Length"] = 8; + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with wrong boundary in body. + */ + public function testWrongBoundary() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary=boundary_poiuytreza'] + ); + + $this->expectExceptionMessage('Boundary not found where it should be.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with no boundary in request headers. + */ + public function testNoBoundaryInHeader() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Error while parsing boundary in Content-Type header.'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related'] + ); + } + + /** + * Test with no boundary in the request's headers. + */ + public function testNoBoundaryInBody() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary=boundary_azertyuiop'], + '' + ); + + $this->expectExceptionMessage('Boundary not found where it should be.'); + $multipartParser->parseNextPart(); + } + + /** + * Test with a boundary with quotes in the request's headers. + */ + public function testBoundaryWithQuotes() { + $bodyObject = $this->getValidBodyObject(); + $multipartParser = $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; boundary="boundary_azertyuiop"'], + ); + + $multipartParser->parseNextPart(); + + // Dummy assertion, we just want to test that the parsing works. + $this->assertTrue(true); + } + + /** + * Test with a wrong Content-Type in the request's headers. + */ + public function testWrongContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Content-Type must be multipart/related'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/form-data; boundary="boundary_azertyuiop"'], + ); + } + + /** + * Test with a wrong key after the content type in the request's headers. + */ + public function testWrongKeyInContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Boundary is invalid'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => 'multipart/related; wrongkey="boundary_azertyuiop"'], + ); + } + + /** + * Test with a null Content-Type in the request's headers. + */ + public function testNullContentType() { + $bodyObject = $this->getValidBodyObject(); + $this->expectExceptionMessage('Content-Type can not be null'); + $this->getMultipartParser( + $bodyObject, + ['Content-Type' => null], + + ); + } +} diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php index 31ca68ba92b..9f5e79a3ac6 100644 --- a/build/integration/features/bootstrap/WebDav.php +++ b/build/integration/features/bootstrap/WebDav.php @@ -537,6 +537,57 @@ trait WebDav { $this->makeDavRequest($user, 'PUT', $file, ['OC-Chunked' => '1'], $data, "uploads"); } + /** + * @Given user :user uploads bulked files :name1 with :content1 and :name2 with :content2 and :name3 with :content3 + * @param string $user + * @param string $name1 + * @param string $content1 + * @param string $name2 + * @param string $content2 + * @param string $name3 + * @param string $content3 + */ + public function userUploadsChunkedFiles($user, $name1, $content1, $name2, $content2, $name3, $content3) { + $boundary = "boundary_azertyuiop"; + + $body = ""; + $body .= '--'.$boundary."\r\n"; + $body .= "X-File-Path: ".$name1."\r\n"; + $body .= "X-File-MD5: f6a6263167c92de8644ac998b3c4e4d1\r\n"; + $body .= "Content-Length: ".strlen($content1)."\r\n"; + $body .= "\r\n"; + $body .= $content1."\r\n"; + $body .= '--'.$boundary."\r\n"; + $body .= "X-File-Path: ".$name2."\r\n"; + $body .= "X-File-MD5: 87c7d4068be07d390a1fffd21bf1e944\r\n"; + $body .= "Content-Length: ".strlen($content2)."\r\n"; + $body .= "\r\n"; + $body .= $content2."\r\n"; + $body .= '--'.$boundary."\r\n"; + $body .= "X-File-Path: ".$name3."\r\n"; + $body .= "X-File-MD5: e86a1cf0678099986a901c79086f5617\r\n"; + $body .= "Content-Length: ".strlen($content3)."\r\n"; + $body .= "\r\n"; + $body .= $content3."\r\n"; + $body .= '--'.$boundary."--\r\n"; + + $stream = fopen('php://temp','r+'); + fwrite($stream, $body); + rewind($stream); + + $client = new GClient(); + $options = [ + 'auth' => [$user, $this->regularUser], + 'headers' => [ + 'Content-Type' => 'multipart/related; boundary='.$boundary, + 'Content-Length' => (string)strlen($body), + ], + 'body' => $body + ]; + + return $client->request("POST", substr($this->baseUrl, 0, -4) . "remote.php/dav/bulk", $options); + } + /** * @Given user :user creates a new chunking upload with id :id */ diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index 66652e6fa26..c98ecc56ec7 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -608,3 +608,14 @@ Feature: webdav-related And user "user0" uploads new chunk file "3" with "CCCCC" to id "chunking-42" When user "user0" moves new chunk file with id "chunking-42" to "/myChunkedFile.txt" with size 15 Then the HTTP status code should be "201" + + Scenario: Upload bulked files + Given user "user0" exists + And user "user0" uploads bulked files "A.txt" with "AAAAA" and "B.txt" with "BBBBB" and "C.txt" with "CCCCC" + When As an "user0" + Then Downloading file "/A.txt" + And Downloaded content should be "AAAAA" + And Downloading file "/B.txt" + And Downloaded content should be "BBBBB" + And Downloading file "/C.txt" + And Downloaded content should be "CCCCC"