diff options
Diffstat (limited to 'apps/dav/lib/BundleUpload/BundlingPlugin.php')
-rw-r--r-- | apps/dav/lib/BundleUpload/BundlingPlugin.php | 456 |
1 files changed, 0 insertions, 456 deletions
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 @@ -<?php -/** - * @author Piotr Mrowczynski <Piotr.Mrowczynski@owncloud.com> - * @author Louis Chemineau <louis@chmn.me> - * - * @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 <http://www.gnu.org/licenses/> - * - */ - -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 |