Introduced the new webdav endpoint remote.php/dav holding the principals and the files collection
This commit is contained in:
parent
9a7a45bc37
commit
c79496b5a3
|
@ -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>
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue