diff options
author | Thomas Müller <thomas.mueller@tmit.eu> | 2013-10-04 14:06:42 +0200 |
---|---|---|
committer | Thomas Müller <thomas.mueller@tmit.eu> | 2013-10-04 14:06:42 +0200 |
commit | aebc330f269f3e88f2891cd13009cde2d50eb59a (patch) | |
tree | 7398bc68d581d01b18ba3e9e79c82491f1ecf871 /lib/private/connector/sabre | |
parent | c62dc4fa80d622ed7651029e9ddff2a6c6327143 (diff) | |
parent | 800bf0769ff4ea63c5dcc77eaaf1bce669b73d13 (diff) | |
download | nextcloud-server-aebc330f269f3e88f2891cd13009cde2d50eb59a.tar.gz nextcloud-server-aebc330f269f3e88f2891cd13009cde2d50eb59a.zip |
Merge branch 'master' into fixing-4011-master
Diffstat (limited to 'lib/private/connector/sabre')
-rw-r--r-- | lib/private/connector/sabre/ServiceUnavailable.php | 22 | ||||
-rw-r--r-- | lib/private/connector/sabre/aborteduploaddetectionplugin.php | 101 | ||||
-rw-r--r-- | lib/private/connector/sabre/auth.php | 84 | ||||
-rw-r--r-- | lib/private/connector/sabre/directory.php | 218 | ||||
-rw-r--r-- | lib/private/connector/sabre/file.php | 192 | ||||
-rw-r--r-- | lib/private/connector/sabre/locks.php | 189 | ||||
-rw-r--r-- | lib/private/connector/sabre/maintenanceplugin.php | 59 | ||||
-rw-r--r-- | lib/private/connector/sabre/node.php | 258 | ||||
-rw-r--r-- | lib/private/connector/sabre/objecttree.php | 142 | ||||
-rw-r--r-- | lib/private/connector/sabre/principal.php | 128 | ||||
-rw-r--r-- | lib/private/connector/sabre/quotaplugin.php | 97 | ||||
-rw-r--r-- | lib/private/connector/sabre/request.php | 50 |
12 files changed, 1540 insertions, 0 deletions
diff --git a/lib/private/connector/sabre/ServiceUnavailable.php b/lib/private/connector/sabre/ServiceUnavailable.php new file mode 100644 index 00000000000..c1cc815c989 --- /dev/null +++ b/lib/private/connector/sabre/ServiceUnavailable.php @@ -0,0 +1,22 @@ +<?php +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller <thomas.mueller@tmit.eu> + * + * @license AGPL3 + */ + +class Sabre_DAV_Exception_ServiceUnavailable extends Sabre_DAV_Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 503; + } +} diff --git a/lib/private/connector/sabre/aborteduploaddetectionplugin.php b/lib/private/connector/sabre/aborteduploaddetectionplugin.php new file mode 100644 index 00000000000..15dca3a6809 --- /dev/null +++ b/lib/private/connector/sabre/aborteduploaddetectionplugin.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright (c) 2013 Thomas Müller <thomas.mueller@tmit.eu> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Class OC_Connector_Sabre_AbortedUploadDetectionPlugin + * + * This plugin will verify if the uploaded data has been stored completely. + * This is done by comparing the content length of the request with the file size on storage. + */ +class OC_Connector_Sabre_AbortedUploadDetectionPlugin extends Sabre_DAV_ServerPlugin { + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * is kept public to allow overwrite for unit testing + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the requires event subscriptions. + * + * @param Sabre_DAV_Server $server + */ + public function initialize(Sabre_DAV_Server $server) { + + $this->server = $server; + + $server->subscribeEvent('afterCreateFile', array($this, 'verifyContentLength'), 10); + $server->subscribeEvent('afterWriteContent', array($this, 'verifyContentLength'), 10); + } + + /** + * @param $filePath + * @param Sabre_DAV_INode $node + * @throws Sabre_DAV_Exception_BadRequest + */ + public function verifyContentLength($filePath, Sabre_DAV_INode $node = null) { + + // ownCloud chunked upload will be handled in its own plugin + $chunkHeader = $this->server->httpRequest->getHeader('OC-Chunked'); + if ($chunkHeader) { + return; + } + + // compare expected and actual size + $expected = $this->getLength(); + if (!$expected) { + return; + } + $actual = $this->getFileView()->filesize($filePath); + if ($actual != $expected) { + $this->getFileView()->unlink($filePath); + throw new Sabre_DAV_Exception_BadRequest('expected filesize ' . $expected . ' got ' . $actual); + } + + } + + /** + * @return string + */ + public function getLength() + { + $req = $this->server->httpRequest; + $length = $req->getHeader('X-Expected-Entity-Length'); + if (!$length) { + $length = $req->getHeader('Content-Length'); + } + + return $length; + } + + /** + * @return \OC\Files\View + */ + public function getFileView() + { + if (is_null($this->fileView)) { + // initialize fileView + $this->fileView = \OC\Files\Filesystem::getView(); + } + + return $this->fileView; + } +} diff --git a/lib/private/connector/sabre/auth.php b/lib/private/connector/sabre/auth.php new file mode 100644 index 00000000000..bf3a49593cb --- /dev/null +++ b/lib/private/connector/sabre/auth.php @@ -0,0 +1,84 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Auth extends Sabre_DAV_Auth_Backend_AbstractBasic { + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @return bool + */ + protected function validateUserPass($username, $password) { + if (OC_User::isLoggedIn()) { + OC_Util::setupFS(OC_User::getUser()); + return true; + } else { + OC_Util::setUpFS();//login hooks may need early access to the filesystem + if(OC_User::login($username, $password)) { + OC_Util::setUpFS(OC_User::getUser()); + return true; + } + else{ + return false; + } + } + } + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + public function getCurrentUser() { + $user = OC_User::getUser(); + if(!$user) { + return null; + } + return $user; + } + + /** + * Override function here. We want to cache authentication cookies + * in the syncing client to avoid HTTP-401 roundtrips. + * If the sync client supplies the cookies, then OC_User::isLoggedIn() + * will return true and we can see this WebDAV request as already authenticated, + * even if there are no HTTP Basic Auth headers. + * In other case, just fallback to the parent implementation. + * + * @return bool + */ + public function authenticate(Sabre_DAV_Server $server, $realm) { + if (OC_User::isLoggedIn()) { + $user = OC_User::getUser(); + OC_Util::setupFS($user); + $this->currentUser = $user; + return true; + } + + return parent::authenticate($server, $realm); + } +} diff --git a/lib/private/connector/sabre/directory.php b/lib/private/connector/sabre/directory.php new file mode 100644 index 00000000000..af0dfd70f08 --- /dev/null +++ b/lib/private/connector/sabre/directory.php @@ -0,0 +1,218 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sabre_DAV_ICollection, Sabre_DAV_IQuota { + + /** + * Creates a new file in the directory + * + * Data will either be supplied as a stream resource, or in certain cases + * as a string. Keep in mind that you may have to support either. + * + * After succesful creation of the file, you may choose to return the ETag + * of the new file here. + * + * The returned ETag must be surrounded by double-quotes (The quotes should + * be part of the actual string). + * + * If you cannot accurately determine the ETag, you should not return it. + * If you don't store the file exactly as-is (you're transforming it + * somehow) you should also not return an ETag. + * + * This means that if a subsequent GET to this new file does not exactly + * return the same contents of what was submitted here, you are strongly + * recommended to omit the ETag. + * + * @param string $name Name of the file + * @param resource|string $data Initial payload + * @throws Sabre_DAV_Exception_Forbidden + * @return null|string + */ + public function createFile($name, $data = null) { + + if (!\OC\Files\Filesystem::isCreatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + $path = $this->path . '/' . $name; + $node = new OC_Connector_Sabre_File($path); + return $node->put($data); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Sabre_DAV_Exception_Forbidden + * @return void + */ + public function createDirectory($name) { + + if (!\OC\Files\Filesystem::isCreatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + $newPath = $this->path . '/' . $name; + if(!\OC\Files\Filesystem::mkdir($newPath)) { + throw new Sabre_DAV_Exception_Forbidden('Could not create directory '.$newPath); + } + + } + + /** + * Returns a specific child node, referenced by its name + * + * @param string $name + * @throws Sabre_DAV_Exception_FileNotFound + * @return Sabre_DAV_INode + */ + public function getChild($name, $info = null) { + + $path = $this->path . '/' . $name; + if (is_null($info)) { + $info = \OC\Files\Filesystem::getFileInfo($path); + } + + if (!$info) { + throw new Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info['mimetype'] == 'httpd/unix-directory') { + $node = new OC_Connector_Sabre_Directory($path); + } else { + $node = new OC_Connector_Sabre_File($path); + } + + $node->setFileinfoCache($info); + return $node; + } + + /** + * Returns an array with all the child nodes + * + * @return Sabre_DAV_INode[] + */ + public function getChildren() { + + $folder_content = \OC\Files\Filesystem::getDirectoryContent($this->path); + $paths = array(); + foreach($folder_content as $info) { + $paths[] = $this->path.'/'.$info['name']; + $properties[$this->path.'/'.$info['name']][self::GETETAG_PROPERTYNAME] = '"' . $info['etag'] . '"'; + } + 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($folder_content as $info) { + $node = $this->getChild($info['name'], $info); + $node->setPropertyCache($properties[$this->path.'/'.$info['name']]); + $nodes[] = $node; + } + return $nodes; + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + public function childExists($name) { + + $path = $this->path . '/' . $name; + return \OC\Files\Filesystem::file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + * @throws Sabre_DAV_Exception_Forbidden + */ + public function delete() { + + if (!\OC\Files\Filesystem::isDeletable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if ($this->path != "/Shared") { + \OC\Files\Filesystem::rmdir($this->path); + } + + } + + /** + * Returns available diskspace information + * + * @return array + */ + public function getQuotaInfo() { + $storageInfo = OC_Helper::getStorageInfo($this->path); + return array( + $storageInfo['used'], + $storageInfo['free'] + ); + + } + + /** + * 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->getETagPropertyForPath($this->path); + } + return $props; + } +} diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php new file mode 100644 index 00000000000..8ffec371e3f --- /dev/null +++ b/lib/private/connector/sabre/file.php @@ -0,0 +1,192 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_DAV_IFile { + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a successful put operation, you may choose to return an ETag. The + * etag must always be surrounded by double-quotes. These quotes must + * appear in the actual string you're returning. + * + * Clients may use the ETag from a PUT request to later on make sure that + * when they update the file, the contents haven't changed in the mean + * time. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @throws Sabre_DAV_Exception_Forbidden + * @return string|null + */ + public function put($data) { + $fs = $this->getFS(); + if ($fs->file_exists($this->path) && + !$fs->isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + // throw an exception if encryption was disabled but the files are still encrypted + if (\OC_Util::encryptedFiles()) { + throw new \Sabre_DAV_Exception_ServiceUnavailable(); + } + + // chunked handling + if (isset($_SERVER['HTTP_OC_CHUNKED'])) { + list(, $name) = \Sabre_DAV_URLUtil::splitPath($this->path); + + $info = OC_FileChunking::decodeName($name); + if (empty($info)) { + throw new Sabre_DAV_Exception_NotImplemented(); + } + $chunk_handler = new OC_FileChunking($info); + $chunk_handler->store($info['index'], $data); + if ($chunk_handler->isComplete()) { + $newPath = $this->path . '/' . $info['name']; + $chunk_handler->file_assemble($newPath); + return $this->getETagPropertyForPath($newPath); + } + + return null; + } + + // mark file as partial while uploading (ignored by the scanner) + $partpath = $this->path . '.part'; + + try { + $putOkay = $fs->file_put_contents($partpath, $data); + if ($putOkay === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::file_put_contents() failed', \OC_Log::ERROR); + $fs->unlink($partpath); + // because we have no clue about the cause we can only throw back a 500/Internal Server Error + throw new Sabre_DAV_Exception(); + } + } catch (\OCP\Files\NotPermittedException $e) { + throw new Sabre_DAV_Exception_Forbidden(); + } + + // rename to correct path + $renameOkay = $fs->rename($partpath, $this->path); + $fileExists = $fs->file_exists($this->path); + if ($renameOkay === false || $fileExists === false) { + \OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR); + $fs->unlink($partpath); + throw new Sabre_DAV_Exception(); + } + + // allow sync clients to send the mtime along in a header + $mtime = OC_Request::hasModificationTime(); + if ($mtime !== false) { + if($fs->touch($this->path, $mtime)) { + header('X-OC-MTime: accepted'); + } + } + + return $this->getETagPropertyForPath($this->path); + } + + /** + * Returns the data + * + * @return string + */ + public function get() { + + //throw exception if encryption is disabled but files are still encrypted + if (\OC_Util::encryptedFiles()) { + throw new \Sabre_DAV_Exception_ServiceUnavailable(); + } else { + return \OC\Files\Filesystem::fopen($this->path, 'rb'); + } + + } + + /** + * Delete the current file + * + * @return void + * @throws Sabre_DAV_Exception_Forbidden + */ + public function delete() { + + if (!\OC\Files\Filesystem::isDeletable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + \OC\Files\Filesystem::unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + public function getSize() { + $this->getFileinfoCache(); + if ($this->fileinfo_cache['size'] > -1) { + return $this->fileinfo_cache['size']; + } else { + return null; + } + } + + /** + * 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() { + $properties = $this->getProperties(array(self::GETETAG_PROPERTYNAME)); + if (isset($properties[self::GETETAG_PROPERTYNAME])) { + return $properties[self::GETETAG_PROPERTYNAME]; + } + return null; + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + public function getContentType() { + if (isset($this->fileinfo_cache['mimetype'])) { + return $this->fileinfo_cache['mimetype']; + } + + return \OC\Files\Filesystem::getMimeType($this->path); + + } +} diff --git a/lib/private/connector/sabre/locks.php b/lib/private/connector/sabre/locks.php new file mode 100644 index 00000000000..69496c15ada --- /dev/null +++ b/lib/private/connector/sabre/locks.php @@ -0,0 +1,189 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Locks extends Sabre_DAV_Locks_Backend_Abstract { + + /** + * Returns a list of Sabre_DAV_Locks_LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + public function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatination prevents us + // from doing this though. + // NOTE: SQLite requires time() to be inserted directly. That's ugly + // but otherwise reading locks from SQLite Databases will return + // nothing + $query = 'SELECT * FROM `*PREFIX*locks`' + .' WHERE `userid` = ? AND (`created` + `timeout`) > '.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; + + } + +} diff --git a/lib/private/connector/sabre/maintenanceplugin.php b/lib/private/connector/sabre/maintenanceplugin.php new file mode 100644 index 00000000000..2eda269afc2 --- /dev/null +++ b/lib/private/connector/sabre/maintenanceplugin.php @@ -0,0 +1,59 @@ +<?php + +/** + * ownCloud + * + * @author Thomas Müller + * @copyright 2013 Thomas Müller <thomas.mueller@tmit.eu> + * + * @license AGPL3 + */ + +require 'ServiceUnavailable.php'; + +class OC_Connector_Sabre_MaintenancePlugin extends Sabre_DAV_ServerPlugin +{ + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the required event subscriptions. + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod', array($this, 'checkMaintenanceMode'), 10); + } + + /** + * This method is called before any HTTP method and returns http status code 503 + * in case the system is in maintenance mode. + * + * @throws Sabre_DAV_Exception_ServiceUnavailable + * @internal param string $method + * @return bool + */ + public function checkMaintenanceMode() { + if (OC_Config::getValue('maintenance', false)) { + throw new Sabre_DAV_Exception_ServiceUnavailable(); + } + if (OC::checkUpgrade(false)) { + throw new Sabre_DAV_Exception_ServiceUnavailable('Upgrade needed'); + } + + return true; + } +} diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php new file mode 100644 index 00000000000..fa27abb381a --- /dev/null +++ b/lib/private/connector/sabre/node.php @@ -0,0 +1,258 @@ +<?php + +/** + * ownCloud + * + * @author Jakob Sack + * @copyright 2011 Jakob Sack kde@jakobsack.de + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IProperties { + const GETETAG_PROPERTYNAME = '{DAV:}getetag'; + const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; + + /** + * Allow configuring the method used to generate Etags + * + * @var array(class_name, function_name) + */ + public static $ETagFunction = null; + + /** + * is kept public to allow overwrite for unit testing + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * The path to the current node + * + * @var string + */ + protected $path; + /** + * node fileinfo cache + * @var array + */ + protected $fileinfo_cache; + /** + * node properties cache + * @var array + */ + protected $property_cache = null; + + /** + * @brief Sets up the node, expects a full path name + * @param string $path + * @return void + */ + public function __construct($path) { + $this->path = $path; + } + + + + /** + * @brief Returns the name of the node + * @return string + */ + public function getName() { + + list(, $name) = Sabre_DAV_URLUtil::splitPath($this->path); + return $name; + + } + + /** + * @brief Renames the node + * @param string $name The new name + * @return void + */ + public function setName($name) { + + // rename is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path); + list(, $newName) = Sabre_DAV_URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + $oldPath = $this->path; + + \OC\Files\Filesystem::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 )); + + } + + public function setFileinfoCache($fileinfo_cache) + { + $this->fileinfo_cache = $fileinfo_cache; + } + + /** + * @brief Ensure that the fileinfo cache is filled + * @note Uses OC_FileCache or a direct stat + */ + protected function getFileinfoCache() { + if (!isset($this->fileinfo_cache)) { + if ($fileinfo_cache = \OC\Files\Filesystem::getFileInfo($this->path)) { + } else { + $fileinfo_cache = \OC\Files\Filesystem::stat($this->path); + } + + $this->fileinfo_cache = $fileinfo_cache; + } + } + + public function setPropertyCache($property_cache) + { + $this->property_cache = $property_cache; + } + + /** + * @brief Returns the last modification time, as a unix timestamp + * @return int + */ + public function getLastModified() { + $this->getFileinfoCache(); + return $this->fileinfo_cache['mtime']; + + } + + /** + * sets the last modification time of the file (mtime) to the value given + * in the second parameter or to now if the second param is empty. + * Even if the modification time is set to a custom value the access time is set to now. + */ + public function touch($mtime) { + + // touch is only allowed if the update privilege is granted + if (!\OC\Files\Filesystem::isUpdatable($this->path)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + + \OC\Files\Filesystem::touch($this->path, $mtime); + } + + /** + * @brief Updates properties on this node, + * @param array $mutations + * @see Sabre_DAV_IProperties::updateProperties + * @return bool|array + */ + 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; + } + + /** + * @brief 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 + */ + 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']; + } + + // Don't call the static getETagPropertyForPath, its result is not cached + $this->getFileinfoCache(); + if ($this->fileinfo_cache['etag']) { + $this->property_cache[self::GETETAG_PROPERTYNAME] = '"'.$this->fileinfo_cache['etag'].'"'; + } else { + $this->property_cache[self::GETETAG_PROPERTYNAME] = null; + } + } + + // 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; + } + + /** + * Returns the ETag surrounded by double-quotes for this path. + * @param string $path Path of the file + * @return string|null Returns null if the ETag can not effectively be determined + */ + protected function getETagPropertyForPath($path) { + $data = $this->getFS()->getFileInfo($path); + if (isset($data['etag'])) { + return '"'.$data['etag'].'"'; + } + return null; + } + + protected function getFS() { + if (is_null($this->fileView)) { + $this->fileView = \OC\Files\Filesystem::getView(); + } + return $this->fileView; + } +} diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php new file mode 100644 index 00000000000..80c3840b99d --- /dev/null +++ b/lib/private/connector/sabre/objecttree.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright (c) 2013 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Connector\Sabre; + +use OC\Files\Filesystem; + +class ObjectTree extends \Sabre_DAV_ObjectTree { + + /** + * keep this public to allow mock injection during unit test + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @throws \Sabre_DAV_Exception_NotFound + * @return \Sabre_DAV_INode + */ + public function getNodeForPath($path) { + + $path = trim($path, '/'); + if (isset($this->cache[$path])) { + return $this->cache[$path]; + } + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + $info = $this->getFileView()->getFileInfo($path); + + if (!$info) { + throw new \Sabre_DAV_Exception_NotFound('File with name ' . $path . ' could not be located'); + } + + if ($info['mimetype'] === 'httpd/unix-directory') { + $node = new \OC_Connector_Sabre_Directory($path); + } else { + $node = new \OC_Connector_Sabre_File($path); + } + + $node->setFileinfoCache($info); + + $this->cache[$path] = $node; + return $node; + + } + + /** + * Moves a file from one location to another + * + * @param string $sourcePath The path to the file which should be moved + * @param string $destinationPath The full destination path, so not just the destination parent node + * @throws \Sabre_DAV_Exception_Forbidden + * @return int + */ + public function move($sourcePath, $destinationPath) { + + $sourceNode = $this->getNodeForPath($sourcePath); + 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); + + // check update privileges + $fs = $this->getFileView(); + if (!$fs->isUpdatable($sourcePath)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if ($sourceDir !== $destinationDir) { + // for a full move we need update privileges on sourcePath and sourceDir as well as destinationDir + if (!$fs->isUpdatable($sourceDir)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + if (!$fs->isUpdatable($destinationDir)) { + throw new \Sabre_DAV_Exception_Forbidden(); + } + } + + $renameOkay = $fs->rename($sourcePath, $destinationPath); + if (!$renameOkay) { + throw new \Sabre_DAV_Exception_Forbidden(''); + } + + $this->markDirty($sourceDir); + $this->markDirty($destinationDir); + + } + + /** + * Copies a file or directory. + * + * This method must work recursively and delete the destination + * if it exists + * + * @param string $source + * @param string $destination + * @return void + */ + public function copy($source, $destination) { + + if (Filesystem::is_file($source)) { + Filesystem::copy($source, $destination); + } else { + Filesystem::mkdir($destination); + $dh = Filesystem::opendir($source); + if(is_resource($dh)) { + while (($subnode = readdir($dh)) !== false) { + + if ($subnode == '.' || $subnode == '..') continue; + $this->copy($source . '/' . $subnode, $destination . '/' . $subnode); + + } + } + } + + list($destinationDir,) = \Sabre_DAV_URLUtil::splitPath($destination); + $this->markDirty($destinationDir); + } + + /** + * @return \OC\Files\View + */ + public function getFileView() { + if (is_null($this->fileView)) { + $this->fileView = \OC\Files\Filesystem::getView(); + } + return $this->fileView; + } +} diff --git a/lib/private/connector/sabre/principal.php b/lib/private/connector/sabre/principal.php new file mode 100644 index 00000000000..59a96797c16 --- /dev/null +++ b/lib/private/connector/sabre/principal.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright (c) 2011 Jakob Sack mail@jakobsack.de + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Connector_Sabre_Principal implements Sabre_DAVACL_IPrincipalBackend { + /** + * Returns a list of principals based on a prefix. + * + * This prefix will often contain something like 'principals'. You are only + * expected to return principals that are in this base path. + * + * You are expected to return at least a 'uri' for every user, you can + * return any additional properties if you wish so. Common properties are: + * {DAV:}displayname + * + * @param string $prefixPath + * @return array + */ + public function getPrincipalsByPrefix( $prefixPath ) { + $principals = array(); + + if ($prefixPath == 'principals') { + foreach(OC_User::getUsers() as $user) { + $user_uri = 'principals/'.$user; + $principals[] = array( + 'uri' => $user_uri, + '{DAV:}displayname' => $user, + ); + } + } + + return $principals; + } + + /** + * Returns a specific principal, specified by it's path. + * The returned structure should be the exact same as from + * getPrincipalsByPrefix. + * + * @param string $path + * @return array + */ + public function getPrincipalByPath($path) { + list($prefix, $name) = explode('/', $path); + + if ($prefix == 'principals' && OC_User::userExists($name)) { + return array( + 'uri' => 'principals/'.$name, + '{DAV:}displayname' => $name, + ); + } + + return null; + } + + /** + * Returns the list of members for a group-principal + * + * @param string $principal + * @return array + */ + public function getGroupMemberSet($principal) { + // TODO: for now the group principal has only one member, the user itself + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new Sabre_DAV_Exception('Principal not found'); + } + + return array( + $principal['uri'] + ); + } + + /** + * Returns the list of groups a principal is a member of + * + * @param string $principal + * @return array + */ + public function getGroupMembership($principal) { + list($prefix, $name) = Sabre_DAV_URLUtil::splitPath($principal); + + $group_membership = array(); + if ($prefix == 'principals') { + $principal = $this->getPrincipalByPath($principal); + if (!$principal) { + throw new Sabre_DAV_Exception('Principal not found'); + } + + // TODO: for now the user principal has only its own groups + return array( + 'principals/'.$name.'/calendar-proxy-read', + 'principals/'.$name.'/calendar-proxy-write', + // The addressbook groups are not supported in Sabre, + // see http://groups.google.com/group/sabredav-discuss/browse_thread/thread/ef2fa9759d55f8c#msg_5720afc11602e753 + //'principals/'.$name.'/addressbook-proxy-read', + //'principals/'.$name.'/addressbook-proxy-write', + ); + } + return $group_membership; + } + + /** + * Updates the list of group members for a group principal. + * + * The principals should be passed as a list of uri's. + * + * @param string $principal + * @param array $members + * @return void + */ + public function setGroupMemberSet($principal, array $members) { + throw new Sabre_DAV_Exception('Setting members of the group is not supported yet'); + } + + function updatePrincipal($path, $mutations) { + return 0; + } + + function searchPrincipals($prefixPath, array $searchProperties) { + return array(); + } +} diff --git a/lib/private/connector/sabre/quotaplugin.php b/lib/private/connector/sabre/quotaplugin.php new file mode 100644 index 00000000000..ea2cb81d1f7 --- /dev/null +++ b/lib/private/connector/sabre/quotaplugin.php @@ -0,0 +1,97 @@ +<?php + +/** + * This plugin check user quota and deny creating files when they exceeds the quota. + * + * @author Sergio Cambra + * @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 { + + /** + * Reference to main server object + * + * @var Sabre_DAV_Server + */ + private $server; + + /** + * is kept public to allow overwrite for unit testing + * + * @var \OC\Files\View + */ + public $fileView; + + /** + * This initializes the plugin. + * + * This function is called by Sabre_DAV_Server, after + * addPlugin is called. + * + * This method should set up the requires event subscriptions. + * + * @param Sabre_DAV_Server $server + * @return void + */ + public function initialize(Sabre_DAV_Server $server) { + + $this->server = $server; + + $server->subscribeEvent('beforeWriteContent', array($this, 'checkQuota'), 10); + $server->subscribeEvent('beforeCreateFile', array($this, 'checkQuota'), 10); + } + + /** + * This method is called before any HTTP method and validates there is enough free space to store the file + * + * @param string $method + * @throws Sabre_DAV_Exception + * @return bool + */ + public function checkQuota($uri, $data = null) { + $length = $this->getLength(); + if ($length) { + if (substr($uri, 0, 1)!=='/') { + $uri='/'.$uri; + } + list($parentUri, $newName) = Sabre_DAV_URLUtil::splitPath($uri); + $freeSpace = $this->getFreeSpace($parentUri); + if ($freeSpace !== \OC\Files\SPACE_UNKNOWN && $length > $freeSpace) { + throw new Sabre_DAV_Exception_InsufficientStorage(); + } + } + return true; + } + + public function getLength() + { + $req = $this->server->httpRequest; + $length = $req->getHeader('X-Expected-Entity-Length'); + if (!$length) { + $length = $req->getHeader('Content-Length'); + } + + $ocLength = $req->getHeader('OC-Total-Length'); + if ($length && $ocLength) { + return max($length, $ocLength); + } + + return $length; + } + + /** + * @param $parentUri + * @return mixed + */ + public function getFreeSpace($parentUri) + { + if (is_null($this->fileView)) { + // initialize fileView + $this->fileView = \OC\Files\Filesystem::getView(); + } + + $freeSpace = $this->fileView->free_space($parentUri); + return $freeSpace; + } +} diff --git a/lib/private/connector/sabre/request.php b/lib/private/connector/sabre/request.php new file mode 100644 index 00000000000..d70c25c4e70 --- /dev/null +++ b/lib/private/connector/sabre/request.php @@ -0,0 +1,50 @@ +<?php + +/** + * ownCloud + * + * @author Stefan Herbrechtsmeier + * @copyright 2012 Stefan Herbrechtsmeier <stefan@herbrechtsmeier.net> + * + * 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 <http://www.gnu.org/licenses/>. + * + */ + +class OC_Connector_Sabre_Request extends Sabre_HTTP_Request { + /** + * Returns the requested uri + * + * @return string + */ + public function getUri() { + return OC_Request::requestUri(); + } + + /** + * 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; + } + } +} |