diff options
Diffstat (limited to 'vendor/sabre/dav/lib/DAV')
23 files changed, 1204 insertions, 88 deletions
diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php index 0251decc1..85c5f30d5 100644 --- a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php @@ -155,8 +155,14 @@ abstract class AbstractDigest implements BackendInterface { $response ); $auth->init(); + + $oldStatus = $response->getStatus() ?: 200; $auth->requireLogin(); + // Preventing the digest utility from modifying the http status code, + // this should be handled by the main plugin. + $response->setStatus($oldStatus); + } } diff --git a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php index 818d8a4ad..4b5f35ac3 100644 --- a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php +++ b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php @@ -4,7 +4,6 @@ namespace Sabre\DAV\Auth; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; -use Sabre\HTTP\URLUtil; use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; @@ -26,6 +25,20 @@ use Sabre\DAV\ServerPlugin; class Plugin extends ServerPlugin { /** + * By default this plugin will require that the user is authenticated, + * and refuse any access if the user is not authenticated. + * + * If this setting is set to false, we let the user through, whether they + * are authenticated or not. + * + * This is useful if you want to allow both authenticated and + * unauthenticated access to your server. + * + * @param bool + */ + public $autoRequireLogin = true; + + /** * authentication backends */ protected $backends; @@ -108,27 +121,6 @@ class Plugin extends ServerPlugin { } /** - * Returns the current username. - * - * This method is deprecated and is only kept for backwards compatibility - * purposes. Please switch to getCurrentPrincipal(). - * - * @deprecated Will be removed in a future version! - * @return string|null - */ - function getCurrentUser() { - - // We just do a 'basename' on the principal to give back a sane value - // here. - list(, $userName) = URLUtil::splitPath( - $this->getCurrentPrincipal() - ); - - return $userName; - - } - - /** * This method is called before any HTTP method and forces users to be authenticated * * @param RequestInterface $request @@ -154,6 +146,50 @@ class Plugin extends ServerPlugin { return; } + + $authResult = $this->check($request, $response); + + if ($authResult[0]) { + // Auth was successful + $this->currentPrincipal = $authResult[1]; + $this->loginFailedReasons = null; + return; + } + + + + // If we got here, it means that no authentication backend was + // successful in authenticating the user. + $this->currentPrincipal = null; + $this->loginFailedReasons = $authResult[1]; + + if ($this->autoRequireLogin) { + $this->challenge($request, $response); + throw new NotAuthenticated(implode(', ', $authResult[1])); + } + + } + + /** + * Checks authentication credentials, and logs the user in if possible. + * + * This method returns an array. The first item in the array is a boolean + * indicating if login was successful. + * + * If login was successful, the second item in the array will contain the + * current principal url/path of the logged in user. + * + * If login was not successful, the second item in the array will contain a + * an array with strings. The strings are a list of reasons why login was + * unsuccesful. For every auth backend there will be one reason, so usually + * there's just one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + if (!$this->backends) { throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.'); } @@ -172,20 +208,56 @@ class Plugin extends ServerPlugin { if ($result[0]) { $this->currentPrincipal = $result[1]; // Exit early - return; + return [true, $result[1]]; } $reasons[] = $result[1]; } - // If we got here, it means that no authentication backend was - // successful in authenticating the user. - $this->currentPrincipal = null; + return [false, $reasons]; + + } + + /** + * This method sends authentication challenges to the user. + * + * This method will for example cause a HTTP Basic backend to set a + * WWW-Authorization header, indicating to the client that it should + * authenticate. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function challenge(RequestInterface $request, ResponseInterface $response) { foreach ($this->backends as $backend) { $backend->challenge($request, $response); } - throw new NotAuthenticated(implode(', ', $reasons)); + + } + + /** + * List of reasons why login failed for the last login operation. + * + * @var string[]|null + */ + protected $loginFailedReasons; + + /** + * Returns a list of reasons why login was unsuccessful. + * + * This method will return the login failed reasons for the last login + * operation. One for each auth backend. + * + * This method returns null if the last authentication attempt was + * successful, or if there was no authentication attempt yet. + * + * @return string[]|null + */ + function getLoginFailedReasons() { + + return $this->loginFailedReasons; } diff --git a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php index 07ca6c3e5..49359a045 100644 --- a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php +++ b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php @@ -48,7 +48,7 @@ class Plugin extends DAV\ServerPlugin { public $uninterestingProperties = [ '{DAV:}supportedlock', '{DAV:}acl-restrictions', - '{DAV:}supported-privilege-set', +// '{DAV:}supported-privilege-set', '{DAV:}supported-method-set', ]; @@ -112,7 +112,7 @@ class Plugin extends DAV\ServerPlugin { $getVars = $request->getQueryParameters(); // CSP headers - $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"); $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null; @@ -354,7 +354,7 @@ class Plugin extends DAV\ServerPlugin { $output = ''; if ($this->enablePost) { - $this->server->emit('onHTMLActionsPanel', [$node, &$output]); + $this->server->emit('onHTMLActionsPanel', [$node, &$output, $path]); } if ($output) { @@ -368,7 +368,7 @@ class Plugin extends DAV\ServerPlugin { $html .= $this->generateFooter(); - $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + $this->server->httpResponse->setHeader('Content-Security-Policy', "default-src 'none'; img-src 'self'; style-src 'self'; font-src 'self';"); return $html; @@ -477,7 +477,7 @@ HTML; $version = DAV\Version::VERSION; return <<<HTML -<footer>Generated by SabreDAV $version (c)2007-2015 <a href="http://sabre.io/">http://sabre.io/</a></footer> +<footer>Generated by SabreDAV $version (c)2007-2016 <a href="http://sabre.io/">http://sabre.io/</a></footer> </body> </html> HTML; @@ -493,9 +493,10 @@ HTML; * * @param DAV\INode $node * @param mixed $output + * @param string $path * @return void */ - function htmlActionsPanel(DAV\INode $node, &$output) { + function htmlActionsPanel(DAV\INode $node, &$output, $path) { if (!$node instanceof DAV\ICollection) return; diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css index c9ab2c74f..8869597f0 100644 --- a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css @@ -96,12 +96,12 @@ header a { vertical-align: middle; border: 0; } -input, button { +input, button, select { font: inherit; color: inherit; } -input[type=text] { +input[type=text], select { border: 1px solid #bbbbbb; line-height: 22px; padding: 5px 10px; @@ -200,7 +200,7 @@ section table { line-height: 40px; } -.actions input[type=text] { +.actions input[type=text], select { width: 450px; } diff --git a/vendor/sabre/dav/lib/DAV/Client.php b/vendor/sabre/dav/lib/DAV/Client.php index d46b397b6..08d5d4702 100644 --- a/vendor/sabre/dav/lib/DAV/Client.php +++ b/vendor/sabre/dav/lib/DAV/Client.php @@ -3,6 +3,7 @@ namespace Sabre\DAV; use Sabre\HTTP; +use Sabre\Uri; /** * SabreDAV DAV client @@ -387,20 +388,10 @@ class Client extends HTTP\Client { */ function getAbsoluteUrl($url) { - // If the url starts with http:// or https://, the url is already absolute. - if (preg_match('/^http(s?):\/\//', $url)) { - return $url; - } - - // If the url starts with a slash, we must calculate the url based off - // the root of the base url. - if (strpos($url, '/') === 0) { - $parts = parse_url($this->baseUri); - return $parts['scheme'] . '://' . $parts['host'] . (isset($parts['port']) ? ':' . $parts['port'] : '') . $url; - } - - // Otherwise... - return $this->baseUri . $url; + return Uri\resolve( + $this->baseUri, + $url + ); } diff --git a/vendor/sabre/dav/lib/DAV/CorePlugin.php b/vendor/sabre/dav/lib/DAV/CorePlugin.php index 3a70b2a7e..a1b052915 100644 --- a/vendor/sabre/dav/lib/DAV/CorePlugin.php +++ b/vendor/sabre/dav/lib/DAV/CorePlugin.php @@ -50,6 +50,8 @@ class CorePlugin extends ServerPlugin { $server->on('propFind', [$this, 'propFindNode'], 120); $server->on('propFind', [$this, 'propFindLate'], 200); + $server->on('exception', [$this, 'exception']); + } /** @@ -844,10 +846,8 @@ class CorePlugin extends ServerPlugin { if ($node instanceof IProperties && $propertyNames = $propFind->get404Properties()) { $nodeProperties = $node->getProperties($propertyNames); - foreach ($propertyNames as $propertyName) { - if (array_key_exists($propertyName, $nodeProperties)) { - $propFind->set($propertyName, $nodeProperties[$propertyName], 200); - } + foreach ($nodeProperties as $propertyName => $propertyValue) { + $propFind->set($propertyName, $propertyValue, 200); } } @@ -905,6 +905,38 @@ class CorePlugin extends ServerPlugin { } /** + * Listens for exception events, and automatically logs them. + * + * @param Exception $e + */ + function exception($e) { + + $logLevel = \Psr\Log\LogLevel::CRITICAL; + if ($e instanceof \Sabre\DAV\Exception) { + // If it's a standard sabre/dav exception, it means we have a http + // status code available. + $code = $e->getHTTPCode(); + + if ($code >= 400 && $code < 500) { + // user error + $logLevel = \Psr\Log\LogLevel::INFO; + } else { + // Server-side error. We mark it's as an error, but it's not + // critical. + $logLevel = \Psr\Log\LogLevel::ERROR; + } + } + + $this->server->getLogger()->log( + $logLevel, + 'Uncaught exception', + [ + 'exception' => $e, + ] + ); + } + + /** * Returns a bunch of meta-data about the plugin. * * Providing this information is optional, and is mainly displayed by the diff --git a/vendor/sabre/dav/lib/DAV/FS/Directory.php b/vendor/sabre/dav/lib/DAV/FS/Directory.php index 963e5554c..362f7a411 100644 --- a/vendor/sabre/dav/lib/DAV/FS/Directory.php +++ b/vendor/sabre/dav/lib/DAV/FS/Directory.php @@ -140,10 +140,10 @@ class Directory extends Node implements DAV\ICollection, DAV\IQuota { * @return array */ function getQuotaInfo() { - + $absolute = realpath($this->path); return [ - disk_total_space($this->path) - disk_free_space($this->path), - disk_free_space($this->path) + disk_total_space($absolute) - disk_free_space($absolute), + disk_free_space($absolute) ]; } diff --git a/vendor/sabre/dav/lib/DAV/File.php b/vendor/sabre/dav/lib/DAV/File.php index e0a0391db..675956b22 100644 --- a/vendor/sabre/dav/lib/DAV/File.php +++ b/vendor/sabre/dav/lib/DAV/File.php @@ -15,12 +15,24 @@ namespace Sabre\DAV; abstract class File extends Node implements IFile { /** - * Updates the data + * Replaces the contents of the file. * - * data is a readable stream resource. + * The data argument is a readable stream resource. * - * @param resource $data - * @return void + * After a succesful 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 string|resource $data + * @return string|null */ function put($data) { diff --git a/vendor/sabre/dav/lib/DAV/ICollection.php b/vendor/sabre/dav/lib/DAV/ICollection.php index 390d9b741..7793070d3 100644 --- a/vendor/sabre/dav/lib/DAV/ICollection.php +++ b/vendor/sabre/dav/lib/DAV/ICollection.php @@ -54,14 +54,14 @@ interface ICollection extends INode { * exist. * * @param string $name - * @return DAV\INode + * @return INode */ function getChild($name); /** * Returns an array with all the child nodes * - * @return DAV\INode[] + * @return INode[] */ function getChildren(); diff --git a/vendor/sabre/dav/lib/DAV/IFile.php b/vendor/sabre/dav/lib/DAV/IFile.php index e16a3a58a..37e7cd33c 100644 --- a/vendor/sabre/dav/lib/DAV/IFile.php +++ b/vendor/sabre/dav/lib/DAV/IFile.php @@ -32,7 +32,7 @@ interface IFile extends INode { * different object on a subsequent GET you are strongly recommended to not * return an ETag, and just return null. * - * @param resource $data + * @param resource|data $data * @return string|null */ function put($data); diff --git a/vendor/sabre/dav/lib/DAV/INode.php b/vendor/sabre/dav/lib/DAV/INode.php index b5e6cb9ef..bb884934d 100644 --- a/vendor/sabre/dav/lib/DAV/INode.php +++ b/vendor/sabre/dav/lib/DAV/INode.php @@ -36,9 +36,10 @@ interface INode { function setName($name); /** - * Returns the last modification time, as a unix timestamp + * Returns the last modification time, as a unix timestamp. Return null + * if the information is not available. * - * @return int + * @return int|null */ function getLastModified(); diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php index 910e4979d..2fe843884 100644 --- a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php @@ -88,6 +88,9 @@ class PDO implements BackendInterface { $stmt->execute([$path]); while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + if (gettype($row['value']) === 'resource') { + $row['value'] = stream_get_contents($row['value']); + } switch ($row['valuetype']) { case null : case self::VT_STRING : @@ -121,7 +124,26 @@ class PDO implements BackendInterface { $propPatch->handleRemaining(function($properties) use ($path) { - $updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)"); + + if ($this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') { + + $updateSql = <<<SQL +INSERT INTO {$this->tableName} (path, name, valuetype, value) +VALUES (:path, :name, :valuetype, :value) +ON CONFLICT (path, name) +DO UPDATE SET valuetype = :valuetype, value = :value +SQL; + + + } else { + $updateSql = <<<SQL +REPLACE INTO {$this->tableName} (path, name, valuetype, value) +VALUES (:path, :name, :valuetype, :value) +SQL; + + } + + $updateStmt = $this->pdo->prepare($updateSql); $deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?"); foreach ($properties as $name => $value) { @@ -136,7 +158,14 @@ class PDO implements BackendInterface { $valueType = self::VT_OBJECT; $value = serialize($value); } - $updateStmt->execute([$path, $name, $valueType, $value]); + + $updateStmt->bindParam('path', $path, \PDO::PARAM_STR); + $updateStmt->bindParam('name', $name, \PDO::PARAM_STR); + $updateStmt->bindParam('valuetype', $valueType, \PDO::PARAM_INT); + $updateStmt->bindParam('value', $value, \PDO::PARAM_LOB); + + $updateStmt->execute(); + } else { $deleteStmt->execute([$path, $name]); } diff --git a/vendor/sabre/dav/lib/DAV/Server.php b/vendor/sabre/dav/lib/DAV/Server.php index b37652812..024b7a557 100644 --- a/vendor/sabre/dav/lib/DAV/Server.php +++ b/vendor/sabre/dav/lib/DAV/Server.php @@ -8,6 +8,10 @@ use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\HTTP\URLUtil; use Sabre\Uri; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; /** * Main DAV server class @@ -16,7 +20,9 @@ use Sabre\Uri; * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ -class Server extends EventEmitter { +class Server extends EventEmitter implements LoggerAwareInterface { + + use LoggerAwareTrait; /** * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree @@ -431,6 +437,20 @@ class Server extends EventEmitter { } /** + * Returns the PSR-3 logger objcet. + * + * @return LoggerInterface + */ + function getLogger() { + + if (!$this->logger) { + $this->logger = new NullLogger(); + } + return $this->logger; + + } + + /** * Handles a http request, and execute a method based on its name * * @param RequestInterface $request @@ -1177,9 +1197,20 @@ class Server extends EventEmitter { if (!$success) { $result = $mkCol->getResult(); - // generateMkCol needs the href key to exist. - $result['href'] = $uri; - return $result; + + $formattedResult = [ + 'href' => $uri, + ]; + + foreach ($result as $propertyName => $status) { + + if (!isset($formattedResult[$status])) { + $formattedResult[$status] = []; + } + $formattedResult[$status][$propertyName] = null; + + } + return $formattedResult; } $this->tree->markDirty($parentUri); diff --git a/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php b/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php new file mode 100644 index 000000000..034aefbdc --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Sharing/ISharedNode.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\DAV\Sharing; + +use Sabre\DAV\INode; + +/** + * This interface represents a resource that has sharing capabilities, either + * because it's possible for an owner to share the resource, or because this is + * an instance of a shared resource. + * + * @copyright Copyright (C) fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface ISharedNode extends INode { + + /** + * Returns the 'access level' for the instance of this shared resource. + * + * The value should be one of the Sabre\DAV\Sharing\Plugin::ACCESS_ + * constants. + * + * @return int + */ + function getShareAccess(); + + /** + * This function must return a URI that uniquely identifies the shared + * resource. This URI should be identical across instances, and is + * also used in several other XML bodies to connect invites to + * resources. + * + * This may simply be a relative reference to the original shared instance, + * but it could also be a urn. As long as it's a valid URI and unique. + * + * @return string + */ + function getShareResourceUri(); + + /** + * Updates the list of sharees. + * + * Every item must be a Sharee object. + * + * @param \Sabre\DAV\Xml\Element\Sharee[] $sharees + * @return void + */ + function updateInvites(array $sharees); + + /** + * Returns the list of people whom this resource is shared with. + * + * Every item in the returned array must be a Sharee object with + * at least the following properties set: + * + * * $href + * * $shareAccess + * * $inviteStatus + * + * and optionally: + * + * * $properties + * + * @return \Sabre\DAV\Xml\Element\Sharee[] + */ + function getInvites(); + +} diff --git a/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php b/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php new file mode 100644 index 000000000..354d06a56 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Sharing/Plugin.php @@ -0,0 +1,342 @@ +<?php + +namespace Sabre\DAV\Sharing; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\INode; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Xml\Property; +use Sabre\DAV\Xml\Element\Sharee; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * This plugin implements HTTP requests and properties related to: + * + * draft-pot-webdav-resource-sharing + * + * This specification allows people to share webdav resources with others. + * + * @copyright Copyright (C) 2007-2015 fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends ServerPlugin { + + const ACCESS_NOTSHARED = 0; + const ACCESS_SHAREDOWNER = 1; + const ACCESS_READ = 2; + const ACCESS_READWRITE = 3; + const ACCESS_NOACCESS = 4; + + const INVITE_NORESPONSE = 1; + const INVITE_ACCEPTED = 2; + const INVITE_DECLINED = 3; + const INVITE_INVALID = 4; + + /** + * Reference to SabreDAV server object. + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * This method should return a list of server-features. + * + * This is for example 'versioning' and is added to the DAV: header + * in an OPTIONS response. + * + * @return array + */ + function getFeatures() { + + return ['resource-sharing']; + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'sharing'; + + } + + /** + * 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 Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + + $server->xml->elementMap['{DAV:}share-resource'] = 'Sabre\\DAV\\Xml\\Request\\ShareResource'; + + array_push( + $server->protectedProperties, + '{DAV:}share-mode' + ); + + $server->on('method:POST', [$this, 'httpPost']); + $server->on('propFind', [$this, 'propFind']); + $server->on('getSupportedPrivilegeSet', [$this, 'getSupportedPrivilegeSet']); + $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); + $server->on('onBrowserPostAction', [$this, 'browserPostAction']); + + } + + /** + * Updates the list of sharees on a shared resource. + * + * The sharees array is a list of people that are to be added modified + * or removed in the list of shares. + * + * @param string $path + * @param Sharee[] $sharees + * @return void + */ + function shareResource($path, array $sharees) { + + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof ISharedNode) { + + throw new Forbidden('Sharing is not allowed on this node'); + + } + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}share'); + } + + foreach ($sharees as $sharee) { + // We're going to attempt to get a local principal uri for a share + // href by emitting the getPrincipalByUri event. + $principal = null; + $this->server->emit('getPrincipalByUri', [$sharee->href, &$principal]); + $sharee->principal = $principal; + } + $node->updateInvites($sharees); + + } + + /** + * This event is triggered when properties are requested for nodes. + * + * This allows us to inject any sharings-specific properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + if ($node instanceof ISharedNode) { + + $propFind->handle('{DAV:}share-access', function() use ($node) { + + return new Property\ShareAccess($node->getShareAccess()); + + }); + $propFind->handle('{DAV:}invite', function() use ($node) { + + return new Property\Invite($node->getInvites()); + + }); + $propFind->handle('{DAV:}share-resource-uri', function() use ($node) { + + return new Property\Href($node->getShareResourceUri()); + + }); + + } + + } + + /** + * We intercept this to handle POST requests on shared resources + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return null|bool + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $contentType = $request->getHeader('Content-Type'); + + // We're only interested in the davsharing content type. + if (strpos($contentType, 'application/davsharing+xml') === false) { + return; + } + + $message = $this->server->xml->parse( + $request->getBody(), + $request->getUrl(), + $documentType + ); + + switch ($documentType) { + + case '{DAV:}share-resource': + + $this->shareResource($path, $message->sharees); + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + + default : + throw new BadRequest('Unexpected document type: ' . $documentType . ' for this Content-Type'); + + } + + } + + /** + * This method is triggered whenever a subsystem reqeuests the privileges + * hat are supported on a particular node. + * + * We need to add a number of privileges for scheduling purposes. + * + * @param INode $node + * @param array $supportedPrivilegeSet + */ + function getSupportedPrivilegeSet(INode $node, array &$supportedPrivilegeSet) { + + if ($node instanceof ISharedNode) { + $supportedPrivilegeSet['{DAV:}share'] = [ + 'abstract' => false, + 'aggregates' => [], + ]; + } + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + function getPluginInfo() { + + return [ + 'name' => $this->getPluginName(), + 'description' => 'This plugin implements WebDAV resource sharing', + 'link' => 'https://github.com/evert/webdav-sharing' + ]; + + } + + /** + * This method is used to generate HTML output for the + * DAV\Browser\Plugin. + * + * @param INode $node + * @param string $output + * @param string $path + * @return bool|null + */ + function htmlActionsPanel(INode $node, &$output, $path) { + + if (!$node instanceof ISharedNode) { + return; + } + + $aclPlugin = $this->server->getPlugin('acl'); + if ($aclPlugin) { + if (!$aclPlugin->checkPrivileges($path, '{DAV:}share', \Sabre\DAVACL\Plugin::R_PARENT, false)) { + // Sharing is not permitted, we will not draw this interface. + return; + } + } + + $output .= '<tr><td colspan="2"><form method="post" action=""> + <h3>Share this resource</h3> + <input type="hidden" name="sabreAction" value="share" /> + <label>Share with (uri):</label> <input type="text" name="href" placeholder="mailto:user@example.org"/><br /> + <label>Access</label> + <select name="access"> + <option value="readwrite">Read-write</option> + <option value="read">Read-only</option> + <option value="no-access">Revoke access</option> + </select><br /> + <input type="submit" value="share" /> + </form> + </td></tr>'; + + } + + /** + * This method is triggered for POST actions generated by the browser + * plugin. + * + * @param string $path + * @param string $action + * @param array $postVars + */ + function browserPostAction($path, $action, $postVars) { + + if ($action !== 'share') { + return; + } + + if (empty($postVars['href'])) { + throw new BadRequest('The "href" POST parameter is required'); + } + if (empty($postVars['access'])) { + throw new BadRequest('The "access" POST parameter is required'); + } + + $accessMap = [ + 'readwrite' => self::ACCESS_READWRITE, + 'read' => self::ACCESS_READ, + 'no-access' => self::ACCESS_NOACCESS, + ]; + + if (!isset($accessMap[$postVars['access']])) { + throw new BadRequest('The "access" POST must be readwrite, read or no-access'); + } + $sharee = new Sharee([ + 'href' => $postVars['href'], + 'access' => $accessMap[$postVars['access']], + ]); + + $this->shareResource( + $path, + [$sharee] + ); + return false; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Tree.php b/vendor/sabre/dav/lib/DAV/Tree.php index 4563f7c72..5d2792503 100644 --- a/vendor/sabre/dav/lib/DAV/Tree.php +++ b/vendor/sabre/dav/lib/DAV/Tree.php @@ -229,7 +229,7 @@ class Tree { // flushing the entire cache $path = trim($path, '/'); foreach ($this->cache as $nodePath => $node) { - if ($nodePath == $path || strpos($nodePath, $path . '/') === 0) + if ($path === '' || $nodePath == $path || strpos($nodePath, $path . '/') === 0) unset($this->cache[$nodePath]); } diff --git a/vendor/sabre/dav/lib/DAV/Version.php b/vendor/sabre/dav/lib/DAV/Version.php index f9331943a..5430b967c 100644 --- a/vendor/sabre/dav/lib/DAV/Version.php +++ b/vendor/sabre/dav/lib/DAV/Version.php @@ -14,6 +14,6 @@ class Version { /** * Full version number */ - const VERSION = '3.1.3'; + const VERSION = '3.2.0-beta1'; } diff --git a/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php b/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php new file mode 100644 index 000000000..dcfd7bd2e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Element/Sharee.php @@ -0,0 +1,199 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Sharing\Plugin; +use Sabre\DAV\Xml\Property\Href; +use Sabre\DAV\Xml\Property\ShareAccess; +use Sabre\Xml\Deserializer; +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * This class represents the {DAV:}sharee element. + * + * @copyright Copyright (C) fruux GmbH. (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Sharee implements Element { + + /** + * A URL. Usually a mailto: address, could also be a principal url. + * This uniquely identifies the sharee. + * + * @var string + */ + public $href; + + /** + * A local principal path. The server will do its best to locate the + * principal uri based on the given uri. If we could find a local matching + * principal uri, this property will contain the value. + * + * @var string|null + */ + public $principal; + + /** + * A list of WebDAV properties that describe the sharee. This might for + * example contain a {DAV:}displayname with the real name of the user. + * + * @var array + */ + public $properties = []; + + /** + * Share access level. One of the Sabre\DAV\Sharing\Plugin::ACCESS + * constants. + * + * Can be one of: + * + * ACCESS_READ + * ACCESS_READWRITE + * ACCESS_SHAREDOWNER + * ACCESS_NOACCESS + * + * depending on context. + * + * @var int + */ + public $access; + + /** + * When a sharee is originally invited to a share, the sharer may add + * a comment. This will be placed in this property. + * + * @var string + */ + public $comment; + + /** + * The status of the invite, should be one of the + * Sabre\DAV\Sharing\Plugin::INVITE constants. + * + * @var int + */ + public $inviteStatus; + + /** + * Creates the object + * + * $properties will be used to populate all internal properties. + * + * @param array $properties + */ + function __construct(array $properties = []) { + + foreach ($properties as $k => $v) { + + if (property_exists($this, $k)) { + $this->$k = $v; + } else { + throw new \InvalidArgumentException('Unknown property: ' . $k); + } + + } + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + + $writer->write([ + new Href($this->href), + '{DAV:}prop' => $this->properties, + '{DAV:}share-access' => new ShareAccess($this->access), + ]); + switch ($this->inviteStatus) { + case Plugin::INVITE_NORESPONSE : + $writer->writeElement('{DAV:}invite-noresponse'); + break; + case Plugin::INVITE_ACCEPTED : + $writer->writeElement('{DAV:}invite-accepted'); + break; + case Plugin::INVITE_DECLINED : + $writer->writeElement('{DAV:}invite-declined'); + break; + case Plugin::INVITE_INVALID : + $writer->writeElement('{DAV:}invite-invalid'); + break; + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + // Temporarily override configuration + $reader->pushContext(); + $reader->elementMap['{DAV:}share-access'] = 'Sabre\DAV\Xml\Property\ShareAccess'; + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Deserializer\keyValue'; + + $elems = Deserializer\keyValue($reader, 'DAV:'); + + // Restore previous configuration + $reader->popContext(); + + $sharee = new self(); + if (!isset($elems['href'])) { + throw new BadRequest('Every {DAV:}sharee must have a {DAV:}href child-element'); + } + $sharee->href = $elems['href']; + + if (isset($elems['prop'])) { + $sharee->properties = $elems['prop']; + } + if (isset($elems['comment'])) { + $sharee->comment = $elems['comment']; + } + if (!isset($elems['share-access'])) { + throw new BadRequest('Every {DAV:}sharee must have a {DAV:}share-access child element'); + } + $sharee->access = $elems['share-access']->getValue(); + return $sharee; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php index 538e98d0f..0027f72e1 100644 --- a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php @@ -7,6 +7,7 @@ use Sabre\DAV\Browser\HtmlOutputHelper; use Sabre\Xml\Element; use Sabre\Xml\Reader; use Sabre\Xml\Writer; +use Sabre\Uri; /** * Href property @@ -32,13 +33,6 @@ class Href implements Element, HtmlOutput { protected $hrefs; /** - * Automatically prefix the url with the server base directory - * - * @var bool - */ - protected $autoPrefix = true; - - /** * Constructor * * You must either pass a string for a single href, or an array of hrefs. @@ -47,16 +41,13 @@ class Href implements Element, HtmlOutput { * and not relative to the servers base uri. * * @param string|string[] $href - * @param bool $autoPrefix */ - function __construct($hrefs, $autoPrefix = true) { + function __construct($hrefs) { if (is_string($hrefs)) { $hrefs = [$hrefs]; } $this->hrefs = $hrefs; - $this->autoPrefix = $autoPrefix; - } @@ -104,9 +95,7 @@ class Href implements Element, HtmlOutput { function xmlSerialize(Writer $writer) { foreach ($this->getHrefs() as $href) { - if ($this->autoPrefix) { - $href = $writer->contextUri . \Sabre\HTTP\encodePath($href); - } + $href = Uri\resolve($writer->contextUri, $href); $writer->writeElement('{DAV:}href', $href); } diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php new file mode 100644 index 000000000..0616ff113 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Invite.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Sharing\Sharee; +use Sabre\Xml\XmlSerializable; +use Sabre\Xml\Writer; + +/** + * This class represents the {DAV:}invite property. + * + * This property is defined here: + * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.2 + * + * This property is used by clients to determine who currently has access to + * a shared resource, what their access level is and what their invite status + * is. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Invite implements XmlSerializable { + + /** + * A list of sharees + * + * @var Sharee[] + */ + public $sharees = []; + + /** + * Creates the property. + * + * @param Sharee[] $sharees + */ + function __construct(array $sharees) { + + $this->sharees = $sharees; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->sharees as $sharee) { + $writer->writeElement('{DAV:}sharee', $sharee); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php b/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php new file mode 100644 index 000000000..76a27b95d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/LocalHref.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\HTTP; + +/** + * LocalHref property + * + * Like the Href property, this element represents {DAV:}href. The difference + * is that this is used stricly for paths on the server. The LocalHref property + * will prepare the path so it's a valid URI. + * + * These two objects behave identically: + * new LocalHref($path) + * new Href(\Sabre\HTTP\encodePath($path)) + * + * LocalPath basically ensures that your spaces are %20, and everything that + * needs to be is uri encoded. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LocalHref extends Href { + + /** + * Constructor + * + * You must either pass a string for a single href, or an array of hrefs. + * + * If auto-prefix is set to false, the hrefs will be treated as absolute + * and not relative to the servers base uri. + * + * @param string|string[] $href + */ + function __construct($hrefs) { + + parent::__construct(array_map( + function($href) { + return \Sabre\HTTP\encodePath($href); + }, + (array)$hrefs + )); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php b/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php new file mode 100644 index 000000000..f27af5415 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/ShareAccess.php @@ -0,0 +1,143 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Sharing\Plugin as SharingPlugin; +use Sabre\DAV\Exception\BadRequest; +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * This class represents the {DAV:}share-access property. + * + * This property is defined here: + * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-03#section-4.4.1 + * + * This property is used to indicate if a resource is a shared resource, and + * whether the instance of the shared resource is the original instance, or + * an instance belonging to a sharee. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ShareAccess implements Element { + + /** + * Either SHARED or SHAREDOWNER + * + * @var int + */ + protected $value; + + /** + * Creates the property. + * + * The constructor value must be one of the + * \Sabre\DAV\Sharing\Plugin::ACCESS_ constants. + * + * @param int $shareAccess + */ + function __construct($shareAccess) { + + $this->value = $shareAccess; + + } + + /** + * Returns the current value. + * + * @return int + */ + function getValue() { + + return $this->value; + + } + + /** + * The xmlSerialize method is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + switch ($this->value) { + + case SharingPlugin::ACCESS_NOTSHARED : + $writer->writeElement('{DAV:}not-shared'); + break; + case SharingPlugin::ACCESS_SHAREDOWNER : + $writer->writeElement('{DAV:}shared-owner'); + break; + case SharingPlugin::ACCESS_READ : + $writer->writeElement('{DAV:}read'); + break; + case SharingPlugin::ACCESS_READWRITE : + $writer->writeElement('{DAV:}read-write'); + break; + case SharingPlugin::ACCESS_NOACCESS : + $writer->writeElement('{DAV:}no-access'); + break; + + } + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree(); + foreach ($elems as $elem) { + switch ($elem['name']) { + case '{DAV:}not-shared' : + return new self(SharingPlugin::ACCESS_NOTSHARED); + case '{DAV:}sharedowner' : + return new self(SharingPlugin::ACCESS_SHAREDOWNER); + case '{DAV:}read' : + return new self(SharingPlugin::ACCESS_READ); + case '{DAV:}read-write' : + return new self(SharingPlugin::ACCESS_READWRITE); + case '{DAV:}no-access' : + return new self(SharingPlugin::ACCESS_NOACCESS); + } + } + throw new BadRequest('Invalid value for {DAV:}share-access element'); + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php b/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php new file mode 100644 index 000000000..965e5857c --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/ShareResource.php @@ -0,0 +1,81 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; +use Sabre\DAV\Xml\Element\Sharee; + +/** + * ShareResource request parser. + * + * This class parses the {DAV:}share-resource POST request as defined in: + * + * https://tools.ietf.org/html/draft-pot-webdav-resource-sharing-01#section-5.3.2.1 + * + * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ShareResource implements XmlDeserializable { + + /** + * The list of new people added or updated or removed from the share. + * + * @var Sharee[] + */ + public $sharees = []; + + /** + * Constructor + * + * @param Sharee[] $sharees + */ + function __construct(array $sharees) { + + $this->sharees = $sharees; + + } + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statictly, this is because in theory this method + * may be used as a type of constructor, or factory method. + * + * Often you want to return an instance of the current class, but you are + * free to return other data as well. + * + * You are responsible for advancing the reader to the next element. Not + * doing anything will result in a never-ending loop. + * + * If you just want to skip parsing for this element altogether, you can + * just call $reader->next(); + * + * $reader->parseInnerTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + + $elems = $reader->parseInnerTree([ + '{DAV:}sharee' => 'Sabre\DAV\Xml\Element\Sharee', + '{DAV:}share-access' => 'Sabre\DAV\Xml\Property\ShareAccess', + '{DAV:}prop' => 'Sabre\Xml\Deserializer\keyValue', + ]); + + $sharees = []; + + foreach ($elems as $elem) { + if ($elem['name'] !== '{DAV:}sharee') continue; + $sharees[] = $elem['value']; + + } + + return new self($sharees); + + } + +} |