diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2015-10-21 15:06:48 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2015-10-26 13:00:00 +0100 |
commit | c79496b5a3ac05f607e4ac0d4036da42e120d28f (patch) | |
tree | c320268a73df4747bf5554fa84a634031fffa59e /apps | |
parent | 9a7a45bc37ff07dcb3d57f91ab8014fd21c4a40e (diff) | |
download | nextcloud-server-c79496b5a3ac05f607e4ac0d4036da42e120d28f.tar.gz nextcloud-server-c79496b5a3ac05f607e4ac0d4036da42e120d28f.zip |
Introduced the new webdav endpoint remote.php/dav holding the principals and the files collection
Diffstat (limited to 'apps')
-rw-r--r-- | apps/dav/appinfo/info.xml | 3 | ||||
-rw-r--r-- | apps/dav/appinfo/v2/remote.php | 11 | ||||
-rw-r--r-- | apps/dav/lib/files/custompropertiesbackend.php | 271 | ||||
-rw-r--r-- | apps/dav/lib/files/fileshome.php | 80 | ||||
-rw-r--r-- | apps/dav/lib/files/rootcollection.php | 28 | ||||
-rw-r--r-- | apps/dav/lib/rootcollection.php | 29 | ||||
-rw-r--r-- | apps/dav/lib/server.php | 54 |
7 files changed, 475 insertions, 1 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 38878ec27f2..8f378f5e18d 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ <description>ownCloud WebDAV endpoint</description> <licence>AGPL</licence> <author>owncloud.org</author> - <version>0.1</version> + <version>0.1.1</version> <requiremin>9.0</requiremin> <shipped>true</shipped> <standalone/> @@ -16,6 +16,7 @@ <remote> <files>appinfo/v1/webdav.php</files> <webdav>appinfo/v1/webdav.php</webdav> + <dav>appinfo/v2/remote.php</dav> </remote> <public> <webdav>appinfo/v1/publicwebdav.php</webdav> diff --git a/apps/dav/appinfo/v2/remote.php b/apps/dav/appinfo/v2/remote.php new file mode 100644 index 00000000000..02457bd3ccc --- /dev/null +++ b/apps/dav/appinfo/v2/remote.php @@ -0,0 +1,11 @@ +<?php + +// no php execution timeout for webdav +set_time_limit(0); + +// Turn off output buffering to prevent memory problems +\OC_Util::obEnd(); + +$request = \OC::$server->getRequest(); +$server = new \OCA\DAV\Server($request, $baseuri); +$server->exec(); diff --git a/apps/dav/lib/files/custompropertiesbackend.php b/apps/dav/lib/files/custompropertiesbackend.php new file mode 100644 index 00000000000..83776997a52 --- /dev/null +++ b/apps/dav/lib/files/custompropertiesbackend.php @@ -0,0 +1,271 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @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 <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\DAV\Files; + +use OCP\IDBConnection; +use OCP\IUser; +use Sabre\DAV\PropertyStorage\Backend\BackendInterface; +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Tree; + +class CustomPropertiesBackend implements 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 Tree + */ + private $tree; + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var IUser + */ + private $user; + + /** + * Properties cache + * + * @var array + */ + private $cache = []; + + /** + * @param Tree $tree node tree + * @param IDBConnection $connection database connection + * @param IUser $user owner of the tree and properties + */ + public function __construct( + Tree $tree, + IDBConnection $connection, + 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) { + + $requestedProps = $propFind->get404Properties(); + + // these might appear + $requestedProps = array_diff( + $requestedProps, + $this->ignoredProperties + ); + + if (empty($requestedProps)) { + return; + } + + $props = $this->getProperties($path, $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) { + $propPatch->handleRemaining(function($changedProps) use ($path) { + return $this->updateProperties($path, $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 string $path + * @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($path, array $requestedProperties) { + 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 string $path node for which to update properties + * @param array $properties array of properties to update + * + * @return bool + */ + private function updateProperties($path, $properties) { + + $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + $insertStatement = 'INSERT INTO `*PREFIX*properties`' . + ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; + + $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . + ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; + + // TODO: use "insert or update" strategy ? + $existing = $this->getProperties($path, 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)) { + $this->connection->executeUpdate($deleteStatement, + array( + $this->user, + $path, + $propertyName + ) + ); + } + } else { + if (!array_key_exists($propertyName, $existing)) { + $this->connection->executeUpdate($insertStatement, + array( + $this->user, + $path, + $propertyName, + $propertyValue + ) + ); + } else { + $this->connection->executeUpdate($updateStatement, + array( + $propertyValue, + $this->user, + $path, + $propertyName + ) + ); + } + } + } + + $this->connection->commit(); + unset($this->cache[$path]); + + return true; + } + +} diff --git a/apps/dav/lib/files/fileshome.php b/apps/dav/lib/files/fileshome.php new file mode 100644 index 00000000000..5e145a2b002 --- /dev/null +++ b/apps/dav/lib/files/fileshome.php @@ -0,0 +1,80 @@ +<?php + +namespace OCA\DAV\Files; + +use OCA\DAV\Connector\Sabre\Directory; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\ICollection; +use Sabre\DAV\SimpleCollection; +use Sabre\HTTP\URLUtil; + +class FilesHome implements ICollection { + + /** + * FilesHome constructor. + * + * @param array $principalInfo + */ + public function __construct($principalInfo) { + $this->principalInfo = $principalInfo; + } + + function createFile($name, $data = null) { + return $this->impl()->createFile($name, $data); + } + + function createDirectory($name) { + $this->impl()->createDirectory($name); + } + + function getChild($name) { + return $this->impl()->getChild($name); + } + + function getChildren() { + return $this->impl()->getChildren(); + } + + function childExists($name) { + return $this->impl()->childExists($name); + } + + function delete() { + $this->impl()->delete(); + } + + function getName() { + list(,$name) = URLUtil::splitPath($this->principalInfo['uri']); + return $name; + } + + function setName($name) { + throw new Forbidden('Permission denied to rename this folder'); + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + return $this->impl()->getLastModified(); + } + + /** + * @return Directory + */ + private function impl() { + // + // TODO: we need to mount filesystem of the give user + // + $user = \OC::$server->getUserSession()->getUser(); + if ($this->getName() !== $user->getUID()) { + return new SimpleCollection($this->getName()); + } + $view = \OC\Files\Filesystem::getView(); + $rootInfo = $view->getFileInfo(''); + $impl = new Directory($view, $rootInfo); + return $impl; + } +} diff --git a/apps/dav/lib/files/rootcollection.php b/apps/dav/lib/files/rootcollection.php new file mode 100644 index 00000000000..bbe3c784a53 --- /dev/null +++ b/apps/dav/lib/files/rootcollection.php @@ -0,0 +1,28 @@ +<?php + +namespace OCA\DAV\Files; + +use Sabre\DAVACL\AbstractPrincipalCollection; +use Sabre\DAVACL\IPrincipal; + +class RootCollection extends AbstractPrincipalCollection { + + /** + * This method returns a node for a principal. + * + * The passed array contains principal information, and is guaranteed to + * at least contain a uri item. Other properties may or may not be + * supplied by the authentication backend. + * + * @param array $principalInfo + * @return IPrincipal + */ + function getChildForPrincipal(array $principalInfo) { + return new FilesHome($principalInfo); + } + + function getName() { + return 'files'; + } + +} diff --git a/apps/dav/lib/rootcollection.php b/apps/dav/lib/rootcollection.php new file mode 100644 index 00000000000..62ec3219caa --- /dev/null +++ b/apps/dav/lib/rootcollection.php @@ -0,0 +1,29 @@ +<?php + +namespace OCA\DAV; + +use OCA\DAV\Connector\Sabre\Principal; +use Sabre\CalDAV\Principal\Collection; +use Sabre\DAV\SimpleCollection; + +class RootCollection extends SimpleCollection { + + public function __construct() { + $principalBackend = new Principal( + \OC::$server->getConfig(), + \OC::$server->getUserManager() + ); + $principalCollection = new Collection($principalBackend); + $principalCollection->disableListing = true; + $filesCollection = new Files\RootCollection($principalBackend); + $filesCollection->disableListing = true; + + $children = [ + $principalCollection, + $filesCollection, + ]; + + parent::__construct('root', $children); + } + +} diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php new file mode 100644 index 00000000000..fa572eb30d1 --- /dev/null +++ b/apps/dav/lib/server.php @@ -0,0 +1,54 @@ +<?php + +namespace OCA\DAV; + +use OCA\DAV\Connector\Sabre\Auth; +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use OCA\DAV\Files\CustomPropertiesBackend; +use OCP\IRequest; +use Sabre\DAV\Auth\Plugin; +use Sabre\HTTP\Util; + +class Server { + + /** @var IRequest */ + private $request; + + public function __construct(IRequest $request, $baseUri) { + $this->request = $request; + $this->baseUri = $baseUri; + $root = new RootCollection(); + $this->server = new \OCA\DAV\Connector\Sabre\Server($root); + + // Backends + $authBackend = new Auth(); + + // Set URL explicitly due to reverse-proxy situations + $this->server->httpRequest->setUrl($this->request->getRequestUri()); + $this->server->setBaseUri($this->baseUri); + + $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); + $this->server->addPlugin(new Plugin($authBackend, 'ownCloud')); + + // wait with registering these until auth is handled and the filesystem is setup + $this->server->on('beforeMethod', function () { + // custom properties plugin must be the last one + $user = \OC::$server->getUserSession()->getUser(); + if (!is_null($user)) { + $this->server->addPlugin( + new \Sabre\DAV\PropertyStorage\Plugin( + new CustomPropertiesBackend( + $this->server->tree, + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserSession()->getUser() + ) + ) + ); + } + }); + } + + public function exec() { + $this->server->exec(); + } +} |