From 9f6dcb9d3e2f43f336dafeb739e04ba9614d9b5f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 12 Feb 2015 12:29:01 +0100 Subject: [PATCH 1/4] 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 59f092231c6..588b1308f4a 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 f9fa792e133..98db95041fe 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 0d5a8f82f2c..8fe5ffe0363 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 30c8588ee3f..abed58b5ee2 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 dad5cb050ca..e47811d0df9 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 68356d3378c..e38af40c08e 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 a095ee6e045..0fc76ea9df2 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 00000000000..eaf13238b63 --- /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 682fd62ee37..f13a0d7bbd8 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 2d2a264fe6f..e18ac859dd5 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 ce110e3cb37..6b6b7b0d856 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 ce6db21d89b..5a3716ae71b 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 2b2753c47e4..6ae57b3ec56 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 9b529d2e0f1..0355376c46b 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 d62f4e4ce53..1932dabd393 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 ec14f1d71b5..a212c9597c4 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 3d4e19ec470..ff55cdceab6 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 7df4453cb25..8fee6a4eb4e 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 8793b612936..585be637813 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 917d9887393..06842cc7b90 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 8601f8aada6..6c0f9f3f950 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 35fd671edd0..00000000000 --- 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 f17f46a81cf..0dc81554d56 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 22b422c20b3..97f9e797579 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 87de08d333e..7756eb45bda 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 56aa5328c78..d8df8f948cb 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 0d70f612ec0..477a3b499c0 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 aaeeeed527e..00000000000 --- 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 a0911563061..00000000000 --- 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 00000000000..ee0c3c4e53d --- /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 599a6ca3f7c..e7fbd1d27b6 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 33dc78f87d8..2ef5fd794be 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 00000000000..54d43d66dda --- /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 1e927deed44..e1ae05b2170 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 2548066214b..3c972fe6f0f 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 5d13aa4421e..1841a79bec7 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 f08637854ce..48f8f319ae4 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 2afea061ec3..f8af73fecfb 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 6fabf30e48f..00000000000 --- 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]); - } -} From 5b6caf763cd3f5365c9ea1c49528c738188f0e8c Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 19 Feb 2015 16:10:38 +0100 Subject: [PATCH 2/4] Removed broken webdav tests The encryption webdav tests are too broad and do not qualify as unit test. Also, since the sabre upgrade they are not fixable. The matching functionality is already tested in other classes on a smaller scale. --- apps/files_encryption/tests/webdav.php | 272 ------------------------- 1 file changed, 272 deletions(-) delete mode 100755 apps/files_encryption/tests/webdav.php diff --git a/apps/files_encryption/tests/webdav.php b/apps/files_encryption/tests/webdav.php deleted file mode 100755 index 8fe5ffe0363..00000000000 --- a/apps/files_encryption/tests/webdav.php +++ /dev/null @@ -1,272 +0,0 @@ - - * @author Christopher Schäpers - * @author Florin Peter - * @author Joas Schilling - * @author Jörn Friedrich Dreyer - * @author Lukas Reschke - * @author Morris Jobke - * @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 - * - */ -namespace OCA\Files_Encryption\Tests; - -/** - * Class Webdav - * - * this class provide basic webdav tests for PUT,GET and DELETE - */ -class Webdav extends TestCase { - - const TEST_ENCRYPTION_WEBDAV_USER1 = "test-webdav-user1"; - - public $userId; - public $pass; - /** - * @var \OC\Files\View - */ - public $view; - public $dataShort; - public $stateFilesTrashbin; - - private $storage; - - public static function setUpBeforeClass() { - parent::setUpBeforeClass(); - - // create test user - self::loginHelper(self::TEST_ENCRYPTION_WEBDAV_USER1, true); - - } - - protected function setUp() { - parent::setUp(); - - // reset backend - \OC_User::useBackend('database'); - - // set user id - \OC_User::setUserId(self::TEST_ENCRYPTION_WEBDAV_USER1); - $this->userId = self::TEST_ENCRYPTION_WEBDAV_USER1; - $this->pass = self::TEST_ENCRYPTION_WEBDAV_USER1; - - // init filesystem view - $this->view = new \OC\Files\View('/'); - list($this->storage, ) = $this->view->resolvePath('/'); - // init short data - $this->dataShort = 'hats'; - - // remember files_trashbin state - $this->stateFilesTrashbin = \OC_App::isEnabled('files_trashbin'); - - // we don't want to tests with app files_trashbin enabled - \OC_App::disable('files_trashbin'); - - // create test user - self::loginHelper(self::TEST_ENCRYPTION_WEBDAV_USER1); - } - - protected function tearDown() { - // reset app files_trashbin - if ($this->stateFilesTrashbin) { - \OC_App::enable('files_trashbin'); - } else { - \OC_App::disable('files_trashbin'); - } - - parent::tearDown(); - } - - public static function tearDownAfterClass() { - // cleanup test user - \OC_User::deleteUser(self::TEST_ENCRYPTION_WEBDAV_USER1); - - parent::tearDownAfterClass(); - } - - /** - * test webdav put random file - */ - function testWebdavPUT() { - - // generate filename - $filename = '/tmp-' . $this->getUniqueID() . '.txt'; - - // set server vars - $_SERVER['REQUEST_METHOD'] = 'OPTIONS'; - - $_SERVER['REQUEST_METHOD'] = 'PUT'; - $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; - $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; - $_SERVER['CONTENT_TYPE'] = 'application/octet-stream'; - $_SERVER['PATH_INFO'] = '/webdav' . $filename; - $_SERVER['CONTENT_LENGTH'] = strlen($this->dataShort); - - // handle webdav request - $this->handleWebdavRequest($this->dataShort); - - // check if file was created - $this->assertTrue($this->view->file_exists('/' . $this->userId . '/files' . $filename)); - - // check if key-file was created - $this->assertTrue($this->view->file_exists( - '/' . $this->userId . '/files_encryption/keys/' . $filename . '/fileKey')); - - // check if shareKey-file was created - $this->assertTrue($this->view->file_exists( - '/' . $this->userId . '/files_encryption/keys/' . $filename . '/' . $this->userId . '.shareKey')); - - // disable encryption proxy to prevent recursive calls - $proxyStatus = \OC_FileProxy::$enabled; - \OC_FileProxy::$enabled = false; - - // get encrypted file content - $encryptedContent = $this->view->file_get_contents('/' . $this->userId . '/files' . $filename); - - // restore proxy state - \OC_FileProxy::$enabled = $proxyStatus; - - // check if encrypted content is valid - $this->assertTrue(\OCA\Files_Encryption\Crypt::isCatfileContent($encryptedContent)); - - // get decrypted file contents - $decrypt = file_get_contents('crypt:///' . $this->userId . '/files' . $filename); - - // check if file content match with the written content - $this->assertEquals($this->dataShort, $decrypt); - - // return filename for next test - return $filename; - } - - /** - * test webdav get random file - * - * @depends testWebdavPUT - */ - function testWebdavGET($filename) { - - // set server vars - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; - $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; - $_SERVER['PATH_INFO'] = '/webdav' . $filename; - - // handle webdav request - $content = $this->handleWebdavRequest(); - - // check if file content match with the written content - $this->assertEquals($this->dataShort, $content); - - // return filename for next test - return $filename; - } - - /** - * test webdav delete random file - * @depends testWebdavGET - */ - function testWebdavDELETE($filename) { - // set server vars - $_SERVER['REQUEST_METHOD'] = 'DELETE'; - $_SERVER['REQUEST_URI'] = '/remote.php/webdav' . $filename; - $_SERVER['HTTP_AUTHORIZATION'] = 'Basic dGVzdC13ZWJkYXYtdXNlcjE6dGVzdC13ZWJkYXYtdXNlcjE='; - $_SERVER['PATH_INFO'] = '/webdav' . $filename; - - // at the beginning the file should exist - $this->assertTrue($this->view->file_exists('/' . $this->userId . '/files' . $filename)); - - // handle webdav request - $content = $this->handleWebdavRequest(); - - // check if file was removed - $this->assertFalse($this->view->file_exists('/' . $this->userId . '/files' . $filename)); - - // check if key-file was removed - $this->assertFalse($this->view->file_exists( - '/' . $this->userId . '/files_encryption/keys/' . $filename . '/fileKey')); - - // check if shareKey-file was removed - $this->assertFalse($this->view->file_exists( - '/' . $this->userId . '/files_encryption/keys/' . $filename . '/' . $this->userId . '.shareKey')); - } - - /** - * handle webdav request - * - * @param bool $body - * @note this init procedure is copied from /apps/files/appinfo/remote.php - */ - function handleWebdavRequest($body = false) { - // Backends - $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(); - - // Create ownCloud Dir - $root = '/' . $this->userId . '/files'; - $view = new \OC\Files\View($root); - $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->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->debugExceptions = true; - - // Totally ugly hack to setup the FS - \OC::$server->getUserSession()->login($this->userId, $this->userId); - \OC_Util::setupFS($this->userId); - - // And off we go! - if ($body) { - $server->httpRequest->setBody($body); - } - - // turn on output buffering - ob_start(); - - // handle request - $server->exec(); - - // file content is written in the output buffer - $content = ob_get_contents(); - - // flush the output buffer and turn off output buffering - ob_end_clean(); - - // return captured content - return $content; - } -} From 432d7be8cc66da0de1e174522333e5b5dd004c91 Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Wed, 18 Feb 2015 13:41:42 +0100 Subject: [PATCH 3/4] Show a empty response for GET on non-files instead of the Browser Plugin --- apps/files/appinfo/remote.php | 3 +- .../sabre/dummygetresponseplugin.php | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 lib/private/connector/sabre/dummygetresponseplugin.php diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index 98db95041fe..e4a105b6d28 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -41,7 +41,8 @@ $server->setBaseUri($baseuri); $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 +// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / +$server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); $server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); $server->addPlugin(new \OC\Connector\Sabre\ExceptionLoggerPlugin('webdav')); diff --git a/lib/private/connector/sabre/dummygetresponseplugin.php b/lib/private/connector/sabre/dummygetresponseplugin.php new file mode 100644 index 00000000000..0b78fe7dcb3 --- /dev/null +++ b/lib/private/connector/sabre/dummygetresponseplugin.php @@ -0,0 +1,43 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Connector\Sabre; + +/** + * Class DummyGetResponsePlugin is a plugin used to not show a "Not implemented" + * error to clients that rely on verifying the functionality of the ownCloud + * WebDAV backend using a simple GET to /. + * + * This is considered a legacy behaviour and implementers should consider sending + * a PROPFIND request instead to verify whether the WebDAV component is working + * properly. + * + * FIXME: Remove once clients are all compliant. + * + * @package OC\Connector\Sabre + */ +class DummyGetResponsePlugin extends \Sabre\DAV\ServerPlugin { + /** @var \Sabre\DAV\Server */ + protected $server; + + /** + * @param \Sabre\DAV\Server $server + * @return void + */ + function initialize(\Sabre\DAV\Server $server) { + $this->server = $server; + $this->server->on('method:GET', [$this,'httpGet'], 200); + } + + /** + * @return false + */ + function httpGet() { + return false; + } +} From b3de86d851e04c73c67c1ca6522455130c8f6c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 23 Feb 2015 13:49:23 +0100 Subject: [PATCH 4/4] Remove locks plugin. Reasoning: - a WebDAV server is not required to implement locking support - WebDAV Locking is know to break the sync algorithm - the current lock implementation is known to be broken (locks are not moved if a file is moved, locks on shared files don't work) --- apps/files/appinfo/remote.php | 2 - apps/files_sharing/publicwebdav.php | 2 - lib/private/connector/sabre/locks.php | 171 -------------------------- 3 files changed, 175 deletions(-) delete mode 100644 lib/private/connector/sabre/locks.php diff --git a/apps/files/appinfo/remote.php b/apps/files/appinfo/remote.php index e4a105b6d28..dbab3256b98 100644 --- a/apps/files/appinfo/remote.php +++ b/apps/files/appinfo/remote.php @@ -28,7 +28,6 @@ */ // Backends $authBackend = new \OC\Connector\Sabre\Auth(); -$lockBackend = new \OC\Connector\Sabre\Locks(); // Fire up server $objectTree = new \OC\Connector\Sabre\ObjectTree(); @@ -40,7 +39,6 @@ $server->setBaseUri($baseuri); // Load plugins $defaults = new OC_Defaults(); $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); -$server->addPlugin(new \Sabre\DAV\Locks\Plugin($lockBackend)); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OC\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OC\Connector\Sabre\FilesPlugin($objectTree)); diff --git a/apps/files_sharing/publicwebdav.php b/apps/files_sharing/publicwebdav.php index abed58b5ee2..e96dabbdd3f 100644 --- a/apps/files_sharing/publicwebdav.php +++ b/apps/files_sharing/publicwebdav.php @@ -34,7 +34,6 @@ OC_Util::obEnd(); // Backends $authBackend = new OCA\Files_Sharing\Connector\PublicAuth(\OC::$server->getConfig()); -$lockBackend = new \OC\Connector\Sabre\Locks(); // Fire up server $objectTree = new \OC\Connector\Sabre\ObjectTree(); @@ -46,7 +45,6 @@ $server->setBaseUri($baseuri); // Load plugins $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($objectTree)); $server->addPlugin(new \OC\Connector\Sabre\MaintenancePlugin()); diff --git a/lib/private/connector/sabre/locks.php b/lib/private/connector/sabre/locks.php deleted file mode 100644 index a212c9597c4..00000000000 --- a/lib/private/connector/sabre/locks.php +++ /dev/null @@ -1,171 +0,0 @@ - '.time().' AND (( `uri` = ?)'; - 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); - - // We need to check locks for every part in the uri. - $uriParts = explode('/', $uri); - - // We already covered the last part of the uri - array_pop($uriParts); - - $currentPath=''; - - foreach($uriParts as $part) { - - if ($currentPath) $currentPath.='/'; - $currentPath.=$part; - //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison - if (\OC_Config::getValue( "dbtype") === 'oci') { - $query.=' OR (`depth` != 0 AND to_char(`uri`) = ?)'; - } else { - $query.=' OR (`depth` != 0 AND `uri` = ?)'; - } - $params[] = $currentPath; - - } - - if ($returnChildLocks) { - - //FIXME oracle hack: need to explicitly cast CLOB to CHAR for comparison - if (\OC_Config::getValue( "dbtype") === 'oci') { - $query.=' OR (to_char(`uri`) LIKE ?)'; - } else { - $query.=' OR (`uri` LIKE ?)'; - } - $params[] = $uri . '/%'; - - } - $query.=')'; - - $result = \OC_DB::executeAudited( $query, $params ); - - $lockList = array(); - while( $row = $result->fetchRow()) { - - $lockInfo = new \Sabre\DAV\Locks\LockInfo(); - $lockInfo->owner = $row['owner']; - $lockInfo->token = $row['token']; - $lockInfo->timeout = $row['timeout']; - $lockInfo->created = $row['created']; - $lockInfo->scope = $row['scope']; - $lockInfo->depth = $row['depth']; - $lockInfo->uri = $row['uri']; - $lockList[] = $lockInfo; - - } - - return $lockList; - - } - - /** - * Locks a uri - * - * @param string $uri - * @param \Sabre\DAV\Locks\LockInfo $lockInfo - * @return bool - */ - public function lock($uri, \Sabre\DAV\Locks\LockInfo $lockInfo) { - - // We're making the lock timeout 5 minutes - $lockInfo->timeout = 300; - $lockInfo->created = time(); - $lockInfo->uri = $uri; - - $locks = $this->getLocks($uri, false); - $exists = false; - foreach($locks as $lock) { - if ($lock->token == $lockInfo->token) { - $exists = true; - break; - } - } - - if ($exists) { - $sql = 'UPDATE `*PREFIX*locks`' - .' SET `owner` = ?, `timeout` = ?, `scope` = ?, `depth` = ?, `uri` = ?, `created` = ?' - .' WHERE `userid` = ? AND `token` = ?'; - $result = \OC_DB::executeAudited( $sql, array( - $lockInfo->owner, - $lockInfo->timeout, - $lockInfo->scope, - $lockInfo->depth, - $uri, - $lockInfo->created, - \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(), - $lockInfo->owner, - $lockInfo->timeout, - $lockInfo->scope, - $lockInfo->depth, - $uri, - $lockInfo->created, - $lockInfo->token) - ); - } - - return true; - - } - - /** - * Removes a lock from a uri - * - * @param string $uri - * @param \Sabre\DAV\Locks\LockInfo $lockInfo - * @return bool - */ - 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') { - //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)); - - return $result === 1; - - } - -}