From 9f6dcb9d3e2f43f336dafeb739e04ba9614d9b5f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 12 Feb 2015 12:29:01 +0100 Subject: [PATCH] Sabre Update to 2.1 - VObject fixes for Sabre\VObject 3.3 - Remove VObject property workarounds - Added prefetching for tags in sabre tags plugin - Moved oc_properties logic to separate PropertyStorage backend (WIP) - Fixed Sabre connector namespaces - Improved files plugin to handle props on-demand - Moved allowed props from server class to files plugin - Fixed tags caching for files that are known to have no tags (less queries) - Added/fixed unit tests for Sabre FilesPlugin, TagsPlugin - Replace OC\Connector\Sabre\Request with direct call to httpRequest->setUrl() - Fix exception detection in DAV client when using Sabre\DAV\Client - Added setETag() on Node instead of using the static FileSystem - Also preload tags/props when depth is infinity --- 3rdparty | 2 +- apps/files/appinfo/remote.php | 33 +- apps/files_encryption/tests/webdav.php | 12 +- apps/files_sharing/publicwebdav.php | 22 +- lib/private/appframework/http/request.php | 4 +- .../connector/sabre/appenabledplugin.php | 2 +- lib/private/connector/sabre/auth.php | 54 +-- .../sabre/custompropertiesbackend.php | 347 ++++++++++++++++++ lib/private/connector/sabre/directory.php | 83 +---- .../sabre/exception/entitytoolarge.php | 25 +- .../connector/sabre/exception/filelocked.php | 28 +- .../sabre/exception/unsupportedmediatype.php | 25 +- .../connector/sabre/exceptionloggerplugin.php | 28 +- lib/private/connector/sabre/file.php | 84 +---- lib/private/connector/sabre/filesplugin.php | 183 +++++---- lib/private/connector/sabre/locks.php | 52 +-- .../connector/sabre/maintenanceplugin.php | 30 +- lib/private/connector/sabre/node.php | 156 ++------ lib/private/connector/sabre/objecttree.php | 54 ++- lib/private/connector/sabre/principal.php | 18 +- lib/private/connector/sabre/quotaplugin.php | 41 +-- lib/private/connector/sabre/request.php | 50 --- lib/private/connector/sabre/server.php | 292 +-------------- lib/private/connector/sabre/taglist.php | 3 +- lib/private/connector/sabre/tagsplugin.php | 193 +++++----- lib/private/files/storage/common.php | 2 +- lib/private/files/storage/dav.php | 73 ++-- lib/private/vobject/compoundproperty.php | 69 ---- lib/private/vobject/stringproperty.php | 77 ---- .../sabre/custompropertiesbackend.php | 248 +++++++++++++ tests/lib/connector/sabre/directory.php | 21 +- tests/lib/connector/sabre/file.php | 18 +- tests/lib/connector/sabre/filesplugin.php | 174 +++++++++ tests/lib/connector/sabre/node.php | 2 +- tests/lib/connector/sabre/objecttree.php | 123 ++++++- tests/lib/connector/sabre/principal.php | 3 +- tests/lib/connector/sabre/quotaplugin.php | 38 +- tests/lib/connector/sabre/tagsplugin.php | 212 +++++++---- tests/lib/vobject.php | 40 -- 39 files changed, 1550 insertions(+), 1371 deletions(-) create mode 100644 lib/private/connector/sabre/custompropertiesbackend.php delete mode 100644 lib/private/connector/sabre/request.php delete mode 100644 lib/private/vobject/compoundproperty.php delete mode 100644 lib/private/vobject/stringproperty.php create mode 100644 tests/lib/connector/sabre/custompropertiesbackend.php create mode 100644 tests/lib/connector/sabre/filesplugin.php delete mode 100644 tests/lib/vobject.php diff --git a/3rdparty b/3rdparty index 59f092231c..588b1308f4 160000 --- a/3rdparty +++ b/3rdparty @@ -1 +1 @@ -Subproject commit 59f092231c6036838746262a4db80997908bb06f +Subproject commit 588b1308f4abf58acb3bb8519f6952d9890cca89 diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index f9fa792e13..98db95041f 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -27,14 +27,14 @@ * */ // Backends -$authBackend = new OC_Connector_Sabre_Auth(); -$lockBackend = new OC_Connector_Sabre_Locks(); -$requestBackend = new OC_Connector_Sabre_Request(); +$authBackend = new \OC\Connector\Sabre\Auth(); +$lockBackend = new \OC\Connector\Sabre\Locks(); // Fire up server $objectTree = new \OC\Connector\Sabre\ObjectTree(); -$server = new OC_Connector_Sabre_Server($objectTree); -$server->httpRequest = $requestBackend; +$server = new \OC\Connector\Sabre\Server($objectTree); +// Set URL explicitly due to reverse-proxy situations +$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); $server->setBaseUri($baseuri); // Load plugins @@ -42,22 +42,33 @@ $defaults = new OC_Defaults(); $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); $server->addPlugin(new \Sabre\DAV\Locks\Plugin($lockBackend)); $server->addPlugin(new \Sabre\DAV\Browser\Plugin(false, false)); // Show something in the Browser, but no upload -$server->addPlugin(new OC_Connector_Sabre_FilesPlugin()); -$server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); -$server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav')); +$server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); +$server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); +$server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav')); // wait with registering these until auth is handled and the filesystem is setup -$server->subscribeEvent('beforeMethod', function () use ($server, $objectTree) { +$server->on('beforeMethod', function () use ($server, $objectTree) { $view = \OC\Files\Filesystem::getView(); $rootInfo = $view->getFileInfo(''); // Create ownCloud Dir $mountManager = \OC\Files\Filesystem::getMountManager(); - $rootDir = new OC_Connector_Sabre_Directory($view, $rootInfo); + $rootDir = new \OC\Connector\Sabre\Directory($view, $rootInfo); $objectTree->init($rootDir, $view, $mountManager); $server->addPlugin(new \OC\Connector\Sabre\TagsPlugin($objectTree, \OC::$server->getTagManager())); - $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view)); + $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); + + // custom properties plugin must be the last one + $server->addPlugin( + new \Sabre\DAV\PropertyStorage\Plugin( + new \OC\Connector\Sabre\CustomPropertiesBackend( + $objectTree, + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession()->getUser() + ) + ) + ); }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request // And off we go! diff --git a/apps/files_encryption/tests/webdav.php b/apps/files_encryption/tests/webdav.php index 0d5a8f82f2..8fe5ffe036 100755 --- a/apps/files_encryption/tests/webdav.php +++ b/apps/files_encryption/tests/webdav.php @@ -216,35 +216,33 @@ class Webdav extends TestCase { */ function handleWebdavRequest($body = false) { // Backends - $authBackend = $this->getMockBuilder('OC_Connector_Sabre_Auth') + $authBackend = $this->getMockBuilder('OC\Connector\Sabre\Auth') ->setMethods(['validateUserPass']) ->getMock(); $authBackend->expects($this->any()) ->method('validateUserPass') ->will($this->returnValue(true)); - $lockBackend = new \OC_Connector_Sabre_Locks(); - $requestBackend = new \OC_Connector_Sabre_Request(); + $lockBackend = new \OC\Connector\Sabre\Locks(); // Create ownCloud Dir $root = '/' . $this->userId . '/files'; $view = new \OC\Files\View($root); - $publicDir = new \OC_Connector_Sabre_Directory($view, $view->getFileInfo('')); + $publicDir = new \OC\Connector\Sabre\Directory($view, $view->getFileInfo('')); $objectTree = new \OC\Connector\Sabre\ObjectTree(); $mountManager = \OC\Files\Filesystem::getMountManager(); $objectTree->init($publicDir, $view, $mountManager); // Fire up server $server = new \Sabre\DAV\Server($publicDir); - $server->httpRequest = $requestBackend; $server->setBaseUri('/remote.php/webdav/'); // Load plugins $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, 'ownCloud')); $server->addPlugin(new \Sabre\DAV\Locks\Plugin($lockBackend)); $server->addPlugin(new \Sabre\DAV\Browser\Plugin(false)); // Show something in the Browser, but no upload - $server->addPlugin(new \OC_Connector_Sabre_QuotaPlugin($view)); - $server->addPlugin(new \OC_Connector_Sabre_MaintenancePlugin()); + $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); + $server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); $server->debugExceptions = true; // Totally ugly hack to setup the FS diff --git a/apps/files_sharing/publicwebdav.php b/apps/files_sharing/publicwebdav.php index 30c8588ee3..abed58b5ee 100644 --- a/apps/files_sharing/publicwebdav.php +++ b/apps/files_sharing/publicwebdav.php @@ -34,13 +34,13 @@ OC_Util::obEnd(); // Backends $authBackend = new OCA\Files_Sharing\Connector\PublicAuth(\OC::$server->getConfig()); -$lockBackend = new OC_Connector_Sabre_Locks(); -$requestBackend = new OC_Connector_Sabre_Request(); +$lockBackend = new \OC\Connector\Sabre\Locks(); // Fire up server $objectTree = new \OC\Connector\Sabre\ObjectTree(); -$server = new OC_Connector_Sabre_Server($objectTree); -$server->httpRequest = $requestBackend; +$server = new \OC\Connector\Sabre\Server($objectTree); +// Set URL explicitly due to reverse-proxy situations +$server->httpRequest->setUrl(\OC::$server->getRequest()->getRequestUri()); $server->setBaseUri($baseuri); // Load plugins @@ -48,12 +48,12 @@ $defaults = new OC_Defaults(); $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); $server->addPlugin(new \Sabre\DAV\Locks\Plugin($lockBackend)); $server->addPlugin(new \Sabre\DAV\Browser\Plugin(false)); // Show something in the Browser, but no upload -$server->addPlugin(new OC_Connector_Sabre_FilesPlugin()); -$server->addPlugin(new OC_Connector_Sabre_MaintenancePlugin()); -$server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav')); +$server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); +$server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); +$server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav')); // wait with registering these until auth is handled and the filesystem is setup -$server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $authBackend) { +$server->on('beforeMethod', function () use ($server, $objectTree, $authBackend) { $share = $authBackend->getShare(); $owner = $share['uid_owner']; $isWritable = $share['permissions'] & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE); @@ -74,14 +74,14 @@ $server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $ // Create ownCloud Dir if ($rootInfo->getType() === 'dir') { - $root = new OC_Connector_Sabre_Directory($view, $rootInfo); + $root = new \OC\Connector\Sabre\Directory($view, $rootInfo); } else { - $root = new OC_Connector_Sabre_File($view, $rootInfo); + $root = new \OC\Connector\Sabre\File($view, $rootInfo); } $mountManager = \OC\Files\Filesystem::getMountManager(); $objectTree->init($root, $view, $mountManager); - $server->addPlugin(new OC_Connector_Sabre_QuotaPlugin($view)); + $server->addPlugin(new \OC\Connector\Sabre\QuotaPlugin($view)); }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request // And off we go! diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php index dad5cb050c..e47811d0df 100644 --- a/lib/private/appframework/http/request.php +++ b/lib/private/appframework/http/request.php @@ -543,7 +543,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { // strip off the script name's dir and file name // FIXME: Sabre does not really belong here - list($path, $name) = \Sabre\DAV\URLUtil::splitPath($scriptName); + list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($scriptName); if (!empty($path)) { if($path === $pathInfo || strpos($pathInfo, $path.'/') === 0) { $pathInfo = substr($pathInfo, strlen($path)); @@ -575,7 +575,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { } $pathInfo = $this->getRawPathInfo(); - // following is taken from \Sabre\DAV\URLUtil::decodePathSegment + // following is taken from \Sabre\HTTP\URLUtil::decodePathSegment $pathInfo = rawurldecode($pathInfo); $encoding = mb_detect_encoding($pathInfo, ['UTF-8', 'ISO-8859-1']); diff --git a/lib/private/connector/sabre/appenabledplugin.php b/lib/private/connector/sabre/appenabledplugin.php index 68356d3378..e38af40c08 100644 --- a/lib/private/connector/sabre/appenabledplugin.php +++ b/lib/private/connector/sabre/appenabledplugin.php @@ -69,7 +69,7 @@ class AppEnabledPlugin extends ServerPlugin { public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; - $this->server->subscribeEvent('beforeMethod', array($this, 'checkAppEnabled'), 30); + $this->server->on('beforeMethod', array($this, 'checkAppEnabled'), 30); } /** diff --git a/lib/private/connector/sabre/auth.php b/lib/private/connector/sabre/auth.php index a095ee6e04..0fc76ea9df 100644 --- a/lib/private/connector/sabre/auth.php +++ b/lib/private/connector/sabre/auth.php @@ -1,31 +1,7 @@ - * @author Bart Visscher - * @author Jakob Sack - * @author Lukas Reschke - * @author Markus Goetz - * @author Michael Gapczynski - * @author Robin McCorkell - * @author Thomas Müller - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Auth extends \Sabre\DAV\Auth\Backend\AbstractBasic { +namespace OC\Connector\Sabre; + +class Auth extends \Sabre\DAV\Auth\Backend\AbstractBasic { const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND'; /** @@ -55,19 +31,19 @@ class OC_Connector_Sabre_Auth extends \Sabre\DAV\Auth\Backend\AbstractBasic { * @return bool */ protected function validateUserPass($username, $password) { - if (OC_User::isLoggedIn() && - $this->isDavAuthenticated(OC_User::getUser()) + if (\OC_User::isLoggedIn() && + $this->isDavAuthenticated(\OC_User::getUser()) ) { - OC_Util::setupFS(OC_User::getUser()); + \OC_Util::setupFS(\OC_User::getUser()); \OC::$server->getSession()->close(); return true; } else { - OC_Util::setUpFS(); //login hooks may need early access to the filesystem - if(OC_User::login($username, $password)) { + \OC_Util::setUpFS(); //login hooks may need early access to the filesystem + if(\OC_User::login($username, $password)) { // make sure we use owncloud's internal username here // and not the HTTP auth supplied one, see issue #14048 - $ocUser = OC_User::getUser(); - OC_Util::setUpFS($ocUser); + $ocUser = \OC_User::getUser(); + \OC_Util::setUpFS($ocUser); \OC::$server->getSession()->set(self::DAV_AUTHENTICATED, $ocUser); \OC::$server->getSession()->close(); return true; @@ -86,7 +62,7 @@ class OC_Connector_Sabre_Auth extends \Sabre\DAV\Auth\Backend\AbstractBasic { * @return string|null */ public function getCurrentUser() { - $user = OC_User::getUser(); + $user = \OC_User::getUser(); if($user && $this->isDavAuthenticated($user)) { return $user; } @@ -117,11 +93,11 @@ class OC_Connector_Sabre_Auth extends \Sabre\DAV\Auth\Backend\AbstractBasic { * @return bool */ private function auth(\Sabre\DAV\Server $server, $realm) { - if (OC_User::handleApacheAuth() || - (OC_User::isLoggedIn() && is_null(\OC::$server->getSession()->get(self::DAV_AUTHENTICATED))) + if (\OC_User::handleApacheAuth() || + (\OC_User::isLoggedIn() && is_null(\OC::$server->getSession()->get(self::DAV_AUTHENTICATED))) ) { - $user = OC_User::getUser(); - OC_Util::setupFS($user); + $user = \OC_User::getUser(); + \OC_Util::setupFS($user); $this->currentUser = $user; \OC::$server->getSession()->close(); return true; diff --git a/lib/private/connector/sabre/custompropertiesbackend.php b/lib/private/connector/sabre/custompropertiesbackend.php new file mode 100644 index 0000000000..eaf13238b6 --- /dev/null +++ b/lib/private/connector/sabre/custompropertiesbackend.php @@ -0,0 +1,347 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ + +namespace OC\Connector\Sabre; + +use \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; +use \Sabre\HTTP\RequestInterface; +use \Sabre\HTTP\ResponseInterface; + +class CustomPropertiesBackend implements \Sabre\DAV\PropertyStorage\Backend\BackendInterface { + + /** + * Ignored properties + * + * @var array + */ + private $ignoredProperties = array( + '{DAV:}getcontentlength', + '{DAV:}getcontenttype', + '{DAV:}getetag', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}quota-available-bytes', + '{http://owncloud.org/ns}permissions', + '{http://owncloud.org/ns}downloadURL', + '{http://owncloud.org/ns}dDC', + '{http://owncloud.org/ns}size', + ); + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCP\IDBConnection + */ + private $connection; + + /** + * @var \OCP\IUser + */ + private $user; + + /** + * Properties cache + * + * @var array + */ + private $cache = []; + + /** + * @param \Sabre\DAV\Tree $tree node tree + * @param \OCP\IDBConnection $connection database connection + * @param \OCP\IUser $user owner of the tree and properties + */ + public function __construct( + \Sabre\DAV\Tree $tree, + \OCP\IDBConnection $connection, + \OCP\IUser $user) { + $this->tree = $tree; + $this->connection = $connection; + $this->user = $user->getUID(); + } + + /** + * Fetches properties for a path. + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + public function propFind($path, PropFind $propFind) { + $node = $this->tree->getNodeForPath($path); + if (!($node instanceof \OC\Connector\Sabre\Node)) { + return; + } + + $requestedProps = $propFind->get404Properties(); + + // these might appear + $requestedProps = array_diff( + $requestedProps, + $this->ignoredProperties + ); + + if (empty($requestedProps)) { + return; + } + + if ($node instanceof \OC\Connector\Sabre\Directory + && $propFind->getDepth() !== 0 + ) { + // note: pre-fetching only supported for depth <= 1 + $this->loadChildrenProperties($node, $requestedProps); + } + + $props = $this->getProperties($node, $requestedProps); + foreach ($props as $propName => $propValue) { + $propFind->set($propName, $propValue); + } + } + + /** + * Updates properties for a path + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function propPatch($path, PropPatch $propPatch) { + $node = $this->tree->getNodeForPath($path); + if (!($node instanceof \OC\Connector\Sabre\Node)) { + return; + } + + $propPatch->handleRemaining(function($changedProps) use ($node) { + return $this->updateProperties($node, $changedProps); + }); + } + + /** + * This method is called after a node is deleted. + * + * @param string $path path of node for which to delete properties + */ + public function delete($path) { + $statement = $this->connection->prepare( + 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array($this->user, '/' . $path)); + $statement->closeCursor(); + + unset($this->cache[$path]); + } + + /** + * This method is called after a successful MOVE + * + * @param string $source + * @param string $destination + * + * @return void + */ + public function move($source, $destination) { + $statement = $this->connection->prepare( + 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ?' + ); + $statement->execute(array('/' . $destination, $this->user, '/' . $source)); + $statement->closeCursor(); + } + + /** + * Returns a list of properties for this nodes.; + * @param \OC\Connector\Sabre\Node $node + * @param array $requestedProperties requested properties or empty array for "all" + * @return array + * @note The properties list is a list of propertynames the client + * requested, encoded as xmlnamespace#tagName, for example: + * http://www.example.org/namespace#author If the array is empty, all + * properties should be returned + */ + private function getProperties(\OC\Connector\Sabre\Node $node, array $requestedProperties) { + $path = $node->getPath(); + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // TODO: chunking if more than 1000 properties + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; + + $whereValues = array($this->user, $path); + $whereTypes = array(null, null); + + if (!empty($requestedProperties)) { + // request only a subset + $sql .= ' AND `propertyname` in (?)'; + $whereValues[] = $requestedProperties; + $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; + } + + $result = $this->connection->executeQuery( + $sql, + $whereValues, + $whereTypes + ); + + $props = []; + while ($row = $result->fetch()) { + $props[$row['propertyname']] = $row['propertyvalue']; + } + + $result->closeCursor(); + + $this->cache[$path] = $props; + return $props; + } + + /** + * Update properties + * + * @param \OC\Connector\Sabre\Node $node node for which to update properties + * @param array $properties array of properties to update + * + * @return bool + */ + private function updateProperties($node, $properties) { + $path = $node->getPath(); + + $deleteStatement = $this->connection->prepare( + 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?' + ); + + $insertStatement = $this->connection->prepare( + 'INSERT INTO `*PREFIX*properties`' . + ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)' + ); + + $updateStatement = $this->connection->prepare( + 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?' + ); + + // TODO: use "insert or update" strategy ? + $existing = $this->getProperties($node, array()); + $this->connection->beginTransaction(); + foreach ($properties as $propertyName => $propertyValue) { + // If it was null, we need to delete the property + if (is_null($propertyValue)) { + if (array_key_exists($propertyName, $existing)) { + $deleteStatement->execute( + array( + $this->user, + $path, + $propertyName + ) + ); + $deleteStatement->closeCursor(); + } + } else { + if (!array_key_exists($propertyName, $existing)) { + $insertStatement->execute( + array( + $this->user, + $path, + $propertyName, + $propertyValue + ) + ); + $insertStatement->closeCursor(); + } else { + $updateStatement->execute( + array( + $propertyValue, + $this->user, + $path, + $propertyName + ) + ); + $updateStatement->closeCursor(); + } + } + } + + $this->connection->commit(); + unset($this->cache[$path]); + + return true; + } + + /** + * Bulk load properties for directory children + * + * @param \OC\Connector\Sabre\Directory $node + * @param array $requestedProperties requested properties + * + * @return void + */ + private function loadChildrenProperties(\OC\Connector\Sabre\Directory $node, $requestedProperties) { + $path = $node->getPath(); + if (isset($this->cache[$path])) { + // we already loaded them at some point + return; + } + + $childNodes = $node->getChildren(); + // pre-fill cache + foreach ($childNodes as $childNode) { + $this->cache[$childNode->getPath()] = []; + } + + $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?'; + $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`'; + + $result = $this->connection->executeQuery( + $sql, + array($this->user, rtrim($path, '/') . '/%', $requestedProperties), + array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) + ); + + $props = []; + $oldPath = null; + $props = []; + while ($row = $result->fetch()) { + $path = $row['propertypath']; + if ($oldPath !== $path) { + // save previously gathered props + $this->cache[$oldPath] = $props; + $oldPath = $path; + // prepare props for next path + $props = []; + } + $props[$row['propertyname']] = $row['propertyvalue']; + } + if (!is_null($oldPath)) { + // save props from last run + $this->cache[$oldPath] = $props; + } + + $result->closeCursor(); + } + +} diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php index 682fd62ee3..f13a0d7bbd 100644 --- a/lib/private/connector/sabre/directory.php +++ b/lib/private/connector/sabre/directory.php @@ -1,4 +1,6 @@ * @author Bart Visscher @@ -24,7 +26,7 @@ * along with this program. If not, see * */ -class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node +class Directory extends \OC\Connector\Sabre\Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota { /** @@ -74,7 +76,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node if (isset($_SERVER['HTTP_OC_CHUNKED'])) { // exit if we can't create a new file and we don't updatable existing file - $info = OC_FileChunking::decodeName($name); + $info = \OC_FileChunking::decodeName($name); if (!$this->fileView->isCreatable($this->path) && !$this->fileView->isUpdatable($this->path . '/' . $info['name']) ) { @@ -91,7 +93,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name; // using a dummy FileInfo is acceptable here since it will be refreshed after the put is complete $info = new \OC\Files\FileInfo($path, null, null, array(), null); - $node = new OC_Connector_Sabre_File($this->fileView, $info); + $node = new \OC\Connector\Sabre\File($this->fileView, $info); return $node->put($data); } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); @@ -143,9 +145,9 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node } if ($info['mimetype'] == 'httpd/unix-directory') { - $node = new OC_Connector_Sabre_Directory($this->fileView, $info); + $node = new \OC\Connector\Sabre\Directory($this->fileView, $info); } else { - $node = new OC_Connector_Sabre_File($this->fileView, $info); + $node = new \OC\Connector\Sabre\File($this->fileView, $info); } return $node; } @@ -161,42 +163,9 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node } $folderContent = $this->fileView->getDirectoryContent($this->path); - $properties = array(); - $paths = array(); - foreach ($folderContent as $info) { - $name = $info->getName(); - $paths[] = $this->path . '/' . $name; - $properties[$this->path . '/' . $name][self::GETETAG_PROPERTYNAME] = '"' . $info->getEtag() . '"'; - } - // TODO: move this to a beforeGetPropertiesForPath event to pre-cache properties - // TODO: only fetch the requested properties - if (count($paths) > 0) { - // - // the number of arguments within IN conditions are limited in most databases - // we chunk $paths into arrays of 200 items each to meet this criteria - // - $chunks = array_chunk($paths, 200, false); - foreach ($chunks as $pack) { - $placeholders = join(',', array_fill(0, count($pack), '?')); - $query = OC_DB::prepare('SELECT * FROM `*PREFIX*properties`' - . ' WHERE `userid` = ?' . ' AND `propertypath` IN (' . $placeholders . ')'); - array_unshift($pack, OC_User::getUser()); // prepend userid - $result = $query->execute($pack); - while ($row = $result->fetchRow()) { - $propertypath = $row['propertypath']; - $propertyname = $row['propertyname']; - $propertyvalue = $row['propertyvalue']; - if ($propertyname !== self::GETETAG_PROPERTYNAME) { - $properties[$propertypath][$propertyname] = $propertyvalue; - } - } - } - } - $nodes = array(); foreach ($folderContent as $info) { $node = $this->getChild($info->getName(), $info); - $node->setPropertyCache($properties[$this->path . '/' . $info->getName()]); $nodes[] = $node; } $this->dirContent = $nodes; @@ -210,7 +179,13 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node * @return bool */ public function childExists($name) { - + // note: here we do NOT resolve the chunk file name to the real file name + // to make sure we return false when checking for file existence with a chunk + // file name. + // This is to make sure that "createFile" is still triggered + // (required old code) instead of "updateFile". + // + // TODO: resolve chunk file name here and implement "updateFile" $path = $this->path . '/' . $name; return $this->fileView->file_exists($path); @@ -245,7 +220,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node return $this->quotaInfo; } try { - $storageInfo = OC_Helper::getStorageInfo($this->info->getPath(), $this->info); + $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info); $this->quotaInfo = array( $storageInfo['used'], $storageInfo['free'] @@ -256,32 +231,4 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node } } - /** - * Returns a list of properties for this nodes.; - * - * The properties list is a list of propertynames the client requested, - * encoded as xmlnamespace#tagName, for example: - * http://www.example.org/namespace#author - * If the array is empty, all properties should be returned - * - * @param array $properties - * @return array - */ - public function getProperties($properties) { - $props = parent::getProperties($properties); - if (in_array(self::GETETAG_PROPERTYNAME, $properties) && !isset($props[self::GETETAG_PROPERTYNAME])) { - $props[self::GETETAG_PROPERTYNAME] = $this->info->getEtag(); - } - return $props; - } - - /** - * Returns the size of the node, in bytes - * - * @return int - */ - public function getSize() { - return $this->info->getSize(); - } - } diff --git a/lib/private/connector/sabre/exception/entitytoolarge.php b/lib/private/connector/sabre/exception/entitytoolarge.php index 2d2a264fe6..e18ac859dd 100644 --- a/lib/private/connector/sabre/exception/entitytoolarge.php +++ b/lib/private/connector/sabre/exception/entitytoolarge.php @@ -1,24 +1,9 @@ - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Exception_EntityTooLarge extends \Sabre\DAV\Exception { + + +namespace OC\Connector\Sabre\Exception; + +class EntityTooLarge extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception diff --git a/lib/private/connector/sabre/exception/filelocked.php b/lib/private/connector/sabre/exception/filelocked.php index ce110e3cb3..6b6b7b0d85 100644 --- a/lib/private/connector/sabre/exception/filelocked.php +++ b/lib/private/connector/sabre/exception/filelocked.php @@ -1,26 +1,10 @@ - * @author Owen Winkler - * @author Thomas Müller - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Exception_FileLocked extends \Sabre\DAV\Exception { + +namespace OC\Connector\Sabre\Exception; + +use Exception; + +class FileLocked extends \Sabre\DAV\Exception { public function __construct($message = "", $code = 0, Exception $previous = null) { if($previous instanceof \OCP\Files\LockNotAcquiredException) { diff --git a/lib/private/connector/sabre/exception/unsupportedmediatype.php b/lib/private/connector/sabre/exception/unsupportedmediatype.php index ce6db21d89..5a3716ae71 100644 --- a/lib/private/connector/sabre/exception/unsupportedmediatype.php +++ b/lib/private/connector/sabre/exception/unsupportedmediatype.php @@ -1,24 +1,9 @@ - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Exception_UnsupportedMediaType extends \Sabre\DAV\Exception { + + +namespace OC\Connector\Sabre\Exception; + +class UnsupportedMediaType extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception diff --git a/lib/private/connector/sabre/exceptionloggerplugin.php b/lib/private/connector/sabre/exceptionloggerplugin.php index 2b2753c47e..6ae57b3ec5 100644 --- a/lib/private/connector/sabre/exceptionloggerplugin.php +++ b/lib/private/connector/sabre/exceptionloggerplugin.php @@ -1,26 +1,8 @@ - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin -{ + +namespace OC\Connector\Sabre; + +class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin { private $nonFatalExceptions = array( 'Sabre\DAV\Exception\NotAuthenticated' => true, // the sync client uses this to find out whether files exist, @@ -54,7 +36,7 @@ class OC_Connector_Sabre_ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin */ public function initialize(\Sabre\DAV\Server $server) { - $server->subscribeEvent('exception', array($this, 'logException'), 10); + $server->on('exception', array($this, 'logException'), 10); } /** diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php index 9b529d2e0f..0355376c46 100644 --- a/lib/private/connector/sabre/file.php +++ b/lib/private/connector/sabre/file.php @@ -1,38 +1,7 @@ - * @author Bart Visscher - * @author Bjoern Schiessle - * @author chli1 - * @author Chris Wilson - * @author Jakob Sack - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Owen Winkler - * @author Robin Appelman - * @author Robin McCorkell - * @author Thomas Müller - * @author Thomas Tanghus - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\DAV\IFile { +namespace OC\Connector\Sabre; + +class File extends \OC\Connector\Sabre\Node implements \Sabre\DAV\IFile { /** * Updates the data @@ -52,11 +21,12 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ * return an ETag, and just return null. * * @param resource $data + * * @throws \Sabre\DAV\Exception\Forbidden - * @throws OC_Connector_Sabre_Exception_UnsupportedMediaType + * @throws \OC\Connector\Sabre\Exception\UnsupportedMediaType * @throws \Sabre\DAV\Exception\BadRequest * @throws \Sabre\DAV\Exception - * @throws OC_Connector_Sabre_Exception_EntityTooLarge + * @throws \OC\Connector\Sabre\Exception\EntityTooLarge * @throws \Sabre\DAV\Exception\ServiceUnavailable * @return string|null */ @@ -110,11 +80,11 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ } catch (\OCP\Files\EntityTooLargeException $e) { // the file is too big to be stored - throw new OC_Connector_Sabre_Exception_EntityTooLarge($e->getMessage()); + throw new \OC\Connector\Sabre\Exception\EntityTooLarge($e->getMessage()); } catch (\OCP\Files\InvalidContentException $e) { // the file content is not permitted - throw new OC_Connector_Sabre_Exception_UnsupportedMediaType($e->getMessage()); + throw new \OC\Connector\Sabre\Exception\UnsupportedMediaType($e->getMessage()); } catch (\OCP\Files\InvalidPathException $e) { // the path for the file was not valid @@ -122,7 +92,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); } catch (\OCP\Files\LockNotAcquiredException $e) { // the file is currently being written to by another process - throw new OC_Connector_Sabre_Exception_FileLocked($e->getMessage(), $e->getCode(), $e); + throw new \OC\Connector\Sabre\Exception\FileLocked($e->getMessage(), $e->getCode(), $e); } catch (\OCA\Files_Encryption\Exception\EncryptionException $e) { throw new \Sabre\DAV\Exception\Forbidden($e->getMessage()); } catch (\OCP\Files\StorageNotAvailableException $e) { @@ -155,7 +125,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ } catch (\OCP\Files\LockNotAcquiredException $e) { // the file is currently being written to by another process - throw new OC_Connector_Sabre_Exception_FileLocked($e->getMessage(), $e->getCode(), $e); + throw new \OC\Connector\Sabre\Exception\FileLocked($e->getMessage(), $e->getCode(), $e); } } @@ -215,34 +185,6 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable("Failed to unlink: ".$e->getMessage()); } - - // remove properties - $this->removeProperties(); - - } - - /** - * Returns the size of the node, in bytes - * - * @return int|float - */ - public function getSize() { - return $this->info->getSize(); - } - - /** - * Returns the ETag for a file - * - * An ETag is a unique identifier representing the current version of the - * file. If the file changes, the ETag MUST change. The ETag is an - * arbitrary string, but MUST be surrounded by double-quotes. - * - * Return null if the ETag can not effectively be determined - * - * @return mixed - */ - public function getETag() { - return '"' . $this->info->getEtag() . '"'; } /** @@ -288,13 +230,13 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements \Sabre\ */ private function createFileChunked($data) { - list($path, $name) = \Sabre\DAV\URLUtil::splitPath($this->path); + list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path); - $info = OC_FileChunking::decodeName($name); + $info = \OC_FileChunking::decodeName($name); if (empty($info)) { throw new \Sabre\DAV\Exception\NotImplemented(); } - $chunk_handler = new OC_FileChunking($info); + $chunk_handler = new \OC_FileChunking($info); $bytesWritten = $chunk_handler->store($info['index'], $data); //detect aborted upload diff --git a/lib/private/connector/sabre/filesplugin.php b/lib/private/connector/sabre/filesplugin.php index d62f4e4ce5..1932dabd39 100644 --- a/lib/private/connector/sabre/filesplugin.php +++ b/lib/private/connector/sabre/filesplugin.php @@ -1,30 +1,21 @@ - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin -{ +namespace OC\Connector\Sabre; + +use \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; +use \Sabre\HTTP\RequestInterface; +use \Sabre\HTTP\ResponseInterface; + +class FilesPlugin extends \Sabre\DAV\ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id'; + const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions'; + const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL'; + const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size'; + const GETETAG_PROPERTYNAME = '{DAV:}getetag'; + const GETLASTMODIFIED_PROPERTYNAME = '{DAV:}getlastmodified'; /** * Reference to main server object @@ -33,6 +24,15 @@ class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin */ private $server; + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + public function __construct(\Sabre\DAV\Tree $tree) { + $this->tree = $tree; + } + /** * This initializes the plugin. * @@ -47,66 +47,98 @@ class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin public function initialize(\Sabre\DAV\Server $server) { $server->xmlNamespaces[self::NS_OWNCLOUD] = 'oc'; - $server->protectedProperties[] = '{' . self::NS_OWNCLOUD . '}id'; - $server->protectedProperties[] = '{' . self::NS_OWNCLOUD . '}permissions'; - $server->protectedProperties[] = '{' . self::NS_OWNCLOUD . '}size'; - $server->protectedProperties[] = '{' . self::NS_OWNCLOUD . '}downloadURL'; + $server->protectedProperties[] = self::FILEID_PROPERTYNAME; + $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME; + $server->protectedProperties[] = self::SIZE_PROPERTYNAME; + $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME; + + // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH + $allowedProperties = ['{DAV:}getetag', '{DAV:}getlastmodified']; + $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties); $this->server = $server; - $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); - $this->server->subscribeEvent('afterBind', array($this, 'sendFileIdHeader')); - $this->server->subscribeEvent('afterWriteContent', array($this, 'sendFileIdHeader')); + $this->server->on('propFind', array($this, 'handleGetProperties')); + $this->server->on('propPatch', array($this, 'handleUpdateProperties')); + $this->server->on('afterBind', array($this, 'sendFileIdHeader')); + $this->server->on('afterWriteContent', array($this, 'sendFileIdHeader')); + $this->server->on('beforeMethod:GET', array($this, 'handleRangeHeaders')); } /** * Adds all ownCloud-specific properties * - * @param string $path + * @param PropFind $propFind * @param \Sabre\DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties * @return void */ - public function beforeGetProperties($path, \Sabre\DAV\INode $node, array &$requestedProperties, array &$returnedProperties) { + public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) { - if ($node instanceof OC_Connector_Sabre_Node) { + if ($node instanceof \OC\Connector\Sabre\Node) { - $fileIdPropertyName = '{' . self::NS_OWNCLOUD . '}id'; - $permissionsPropertyName = '{' . self::NS_OWNCLOUD . '}permissions'; - if (array_search($fileIdPropertyName, $requestedProperties)) { - unset($requestedProperties[array_search($fileIdPropertyName, $requestedProperties)]); - } - if (array_search($permissionsPropertyName, $requestedProperties)) { - unset($requestedProperties[array_search($permissionsPropertyName, $requestedProperties)]); - } + $propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) { + return $node->getFileId(); + }); - /** @var $node OC_Connector_Sabre_Node */ - $fileId = $node->getFileId(); - if (!is_null($fileId)) { - $returnedProperties[200][$fileIdPropertyName] = $fileId; - } + $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) { + return $node->getDavPermissions(); + }); - $permissions = $node->getDavPermissions(); - if (!is_null($permissions)) { - $returnedProperties[200][$permissionsPropertyName] = $permissions; - } + $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) { + return $node->getEtag(); + }); } - if ($node instanceof OC_Connector_Sabre_File) { - /** @var $node OC_Connector_Sabre_File */ - $directDownloadUrl = $node->getDirectDownload(); - if (isset($directDownloadUrl['url'])) { - $directDownloadUrlPropertyName = '{' . self::NS_OWNCLOUD . '}downloadURL'; - $returnedProperties[200][$directDownloadUrlPropertyName] = $directDownloadUrl['url']; + if ($node instanceof \OC\Connector\Sabre\File) { + $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) { + /** @var $node \OC\Connector\Sabre\File */ + $directDownloadUrl = $node->getDirectDownload(); + if (isset($directDownloadUrl['url'])) { + return $directDownloadUrl['url']; + } + return false; + }); + } + + if ($node instanceof \OC\Connector\Sabre\Directory) { + $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { + return $node->getSize(); + }); + } + } + + /** + * Update ownCloud-specific properties + * + * @param string $path + * @param PropPatch $propPatch + * + * @return void + */ + public function handleUpdateProperties($path, PropPatch $propPatch) { + $propPatch->handle(self::GETLASTMODIFIED_PROPERTYNAME, function($time) use ($path) { + if (empty($time)) { + return false; } - } - - if ($node instanceof OC_Connector_Sabre_Directory) { - $sizePropertyName = '{' . self::NS_OWNCLOUD . '}size'; - - /** @var $node OC_Connector_Sabre_Directory */ - $returnedProperties[200][$sizePropertyName] = $node->getSize(); - } + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + $node->touch($time); + return true; + }); + $propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($path) { + if (empty($etag)) { + return false; + } + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + if ($node->setEtag($etag) !== -1) { + return true; + } + return false; + }); } /** @@ -117,8 +149,8 @@ class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { // chunked upload handling if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - list($path, $name) = \Sabre\DAV\URLUtil::splitPath($filePath); - $info = OC_FileChunking::decodeName($name); + list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath); + $info = \OC_FileChunking::decodeName($name); if (!empty($info)) { $filePath = $path . '/' . $info['name']; } @@ -129,7 +161,7 @@ class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin return; } $node = $this->server->tree->getNodeForPath($filePath); - if ($node instanceof OC_Connector_Sabre_Node) { + if ($node instanceof \OC\Connector\Sabre\Node) { $fileId = $node->getFileId(); if (!is_null($fileId)) { $this->server->httpResponse->setHeader('OC-FileId', $fileId); @@ -137,4 +169,17 @@ class OC_Connector_Sabre_FilesPlugin extends \Sabre\DAV\ServerPlugin } } + /** + * Remove range headers if encryption is enabled. + * + * @param RequestInterface $request + * @param ResponseInterface $response + */ + public function handleRangeHeaders(RequestInterface $request, ResponseInterface $response) { + if (\OC_App::isEnabled('files_encryption')) { + // encryption does not support range requests (yet) + $request->removeHeader('range'); + } + } + } diff --git a/lib/private/connector/sabre/locks.php b/lib/private/connector/sabre/locks.php index ec14f1d71b..a212c9597c 100644 --- a/lib/private/connector/sabre/locks.php +++ b/lib/private/connector/sabre/locks.php @@ -1,29 +1,9 @@ - * @author Felix Moeller - * @author Jakob Sack - * @author Jörn Friedrich Dreyer - * @author Robin Appelman - * @author Thomas Müller - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend { + +namespace OC\Connector\Sabre; + + +class Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend { /** * Returns a list of \Sabre\DAV\Locks_LockInfo objects @@ -48,12 +28,12 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend // nothing $query = 'SELECT * FROM `*PREFIX*locks`' .' WHERE `userid` = ? AND (`created` + `timeout`) > '.time().' AND (( `uri` = ?)'; - if (OC_Config::getValue( "dbtype") === 'oci') { + if (\OC_Config::getValue( "dbtype") === 'oci') { //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison $query = 'SELECT * FROM `*PREFIX*locks`' .' WHERE `userid` = ? AND (`created` + `timeout`) > '.time().' AND (( to_char(`uri`) = ?)'; } - $params = array(OC_User::getUser(), $uri); + $params = array(\OC_User::getUser(), $uri); // We need to check locks for every part in the uri. $uriParts = explode('/', $uri); @@ -68,7 +48,7 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend if ($currentPath) $currentPath.='/'; $currentPath.=$part; //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison - if (OC_Config::getValue( "dbtype") === 'oci') { + if (\OC_Config::getValue( "dbtype") === 'oci') { $query.=' OR (`depth` != 0 AND to_char(`uri`) = ?)'; } else { $query.=' OR (`depth` != 0 AND `uri` = ?)'; @@ -80,7 +60,7 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend if ($returnChildLocks) { //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison - if (OC_Config::getValue( "dbtype") === 'oci') { + if (\OC_Config::getValue( "dbtype") === 'oci') { $query.=' OR (to_char(`uri`) LIKE ?)'; } else { $query.=' OR (`uri` LIKE ?)'; @@ -90,7 +70,7 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend } $query.=')'; - $result = OC_DB::executeAudited( $query, $params ); + $result = \OC_DB::executeAudited( $query, $params ); $lockList = array(); while( $row = $result->fetchRow()) { @@ -138,22 +118,22 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend $sql = 'UPDATE `*PREFIX*locks`' .' SET `owner` = ?, `timeout` = ?, `scope` = ?, `depth` = ?, `uri` = ?, `created` = ?' .' WHERE `userid` = ? AND `token` = ?'; - $result = OC_DB::executeAudited( $sql, array( + $result = \OC_DB::executeAudited( $sql, array( $lockInfo->owner, $lockInfo->timeout, $lockInfo->scope, $lockInfo->depth, $uri, $lockInfo->created, - OC_User::getUser(), + \OC_User::getUser(), $lockInfo->token) ); } else { $sql = 'INSERT INTO `*PREFIX*locks`' .' (`userid`,`owner`,`timeout`,`scope`,`depth`,`uri`,`created`,`token`)' .' VALUES (?,?,?,?,?,?,?,?)'; - $result = OC_DB::executeAudited( $sql, array( - OC_User::getUser(), + $result = \OC_DB::executeAudited( $sql, array( + \OC_User::getUser(), $lockInfo->owner, $lockInfo->timeout, $lockInfo->scope, @@ -178,11 +158,11 @@ class OC_Connector_Sabre_Locks extends \Sabre\DAV\Locks\Backend\AbstractBackend public function unlock($uri, \Sabre\DAV\Locks\LockInfo $lockInfo) { $sql = 'DELETE FROM `*PREFIX*locks` WHERE `userid` = ? AND `uri` = ? AND `token` = ?'; - if (OC_Config::getValue( "dbtype") === 'oci') { + if (\OC_Config::getValue( "dbtype") === 'oci') { //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison $sql = 'DELETE FROM `*PREFIX*locks` WHERE `userid` = ? AND to_char(`uri`) = ? AND `token` = ?'; } - $result = OC_DB::executeAudited( $sql, array(OC_User::getUser(), $uri, $lockInfo->token)); + $result = \OC_DB::executeAudited( $sql, array(\OC_User::getUser(), $uri, $lockInfo->token)); return $result === 1; diff --git a/lib/private/connector/sabre/maintenanceplugin.php b/lib/private/connector/sabre/maintenanceplugin.php index 3d4e19ec47..ff55cdceab 100644 --- a/lib/private/connector/sabre/maintenanceplugin.php +++ b/lib/private/connector/sabre/maintenanceplugin.php @@ -1,25 +1,7 @@ - * @author Thomas Müller - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_MaintenancePlugin extends \Sabre\DAV\ServerPlugin +namespace OC\Connector\Sabre; + +class MaintenancePlugin extends \Sabre\DAV\ServerPlugin { /** @@ -43,7 +25,7 @@ class OC_Connector_Sabre_MaintenancePlugin extends \Sabre\DAV\ServerPlugin public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; - $this->server->subscribeEvent('beforeMethod', array($this, 'checkMaintenanceMode'), 10); + $this->server->on('beforeMethod', array($this, 'checkMaintenanceMode'), 10); } /** @@ -55,10 +37,10 @@ class OC_Connector_Sabre_MaintenancePlugin extends \Sabre\DAV\ServerPlugin * @return bool */ public function checkMaintenanceMode() { - if (OC_Config::getValue('maintenance', false)) { + if (\OC_Config::getValue('maintenance', false)) { throw new \Sabre\DAV\Exception\ServiceUnavailable(); } - if (OC::checkUpgrade(false)) { + if (\OC::checkUpgrade(false)) { throw new \Sabre\DAV\Exception\ServiceUnavailable('Upgrade needed'); } diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php index 7df4453cb2..8fee6a4eb4 100644 --- a/lib/private/connector/sabre/node.php +++ b/lib/private/connector/sabre/node.php @@ -1,36 +1,4 @@ - * @author Bart Visscher - * @author Björn Schießle - * @author Jakob Sack - * @author Jörn Friedrich Dreyer - * @author Klaas Freitag - * @author Markus Goetz - * @author Morris Jobke - * @author Robin Appelman - * @author Sam Tuke - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use Sabre\DAV\URLUtil; -use OC\Connector\Sabre\TagList; /** * ownCloud @@ -52,10 +20,10 @@ use OC\Connector\Sabre\TagList; * License along with this library. If not, see . * */ -abstract class OC_Connector_Sabre_Node implements \Sabre\DAV\INode, \Sabre\DAV\IProperties { - const GETETAG_PROPERTYNAME = '{DAV:}getetag'; - const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; +namespace OC\Connector\Sabre; + +abstract class Node implements \Sabre\DAV\INode { /** * Allow configuring the method used to generate Etags * @@ -110,6 +78,15 @@ abstract class OC_Connector_Sabre_Node implements \Sabre\DAV\INode, \Sabre\DAV\I return $this->info->getName(); } + /** + * Returns the full path + * + * @return string + */ + public function getPath() { + return $this->path; + } + /** * Renames the node * @param string $name The new name @@ -123,23 +100,19 @@ abstract class OC_Connector_Sabre_Node implements \Sabre\DAV\INode, \Sabre\DAV\I throw new \Sabre\DAV\Exception\Forbidden(); } - list($parentPath,) = URLUtil::splitPath($this->path); - list(, $newName) = URLUtil::splitPath($name); + list($parentPath,) = \Sabre\HTTP\URLUtil::splitPath($this->path); + list(, $newName) = \Sabre\HTTP\URLUtil::splitPath($name); if (!\OCP\Util::isValidFileName($newName)) { throw new \Sabre\DAV\Exception\BadRequest(); } $newPath = $parentPath . '/' . $newName; - $oldPath = $this->path; $this->fileView->rename($this->path, $newPath); $this->path = $newPath; - $query = OC_DB::prepare('UPDATE `*PREFIX*properties` SET `propertypath` = ?' - . ' WHERE `userid` = ? AND `propertypath` = ?'); - $query->execute(array($newPath, OC_User::getUser(), $oldPath)); $this->refreshInfo(); } @@ -170,91 +143,38 @@ abstract class OC_Connector_Sabre_Node implements \Sabre\DAV\INode, \Sabre\DAV\I } /** - * Updates properties on this node, - * @see \Sabre\DAV\IProperties::updateProperties - * @param array $properties - * @return boolean + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the + * file. If the file changes, the ETag MUST change. The ETag is an + * arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string */ - public function updateProperties($properties) { - $existing = $this->getProperties(array()); - foreach ($properties as $propertyName => $propertyValue) { - // If it was null, we need to delete the property - if (is_null($propertyValue)) { - if (array_key_exists($propertyName, $existing)) { - $query = OC_DB::prepare('DELETE FROM `*PREFIX*properties`' - . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'); - $query->execute(array(OC_User::getUser(), $this->path, $propertyName)); - } - } else { - if (strcmp($propertyName, self::GETETAG_PROPERTYNAME) === 0) { - \OC\Files\Filesystem::putFileInfo($this->path, array('etag' => $propertyValue)); - } elseif (strcmp($propertyName, self::LASTMODIFIED_PROPERTYNAME) === 0) { - $this->touch($propertyValue); - } else { - if (!array_key_exists($propertyName, $existing)) { - $query = OC_DB::prepare('INSERT INTO `*PREFIX*properties`' - . ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'); - $query->execute(array(OC_User::getUser(), $this->path, $propertyName, $propertyValue)); - } else { - $query = OC_DB::prepare('UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' - . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'); - $query->execute(array($propertyValue, OC_User::getUser(), $this->path, $propertyName)); - } - } - } - - } - $this->setPropertyCache(null); - return true; + public function getETag() { + return '"' . $this->info->getEtag() . '"'; } /** - * removes all properties for this node and user + * Sets the ETag + * + * @param string $etag + * + * @return int file id of updated file or -1 on failure */ - public function removeProperties() { - $query = OC_DB::prepare('DELETE FROM `*PREFIX*properties`' - . ' WHERE `userid` = ? AND `propertypath` = ?'); - $query->execute(array(OC_User::getUser(), $this->path)); - - $this->setPropertyCache(null); + public function setETag($etag) { + return $this->fileView->putFileInfo($this->path, array('etag' => $etag)); } /** - * Returns a list of properties for this nodes.; - * @param array $properties - * @return array - * @note The properties list is a list of propertynames the client - * requested, encoded as xmlnamespace#tagName, for example: - * http://www.example.org/namespace#author If the array is empty, all - * properties should be returned + * Returns the size of the node, in bytes + * + * @return int|float */ - public function getProperties($properties) { - - if (is_null($this->property_cache)) { - $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; - $result = OC_DB::executeAudited($sql, array(OC_User::getUser(), $this->path)); - - $this->property_cache = array(); - while ($row = $result->fetchRow()) { - $this->property_cache[$row['propertyname']] = $row['propertyvalue']; - } - - $this->property_cache[self::GETETAG_PROPERTYNAME] = '"' . $this->info->getEtag() . '"'; - } - - // if the array was empty, we need to return everything - if (count($properties) == 0) { - return $this->property_cache; - } - - $props = array(); - foreach ($properties as $property) { - if (isset($this->property_cache[$property])) { - $props[$property] = $this->property_cache[$property]; - } - } - - return $props; + public function getSize() { + return $this->info->getSize(); } /** @@ -271,7 +191,7 @@ abstract class OC_Connector_Sabre_Node implements \Sabre\DAV\INode, \Sabre\DAV\I */ public function getFileId() { if ($this->info->getId()) { - $instanceId = OC_Util::getInstanceId(); + $instanceId = \OC_Util::getInstanceId(); $id = sprintf('%08d', $this->info->getId()); return $id . $instanceId; } diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php index 8793b61293..585be63781 100644 --- a/lib/private/connector/sabre/objecttree.php +++ b/lib/private/connector/sabre/objecttree.php @@ -30,7 +30,7 @@ use OC\Files\Mount\MoveableMount; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; -class ObjectTree extends \Sabre\DAV\ObjectTree { +class ObjectTree extends \Sabre\DAV\Tree { /** * @var \OC\Files\View @@ -44,8 +44,6 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { /** * Creates the object - * - * This method expects the rootObject to be passed as a parameter */ public function __construct() { } @@ -61,6 +59,35 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { $this->mountManager = $mountManager; } + /** + * If the given path is a chunked file name, converts it + * to the real file name. Only applies if the OC-CHUNKED header + * is present. + * + * @param string $path chunk file path to convert + * + * @return string path to real file + */ + private function resolveChunkFile($path) { + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + // resolve to real file name to find the proper node + list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path); + if ($dir == '/' || $dir == '.') { + $dir = ''; + } + + $info = \OC_FileChunking::decodeName($name); + // only replace path if it was really the chunked file + if (isset($info['transferid'])) { + // getNodePath is called for multiple nodes within a chunk + // upload call + $path = $dir . '/' . $info['name']; + $path = ltrim($path, '/'); + } + } + return $path; + } + /** * Returns the INode object for the requested path * @@ -102,12 +129,15 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { $info = null; } } else { + // resolve chunk file name to real name, if applicable + $path = $this->resolveChunkFile($path); + // read from cache try { $info = $this->fileView->getFileInfo($path); } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available'); - } catch (StorageInvalidException $e){ + } catch (StorageInvalidException $e) { throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); } } @@ -117,9 +147,9 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { } if ($info->getType() === 'dir') { - $node = new \OC_Connector_Sabre_Directory($this->fileView, $info); + $node = new \OC\Connector\Sabre\Directory($this->fileView, $info); } else { - $node = new \OC_Connector_Sabre_File($this->fileView, $info); + $node = new \OC\Connector\Sabre\File($this->fileView, $info); } $this->cache[$path] = $node; @@ -146,8 +176,8 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { if ($sourceNode instanceof \Sabre\DAV\ICollection and $this->nodeExists($destinationPath)) { throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode . ', target exists'); } - list($sourceDir,) = \Sabre\DAV\URLUtil::splitPath($sourcePath); - list($destinationDir,) = \Sabre\DAV\URLUtil::splitPath($destinationPath); + list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourcePath); + list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destinationPath); $isMovableMount = false; $sourceMount = $this->mountManager->find($this->fileView->getAbsolutePath($sourcePath)); @@ -183,12 +213,6 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } - // update properties - $query = \OC_DB::prepare('UPDATE `*PREFIX*properties` SET `propertypath` = ?' - . ' WHERE `userid` = ? AND `propertypath` = ?'); - $query->execute(array(\OC\Files\Filesystem::normalizePath($destinationPath), \OC_User::getUser(), - \OC\Files\Filesystem::normalizePath($sourcePath))); - $this->markDirty($sourceDir); $this->markDirty($destinationDir); @@ -229,7 +253,7 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } - list($destinationDir,) = \Sabre\DAV\URLUtil::splitPath($destination); + list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); $this->markDirty($destinationDir); } } diff --git a/lib/private/connector/sabre/principal.php b/lib/private/connector/sabre/principal.php index 917d988739..06842cc7b9 100644 --- a/lib/private/connector/sabre/principal.php +++ b/lib/private/connector/sabre/principal.php @@ -30,6 +30,7 @@ namespace OC\Connector\Sabre; use OCP\IUserManager; use OCP\IConfig; +use \Sabre\DAV\PropPatch; class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { /** @var IConfig */ @@ -137,7 +138,7 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { * @throws \Sabre\DAV\Exception */ public function getGroupMembership($principal) { - list($prefix, $name) = \Sabre\DAV\URLUtil::splitPath($principal); + list($prefix, $name) = \Sabre\HTTP\URLUtil::splitPath($principal); $group_membership = array(); if ($prefix === 'principals') { @@ -174,19 +175,28 @@ class Principal implements \Sabre\DAVACL\PrincipalBackend\BackendInterface { /** * @param string $path - * @param array $mutations + * @param PropPatch $propPatch * @return int */ - function updatePrincipal($path, $mutations) { + function updatePrincipal($path, PropPatch $propPatch) { return 0; } /** * @param string $prefixPath * @param array $searchProperties + * @param string $test * @return array */ - function searchPrincipals($prefixPath, array $searchProperties) { + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { return []; } + + /** + * @param string $uri + * @return string + */ + function findByUri($uri) { + return ''; + } } diff --git a/lib/private/connector/sabre/quotaplugin.php b/lib/private/connector/sabre/quotaplugin.php index 8601f8aada..6c0f9f3f95 100644 --- a/lib/private/connector/sabre/quotaplugin.php +++ b/lib/private/connector/sabre/quotaplugin.php @@ -1,31 +1,6 @@ - * @author Morris Jobke - * @author Robin Appelman - * @author Robin McCorkell - * @author scambra - * @author Scrutinizer Auto-Fixer - * @author Thomas Müller - * @author Vincent Petry - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -use Sabre\DAV\URLUtil; + +namespace OC\Connector\Sabre; /** * This plugin check user quota and deny creating files when they exceeds the quota. @@ -34,7 +9,7 @@ use Sabre\DAV\URLUtil; * @copyright Copyright (C) 2012 entreCables S.L. All rights reserved. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ -class OC_Connector_Sabre_QuotaPlugin extends \Sabre\DAV\ServerPlugin { +class QuotaPlugin extends \Sabre\DAV\ServerPlugin { /** * @var \OC\Files\View @@ -70,8 +45,8 @@ class OC_Connector_Sabre_QuotaPlugin extends \Sabre\DAV\ServerPlugin { $this->server = $server; - $server->subscribeEvent('beforeWriteContent', array($this, 'checkQuota'), 10); - $server->subscribeEvent('beforeCreateFile', array($this, 'checkQuota'), 10); + $server->on('beforeWriteContent', array($this, 'checkQuota'), 10); + $server->on('beforeCreateFile', array($this, 'checkQuota'), 10); } /** @@ -88,11 +63,11 @@ class OC_Connector_Sabre_QuotaPlugin extends \Sabre\DAV\ServerPlugin { if (substr($uri, 0, 1) !== '/') { $uri = '/' . $uri; } - list($parentUri, $newName) = URLUtil::splitPath($uri); + list($parentUri, $newName) = \Sabre\HTTP\URLUtil::splitPath($uri); $req = $this->server->httpRequest; if ($req->getHeader('OC-Chunked')) { - $info = OC_FileChunking::decodeName($newName); - $chunkHandler = new OC_FileChunking($info); + $info = \OC_FileChunking::decodeName($newName); + $chunkHandler = new \OC_FileChunking($info); // subtract the already uploaded size to see whether // there is still enough space for the remaining chunks $length -= $chunkHandler->getCurrentSize(); diff --git a/lib/private/connector/sabre/request.php b/lib/private/connector/sabre/request.php deleted file mode 100644 index 35fd671edd..0000000000 --- a/lib/private/connector/sabre/request.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @author Lukas Reschke - * @author Stefan Herbrechtsmeier - * @author Thomas Müller - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -class OC_Connector_Sabre_Request extends \Sabre\HTTP\Request { - /** - * Returns the requested uri - * - * @return string - */ - public function getUri() { - return \OC::$server->getRequest()->getRequestUri(); - } - - /** - * Returns a specific item from the _SERVER array. - * - * Do not rely on this feature, it is for internal use only. - * - * @param string $field - * @return string - */ - public function getRawServerValue($field) { - if($field == 'REQUEST_URI') { - return $this->getUri(); - } - else{ - return isset($this->_SERVER[$field])?$this->_SERVER[$field]:null; - } - } -} diff --git a/lib/private/connector/sabre/server.php b/lib/private/connector/sabre/server.php index f17f46a81c..0dc81554d5 100644 --- a/lib/private/connector/sabre/server.php +++ b/lib/private/connector/sabre/server.php @@ -1,38 +1,13 @@ - * @author Jörn Friedrich Dreyer - * @author scolebrook - * @author Thomas Müller - * @author Vincent Petry + * Class \OC\Connector\Sabre\Server * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see + * This class overrides some methods from @see \Sabre\DAV\Server. * */ -class OC_Connector_Sabre_Server extends Sabre\DAV\Server { - - /** - * @var string - */ - private $overLoadedUri = null; - - /** - * @var boolean - */ - private $ignoreRangeHeader = false; +class Server extends \Sabre\DAV\Server { /** * @see \Sabre\DAV\Server @@ -40,261 +15,6 @@ class OC_Connector_Sabre_Server extends Sabre\DAV\Server { public function __construct($treeOrNode = null) { parent::__construct($treeOrNode); self::$exposeVersion = false; - } - - public function getRequestUri() { - - if (!is_null($this->overLoadedUri)) { - return $this->overLoadedUri; - } - - return parent::getRequestUri(); - } - - public function checkPreconditions($handleAsGET = false) { - // chunked upload handling - if (isset($_SERVER['HTTP_OC_CHUNKED'])) { - $filePath = parent::getRequestUri(); - list($path, $name) = \Sabre\DAV\URLUtil::splitPath($filePath); - $info = OC_FileChunking::decodeName($name); - if (!empty($info)) { - $filePath = $path . '/' . $info['name']; - $this->overLoadedUri = $filePath; - } - } - - $result = parent::checkPreconditions($handleAsGET); - $this->overLoadedUri = null; - return $result; - } - - public function getHTTPRange() { - if ($this->ignoreRangeHeader) { - return null; - } - return parent::getHTTPRange(); - } - - protected function httpGet($uri) { - $range = $this->getHTTPRange(); - - if (OC_App::isEnabled('files_encryption') && $range) { - // encryption does not support range requests - $this->ignoreRangeHeader = true; - } - return parent::httpGet($uri); - } - - /** - * @see \Sabre\DAV\Server - */ - protected function httpPropfind($uri) { - - // $xml = new \Sabre\DAV\XMLReader(file_get_contents('php://input')); - $requestedProperties = $this->parsePropFindRequest($this->httpRequest->getBody(true)); - - $depth = $this->getHTTPDepth(1); - // The only two options for the depth of a propfind is 0 or 1 - // if ($depth!=0) $depth = 1; - - $newProperties = $this->getPropertiesForPath($uri,$requestedProperties,$depth); - - // This is a multi-status response - $this->httpResponse->sendStatus(207); - $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->httpResponse->setHeader('Vary','Brief,Prefer'); - - // Normally this header is only needed for OPTIONS responses, however.. - // iCal seems to also depend on these being set for PROPFIND. Since - // this is not harmful, we'll add it. - $features = array('1','3', 'extended-mkcol'); - foreach($this->plugins as $plugin) { - $features = array_merge($features,$plugin->getFeatures()); - } - - $this->httpResponse->setHeader('DAV',implode(', ',$features)); - - $prefer = $this->getHTTPPrefer(); - $minimal = $prefer['return-minimal']; - - $data = $this->generateMultiStatus($newProperties, $minimal); - $this->httpResponse->sendBody($data); - - } - - /** - * Small helper to support PROPFIND with DEPTH_INFINITY. - * @param string $path - */ - private function addPathNodesRecursively(&$nodes, $path) { - foreach($this->tree->getChildren($path) as $childNode) { - $nodes[$path . '/' . $childNode->getName()] = $childNode; - if ($childNode instanceof \Sabre\DAV\ICollection) - $this->addPathNodesRecursively($nodes, $path . '/' . $childNode->getName()); - } - } - - public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) { - - // if ($depth!=0) $depth = 1; - - $path = rtrim($path,'/'); - - // This event allows people to intercept these requests early on in the - // process. - // - // We're not doing anything with the result, but this can be helpful to - // pre-fetch certain expensive live properties. - $this->broadCastEvent('beforeGetPropertiesForPath', array($path, $propertyNames, $depth)); - - $returnPropertyList = array(); - - $parentNode = $this->tree->getNodeForPath($path); - $nodes = array( - $path => $parentNode - ); - if ($depth==1 && $parentNode instanceof \Sabre\DAV\ICollection) { - foreach($this->tree->getChildren($path) as $childNode) - $nodes[$path . '/' . $childNode->getName()] = $childNode; - } else if ($depth == self::DEPTH_INFINITY && $parentNode instanceof \Sabre\DAV\ICollection) { - $this->addPathNodesRecursively($nodes, $path); - } - - // If the propertyNames array is empty, it means all properties are requested. - // We shouldn't actually return everything we know though, and only return a - // sensible list. - $allProperties = count($propertyNames)==0; - - foreach($nodes as $myPath=>$node) { - - $currentPropertyNames = $propertyNames; - - $newProperties = array( - '200' => array(), - '404' => array(), - ); - - if ($allProperties) { - // Default list of propertyNames, when all properties were requested. - $currentPropertyNames = array( - '{DAV:}getlastmodified', - '{DAV:}getcontentlength', - '{DAV:}resourcetype', - '{DAV:}quota-used-bytes', - '{DAV:}quota-available-bytes', - '{DAV:}getetag', - '{DAV:}getcontenttype', - ); - } - - // If the resourceType was not part of the list, we manually add it - // and mark it for removal. We need to know the resourcetype in order - // to make certain decisions about the entry. - // WebDAV dictates we should add a / and the end of href's for collections - $removeRT = false; - if (!in_array('{DAV:}resourcetype',$currentPropertyNames)) { - $currentPropertyNames[] = '{DAV:}resourcetype'; - $removeRT = true; - } - - $result = $this->broadcastEvent('beforeGetProperties',array($myPath, $node, &$currentPropertyNames, &$newProperties)); - // If this method explicitly returned false, we must ignore this - // node as it is inaccessible. - if ($result===false) continue; - - if (count($currentPropertyNames) > 0) { - - if ($node instanceof \Sabre\DAV\IProperties) { - $nodeProperties = $node->getProperties($currentPropertyNames); - - // The getProperties method may give us too much, - // properties, in case the implementor was lazy. - // - // So as we loop through this list, we will only take the - // properties that were actually requested and discard the - // rest. - foreach($currentPropertyNames as $k=>$currentPropertyName) { - if (isset($nodeProperties[$currentPropertyName])) { - unset($currentPropertyNames[$k]); - $newProperties[200][$currentPropertyName] = $nodeProperties[$currentPropertyName]; - } - } - - } - - } - - foreach($currentPropertyNames as $prop) { - - if (isset($newProperties[200][$prop])) continue; - - switch($prop) { - case '{DAV:}getlastmodified' : if ($node->getLastModified()) $newProperties[200][$prop] = new \Sabre\DAV\Property\GetLastModified($node->getLastModified()); break; - case '{DAV:}getcontentlength' : - if ($node instanceof \Sabre\DAV\IFile) { - $size = $node->getSize(); - if (!is_null($size)) { - $newProperties[200][$prop] = 0 + $size; - } - } - break; - case '{DAV:}quota-used-bytes' : - if ($node instanceof \Sabre\DAV\IQuota) { - $quotaInfo = $node->getQuotaInfo(); - $newProperties[200][$prop] = $quotaInfo[0]; - } - break; - case '{DAV:}quota-available-bytes' : - if ($node instanceof \Sabre\DAV\IQuota) { - $quotaInfo = $node->getQuotaInfo(); - $newProperties[200][$prop] = $quotaInfo[1]; - } - break; - case '{DAV:}getetag' : if ($node instanceof \Sabre\DAV\IFile && $etag = $node->getETag()) $newProperties[200][$prop] = $etag; break; - case '{DAV:}getcontenttype' : if ($node instanceof \Sabre\DAV\IFile && $ct = $node->getContentType()) $newProperties[200][$prop] = $ct; break; - case '{DAV:}supported-report-set' : - $reports = array(); - foreach($this->plugins as $plugin) { - $reports = array_merge($reports, $plugin->getSupportedReportSet($myPath)); - } - $newProperties[200][$prop] = new \Sabre\DAV\Property\SupportedReportSet($reports); - break; - case '{DAV:}resourcetype' : - $newProperties[200]['{DAV:}resourcetype'] = new \Sabre\DAV\Property\ResourceType(); - foreach($this->resourceTypeMapping as $className => $resourceType) { - if ($node instanceof $className) $newProperties[200]['{DAV:}resourcetype']->add($resourceType); - } - break; - - } - - // If we were unable to find the property, we will list it as 404. - if (!$allProperties && !isset($newProperties[200][$prop])) $newProperties[404][$prop] = null; - - } - - $this->broadcastEvent('afterGetProperties',array(trim($myPath,'/'),&$newProperties, $node)); - - $newProperties['href'] = trim($myPath,'/'); - - // Its is a WebDAV recommendation to add a trailing slash to collectionnames. - // Apple's iCal also requires a trailing slash for principals (rfc 3744), though this is non-standard. - if ($myPath!='' && isset($newProperties[200]['{DAV:}resourcetype'])) { - $rt = $newProperties[200]['{DAV:}resourcetype']; - if ($rt->is('{DAV:}collection') || $rt->is('{DAV:}principal')) { - $newProperties['href'] .='/'; - } - } - - // If the resourcetype property was manually added to the requested property list, - // we will remove it again. - if ($removeRT) unset($newProperties[200]['{DAV:}resourcetype']); - - $returnPropertyList[] = $newProperties; - - } - - return $returnPropertyList; - + $this->enablePropfindDepthInfinity = true; } } diff --git a/lib/private/connector/sabre/taglist.php b/lib/private/connector/sabre/taglist.php index 22b422c20b..97f9e79757 100644 --- a/lib/private/connector/sabre/taglist.php +++ b/lib/private/connector/sabre/taglist.php @@ -83,9 +83,10 @@ class TagList extends DAV\Property { * It will only decode tag values. * * @param \DOMElement $dom + * @param array $propertyMap * @return \OC\Connector\Sabre\TagList */ - static function unserialize(\DOMElement $dom) { + static function unserialize(\DOMElement $dom, array $propertyMap) { $tags = array(); foreach($dom->childNodes as $child) { diff --git a/lib/private/connector/sabre/tagsplugin.php b/lib/private/connector/sabre/tagsplugin.php index 87de08d333..7756eb45bd 100644 --- a/lib/private/connector/sabre/tagsplugin.php +++ b/lib/private/connector/sabre/tagsplugin.php @@ -41,6 +41,9 @@ namespace OC\Connector\Sabre; * */ +use \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; + class TagsPlugin extends \Sabre\DAV\ServerPlugin { @@ -76,13 +79,19 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin private $cachedTags; /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @param \Sabre\DAV\Tree $tree tree * @param \OCP\ITagManager $tagManager tag manager */ - public function __construct(\Sabre\DAV\ObjectTree $objectTree, \OCP\ITagManager $tagManager) { - $this->objectTree = $objectTree; + public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) { + $this->tree = $tree; $this->tagManager = $tagManager; $this->tagger = null; - $this->cachedTags = null; + $this->cachedTags = array(); } /** @@ -102,25 +111,8 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin $server->propertyMap[self::TAGS_PROPERTYNAME] = 'OC\\Connector\\Sabre\\TagList'; $this->server = $server; - $this->server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties')); - $this->server->subscribeEvent('beforeGetPropertiesForPath', array($this, 'beforeGetPropertiesForPath')); - $this->server->subscribeEvent('updateProperties', array($this, 'updateProperties')); - } - - /** - * Searches and removes a value from the given array - * - * @param array $requestedProps - * @param string $propName to remove - * @return boolean true if the property was present, false otherwise - */ - private function findAndRemoveProperty(&$requestedProps, $propName) { - $index = array_search($propName, $requestedProps); - if ($index !== false) { - unset($requestedProps[$index]); - return true; - } - return false; + $this->server->on('propFind', array($this, 'handleGetProperties')); + $this->server->on('propPatch', array($this, 'handleUpdateProperties')); } /** @@ -166,7 +158,10 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin return $this->cachedTags[$fileId]; } else { $tags = $this->getTagger()->getTagsForObjects(array($fileId)); - if ($tags) { + if ($tags !== false) { + if (empty($tags)) { + return array(); + } return current($tags); } } @@ -199,109 +194,99 @@ class TagsPlugin extends \Sabre\DAV\ServerPlugin } } - /** - * Pre-fetch tags info - * - * @param string $path - * @param array $requestedProperties - * @param integer $depth - * @return void - */ - public function beforeGetPropertiesForPath( - $path, - array $requestedProperties, - $depth - ) { - $node = $this->objectTree->getNodeForPath($path); - if (!($node instanceof \OC_Connector_Sabre_Directory)) { - return; - } - - if ($this->findAndRemoveProperty($requestedProperties, self::TAGS_PROPERTYNAME) - || $this->findAndRemoveProperty($requestedProperties, self::FAVORITE_PROPERTYNAME) - ) { - $fileIds = array(); - // note: pre-fetching only supported for depth <= 1 - $folderContent = $node->getChildren(); - // TODO: refactor somehow with the similar array that is created - // in getChildren() - foreach ($folderContent as $info) { - $fileIds[] = $info->getId(); - } - $tags = $this->getTagger()->getTagsForObjects($fileIds); - if ($tags) { - $this->cachedTags = $tags; - } - } - } - /** * Adds tags and favorites properties to the response, * if requested. * - * @param string $path + * @param PropFind $propFind * @param \Sabre\DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties * @return void */ - public function beforeGetProperties( - $path, - \Sabre\DAV\INode $node, - array &$requestedProperties, - array &$returnedProperties + public function handleGetProperties( + PropFind $propFind, + \Sabre\DAV\INode $node ) { - if (!($node instanceof \OC_Connector_Sabre_Node)) { + if (!($node instanceof \OC\Connector\Sabre\Node)) { return; } + // need prefetch ? + if ($node instanceof \OC\Connector\Sabre\Directory + && $propFind->getDepth() !== 0 + && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) + || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) + )) { + // note: pre-fetching only supported for depth <= 1 + $folderContent = $node->getChildren(); + $fileIds[] = (int)$node->getId(); + foreach ($folderContent as $info) { + $fileIds[] = (int)$info->getId(); + } + $tags = $this->getTagger()->getTagsForObjects($fileIds); + if ($tags === false) { + // the tags API returns false on error... + $tags = array(); + } + + $this->cachedTags = $this->cachedTags + $tags; + $emptyFileIds = array_diff($fileIds, array_keys($tags)); + // also cache the ones that were not found + foreach ($emptyFileIds as $fileId) { + $this->cachedTags[$fileId] = []; + } + } + $tags = null; $isFav = null; - if ($this->findAndRemoveProperty($requestedProperties, self::TAGS_PROPERTYNAME)) { + + $propFind->handle(self::TAGS_PROPERTYNAME, function() use ($tags, &$isFav, $node) { list($tags, $isFav) = $this->getTagsAndFav($node->getId()); - $returnedProperties[200][self::TAGS_PROPERTYNAME] = new TagList($tags); - } - if ($this->findAndRemoveProperty($requestedProperties, self::FAVORITE_PROPERTYNAME)) { - if (is_null($tags)) { - list($tags, $isFav) = $this->getTagsAndFav($node->getId()); + return new TagList($tags); + }); + + $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) { + if (is_null($isFav)) { + list(, $isFav) = $this->getTagsAndFav($node->getId()); } - $returnedProperties[200][self::FAVORITE_PROPERTYNAME] = $isFav; - } + return $isFav; + }); } /** * Updates tags and favorites properties, if applicable. * * @param string $path - * @param \Sabre\DAV\INode $node - * @param array $requestedProperties - * @param array $returnedProperties - * @return bool success status + * @param PropPatch $propPatch + * + * @return void */ - public function updateProperties(array &$properties, array &$result, \Sabre\DAV\INode $node) { - if (!($node instanceof \OC_Connector_Sabre_Node)) { - return; - } - - $fileId = $node->getId(); - if (isset($properties[self::TAGS_PROPERTYNAME])) { - $tagsProp = $properties[self::TAGS_PROPERTYNAME]; - unset($properties[self::TAGS_PROPERTYNAME]); - $this->updateTags($fileId, $tagsProp->getTags()); - $result[200][self::TAGS_PROPERTYNAME] = new TagList($tagsProp->getTags()); - } - if (isset($properties[self::FAVORITE_PROPERTYNAME])) { - $favState = $properties[self::FAVORITE_PROPERTYNAME]; - unset($properties[self::FAVORITE_PROPERTYNAME]); - if ((int)$favState === 1 || $favState === 'true') { - $favState = true; - $this->getTagger()->tagAs($fileId, self::TAG_FAVORITE); - } else { - $favState = false; - $this->getTagger()->unTag($fileId, self::TAG_FAVORITE); + public function handleUpdateProperties($path, PropPatch $propPatch) { + $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($path) { + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; } - $result[200][self::FAVORITE_PROPERTYNAME] = $favState; - } - return true; + $this->updateTags($node->getId(), $tagList->getTags()); + return true; + }); + + $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($path) { + $node = $this->tree->getNodeForPath($path); + if (is_null($node)) { + return 404; + } + if ((int)$favState === 1 || $favState === 'true') { + $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE); + } else { + $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE); + } + + if (is_null($favState)) { + // confirm deletion + return 204; + } + + return 200; + }); } } diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php index 56aa5328c7..d8df8f948c 100644 --- a/lib/private/files/storage/common.php +++ b/lib/private/files/storage/common.php @@ -382,7 +382,7 @@ abstract class Common implements \OC\Files\Storage\Storage { * @return string|false */ public function getETag($path) { - $ETagFunction = \OC_Connector_Sabre_Node::$ETagFunction; + $ETagFunction = \OC\Connector\Sabre\Node::$ETagFunction; if ($ETagFunction) { $hash = call_user_func($ETagFunction, $path); return $hash; diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php index 0d70f612ec..477a3b499c 100644 --- a/lib/private/files/storage/dav.php +++ b/lib/private/files/storage/dav.php @@ -38,7 +38,7 @@ namespace OC\Files\Storage; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; -use Sabre\DAV\Exception; +use Sabre\DAV\ClientHttpException; class DAV extends \OC\Files\Storage\Common { protected $password; @@ -104,6 +104,7 @@ class DAV extends \OC\Files\Storage\Common { ); $this->client = new \Sabre\DAV\Client($settings); + $this->client->setThrowExceptions(true); if ($this->secure === true && $this->certPath) { $this->client->addTrustedCertificates($this->certPath); @@ -152,9 +153,10 @@ class DAV extends \OC\Files\Storage\Common { } \OC\Files\Stream\Dir::register($id, $content); return opendir('fakedir://' . $id); - } catch (Exception\NotFound $e) { - return false; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -174,9 +176,10 @@ class DAV extends \OC\Files\Storage\Common { $responseType = $response["{DAV:}resourcetype"]->resourceType; } return (count($responseType) > 0 and $responseType[0] == "{DAV:}collection") ? 'dir' : 'file'; - } catch (Exception\NotFound $e) { - return false; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -192,9 +195,10 @@ class DAV extends \OC\Files\Storage\Common { try { $this->client->propfind($this->encodePath($path), array('{DAV:}resourcetype')); return true; //no 404 exception - } catch (Exception\NotFound $e) { - return false; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -311,9 +315,10 @@ class DAV extends \OC\Files\Storage\Common { if ($this->file_exists($path)) { try { $this->client->proppatch($this->encodePath($path), array('{DAV:}lastmodified' => $mtime)); - } catch (Exception\NotImplemented $e) { - return false; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 501) { + return false; + } $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -367,7 +372,7 @@ class DAV extends \OC\Files\Storage\Common { $this->removeCachedFile($path1); $this->removeCachedFile($path2); return true; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -385,7 +390,7 @@ class DAV extends \OC\Files\Storage\Common { $this->client->request('COPY', $path1, null, array('Destination' => $path2)); $this->removeCachedFile($path2); return true; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -404,11 +409,12 @@ class DAV extends \OC\Files\Storage\Common { 'mtime' => strtotime($response['{DAV:}getlastmodified']), 'size' => (int)isset($response['{DAV:}getcontentlength']) ? $response['{DAV:}getcontentlength'] : 0, ); - } catch (Exception\NotFound $e) { - return array(); - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return array(); + } $this->convertSabreException($e); - return false; + return array(); } catch (\Exception $e) { // TODO: log for now, but in the future need to wrap/rethrow exception \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); @@ -433,9 +439,10 @@ class DAV extends \OC\Files\Storage\Common { } else { return false; } - } catch (Exception\NotFound $e) { - return false; - } catch (\Sabre\DAV\Exception $e) { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404) { + return false; + } $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -478,14 +485,11 @@ class DAV extends \OC\Files\Storage\Common { try { $response = $this->client->request($method, $this->encodePath($path), $body); return $response['statusCode'] == $expected; - } catch (Exception\NotFound $e) { - if ($method === 'DELETE') { + } catch (ClientHttpException $e) { + if ($e->getHttpStatus() === 404 && $method === 'DELETE') { return false; } - $this->convertSabreException($e); - return false; - } catch (\Sabre\DAV\Exception $e) { $this->convertSabreException($e); return false; } catch (\Exception $e) { @@ -591,9 +595,10 @@ class DAV extends \OC\Files\Storage\Common { $remoteMtime = strtotime($response['{DAV:}getlastmodified']); return $remoteMtime > $time; } - } catch (Exception\NotFound $e) { - return false; } catch (Exception $e) { + if ($e->getHttpStatus() === 404) { + return false; + } $this->convertSabreException($e); return false; } @@ -603,19 +608,19 @@ class DAV extends \OC\Files\Storage\Common { * Convert sabre DAV exception to a storage exception, * then throw it * - * @param \Sabre\Dav\Exception $e sabre exception + * @param ClientException $e sabre exception * @throws StorageInvalidException if the storage is invalid, for example * when the authentication expired or is invalid * @throws StorageNotAvailableException if the storage is not available, * which might be temporary */ - private function convertSabreException(\Sabre\Dav\Exception $e) { + private function convertSabreException(ClientException $e) { \OCP\Util::writeLog('files_external', $e->getMessage(), \OCP\Util::ERROR); - if ($e instanceof \Sabre\DAV\Exception\NotAuthenticated) { + if ($e->getHttpStatus() === 401) { // either password was changed or was invalid all along throw new StorageInvalidException(get_class($e).': '.$e->getMessage()); - } else if ($e instanceof \Sabre\DAV\Exception\MethodNotAllowed) { - // ignore exception, false will be returned + } else if ($e->getHttpStatus() === 405) { + // ignore exception for MethodNotAllowed, false will be returned return; } diff --git a/lib/private/vobject/compoundproperty.php b/lib/private/vobject/compoundproperty.php deleted file mode 100644 index aaeeeed527..0000000000 --- a/lib/private/vobject/compoundproperty.php +++ /dev/null @@ -1,69 +0,0 @@ - - * @author Thomas Tanghus - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC\VObject; - -/** - * This class overrides \Sabre\VObject\Property::serialize() to not - * double escape commas and semi-colons in compound properties. -*/ -class CompoundProperty extends \Sabre\VObject\Property\Compound { - - /** - * Turns the object back into a serialized blob. - * - * @return string - */ - public function serialize() { - - $str = $this->name; - if ($this->group) { - $str = $this->group . '.' . $this->name; - } - - foreach($this->parameters as $param) { - $str.=';' . $param->serialize(); - } - $src = array( - "\n", - ); - $out = array( - '\n', - ); - $str.=':' . str_replace($src, $out, $this->value); - - $out = ''; - while(strlen($str) > 0) { - if (strlen($str) > 75) { - $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; - $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); - } else { - $out .= $str . "\r\n"; - $str = ''; - break; - } - } - - return $out; - - } - -} diff --git a/lib/private/vobject/stringproperty.php b/lib/private/vobject/stringproperty.php deleted file mode 100644 index a091156306..0000000000 --- a/lib/private/vobject/stringproperty.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @author Thomas Tanghus - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see - * - */ -namespace OC\VObject; - -/** - * This class overrides \Sabre\VObject\Property::serialize() properly - * escape commas and semi-colons in string properties. -*/ -class StringProperty extends \Sabre\VObject\Property { - - /** - * Turns the object back into a serialized blob. - * - * @return string - */ - public function serialize() { - - $str = $this->name; - if ($this->group) { - $str = $this->group . '.' . $this->name; - } - - foreach($this->parameters as $param) { - $str.=';' . $param->serialize(); - } - - $src = array( - '\\', - "\n", - ';', - ',', - ); - $out = array( - '\\\\', - '\n', - '\;', - '\,', - ); - $value = strtr($this->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\')); - $str.=':' . str_replace($src, $out, $value); - - $out = ''; - while(strlen($str) > 0) { - if (strlen($str) > 75) { - $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; - $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); - } else { - $out .= $str . "\r\n"; - $str = ''; - break; - } - } - - return $out; - - } - -} diff --git a/tests/lib/connector/sabre/custompropertiesbackend.php b/tests/lib/connector/sabre/custompropertiesbackend.php new file mode 100644 index 0000000000..ee0c3c4e53 --- /dev/null +++ b/tests/lib/connector/sabre/custompropertiesbackend.php @@ -0,0 +1,248 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class CustomPropertiesBackend extends \Test\TestCase { + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OC\Connector\Sabre\CustomPropertiesBackend + */ + private $plugin; + + /** + * @var \OCP\IUser + */ + private $user; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + + $userId = $this->getUniqueID('testcustompropertiesuser'); + + $this->user = $this->getMock('\OCP\IUser'); + $this->user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue($userId)); + + $this->plugin = new \OC\Connector\Sabre\CustomPropertiesBackend( + $this->tree, + \OC::$server->getDatabaseConnection(), + $this->user + ); + } + + public function tearDown() { + $connection = \OC::$server->getDatabaseConnection(); + $deleteStatement = $connection->prepare( + 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ?' + ); + $deleteStatement->execute( + array( + $this->user->getUID(), + ) + ); + $deleteStatement->closeCursor(); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $node->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath')); + + return $node; + } + + private function applyDefaultProps($path = '/dummypath') { + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => 'value1', + 'customprop2' => 'value2', + )); + + $this->plugin->propPatch( + $path, + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result['customprop']); + $this->assertEquals(200, $result['customprop2']); + } + + /** + * Test setting/getting properties + */ + public function testSetGetPropertiesForFile() { + $node = $this->createTestNode('\OC\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propFind = new \Sabre\DAV\PropFind( + '/dummypath', + array( + 'customprop', + 'customprop2', + 'unsetprop', + ), + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFind + ); + + $this->assertEquals('value1', $propFind->get('customprop')); + $this->assertEquals('value2', $propFind->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFind->get404Properties()); + } + + /** + * Test getting properties from directory + */ + public function testGetPropertiesForDirectory() { + $rootNode = $this->createTestNode('\OC\Connector\Sabre\Directory'); + + $nodeSub = $this->getMockBuilder('\OC\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $nodeSub->expects($this->any()) + ->method('getId') + ->will($this->returnValue(456)); + + $nodeSub->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/dummypath/test.txt')); + + $rootNode->expects($this->once()) + ->method('getChildren') + ->will($this->returnValue(array($nodeSub))); + + $this->tree->expects($this->at(0)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(1)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->tree->expects($this->at(2)) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($rootNode)); + + $this->tree->expects($this->at(3)) + ->method('getNodeForPath') + ->with('/dummypath/test.txt') + ->will($this->returnValue($nodeSub)); + + $this->applyDefaultProps('/dummypath'); + $this->applyDefaultProps('/dummypath/test.txt'); + + $propNames = array( + 'customprop', + 'customprop2', + 'unsetprop', + ); + + $propFindRoot = new \Sabre\DAV\PropFind( + '/dummypath', + $propNames, + 1 + ); + + $propFindSub = new \Sabre\DAV\PropFind( + '/dummypath/test.txt', + $propNames, + 0 + ); + + $this->plugin->propFind( + '/dummypath', + $propFindRoot + ); + + $this->plugin->propFind( + '/dummypath/test.txt', + $propFindSub + ); + + // TODO: find a way to assert that no additional SQL queries were + // run while doing the second propFind + + $this->assertEquals('value1', $propFindRoot->get('customprop')); + $this->assertEquals('value2', $propFindRoot->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindRoot->get404Properties()); + + $this->assertEquals('value1', $propFindSub->get('customprop')); + $this->assertEquals('value2', $propFindSub->get('customprop2')); + $this->assertEquals(array('unsetprop'), $propFindSub->get404Properties()); + } + + /** + * Test delete property + */ + public function testDeleteProperty() { + $node = $this->createTestNode('\OC\Connector\Sabre\File'); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->applyDefaultProps(); + + $propPatch = new \Sabre\DAV\PropPatch(array( + 'customprop' => null, + )); + + $this->plugin->propPatch( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(204, $result['customprop']); + } +} diff --git a/tests/lib/connector/sabre/directory.php b/tests/lib/connector/sabre/directory.php index 599a6ca3f7..e7fbd1d27b 100644 --- a/tests/lib/connector/sabre/directory.php +++ b/tests/lib/connector/sabre/directory.php @@ -27,7 +27,7 @@ class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { ->method('getPath') ->will($this->returnValue('')); - return new OC_Connector_Sabre_Directory($this->view, $this->info); + return new \OC\Connector\Sabre\Directory($this->view, $this->info); } /** @@ -131,7 +131,7 @@ class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { ->method('getRelativePath') ->will($this->returnValue('')); - $dir = new OC_Connector_Sabre_Directory($this->view, $this->info); + $dir = new \OC\Connector\Sabre\Directory($this->view, $this->info); $nodes = $dir->getChildren(); $this->assertEquals(2, count($nodes)); @@ -139,21 +139,6 @@ class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { // calling a second time just returns the cached values, // does not call getDirectoryContents again $nodes = $dir->getChildren(); - - $properties = array('testprop', OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME); - $this->assertEquals(2, count($nodes)); - $this->assertEquals( - array( - OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME => '"abc"' - ), - $nodes[0]->getProperties($properties) - ); - $this->assertEquals( - array( - OC_Connector_Sabre_Node::GETETAG_PROPERTYNAME => '"def"' - ), - $nodes[1]->getProperties($properties) - ); } public function testGetQuotaInfo() { @@ -182,7 +167,7 @@ class Test_OC_Connector_Sabre_Directory extends \Test\TestCase { ->method('getStorage') ->will($this->returnValue($storage)); - $dir = new OC_Connector_Sabre_Directory($this->view, $this->info); + $dir = new \OC\Connector\Sabre\Directory($this->view, $this->info); $this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free } } diff --git a/tests/lib/connector/sabre/file.php b/tests/lib/connector/sabre/file.php index 33dc78f87d..2ef5fd794b 100644 --- a/tests/lib/connector/sabre/file.php +++ b/tests/lib/connector/sabre/file.php @@ -26,7 +26,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions'=>\OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->put('test data'); @@ -52,7 +52,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); $this->assertNotEmpty($file->put('test data')); } @@ -86,7 +86,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->put('test data'); @@ -109,7 +109,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { $info = new \OC\Files\FileInfo('/super*star.txt', null, null, array( 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->put('test data'); @@ -130,7 +130,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { $info = new \OC\Files\FileInfo('/super*star.txt', null, null, array( 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); $file->setName('/super*star.txt'); } @@ -163,7 +163,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->put('test data'); @@ -185,7 +185,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->delete(); @@ -203,7 +203,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => 0 ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->delete(); @@ -226,7 +226,7 @@ class Test_OC_Connector_Sabre_File extends \Test\TestCase { 'permissions' => \OCP\Constants::PERMISSION_ALL ), null); - $file = new OC_Connector_Sabre_File($view, $info); + $file = new \OC\Connector\Sabre\File($view, $info); // action $file->delete(); diff --git a/tests/lib/connector/sabre/filesplugin.php b/tests/lib/connector/sabre/filesplugin.php new file mode 100644 index 0000000000..54d43d66dd --- /dev/null +++ b/tests/lib/connector/sabre/filesplugin.php @@ -0,0 +1,174 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class FilesPlugin extends \Test\TestCase { + const GETETAG_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::GETETAG_PROPERTYNAME; + const FILEID_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::FILEID_PROPERTYNAME; + const SIZE_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::SIZE_PROPERTYNAME; + const PERMISSIONS_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::PERMISSIONS_PROPERTYNAME; + const GETLASTMODIFIED_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::GETLASTMODIFIED_PROPERTYNAME; + const DOWNLOADURL_PROPERTYNAME = \OC\Connector\Sabre\FilesPlugin::DOWNLOADURL_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\ObjectTree + */ + private $tree; + + /** + * @var \OC\Connector\Sabre\FilesPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->plugin = new \OC\Connector\Sabre\FilesPlugin($this->tree); + $this->plugin->initialize($this->server); + } + + private function createTestNode($class) { + $node = $this->getMockBuilder($class) + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $node->expects($this->any()) + ->method('getFileId') + ->will($this->returnValue(123)); + $node->expects($this->any()) + ->method('getEtag') + ->will($this->returnValue('"abc"')); + $node->expects($this->any()) + ->method('getDavPermissions') + ->will($this->returnValue('R')); + + return $node; + } + + /** + */ + public function testGetPropertiesForFile() { + $node = $this->createTestNode('\OC\Connector\Sabre\File'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->once()) + ->method('getDirectDownload') + ->will($this->returnValue(array('url' => 'http://example.com/'))); + $node->expects($this->never()) + ->method('getSize'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('R', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::SIZE_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testGetPropertiesForDirectory() { + $node = $this->createTestNode('\OC\Connector\Sabre\Directory'); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + array( + self::GETETAG_PROPERTYNAME, + self::FILEID_PROPERTYNAME, + self::SIZE_PROPERTYNAME, + self::PERMISSIONS_PROPERTYNAME, + self::DOWNLOADURL_PROPERTYNAME, + ), + 0 + ); + + $node->expects($this->never()) + ->method('getDirectDownload'); + $node->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(1025)); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); + $this->assertEquals(123, $propFind->get(self::FILEID_PROPERTYNAME)); + $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals('R', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(array(self::DOWNLOADURL_PROPERTYNAME), $propFind->get404Properties()); + } + + public function testUpdateProps() { + $node = $this->createTestNode('\OC\Connector\Sabre\File'); + + $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT'; + + $node->expects($this->once()) + ->method('touch') + ->with($testDate); + + $node->expects($this->once()) + ->method('setEtag') + ->with('newetag') + ->will($this->returnValue(true)); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::GETETAG_PROPERTYNAME => 'newetag', + self::GETLASTMODIFIED_PROPERTYNAME => $testDate + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::GETLASTMODIFIED_PROPERTYNAME]); + $this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]); + } + +} diff --git a/tests/lib/connector/sabre/node.php b/tests/lib/connector/sabre/node.php index 1e927deed4..e1ae05b217 100644 --- a/tests/lib/connector/sabre/node.php +++ b/tests/lib/connector/sabre/node.php @@ -49,7 +49,7 @@ class Node extends \Test\TestCase { ->will($this->returnValue($type)); $view = $this->getMock('\OC\Files\View'); - $node = new \OC_Connector_Sabre_File($view, $info); + $node = new \OC\Connector\Sabre\File($view, $info); $this->assertEquals($expected, $node->getDavPermissions()); } } diff --git a/tests/lib/connector/sabre/objecttree.php b/tests/lib/connector/sabre/objecttree.php index 2548066214..3c972fe6f0 100644 --- a/tests/lib/connector/sabre/objecttree.php +++ b/tests/lib/connector/sabre/objecttree.php @@ -10,7 +10,7 @@ namespace Test\OC\Connector\Sabre; use OC\Files\FileInfo; -use OC_Connector_Sabre_Directory; +use OC\Connector\Sabre\Directory; use PHPUnit_Framework_TestCase; class TestDoubleFileView extends \OC\Files\View { @@ -103,7 +103,7 @@ class ObjectTree extends \Test\TestCase { $info = new FileInfo('', null, null, array(), null); - $rootDir = new OC_Connector_Sabre_Directory($view, $info); + $rootDir = new Directory($view, $info); $objectTree = $this->getMock('\OC\Connector\Sabre\ObjectTree', array('nodeExists', 'getNodeForPath'), array($rootDir, $view)); @@ -119,4 +119,123 @@ class ObjectTree extends \Test\TestCase { $objectTree->move($source, $dest); } + /** + * @dataProvider nodeForPathProvider + */ + public function testGetNodeForPath( + $inputFileName, + $fileInfoQueryPath, + $outputFileName, + $type, + $enableChunkingHeader + ) { + + if ($enableChunkingHeader) { + $_SERVER['HTTP_OC_CHUNKED'] = true; + } + + $rootNode = $this->getMockBuilder('\OC\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $mountManager = $this->getMock('\OC\Files\Mount\Manager'); + $view = $this->getMock('\OC\Files\View'); + $fileInfo = $this->getMock('\OCP\Files\FileInfo'); + $fileInfo->expects($this->once()) + ->method('getType') + ->will($this->returnValue($type)); + $fileInfo->expects($this->once()) + ->method('getName') + ->will($this->returnValue($outputFileName)); + + $view->expects($this->once()) + ->method('getFileInfo') + ->with($fileInfoQueryPath) + ->will($this->returnValue($fileInfo)); + + $tree = new \OC\Connector\Sabre\ObjectTree(); + $tree->init($rootNode, $view, $mountManager); + + $node = $tree->getNodeForPath($inputFileName); + + $this->assertNotNull($node); + $this->assertEquals($outputFileName, $node->getName()); + + if ($type === 'file') { + $this->assertTrue($node instanceof \OC\Connector\Sabre\File); + } else { + $this->assertTrue($node instanceof \OC\Connector\Sabre\Directory); + } + + unset($_SERVER['HTTP_OC_CHUNKED']); + } + + function nodeForPathProvider() { + return array( + // regular file + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + false + ), + // regular file with chunking + array( + 'regularfile.txt', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular directory with chunking + array( + 'regulardir', + 'regulardir', + 'regulardir', + 'dir', + true + ), + // file with chunky file name + array( + 'regularfile.txt-chunking-123566789-10-1', + 'regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + // regular file in subdir + array( + 'subdir/regularfile.txt', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + false + ), + // regular directory in subdir + array( + 'subdir/regulardir', + 'subdir/regulardir', + 'regulardir', + 'dir', + false + ), + // file with chunky file name in subdir + array( + 'subdir/regularfile.txt-chunking-123566789-10-1', + 'subdir/regularfile.txt', + 'regularfile.txt', + 'file', + true + ), + ); + } + } diff --git a/tests/lib/connector/sabre/principal.php b/tests/lib/connector/sabre/principal.php index 5d13aa4421..1841a79bec 100644 --- a/tests/lib/connector/sabre/principal.php +++ b/tests/lib/connector/sabre/principal.php @@ -10,6 +10,7 @@ namespace Test\Connector\Sabre; +use \Sabre\DAV\PropPatch; use OCP\IUserManager; use OCP\IConfig; @@ -240,7 +241,7 @@ class Principal extends \Test\TestCase { } public function testUpdatePrincipal() { - $this->assertSame(0, $this->connector->updatePrincipal('foo', [])); + $this->assertSame(0, $this->connector->updatePrincipal('foo', new PropPatch(array()))); } public function testSearchPrincipals() { diff --git a/tests/lib/connector/sabre/quotaplugin.php b/tests/lib/connector/sabre/quotaplugin.php index f08637854c..48f8f319ae 100644 --- a/tests/lib/connector/sabre/quotaplugin.php +++ b/tests/lib/connector/sabre/quotaplugin.php @@ -14,14 +14,14 @@ class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { private $server; /** - * @var OC_Connector_Sabre_QuotaPlugin + * @var \OC\Connector\Sabre\QuotaPlugin */ private $plugin; private function init($quota) { $view = $this->buildFileViewMock($quota); $this->server = new \Sabre\DAV\Server(); - $this->plugin = new OC_Connector_Sabre_QuotaPlugin($view); + $this->plugin = new \OC\Connector\Sabre\QuotaPlugin($view); $this->plugin->initialize($this->server); } @@ -30,7 +30,7 @@ class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { */ public function testLength($expected, $headers) { $this->init(0); - $this->server->httpRequest = new \Sabre\HTTP\Request($headers); + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); $length = $this->plugin->getLength(); $this->assertEquals($expected, $length); } @@ -41,7 +41,7 @@ class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { public function testCheckQuota($quota, $headers) { $this->init($quota); - $this->server->httpRequest = new Sabre\HTTP\Request($headers); + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); $result = $this->plugin->checkQuota(''); $this->assertTrue($result); } @@ -53,39 +53,39 @@ class Test_OC_Connector_Sabre_QuotaPlugin extends \Test\TestCase { public function testCheckExceededQuota($quota, $headers) { $this->init($quota); - $this->server->httpRequest = new Sabre\HTTP\Request($headers); + $this->server->httpRequest = new \Sabre\HTTP\Request(null, null, $headers); $this->plugin->checkQuota(''); } public function quotaOkayProvider() { return array( array(1024, array()), - array(1024, array('HTTP_X_EXPECTED_ENTITY_LENGTH' => '1024')), - array(1024, array('HTTP_CONTENT_LENGTH' => '512')), - array(1024, array('HTTP_OC_TOTAL_LENGTH' => '1024', 'HTTP_CONTENT_LENGTH' => '512')), - // \OCP\Files\FileInfo::SPACE_UNKNOWN = -2 + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(1024, array('CONTENT-LENGTH' => '512')), + array(1024, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), + // \OCP\Files\FileInfo::SPACE-UNKNOWN = -2 array(-2, array()), - array(-2, array('HTTP_X_EXPECTED_ENTITY_LENGTH' => '1024')), - array(-2, array('HTTP_CONTENT_LENGTH' => '512')), - array(-2, array('HTTP_OC_TOTAL_LENGTH' => '1024', 'HTTP_CONTENT_LENGTH' => '512')), + array(-2, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(-2, array('CONTENT-LENGTH' => '512')), + array(-2, array('OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512')), ); } public function quotaExceededProvider() { return array( - array(1023, array('HTTP_X_EXPECTED_ENTITY_LENGTH' => '1024')), - array(511, array('HTTP_CONTENT_LENGTH' => '512')), - array(2047, array('HTTP_OC_TOTAL_LENGTH' => '2048', 'HTTP_CONTENT_LENGTH' => '1024')), + array(1023, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(511, array('CONTENT-LENGTH' => '512')), + array(2047, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), ); } public function lengthProvider() { return array( array(null, array()), - array(1024, array('HTTP_X_EXPECTED_ENTITY_LENGTH' => '1024')), - array(512, array('HTTP_CONTENT_LENGTH' => '512')), - array(2048, array('HTTP_OC_TOTAL_LENGTH' => '2048', 'HTTP_CONTENT_LENGTH' => '1024')), - array(4096, array('HTTP_OC_TOTAL_LENGTH' => '2048', 'HTTP_X_EXPECTED_ENTITY_LENGTH' => '4096')), + array(1024, array('X-EXPECTED-ENTITY-LENGTH' => '1024')), + array(512, array('CONTENT-LENGTH' => '512')), + array(2048, array('OC-TOTAL-LENGTH' => '2048', 'CONTENT-LENGTH' => '1024')), + array(4096, array('OC-TOTAL-LENGTH' => '2048', 'X-EXPECTED-ENTITY-LENGTH' => '4096')), ); } diff --git a/tests/lib/connector/sabre/tagsplugin.php b/tests/lib/connector/sabre/tagsplugin.php index 2afea061ec..f8af73fecf 100644 --- a/tests/lib/connector/sabre/tagsplugin.php +++ b/tests/lib/connector/sabre/tagsplugin.php @@ -42,7 +42,7 @@ class TagsPlugin extends \Test\TestCase { public function setUp() { parent::setUp(); $this->server = new \Sabre\DAV\Server(); - $this->tree = $this->getMockBuilder('\Sabre\DAV\ObjectTree') + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') ->disableOriginalConstructor() ->getMock(); $this->tagger = $this->getMock('\OCP\ITags'); @@ -59,7 +59,7 @@ class TagsPlugin extends \Test\TestCase { * @dataProvider tagsGetPropertiesDataProvider */ public function testGetProperties($tags, $requestedProperties, $expectedProperties) { - $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + $node = $this->getMockBuilder('\OC\Connector\Sabre\Node') ->disableOriginalConstructor() ->getMock(); $node->expects($this->any()) @@ -76,29 +76,35 @@ class TagsPlugin extends \Test\TestCase { ->with($this->equalTo(array(123))) ->will($this->returnValue(array(123 => $tags))); - $returnedProperties = array(); - - $this->plugin->beforeGetProperties( - '', - $node, + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', $requestedProperties, - $returnedProperties + 0 ); - $this->assertEquals($expectedProperties, $returnedProperties); + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); } /** * @dataProvider tagsGetPropertiesDataProvider */ public function testPreloadThenGetProperties($tags, $requestedProperties, $expectedProperties) { - $node1 = $this->getMockBuilder('\OC_Connector_Sabre_File') + $node1 = $this->getMockBuilder('\OC\Connector\Sabre\File') ->disableOriginalConstructor() ->getMock(); $node1->expects($this->any()) ->method('getId') ->will($this->returnValue(111)); - $node2 = $this->getMockBuilder('\OC_Connector_Sabre_File') + $node2 = $this->getMockBuilder('\OC\Connector\Sabre\File') ->disableOriginalConstructor() ->getMock(); $node2->expects($this->any()) @@ -113,7 +119,7 @@ class TagsPlugin extends \Test\TestCase { $expectedCallCount = 1; } - $node = $this->getMockBuilder('\OC_Connector_Sabre_Directory') + $node = $this->getMockBuilder('\OC\Connector\Sabre\Directory') ->disableOriginalConstructor() ->getMock(); $node->expects($this->any()) @@ -123,14 +129,9 @@ class TagsPlugin extends \Test\TestCase { ->method('getChildren') ->will($this->returnValue(array($node1, $node2))); - $this->tree->expects($this->once()) - ->method('getNodeForPath') - ->with('/subdir') - ->will($this->returnValue($node)); - $this->tagger->expects($this->exactly($expectedCallCount)) ->method('getTagsForObjects') - ->with($this->equalTo(array(111, 222))) + ->with($this->equalTo(array(123, 111, 222))) ->will($this->returnValue( array( 111 => $tags, @@ -138,22 +139,41 @@ class TagsPlugin extends \Test\TestCase { ) )); - $returnedProperties = array(); - - $this->plugin->beforeGetPropertiesForPath( + // simulate sabre recursive PROPFIND traversal + $propFindRoot = new \Sabre\DAV\PropFind( '/subdir', $requestedProperties, 1 ); - - $this->plugin->beforeGetProperties( + $propFind1 = new \Sabre\DAV\PropFind( '/subdir/test.txt', - $node1, $requestedProperties, - $returnedProperties + 0 + ); + $propFind2 = new \Sabre\DAV\PropFind( + '/subdir/test2.txt', + $requestedProperties, + 0 ); - $this->assertEquals($expectedProperties, $returnedProperties); + $this->plugin->handleGetProperties( + $propFindRoot, + $node + ); + $this->plugin->handleGetProperties( + $propFind1, + $node1 + ); + $this->plugin->handleGetProperties( + $propFind2, + $node2 + ); + + $result = $propFind1->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($expectedProperties, $result); } function tagsGetPropertiesDataProvider() { @@ -193,7 +213,9 @@ class TagsPlugin extends \Test\TestCase { array( array('tag1', 'tag2', self::TAG_FAVORITE), array(), - array(), + array( + 200 => array() + ), ), // request both with none set, receive both array( @@ -212,13 +234,18 @@ class TagsPlugin extends \Test\TestCase { public function testUpdateTags() { // this test will replace the existing tags "tagremove" with "tag1" and "tag2" // and keep "tagkeep" - $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + $node = $this->getMockBuilder('\OC\Connector\Sabre\Node') ->disableOriginalConstructor() ->getMock(); $node->expects($this->any()) ->method('getId') ->will($this->returnValue(123)); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + $this->tagger->expects($this->at(0)) ->method('getTagsForObjects') ->with($this->equalTo(array(123))) @@ -238,58 +265,109 @@ class TagsPlugin extends \Test\TestCase { ->with(123, 'tagremove'); // properties to set - $properties = array( + $propPatch = new \Sabre\DAV\PropPatch(array( self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) - ); - $result = array(); + )); - $this->plugin->updateProperties( - $properties, - $result, - $node + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch ); + $propPatch->commit(); + // all requested properties removed, as they were processed already - $this->assertEmpty($properties); + $this->assertEmpty($propPatch->getRemainingMutations()); - $this->assertEquals( - new \OC\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')), - $result[200][self::TAGS_PROPERTYNAME] - ); - $this->assertFalse(isset($result[200][self::FAVORITE_PROPERTYNAME])); + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(isset($result[self::FAVORITE_PROPERTYNAME])); } - public function testUpdateFav() { - // this test will replace the existing tags "tagremove" with "tag1" and "tag2" - // and keep "tagkeep" - $node = $this->getMockBuilder('\OC_Connector_Sabre_Node') + public function testUpdateTagsFromScratch() { + $node = $this->getMockBuilder('\OC\Connector\Sabre\Node') ->disableOriginalConstructor() ->getMock(); $node->expects($this->any()) ->method('getId') ->will($this->returnValue(123)); + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + + $this->tagger->expects($this->at(0)) + ->method('getTagsForObjects') + ->with($this->equalTo(array(123))) + ->will($this->returnValue(array())); + + // then tag as tag1 and tag2 + $this->tagger->expects($this->at(1)) + ->method('tagAs') + ->with(123, 'tag1'); + $this->tagger->expects($this->at(2)) + ->method('tagAs') + ->with(123, 'tag2'); + + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( + self::TAGS_PROPERTYNAME => new \OC\Connector\Sabre\TagList(array('tag1', 'tag2', 'tagkeep')) + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch + ); + + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); + $this->assertFalse(false, isset($result[self::FAVORITE_PROPERTYNAME])); + } + + public function testUpdateFav() { + // this test will replace the existing tags "tagremove" with "tag1" and "tag2" + // and keep "tagkeep" + $node = $this->getMockBuilder('\OC\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + + $this->tree->expects($this->any()) + ->method('getNodeForPath') + ->with('/dummypath') + ->will($this->returnValue($node)); + // set favorite tag $this->tagger->expects($this->once()) ->method('tagAs') ->with(123, self::TAG_FAVORITE); // properties to set - $properties = array( + $propPatch = new \Sabre\DAV\PropPatch(array( self::FAVORITE_PROPERTYNAME => true + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch ); - $result = array(); - $this->plugin->updateProperties( - $properties, - $result, - $node - ); + + $propPatch->commit(); // all requested properties removed, as they were processed already - $this->assertEmpty($properties); + $this->assertEmpty($propPatch->getRemainingMutations()); - $this->assertTrue($result[200][self::FAVORITE_PROPERTYNAME]); - $this->assertFalse(isset($result[200][self::TAGS_PROPERTYNAME])); + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); // unfavorite now // set favorite tag @@ -297,18 +375,24 @@ class TagsPlugin extends \Test\TestCase { ->method('unTag') ->with(123, self::TAG_FAVORITE); - $properties = array( + // properties to set + $propPatch = new \Sabre\DAV\PropPatch(array( self::FAVORITE_PROPERTYNAME => false - ); - $result = array(); - $this->plugin->updateProperties( - $properties, - $result, - $node + )); + + $this->plugin->handleUpdateProperties( + '/dummypath', + $propPatch ); - $this->assertFalse($result[200][self::FAVORITE_PROPERTYNAME]); - $this->assertFalse(isset($result[200][self::TAGS_PROPERTYNAME])); + $propPatch->commit(); + + // all requested properties removed, as they were processed already + $this->assertEmpty($propPatch->getRemainingMutations()); + + $result = $propPatch->getResult(); + $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); + $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); } } diff --git a/tests/lib/vobject.php b/tests/lib/vobject.php deleted file mode 100644 index 6fabf30e48..0000000000 --- a/tests/lib/vobject.php +++ /dev/null @@ -1,40 +0,0 @@ -assertEquals("SUMMARY:Escape\;this\,please\r\n", $property->serialize()); - } - - function testCompoundProperty() { - - $arr = array( - 'ABC, Inc.', - 'North American Division', - 'Marketing;Sales', - ); - - $property = Sabre\VObject\Property::create('ORG'); - $property->setParts($arr); - - $this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $property->value); - $this->assertEquals('ORG:ABC\, Inc.;North American Division;Marketing\;Sales' . "\r\n", $property->serialize()); - $this->assertEquals(3, count($property->getParts())); - $parts = $property->getParts(); - $this->assertEquals('Marketing;Sales', $parts[2]); - } -}