diff options
Diffstat (limited to 'vendor/sabre/dav/lib/DAV')
107 files changed, 14942 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php new file mode 100644 index 000000000..40a95f8bf --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php @@ -0,0 +1,144 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\DAV; +use Sabre\HTTP; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Basic authentication backend class + * + * This class can be used by authentication objects wishing to use HTTP Basic + * Most of the digest logic is handled, implementors just need to worry about + * the validateUserPass method. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author James David Low (http://jameslow.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractBasic implements BackendInterface { + + /** + * Authentication Realm. + * + * The realm is often displayed by browser clients when showing the + * authentication dialog. + * + * @var string + */ + protected $realm = 'sabre/dav'; + + /** + * This is the prefix that will be used to generate principal urls. + * + * @var string + */ + protected $principalPrefix = 'principals/'; + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + abstract protected function validateUserPass($username, $password); + + /** + * Sets the authentication realm for this backend. + * + * @param string $realm + * @return void + */ + function setRealm($realm) { + + $this->realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + + $userpass = $auth->getCredentials(); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"]; + } + if (!$this->validateUserPass($userpass[0], $userpass[1])) { + return [false, "Username or password was incorrect"]; + } + return [true, $this->principalPrefix . $userpass[0]]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Basic( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php new file mode 100644 index 000000000..ae7a8a12f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBearer.php @@ -0,0 +1,138 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\DAV; +use Sabre\HTTP; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Bearer authentication backend class + * + * This class can be used by authentication objects wishing to use HTTP Bearer + * Most of the digest logic is handled, implementors just need to worry about + * the validateBearerToken method. + * + * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). + * @author François Kooman (https://tuxed.net/) + * @author James David Low (http://jameslow.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractBearer implements BackendInterface { + + /** + * Authentication Realm. + * + * The realm is often displayed by browser clients when showing the + * authentication dialog. + * + * @var string + */ + protected $realm = 'sabre/dav'; + + /** + * Validates a Bearer token + * + * This method should return the full principal url, or false if the + * token was incorrect. + * + * @param string $bearerToken + * @return string|false + */ + abstract protected function validateBearerToken($bearerToken); + + /** + * Sets the authentication realm for this backend. + * + * @param string $realm + * @return void + */ + function setRealm($realm) { + + $this->realm = $realm; + + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + + $bearerToken = $auth->getToken($request); + if (!$bearerToken) { + return [false, "No 'Authorization: Bearer' header found. Either the client didn't send one, or the server is mis-configured"]; + } + $principalUrl = $this->validateBearerToken($bearerToken); + if (!$principalUrl) { + return [false, "Bearer token was incorrect"]; + } + return [true, $principalUrl]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Bearer Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Bearer( + $this->realm, + $request, + $response + ); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php new file mode 100644 index 000000000..0251decc1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php @@ -0,0 +1,162 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\HTTP; +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Digest authentication backend class + * + * This class can be used by authentication objects wishing to use HTTP Digest + * Most of the digest logic is handled, implementors just need to worry about + * the getDigestHash method + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractDigest implements BackendInterface { + + /** + * Authentication Realm. + * + * The realm is often displayed by browser clients when showing the + * authentication dialog. + * + * @var string + */ + protected $realm = 'SabreDAV'; + + /** + * This is the prefix that will be used to generate principal urls. + * + * @var string + */ + protected $principalPrefix = 'principals/'; + + /** + * Sets the authentication realm for this backend. + * + * Be aware that for Digest authentication, the realm influences the digest + * hash. Choose the realm wisely, because if you change it later, all the + * existing hashes will break and nobody can authenticate. + * + * @param string $realm + * @return void + */ + function setRealm($realm) { + + $this->realm = $realm; + + } + + /** + * Returns a users digest hash based on the username and realm. + * + * If the user was not known, null must be returned. + * + * @param string $realm + * @param string $username + * @return string|null + */ + abstract function getDigestHash($realm, $username); + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $digest = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $digest->init(); + + $username = $digest->getUsername(); + + // No username was given + if (!$username) { + return [false, "No 'Authorization: Digest' header found. Either the client didn't send one, or the server is misconfigured"]; + } + + $hash = $this->getDigestHash($this->realm, $username); + // If this was false, the user account didn't exist + if ($hash === false || is_null($hash)) { + return [false, "Username or password was incorrect"]; + } + if (!is_string($hash)) { + throw new DAV\Exception('The returned value from getDigestHash must be a string or null'); + } + + // If this was false, the password or part of the hash was incorrect. + if (!$digest->validateA1($hash)) { + return [false, "Username or password was incorrect"]; + } + + return [true, $this->principalPrefix . $username]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + $auth = new HTTP\Auth\Digest( + $this->realm, + $request, + $response + ); + $auth->init(); + $auth->requireLogin(); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php new file mode 100644 index 000000000..e203d2685 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php @@ -0,0 +1,96 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * Apache authenticator + * + * This authentication backend assumes that authentication has been + * configured in apache, rather than within SabreDAV. + * + * Make sure apache is properly configured for this to work. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Apache implements BackendInterface { + + /** + * This is the prefix that will be used to generate principal urls. + * + * @var string + */ + protected $principalPrefix = 'principals/'; + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + $remoteUser = $request->getRawServerValue('REMOTE_USER'); + if (is_null($remoteUser)) { + $remoteUser = $request->getRawServerValue('REDIRECT_REMOTE_USER'); + } + if (is_null($remoteUser)) { + return [false, 'No REMOTE_USER property was found in the PHP $_SERVER super-global. This likely means your server is not configured correctly']; + } + + return [true, $this->principalPrefix . $remoteUser]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php new file mode 100644 index 000000000..0fb2210f4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * This is the base class for any authentication object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface BackendInterface { + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response); + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the opportunity to set authentication headers. The 401 + * status code will already be set. + * + * In this case of Basic Auth, this would for example mean that the + * following header needs to be set: + * + * $response->addHeader('WWW-Authenticate', 'Basic realm=SabreDAV'); + * + * Keep in mind that in the case of multiple authentication backends, other + * WWW-Authenticate headers may already have been set, and you'll want to + * append your own WWW-Authenticate header instead of overwriting the + * existing one. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response); + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php new file mode 100644 index 000000000..7ad8f48b2 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +/** + * Extremely simply HTTP Basic auth backend. + * + * This backend basically works by calling a callback, which receives a + * username and password. + * The callback must return true or false depending on if authentication was + * correct. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class BasicCallBack extends AbstractBasic { + + /** + * Callback + * + * @var callable + */ + protected $callBack; + + /** + * Creates the backend. + * + * A callback must be provided to handle checking the username and + * password. + * + * @param callable $callBack + * @return void + */ + function __construct(callable $callBack) { + + $this->callBack = $callBack; + + } + + /** + * Validates a username and password + * + * This method should return true or false depending on if login + * succeeded. + * + * @param string $username + * @param string $password + * @return bool + */ + protected function validateUserPass($username, $password) { + + $cb = $this->callBack; + return $cb($username, $password); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php new file mode 100644 index 000000000..6756e68df --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php @@ -0,0 +1,77 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\DAV; + +/** + * This is an authentication backend that uses a file to manage passwords. + * + * The backend file must conform to Apache's htdigest format + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends AbstractDigest { + + /** + * List of users + * + * @var array + */ + protected $users = []; + + /** + * Creates the backend object. + * + * If the filename argument is passed in, it will parse out the specified file fist. + * + * @param string|null $filename + */ + function __construct($filename = null) { + + if (!is_null($filename)) + $this->loadFile($filename); + + } + + /** + * Loads an htdigest-formatted file. This method can be called multiple times if + * more than 1 file is used. + * + * @param string $filename + * @return void + */ + function loadFile($filename) { + + foreach (file($filename, FILE_IGNORE_NEW_LINES) as $line) { + + if (substr_count($line, ":") !== 2) + throw new DAV\Exception('Malformed htdigest file. Every line should contain 2 colons'); + + list($username, $realm, $A1) = explode(':', $line); + + if (!preg_match('/^[a-zA-Z0-9]{32}$/', $A1)) + throw new DAV\Exception('Malformed htdigest file. Invalid md5 hash'); + + $this->users[$realm . ':' . $username] = $A1; + + } + + } + + /** + * Returns a users' information + * + * @param string $realm + * @param string $username + * @return string + */ + function getDigestHash($realm, $username) { + + return isset($this->users[$realm . ':' . $username]) ? $this->users[$realm . ':' . $username] : false; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php new file mode 100644 index 000000000..76ad89391 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +/** + * This is an authentication backend that uses a database to manage passwords. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PDO extends AbstractDigest { + + /** + * Reference to PDO connection + * + * @var PDO + */ + protected $pdo; + + /** + * PDO table name we'll be using + * + * @var string + */ + public $tableName = 'users'; + + + /** + * Creates the backend object. + * + * If the filename argument is passed in, it will parse out the specified file fist. + * + * @param PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Returns the digest hash for a user. + * + * @param string $realm + * @param string $username + * @return string|null + */ + function getDigestHash($realm, $username) { + + $stmt = $this->pdo->prepare('SELECT digesta1 FROM ' . $this->tableName . ' WHERE username = ?'); + $stmt->execute([$username]); + return $stmt->fetchColumn() ?: null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php new file mode 100644 index 000000000..818d8a4ad --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Auth/Plugin.php @@ -0,0 +1,213 @@ +<?php + +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; + +/** + * This plugin provides Authentication for a WebDAV server. + * + * It works by providing a Auth\Backend class. Several examples of these + * classes can be found in the Backend directory. + * + * It's possible to provide more than one backend to this plugin. If more than + * one backend was provided, each backend will attempt to authenticate. Only if + * all backends fail, we throw a 401. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends ServerPlugin { + + /** + * authentication backends + */ + protected $backends; + + /** + * The currently logged in principal. Will be `null` if nobody is currently + * logged in. + * + * @var string|null + */ + protected $currentPrincipal; + + /** + * Creates the authentication plugin + * + * @param Backend\BackendInterface $authBackend + */ + function __construct(Backend\BackendInterface $authBackend = null) { + + if (!is_null($authBackend)) { + $this->addBackend($authBackend); + } + + } + + /** + * Adds an authentication backend to the plugin. + * + * @param Backend\BackendInterface $authBackend + * @return void + */ + function addBackend(Backend\BackendInterface $authBackend) { + + $this->backends[] = $authBackend; + + } + + /** + * Initializes the plugin. This function is automatically called by the server + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $server->on('beforeMethod', [$this, 'beforeMethod'], 10); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'auth'; + + } + + /** + * Returns the currently logged-in principal. + * + * This will return a string such as: + * + * principals/username + * principals/users/username + * + * This method will return null if nobody is logged in. + * + * @return string|null + */ + function getCurrentPrincipal() { + + return $this->currentPrincipal; + + } + + /** + * 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 + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if ($this->currentPrincipal) { + + // We already have authentication information. This means that the + // event has already fired earlier, and is now likely fired for a + // sub-request. + // + // We don't want to authenticate users twice, so we simply don't do + // anything here. See Issue #700 for additional reasoning. + // + // This is not a perfect solution, but will be fixed once the + // "currently authenticated principal" is information that's not + // not associated with the plugin, but rather per-request. + // + // See issue #580 for more information about that. + return; + + } + if (!$this->backends) { + throw new \Sabre\DAV\Exception('No authentication backends were configured on this server.'); + } + $reasons = []; + foreach ($this->backends as $backend) { + + $result = $backend->check( + $request, + $response + ); + + if (!is_array($result) || count($result) !== 2 || !is_bool($result[0]) || !is_string($result[1])) { + throw new \Sabre\DAV\Exception('The authentication backend did not return a correct value from the check() method.'); + } + + if ($result[0]) { + $this->currentPrincipal = $result[1]; + // Exit early + return; + } + $reasons[] = $result[1]; + + } + + // If we got here, it means that no authentication backend was + // successful in authenticating the user. + $this->currentPrincipal = null; + + foreach ($this->backends as $backend) { + $backend->challenge($request, $response); + } + throw new NotAuthenticated(implode(', ', $reasons)); + + } + + /** + * 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' => 'Generic authentication plugin', + 'link' => 'http://sabre.io/dav/authentication/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php b/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php new file mode 100644 index 000000000..01cddc230 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php @@ -0,0 +1,101 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\HTTP\URLUtil; +use Sabre\DAV; +use Sabre\DAV\PropFind; +use Sabre\DAV\Inode; + +/** + * GuessContentType plugin + * + * A lot of the built-in File objects just return application/octet-stream + * as a content-type by default. This is a problem for some clients, because + * they expect a correct contenttype. + * + * There's really no accurate, fast and portable way to determine the contenttype + * so this extension does what the rest of the world does, and guesses it based + * on the file extension. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class GuessContentType extends DAV\ServerPlugin { + + /** + * List of recognized file extensions + * + * Feel free to add more + * + * @var array + */ + public $extensionMap = [ + + // images + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + + // groupware + 'ics' => 'text/calendar', + 'vcf' => 'text/vcard', + + // text + 'txt' => 'text/plain', + + ]; + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + // Using a relatively low priority (200) to allow other extensions + // to set the content-type first. + $server->on('propFind', [$this, 'propFind'], 200); + + } + + /** + * Our PROPFIND handler + * + * Here we set a contenttype, if the node didn't already have one. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getcontenttype', function() use ($propFind) { + + list(, $fileName) = URLUtil::splitPath($propFind->getPath()); + return $this->getContentType($fileName); + + }); + + } + + /** + * Simple method to return the contenttype + * + * @param string $fileName + * @return string + */ + protected function getContentType($fileName) { + + // Just grabbing the extension + $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1)); + if (isset($this->extensionMap[$extension])) { + return $this->extensionMap[$extension]; + } + return 'application/octet-stream'; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php new file mode 100644 index 000000000..f4be6b348 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php @@ -0,0 +1,34 @@ +<?php + +namespace Sabre\DAV\Browser; + +/** + * WebDAV properties that implement this interface are able to generate their + * own html output for the browser plugin. + * + * This is only useful for display purposes, and might make it a bit easier for + * people to read and understand the value of some properties. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface HtmlOutput { + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html); + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php new file mode 100644 index 000000000..249d54047 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php @@ -0,0 +1,117 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\Uri; +use Sabre\Xml\Service as XmlService; + +/** + * This class provides a few utility functions for easily generating HTML for + * the browser plugin. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HtmlOutputHelper { + + /** + * Link to the root of the application. + * + * @var string + */ + protected $baseUri; + + /** + * List of xml namespaces. + * + * @var array + */ + protected $namespaceMap; + + /** + * Creates the object. + * + * baseUri must point to the root of the application. This will be used to + * easily generate links. + * + * The namespaceMap contains an array with the list of xml namespaces and + * their prefixes. WebDAV uses a lot of XML with complex namespaces, so + * that can be used to make output a lot shorter. + * + * @param string $baseUri + * @param array $namespaceMap + */ + function __construct($baseUri, array $namespaceMap) { + + $this->baseUri = $baseUri; + $this->namespaceMap = $namespaceMap; + + } + + /** + * Generates a 'full' url based on a relative one. + * + * For relative urls, the base of the application is taken as the reference + * url, not the 'current url of the current request'. + * + * Absolute urls are left alone. + * + * @param string $path + * @return string + */ + function fullUrl($path) { + + return Uri\resolve($this->baseUri, $path); + + } + + /** + * Escape string for HTML output. + * + * @param string $input + * @return string + */ + function h($input) { + + return htmlspecialchars($input, ENT_COMPAT, 'UTF-8'); + + } + + /** + * Generates a full <a>-tag. + * + * Url is automatically expanded. If label is not specified, we re-use the + * url. + * + * @param string $url + * @param string $label + * @return string + */ + function link($url, $label = null) { + + $url = $this->h($this->fullUrl($url)); + return '<a href="' . $url . '">' . ($label ? $this->h($label) : $url) . '</a>'; + + } + + /** + * This method takes an xml element in clark-notation, and turns it into a + * shortened version with a prefix, if it was a known namespace. + * + * @param string $element + * @return string + */ + function xmlName($element) { + + list($ns, $localName) = XmlService::parseClarkNotation($element); + if (isset($this->namespaceMap[$ns])) { + $propName = $this->namespaceMap[$ns] . ':' . $localName; + } else { + $propName = $element; + } + return "<span title=\"" . $this->h($element) . "\">" . $this->h($propName) . "</span>"; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php b/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php new file mode 100644 index 000000000..38ee63bcd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php @@ -0,0 +1,60 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * This is a simple plugin that will map any GET request for non-files to + * PROPFIND allprops-requests. + * + * This should allow easy debugging of PROPFIND + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MapGetToPropFind extends DAV\ServerPlugin { + + /** + * reference to server class + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * Initializes the plugin and subscribes to events + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + } + + /** + * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $node = $this->server->tree->getNodeForPath($request->getPath()); + if ($node instanceof DAV\IFile) return; + + $subRequest = clone $request; + $subRequest->setMethod('PROPFIND'); + + $this->server->invokeMethod($subRequest, $response); + return false; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php new file mode 100644 index 000000000..07ca6c3e5 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/Plugin.php @@ -0,0 +1,797 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV; +use Sabre\DAV\MkCol; +use Sabre\HTTP\URLUtil; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * Browser Plugin + * + * This plugin provides a html representation, so that a WebDAV server may be accessed + * using a browser. + * + * The class intercepts GET requests to collection resources and generates a simple + * html index. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * reference to server class + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * enablePost turns on the 'actions' panel, which allows people to create + * folders and upload files straight from a browser. + * + * @var bool + */ + protected $enablePost = true; + + /** + * A list of properties that are usually not interesting. This can cut down + * the browser output a bit by removing the properties that most people + * will likely not want to see. + * + * @var array + */ + public $uninterestingProperties = [ + '{DAV:}supportedlock', + '{DAV:}acl-restrictions', + '{DAV:}supported-privilege-set', + '{DAV:}supported-method-set', + ]; + + /** + * Creates the object. + * + * By default it will allow file creation and uploads. + * Specify the first argument as false to disable this + * + * @param bool $enablePost + */ + function __construct($enablePost = true) { + + $this->enablePost = $enablePost; + + } + + /** + * Initializes the plugin and subscribes to events + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGetEarly'], 90); + $this->server->on('method:GET', [$this, 'httpGet'], 200); + $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'], 200); + if ($this->enablePost) $this->server->on('method:POST', [$this, 'httpPOST']); + } + + /** + * This method intercepts GET requests that have ?sabreAction=info + * appended to the URL + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGetEarly(RequestInterface $request, ResponseInterface $response) { + + $params = $request->getQueryParameters(); + if (isset($params['sabreAction']) && $params['sabreAction'] === 'info') { + return $this->httpGet($request, $response); + } + + } + + /** + * This method intercepts GET requests to collections and returns the html + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + // We're not using straight-up $_GET, because we want everything to be + // unit testable. + $getVars = $request->getQueryParameters(); + + // CSP headers + $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + + $sabreAction = isset($getVars['sabreAction']) ? $getVars['sabreAction'] : null; + + switch ($sabreAction) { + + case 'asset' : + // Asset handling, such as images + $this->serveAsset(isset($getVars['assetName']) ? $getVars['assetName'] : null); + return false; + default : + case 'info' : + try { + $this->server->tree->getNodeForPath($request->getPath()); + } catch (DAV\Exception\NotFound $e) { + // We're simply stopping when the file isn't found to not interfere + // with other plugins. + return; + } + + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generateDirectoryIndex($request->getPath()) + ); + + return false; + + case 'plugins' : + $response->setStatus(200); + $response->setHeader('Content-Type', 'text/html; charset=utf-8'); + + $response->setBody( + $this->generatePluginListing() + ); + + return false; + + } + + } + + /** + * Handles POST requests for tree operations. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPOST(RequestInterface $request, ResponseInterface $response) { + + $contentType = $request->getHeader('Content-Type'); + list($contentType) = explode(';', $contentType); + if ($contentType !== 'application/x-www-form-urlencoded' && + $contentType !== 'multipart/form-data') { + return; + } + $postVars = $request->getPostData(); + + if (!isset($postVars['sabreAction'])) + return; + + $uri = $request->getPath(); + + if ($this->server->emit('onBrowserPostAction', [$uri, $postVars['sabreAction'], $postVars])) { + + switch ($postVars['sabreAction']) { + + case 'mkcol' : + if (isset($postVars['name']) && trim($postVars['name'])) { + // Using basename() because we won't allow slashes + list(, $folderName) = URLUtil::splitPath(trim($postVars['name'])); + + if (isset($postVars['resourceType'])) { + $resourceType = explode(',', $postVars['resourceType']); + } else { + $resourceType = ['{DAV:}collection']; + } + + $properties = []; + foreach ($postVars as $varName => $varValue) { + // Any _POST variable in clark notation is treated + // like a property. + if ($varName[0] === '{') { + // PHP will convert any dots to underscores. + // This leaves us with no way to differentiate + // the two. + // Therefore we replace the string *DOT* with a + // real dot. * is not allowed in uris so we + // should be good. + $varName = str_replace('*DOT*', '.', $varName); + $properties[$varName] = $varValue; + } + } + + $mkCol = new MkCol( + $resourceType, + $properties + ); + $this->server->createCollection($uri . '/' . $folderName, $mkCol); + } + break; + + // @codeCoverageIgnoreStart + case 'put' : + + if ($_FILES) $file = current($_FILES); + else break; + + list(, $newName) = URLUtil::splitPath(trim($file['name'])); + if (isset($postVars['name']) && trim($postVars['name'])) + $newName = trim($postVars['name']); + + // Making sure we only have a 'basename' component + list(, $newName) = URLUtil::splitPath($newName); + + if (is_uploaded_file($file['tmp_name'])) { + $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'], 'r')); + } + break; + // @codeCoverageIgnoreEnd + + } + + } + $response->setHeader('Location', $request->getUrl()); + $response->setStatus(302); + return false; + + } + + /** + * Escapes a string for html. + * + * @param string $value + * @return string + */ + function escapeHTML($value) { + + return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); + + } + + /** + * Generates the html directory index for a given url + * + * @param string $path + * @return string + */ + function generateDirectoryIndex($path) { + + $html = $this->generateHeader($path ? $path : '/', $path); + + $node = $this->server->tree->getNodeForPath($path); + if ($node instanceof DAV\ICollection) { + + $html .= "<section><h1>Nodes</h1>\n"; + $html .= "<table class=\"nodeTable\">"; + + $subNodes = $this->server->getPropertiesForChildren($path, [ + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ]); + + foreach ($subNodes as $subPath => $subProps) { + + $subNode = $this->server->tree->getNodeForPath($subPath); + $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($subPath); + list(, $displayPath) = URLUtil::splitPath($subPath); + + $subNodes[$subPath]['subNode'] = $subNode; + $subNodes[$subPath]['fullPath'] = $fullPath; + $subNodes[$subPath]['displayPath'] = $displayPath; + } + uasort($subNodes, [$this, 'compareNodes']); + + foreach ($subNodes as $subProps) { + $type = [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + if (isset($subProps['{DAV:}resourcetype'])) { + $type = $this->mapResourceType($subProps['{DAV:}resourcetype']->getValue(), $subProps['subNode']); + } + + $html .= '<tr>'; + $html .= '<td class="nameColumn"><a href="' . $this->escapeHTML($subProps['fullPath']) . '"><span class="oi" data-glyph="' . $this->escapeHTML($type['icon']) . '"></span> ' . $this->escapeHTML($subProps['displayPath']) . '</a></td>'; + $html .= '<td class="typeColumn">' . $this->escapeHTML($type['string']) . '</td>'; + $html .= '<td>'; + if (isset($subProps['{DAV:}getcontentlength'])) { + $html .= $this->escapeHTML($subProps['{DAV:}getcontentlength'] . ' bytes'); + } + $html .= '</td><td>'; + if (isset($subProps['{DAV:}getlastmodified'])) { + $lastMod = $subProps['{DAV:}getlastmodified']->getTime(); + $html .= $this->escapeHTML($lastMod->format('F j, Y, g:i a')); + } + $html .= '</td>'; + + $buttonActions = ''; + if ($subProps['subNode'] instanceof DAV\IFile) { + $buttonActions = '<a href="' . $this->escapeHTML($subProps['fullPath']) . '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>'; + } + $this->server->emit('browserButtonActions', [$subProps['fullPath'], $subProps['subNode'], &$buttonActions]); + + $html .= '<td>' . $buttonActions . '</td>'; + $html .= '</tr>'; + } + + $html .= '</table>'; + + } + + $html .= "</section>"; + $html .= "<section><h1>Properties</h1>"; + $html .= "<table class=\"propTable\">"; + + // Allprops request + $propFind = new PropFindAll($path); + $properties = $this->server->getPropertiesByNode($propFind, $node); + + $properties = $propFind->getResultForMultiStatus()[200]; + + foreach ($properties as $propName => $propValue) { + if (!in_array($propName, $this->uninterestingProperties)) { + $html .= $this->drawPropertyRow($propName, $propValue); + } + + } + + + $html .= "</table>"; + $html .= "</section>"; + + /* Start of generating actions */ + + $output = ''; + if ($this->enablePost) { + $this->server->emit('onHTMLActionsPanel', [$node, &$output]); + } + + if ($output) { + + $html .= "<section><h1>Actions</h1>"; + $html .= "<div class=\"actions\">\n"; + $html .= $output; + $html .= "</div>\n"; + $html .= "</section>\n"; + } + + $html .= $this->generateFooter(); + + $this->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); + + return $html; + + } + + /** + * Generates the 'plugins' page. + * + * @return string + */ + function generatePluginListing() { + + $html = $this->generateHeader('Plugins'); + + $html .= "<section><h1>Plugins</h1>"; + $html .= "<table class=\"propTable\">"; + foreach ($this->server->getPlugins() as $plugin) { + $info = $plugin->getPluginInfo(); + $html .= '<tr><th>' . $info['name'] . '</th>'; + $html .= '<td>' . $info['description'] . '</td>'; + $html .= '<td>'; + if (isset($info['link']) && $info['link']) { + $html .= '<a href="' . $this->escapeHTML($info['link']) . '"><span class="oi" data-glyph="book"></span></a>'; + } + $html .= '</td></tr>'; + } + $html .= "</table>"; + $html .= "</section>"; + + /* Start of generating actions */ + + $html .= $this->generateFooter(); + + return $html; + + } + + /** + * Generates the first block of HTML, including the <head> tag and page + * header. + * + * Returns footer. + * + * @param string $title + * @param string $path + * @return void + */ + function generateHeader($title, $path = null) { + + $version = DAV\Version::VERSION; + + $vars = [ + 'title' => $this->escapeHTML($title), + 'favicon' => $this->escapeHTML($this->getAssetUrl('favicon.ico')), + 'style' => $this->escapeHTML($this->getAssetUrl('sabredav.css')), + 'iconstyle' => $this->escapeHTML($this->getAssetUrl('openiconic/open-iconic.css')), + 'logo' => $this->escapeHTML($this->getAssetUrl('sabredav.png')), + 'baseUrl' => $this->server->getBaseUri(), + ]; + + $html = <<<HTML +<!DOCTYPE html> +<html> +<head> + <title>$vars[title] - sabre/dav $version</title> + <link rel="shortcut icon" href="$vars[favicon]" type="image/vnd.microsoft.icon" /> + <link rel="stylesheet" href="$vars[style]" type="text/css" /> + <link rel="stylesheet" href="$vars[iconstyle]" type="text/css" /> + +</head> +<body> + <header> + <div class="logo"> + <a href="$vars[baseUrl]"><img src="$vars[logo]" alt="sabre/dav" /> $vars[title]</a> + </div> + </header> + + <nav> +HTML; + + // If the path is empty, there's no parent. + if ($path) { + list($parentUri) = URLUtil::splitPath($path); + $fullPath = $this->server->getBaseUri() . URLUtil::encodePath($parentUri); + $html .= '<a href="' . $fullPath . '" class="btn">⇤ Go to parent</a>'; + } else { + $html .= '<span class="btn disabled">⇤ Go to parent</span>'; + } + + $html .= ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>'; + + $html .= "</nav>"; + + return $html; + + } + + /** + * Generates the page footer. + * + * Returns html. + * + * @return string + */ + function generateFooter() { + + $version = DAV\Version::VERSION; + return <<<HTML +<footer>Generated by SabreDAV $version (c)2007-2015 <a href="http://sabre.io/">http://sabre.io/</a></footer> +</body> +</html> +HTML; + + } + + /** + * This method is used to generate the 'actions panel' output for + * collections. + * + * This specifically generates the interfaces for creating new files, and + * creating new directories. + * + * @param DAV\INode $node + * @param mixed $output + * @return void + */ + function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + if (get_class($node) === 'Sabre\\DAV\\SimpleCollection') + return; + + ob_start(); + echo '<form method="post" action=""> + <h3>Create new folder</h3> + <input type="hidden" name="sabreAction" value="mkcol" /> + <label>Name:</label> <input type="text" name="name" /><br /> + <input type="submit" value="create" /> + </form> + <form method="post" action="" enctype="multipart/form-data"> + <h3>Upload file</h3> + <input type="hidden" name="sabreAction" value="put" /> + <label>Name (optional):</label> <input type="text" name="name" /><br /> + <label>File:</label> <input type="file" name="file" /><br /> + <input type="submit" value="upload" /> + </form> + '; + + $output .= ob_get_clean(); + + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + + return $this->server->getBaseUri() . '?sabreAction=asset&assetName=' . urlencode($assetName); + + } + + /** + * This method returns a local pathname to an asset. + * + * @param string $assetName + * @return string + * @throws DAV\Exception\NotFound + */ + protected function getLocalAssetPath($assetName) { + + $assetDir = __DIR__ . '/assets/'; + $path = $assetDir . $assetName; + + // Making sure people aren't trying to escape from the base path. + $path = str_replace('\\', '/', $path); + if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') { + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + if (strpos(realpath($path), realpath($assetDir)) === 0 && file_exists($path)) { + return $path; + } + throw new DAV\Exception\NotFound('Path does not exist, or escaping from the base path was detected'); + } + + /** + * This method reads an asset from disk and generates a full http response. + * + * @param string $assetName + * @return void + */ + protected function serveAsset($assetName) { + + $assetPath = $this->getLocalAssetPath($assetName); + + // Rudimentary mime type detection + $mime = 'application/octet-stream'; + $map = [ + 'ico' => 'image/vnd.microsoft.icon', + 'png' => 'image/png', + 'css' => 'text/css', + ]; + + $ext = substr($assetName, strrpos($assetName, '.') + 1); + if (isset($map[$ext])) { + $mime = $map[$ext]; + } + + $this->server->httpResponse->setHeader('Content-Type', $mime); + $this->server->httpResponse->setHeader('Content-Length', filesize($assetPath)); + $this->server->httpResponse->setHeader('Cache-Control', 'public, max-age=1209600'); + $this->server->httpResponse->setStatus(200); + $this->server->httpResponse->setBody(fopen($assetPath, 'r')); + + } + + /** + * Sort helper function: compares two directory entries based on type and + * display name. Collections sort above other types. + * + * @param array $a + * @param array $b + * @return int + */ + protected function compareNodes($a, $b) { + + $typeA = (isset($a['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $a['{DAV:}resourcetype']->getValue())) + : false; + + $typeB = (isset($b['{DAV:}resourcetype'])) + ? (in_array('{DAV:}collection', $b['{DAV:}resourcetype']->getValue())) + : false; + + // If same type, sort alphabetically by filename: + if ($typeA === $typeB) { + return strnatcasecmp($a['displayPath'], $b['displayPath']); + } + return (($typeA < $typeB) ? 1 : -1); + + } + + /** + * Maps a resource type to a human-readable string and icon. + * + * @param array $resourceTypes + * @param INode $node + * @return array + */ + private function mapResourceType(array $resourceTypes, $node) { + + if (!$resourceTypes) { + if ($node instanceof DAV\IFile) { + return [ + 'string' => 'File', + 'icon' => 'file', + ]; + } else { + return [ + 'string' => 'Unknown', + 'icon' => 'cog', + ]; + } + } + + $types = [ + '{http://calendarserver.org/ns/}calendar-proxy-write' => [ + 'string' => 'Proxy-Write', + 'icon' => 'people', + ], + '{http://calendarserver.org/ns/}calendar-proxy-read' => [ + 'string' => 'Proxy-Read', + 'icon' => 'people', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => [ + 'string' => 'Outbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => [ + 'string' => 'Inbox', + 'icon' => 'inbox', + ], + '{urn:ietf:params:xml:ns:caldav}calendar' => [ + 'string' => 'Calendar', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}shared-owner' => [ + 'string' => 'Shared', + 'icon' => 'calendar', + ], + '{http://calendarserver.org/ns/}subscribed' => [ + 'string' => 'Subscription', + 'icon' => 'calendar', + ], + '{urn:ietf:params:xml:ns:carddav}directory' => [ + 'string' => 'Directory', + 'icon' => 'globe', + ], + '{urn:ietf:params:xml:ns:carddav}addressbook' => [ + 'string' => 'Address book', + 'icon' => 'book', + ], + '{DAV:}principal' => [ + 'string' => 'Principal', + 'icon' => 'person', + ], + '{DAV:}collection' => [ + 'string' => 'Collection', + 'icon' => 'folder', + ], + ]; + + $info = [ + 'string' => [], + 'icon' => 'cog', + ]; + foreach ($resourceTypes as $k => $resourceType) { + if (isset($types[$resourceType])) { + $info['string'][] = $types[$resourceType]['string']; + } else { + $info['string'][] = $resourceType; + } + } + foreach ($types as $key => $resourceInfo) { + if (in_array($key, $resourceTypes)) { + $info['icon'] = $resourceInfo['icon']; + break; + } + } + $info['string'] = implode(', ', $info['string']); + + return $info; + + } + + /** + * Draws a table row for a property + * + * @param string $name + * @param mixed $value + * @return string + */ + private function drawPropertyRow($name, $value) { + + $html = new HtmlOutputHelper( + $this->server->getBaseUri(), + $this->server->xml->namespaceMap + ); + + return "<tr><th>" . $html->xmlName($name) . "</th><td>" . $this->drawPropertyValue($html, $value) . "</td></tr>"; + + } + + /** + * Draws a table row for a property + * + * @param HtmlOutputHelper $html + * @param mixed $value + * @return string + */ + private function drawPropertyValue($html, $value) { + + if (is_scalar($value)) { + return $html->h($value); + } elseif ($value instanceof HtmlOutput) { + return $value->toHtml($html); + } elseif ($value instanceof \Sabre\Xml\XmlSerializable) { + + // There's no default html output for this property, we're going + // to output the actual xml serialization instead. + $xml = $this->server->xml->write('{DAV:}root', $value, $this->server->getBaseUri()); + // removing first and last line, as they contain our root + // element. + $xml = explode("\n", $xml); + $xml = array_slice($xml, 2, -2); + return "<pre>" . $html->h(implode("\n", $xml)) . "</pre>"; + + } else { + return "<em>unknown</em>"; + } + + } + + /** + * 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 'browser'; + + } + + /** + * 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' => 'Generates HTML indexes and debug information for your sabre/dav server', + 'link' => 'http://sabre.io/dav/browser-plugin/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php b/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php new file mode 100644 index 000000000..1ac439672 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php @@ -0,0 +1,132 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV\PropFind; + +/** + * This class is used by the browser plugin to trick the system in returning + * every defined property. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropFindAll extends PropFind { + + /** + * Creates the PROPFIND object + * + * @param string $path + */ + function __construct($path) { + + parent::__construct($path, []); + + } + + /** + * Handles a specific property. + * + * This method checks wether the specified property was requested in this + * PROPFIND request, and if so, it will call the callback and use the + * return value for it's value. + * + * Example: + * + * $propFind->handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->result[$propertyName] = [200, $value]; + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : 404; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + // If there's nothing in this list, we're adding one fictional item. + if (!$result) { + $result[] = '{http://sabredav.org/ns}idk'; + } + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE new file mode 100644 index 000000000..2199f4a69 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Waybury + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
\ No newline at end of file diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css new file mode 100644 index 000000000..e74867400 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css @@ -0,0 +1,510 @@ +@font-face { + font-family: 'Icons'; + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot'); + src: url('?sabreAction=asset&assetName=openiconic/open-iconic.eot?#iconic-sm') format('embedded-opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.woff') format('woff'), url('?sabreAction=asset&assetName=openiconic/open-iconic.ttf') format('truetype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.otf') format('opentype'), url('?sabreAction=asset&assetName=openiconic/open-iconic.svg#iconic-sm') format('svg'); + font-weight: normal; + font-style: normal; +} + +.oi[data-glyph].oi-text-replace { + font-size: 0; + line-height: 0; +} + +.oi[data-glyph].oi-text-replace:before { + width: 1em; + text-align: center; +} + +.oi[data-glyph]:before { + font-family: 'Icons'; + display: inline-block; + speak: none; + line-height: 1; + vertical-align: baseline; + font-weight: normal; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.oi[data-glyph]:empty:before { + width: 1em; + text-align: center; + box-sizing: content-box; +} + +.oi[data-glyph].oi-align-left:before { + text-align: left; +} + +.oi[data-glyph].oi-align-right:before { + text-align: right; +} + +.oi[data-glyph].oi-align-center:before { + text-align: center; +} + +.oi[data-glyph].oi-flip-horizontal:before { + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.oi[data-glyph].oi-flip-vertical:before { + -webkit-transform: scale(1, -1); + -ms-transform: scale(-1, 1); + transform: scale(1, -1); +} +.oi[data-glyph].oi-flip-horizontal-vertical:before { + -webkit-transform: scale(-1, -1); + -ms-transform: scale(-1, 1); + transform: scale(-1, -1); +} + + +.oi[data-glyph=account-login]:before { content:'\e000'; } + +.oi[data-glyph=account-logout]:before { content:'\e001'; } + +.oi[data-glyph=action-redo]:before { content:'\e002'; } + +.oi[data-glyph=action-undo]:before { content:'\e003'; } + +.oi[data-glyph=align-center]:before { content:'\e004'; } + +.oi[data-glyph=align-left]:before { content:'\e005'; } + +.oi[data-glyph=align-right]:before { content:'\e006'; } + +.oi[data-glyph=aperture]:before { content:'\e007'; } + +.oi[data-glyph=arrow-bottom]:before { content:'\e008'; } + +.oi[data-glyph=arrow-circle-bottom]:before { content:'\e009'; } + +.oi[data-glyph=arrow-circle-left]:before { content:'\e00a'; } + +.oi[data-glyph=arrow-circle-right]:before { content:'\e00b'; } + +.oi[data-glyph=arrow-circle-top]:before { content:'\e00c'; } + +.oi[data-glyph=arrow-left]:before { content:'\e00d'; } + +.oi[data-glyph=arrow-right]:before { content:'\e00e'; } + +.oi[data-glyph=arrow-thick-bottom]:before { content:'\e00f'; } + +.oi[data-glyph=arrow-thick-left]:before { content:'\e010'; } + +.oi[data-glyph=arrow-thick-right]:before { content:'\e011'; } + +.oi[data-glyph=arrow-thick-top]:before { content:'\e012'; } + +.oi[data-glyph=arrow-top]:before { content:'\e013'; } + +.oi[data-glyph=audio-spectrum]:before { content:'\e014'; } + +.oi[data-glyph=audio]:before { content:'\e015'; } + +.oi[data-glyph=badge]:before { content:'\e016'; } + +.oi[data-glyph=ban]:before { content:'\e017'; } + +.oi[data-glyph=bar-chart]:before { content:'\e018'; } + +.oi[data-glyph=basket]:before { content:'\e019'; } + +.oi[data-glyph=battery-empty]:before { content:'\e01a'; } + +.oi[data-glyph=battery-full]:before { content:'\e01b'; } + +.oi[data-glyph=beaker]:before { content:'\e01c'; } + +.oi[data-glyph=bell]:before { content:'\e01d'; } + +.oi[data-glyph=bluetooth]:before { content:'\e01e'; } + +.oi[data-glyph=bold]:before { content:'\e01f'; } + +.oi[data-glyph=bolt]:before { content:'\e020'; } + +.oi[data-glyph=book]:before { content:'\e021'; } + +.oi[data-glyph=bookmark]:before { content:'\e022'; } + +.oi[data-glyph=box]:before { content:'\e023'; } + +.oi[data-glyph=briefcase]:before { content:'\e024'; } + +.oi[data-glyph=british-pound]:before { content:'\e025'; } + +.oi[data-glyph=browser]:before { content:'\e026'; } + +.oi[data-glyph=brush]:before { content:'\e027'; } + +.oi[data-glyph=bug]:before { content:'\e028'; } + +.oi[data-glyph=bullhorn]:before { content:'\e029'; } + +.oi[data-glyph=calculator]:before { content:'\e02a'; } + +.oi[data-glyph=calendar]:before { content:'\e02b'; } + +.oi[data-glyph=camera-slr]:before { content:'\e02c'; } + +.oi[data-glyph=caret-bottom]:before { content:'\e02d'; } + +.oi[data-glyph=caret-left]:before { content:'\e02e'; } + +.oi[data-glyph=caret-right]:before { content:'\e02f'; } + +.oi[data-glyph=caret-top]:before { content:'\e030'; } + +.oi[data-glyph=cart]:before { content:'\e031'; } + +.oi[data-glyph=chat]:before { content:'\e032'; } + +.oi[data-glyph=check]:before { content:'\e033'; } + +.oi[data-glyph=chevron-bottom]:before { content:'\e034'; } + +.oi[data-glyph=chevron-left]:before { content:'\e035'; } + +.oi[data-glyph=chevron-right]:before { content:'\e036'; } + +.oi[data-glyph=chevron-top]:before { content:'\e037'; } + +.oi[data-glyph=circle-check]:before { content:'\e038'; } + +.oi[data-glyph=circle-x]:before { content:'\e039'; } + +.oi[data-glyph=clipboard]:before { content:'\e03a'; } + +.oi[data-glyph=clock]:before { content:'\e03b'; } + +.oi[data-glyph=cloud-download]:before { content:'\e03c'; } + +.oi[data-glyph=cloud-upload]:before { content:'\e03d'; } + +.oi[data-glyph=cloud]:before { content:'\e03e'; } + +.oi[data-glyph=cloudy]:before { content:'\e03f'; } + +.oi[data-glyph=code]:before { content:'\e040'; } + +.oi[data-glyph=cog]:before { content:'\e041'; } + +.oi[data-glyph=collapse-down]:before { content:'\e042'; } + +.oi[data-glyph=collapse-left]:before { content:'\e043'; } + +.oi[data-glyph=collapse-right]:before { content:'\e044'; } + +.oi[data-glyph=collapse-up]:before { content:'\e045'; } + +.oi[data-glyph=command]:before { content:'\e046'; } + +.oi[data-glyph=comment-square]:before { content:'\e047'; } + +.oi[data-glyph=compass]:before { content:'\e048'; } + +.oi[data-glyph=contrast]:before { content:'\e049'; } + +.oi[data-glyph=copywriting]:before { content:'\e04a'; } + +.oi[data-glyph=credit-card]:before { content:'\e04b'; } + +.oi[data-glyph=crop]:before { content:'\e04c'; } + +.oi[data-glyph=dashboard]:before { content:'\e04d'; } + +.oi[data-glyph=data-transfer-download]:before { content:'\e04e'; } + +.oi[data-glyph=data-transfer-upload]:before { content:'\e04f'; } + +.oi[data-glyph=delete]:before { content:'\e050'; } + +.oi[data-glyph=dial]:before { content:'\e051'; } + +.oi[data-glyph=document]:before { content:'\e052'; } + +.oi[data-glyph=dollar]:before { content:'\e053'; } + +.oi[data-glyph=double-quote-sans-left]:before { content:'\e054'; } + +.oi[data-glyph=double-quote-sans-right]:before { content:'\e055'; } + +.oi[data-glyph=double-quote-serif-left]:before { content:'\e056'; } + +.oi[data-glyph=double-quote-serif-right]:before { content:'\e057'; } + +.oi[data-glyph=droplet]:before { content:'\e058'; } + +.oi[data-glyph=eject]:before { content:'\e059'; } + +.oi[data-glyph=elevator]:before { content:'\e05a'; } + +.oi[data-glyph=ellipses]:before { content:'\e05b'; } + +.oi[data-glyph=envelope-closed]:before { content:'\e05c'; } + +.oi[data-glyph=envelope-open]:before { content:'\e05d'; } + +.oi[data-glyph=euro]:before { content:'\e05e'; } + +.oi[data-glyph=excerpt]:before { content:'\e05f'; } + +.oi[data-glyph=expand-down]:before { content:'\e060'; } + +.oi[data-glyph=expand-left]:before { content:'\e061'; } + +.oi[data-glyph=expand-right]:before { content:'\e062'; } + +.oi[data-glyph=expand-up]:before { content:'\e063'; } + +.oi[data-glyph=external-link]:before { content:'\e064'; } + +.oi[data-glyph=eye]:before { content:'\e065'; } + +.oi[data-glyph=eyedropper]:before { content:'\e066'; } + +.oi[data-glyph=file]:before { content:'\e067'; } + +.oi[data-glyph=fire]:before { content:'\e068'; } + +.oi[data-glyph=flag]:before { content:'\e069'; } + +.oi[data-glyph=flash]:before { content:'\e06a'; } + +.oi[data-glyph=folder]:before { content:'\e06b'; } + +.oi[data-glyph=fork]:before { content:'\e06c'; } + +.oi[data-glyph=fullscreen-enter]:before { content:'\e06d'; } + +.oi[data-glyph=fullscreen-exit]:before { content:'\e06e'; } + +.oi[data-glyph=globe]:before { content:'\e06f'; } + +.oi[data-glyph=graph]:before { content:'\e070'; } + +.oi[data-glyph=grid-four-up]:before { content:'\e071'; } + +.oi[data-glyph=grid-three-up]:before { content:'\e072'; } + +.oi[data-glyph=grid-two-up]:before { content:'\e073'; } + +.oi[data-glyph=hard-drive]:before { content:'\e074'; } + +.oi[data-glyph=header]:before { content:'\e075'; } + +.oi[data-glyph=headphones]:before { content:'\e076'; } + +.oi[data-glyph=heart]:before { content:'\e077'; } + +.oi[data-glyph=home]:before { content:'\e078'; } + +.oi[data-glyph=image]:before { content:'\e079'; } + +.oi[data-glyph=inbox]:before { content:'\e07a'; } + +.oi[data-glyph=infinity]:before { content:'\e07b'; } + +.oi[data-glyph=info]:before { content:'\e07c'; } + +.oi[data-glyph=italic]:before { content:'\e07d'; } + +.oi[data-glyph=justify-center]:before { content:'\e07e'; } + +.oi[data-glyph=justify-left]:before { content:'\e07f'; } + +.oi[data-glyph=justify-right]:before { content:'\e080'; } + +.oi[data-glyph=key]:before { content:'\e081'; } + +.oi[data-glyph=laptop]:before { content:'\e082'; } + +.oi[data-glyph=layers]:before { content:'\e083'; } + +.oi[data-glyph=lightbulb]:before { content:'\e084'; } + +.oi[data-glyph=link-broken]:before { content:'\e085'; } + +.oi[data-glyph=link-intact]:before { content:'\e086'; } + +.oi[data-glyph=list-rich]:before { content:'\e087'; } + +.oi[data-glyph=list]:before { content:'\e088'; } + +.oi[data-glyph=location]:before { content:'\e089'; } + +.oi[data-glyph=lock-locked]:before { content:'\e08a'; } + +.oi[data-glyph=lock-unlocked]:before { content:'\e08b'; } + +.oi[data-glyph=loop-circular]:before { content:'\e08c'; } + +.oi[data-glyph=loop-square]:before { content:'\e08d'; } + +.oi[data-glyph=loop]:before { content:'\e08e'; } + +.oi[data-glyph=magnifying-glass]:before { content:'\e08f'; } + +.oi[data-glyph=map-marker]:before { content:'\e090'; } + +.oi[data-glyph=map]:before { content:'\e091'; } + +.oi[data-glyph=media-pause]:before { content:'\e092'; } + +.oi[data-glyph=media-play]:before { content:'\e093'; } + +.oi[data-glyph=media-record]:before { content:'\e094'; } + +.oi[data-glyph=media-skip-backward]:before { content:'\e095'; } + +.oi[data-glyph=media-skip-forward]:before { content:'\e096'; } + +.oi[data-glyph=media-step-backward]:before { content:'\e097'; } + +.oi[data-glyph=media-step-forward]:before { content:'\e098'; } + +.oi[data-glyph=media-stop]:before { content:'\e099'; } + +.oi[data-glyph=medical-cross]:before { content:'\e09a'; } + +.oi[data-glyph=menu]:before { content:'\e09b'; } + +.oi[data-glyph=microphone]:before { content:'\e09c'; } + +.oi[data-glyph=minus]:before { content:'\e09d'; } + +.oi[data-glyph=monitor]:before { content:'\e09e'; } + +.oi[data-glyph=moon]:before { content:'\e09f'; } + +.oi[data-glyph=move]:before { content:'\e0a0'; } + +.oi[data-glyph=musical-note]:before { content:'\e0a1'; } + +.oi[data-glyph=paperclip]:before { content:'\e0a2'; } + +.oi[data-glyph=pencil]:before { content:'\e0a3'; } + +.oi[data-glyph=people]:before { content:'\e0a4'; } + +.oi[data-glyph=person]:before { content:'\e0a5'; } + +.oi[data-glyph=phone]:before { content:'\e0a6'; } + +.oi[data-glyph=pie-chart]:before { content:'\e0a7'; } + +.oi[data-glyph=pin]:before { content:'\e0a8'; } + +.oi[data-glyph=play-circle]:before { content:'\e0a9'; } + +.oi[data-glyph=plus]:before { content:'\e0aa'; } + +.oi[data-glyph=power-standby]:before { content:'\e0ab'; } + +.oi[data-glyph=print]:before { content:'\e0ac'; } + +.oi[data-glyph=project]:before { content:'\e0ad'; } + +.oi[data-glyph=pulse]:before { content:'\e0ae'; } + +.oi[data-glyph=puzzle-piece]:before { content:'\e0af'; } + +.oi[data-glyph=question-mark]:before { content:'\e0b0'; } + +.oi[data-glyph=rain]:before { content:'\e0b1'; } + +.oi[data-glyph=random]:before { content:'\e0b2'; } + +.oi[data-glyph=reload]:before { content:'\e0b3'; } + +.oi[data-glyph=resize-both]:before { content:'\e0b4'; } + +.oi[data-glyph=resize-height]:before { content:'\e0b5'; } + +.oi[data-glyph=resize-width]:before { content:'\e0b6'; } + +.oi[data-glyph=rss-alt]:before { content:'\e0b7'; } + +.oi[data-glyph=rss]:before { content:'\e0b8'; } + +.oi[data-glyph=script]:before { content:'\e0b9'; } + +.oi[data-glyph=share-boxed]:before { content:'\e0ba'; } + +.oi[data-glyph=share]:before { content:'\e0bb'; } + +.oi[data-glyph=shield]:before { content:'\e0bc'; } + +.oi[data-glyph=signal]:before { content:'\e0bd'; } + +.oi[data-glyph=signpost]:before { content:'\e0be'; } + +.oi[data-glyph=sort-ascending]:before { content:'\e0bf'; } + +.oi[data-glyph=sort-descending]:before { content:'\e0c0'; } + +.oi[data-glyph=spreadsheet]:before { content:'\e0c1'; } + +.oi[data-glyph=star]:before { content:'\e0c2'; } + +.oi[data-glyph=sun]:before { content:'\e0c3'; } + +.oi[data-glyph=tablet]:before { content:'\e0c4'; } + +.oi[data-glyph=tag]:before { content:'\e0c5'; } + +.oi[data-glyph=tags]:before { content:'\e0c6'; } + +.oi[data-glyph=target]:before { content:'\e0c7'; } + +.oi[data-glyph=task]:before { content:'\e0c8'; } + +.oi[data-glyph=terminal]:before { content:'\e0c9'; } + +.oi[data-glyph=text]:before { content:'\e0ca'; } + +.oi[data-glyph=thumb-down]:before { content:'\e0cb'; } + +.oi[data-glyph=thumb-up]:before { content:'\e0cc'; } + +.oi[data-glyph=timer]:before { content:'\e0cd'; } + +.oi[data-glyph=transfer]:before { content:'\e0ce'; } + +.oi[data-glyph=trash]:before { content:'\e0cf'; } + +.oi[data-glyph=underline]:before { content:'\e0d0'; } + +.oi[data-glyph=vertical-align-bottom]:before { content:'\e0d1'; } + +.oi[data-glyph=vertical-align-center]:before { content:'\e0d2'; } + +.oi[data-glyph=vertical-align-top]:before { content:'\e0d3'; } + +.oi[data-glyph=video]:before { content:'\e0d4'; } + +.oi[data-glyph=volume-high]:before { content:'\e0d5'; } + +.oi[data-glyph=volume-low]:before { content:'\e0d6'; } + +.oi[data-glyph=volume-off]:before { content:'\e0d7'; } + +.oi[data-glyph=warning]:before { content:'\e0d8'; } + +.oi[data-glyph=wifi]:before { content:'\e0d9'; } + +.oi[data-glyph=wrench]:before { content:'\e0da'; } + +.oi[data-glyph=x]:before { content:'\e0db'; } + +.oi[data-glyph=yen]:before { content:'\e0dc'; } + +.oi[data-glyph=zoom-in]:before { content:'\e0dd'; } + +.oi[data-glyph=zoom-out]:before { content:'\e0de'; } diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot Binary files differnew file mode 100644 index 000000000..7ca7c170f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf Binary files differnew file mode 100644 index 000000000..d79fb13a1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg new file mode 100644 index 000000000..0792c003a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg @@ -0,0 +1,543 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<!-- +2014-4-30: Created. +--> +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata> +Created by FontForge 20120731 at Wed Apr 30 22:56:47 2014 + By P.J. Onori +Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) +</metadata> +<defs> +<font id="open-iconic" horiz-adv-x="800" > + <font-face + font-family="Icons" + font-weight="400" + font-stretch="normal" + units-per-em="800" + panose-1="2 0 5 3 0 0 0 0 0 0" + ascent="800" + descent="0" + bbox="-0.25 -101 802 800.126" + underline-thickness="50" + underline-position="-100" + unicode-range="U+E000-E0DE" + /> + <missing-glyph /> + <glyph glyph-name="" unicode="" +d="M300 700h500v-700h-500v100h400v500h-400v100zM400 500l200 -150l-200 -150v100h-400v100h400v100z" /> + <glyph glyph-name="1" unicode="" +d="M300 700h500v-700h-500v100h400v500h-400v100zM200 500v-100h400v-100h-400v-100l-200 150z" /> + <glyph glyph-name="2" unicode="" +d="M350 700c193 0 350 -157 350 -350v-50h100l-200 -200l-200 200h100v50c0 138 -112 250 -250 250s-250 -112 -250 -250c0 193 157 350 350 350z" /> + <glyph glyph-name="3" unicode="" +d="M450 700c193 0 350 -157 350 -350c0 138 -112 250 -250 250s-250 -112 -250 -250v-50h100l-200 -200l-200 200h100v50c0 193 157 350 350 350z" /> + <glyph glyph-name="4" unicode="" +d="M0 700h800v-100h-800v100zM100 500h600v-100h-600v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" /> + <glyph glyph-name="5" unicode="" +d="M0 700h800v-100h-800v100zM0 500h600v-100h-600v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" /> + <glyph glyph-name="6" unicode="" +d="M0 700h800v-100h-800v100zM200 500h600v-100h-600v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" /> + <glyph glyph-name="7" unicode="" +d="M400 700c75 0 144 -23 203 -59l-72 -225l-322 234c57 31 122 50 191 50zM125 588l191 -138l-310 -222c-4 23 -6 47 -6 72c0 114 49 215 125 288zM688 575c69 -72 112 -168 112 -275c0 -35 -4 -68 -12 -100h-222zM216 256l112 -350c-128 23 -232 109 -287 222zM372 100 +h372c-64 -109 -177 -186 -310 -197z" /> + <glyph glyph-name="8" unicode="" horiz-adv-x="600" +d="M200 800h100v-500h200l-247 -300l-253 300h200v500z" /> + <glyph glyph-name="9" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 700v-300h-200l300 -300l300 300h-200v300h-200z" /> + <glyph glyph-name="a" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300l300 -300v200h300v200h-300v200z" /> + <glyph glyph-name="b" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700v-200h-300v-200h300v-200l300 300z" /> + <glyph glyph-name="c" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700l-300 -300h200v-300h200v300h200z" /> + <glyph glyph-name="d" unicode="" +d="M300 600v-200h500v-100h-500v-200l-300 247z" /> + <glyph glyph-name="e" unicode="" +d="M500 600l300 -247l-300 -253v200h-500v100h500v200z" /> + <glyph glyph-name="f" unicode="" horiz-adv-x="600" +d="M200 800h200v-500h200l-297 -300l-303 300h200v500z" /> + <glyph glyph-name="10" unicode="" +d="M300 700v-200h500v-200h-500v-200l-300 297z" /> + <glyph glyph-name="11" unicode="" +d="M500 700l300 -297l-300 -303v200h-500v200h500v200z" /> + <glyph glyph-name="12" unicode="" horiz-adv-x="600" +d="M297 800l303 -300h-200v-500h-200v500h-200z" /> + <glyph glyph-name="13" unicode="" horiz-adv-x="600" +d="M247 800l253 -300h-200v-500h-100v500h-200z" /> + <glyph glyph-name="14" unicode="" +d="M400 800h100v-800h-100v800zM200 700h100v-600h-100v600zM600 600h100v-400h-100v400zM0 500h100v-200h-100v200z" /> + <glyph glyph-name="15" unicode="" +d="M119 600l69 -72c-55 -54 -88 -130 -88 -212s33 -156 88 -210l-69 -72c-73 72 -119 172 -119 282s46 212 119 284zM681 600c73 -73 119 -174 119 -284s-46 -210 -119 -282l-69 72c55 54 88 126 88 210s-33 157 -88 212zM259 460l69 -72c-18 -18 -28 -45 -28 -72 +s10 -51 28 -69l-69 -72c-36 36 -59 86 -59 141s23 108 59 144zM541 459c36 -36 59 -87 59 -143s-23 -105 -59 -141l-69 72c18 18 28 41 28 69s-10 54 -28 72z" /> + <glyph glyph-name="16" unicode="" horiz-adv-x="400" +d="M200 800c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM100 319c31 -11 65 -19 100 -19s69 8 100 19v-319l-100 100l-100 -100v319z" /> + <glyph glyph-name="17" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300c0 -66 21 -126 56 -175l419 419c-49 35 -109 56 -175 56zM644 575l-419 -419c49 -35 109 -56 175 -56c166 0 300 134 300 300 +c0 66 -21 126 -56 175z" /> + <glyph glyph-name="18" unicode="" +d="M0 700h100v-600h700v-100h-800v700zM500 700h200v-500h-200v500zM200 500h200v-300h-200v300z" /> + <glyph glyph-name="19" unicode="" +d="M397 800c13 1 24 -4 34 -13c2 -1 214 -254 241 -287h128v-100h-100v-366c0 -18 -16 -34 -34 -34h-532c-18 0 -34 16 -34 34v366h-100v100h128l234 281c8 10 22 18 35 19zM400 672l-144 -172h288zM250 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50 +v100c0 28 -22 50 -50 50zM550 300c-28 0 -50 -22 -50 -50v-100c0 -28 22 -50 50 -50s50 22 50 50v100c0 28 -22 50 -50 50z" /> + <glyph glyph-name="1a" unicode="" +d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9zM100 600v-400h500v400h-500z" /> + <glyph glyph-name="1b" unicode="" +d="M9 700h682c6 0 9 -4 9 -10v-190h100v-200h-100v-191c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v582c0 6 3 9 9 9z" /> + <glyph glyph-name="1c" unicode="" +d="M92 650c0 23 19 50 45 50h3h5h5h500c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-141c9 -17 120 -231 166 -309c15 -26 34 -61 34 -106c0 -39 -15 -77 -41 -103c-27 -27 -65 -41 -103 -41h-512c-39 0 -77 15 -103 41c-27 27 -41 65 -41 103c0 45 19 79 34 106 +c46 78 157 292 166 309v141h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51zM500 600h-200v-162l-6 -10s-65 -123 -122 -228h456c-57 105 -122 228 -122 228l-6 10v162z" /> + <glyph glyph-name="1d" unicode="" +d="M400 800c110 0 200 -90 200 -200c0 -104 52 -198 134 -266c42 -34 66 -82 66 -134h-800c0 52 24 100 66 134c82 68 134 162 134 266c0 110 90 200 200 200zM300 100h200c0 -55 -45 -100 -100 -100s-100 45 -100 100z" /> + <glyph glyph-name="1e" unicode="" horiz-adv-x="600" +d="M150 800h50l350 -250l-225 -147l225 -153l-350 -250h-50v250l-75 -75l-75 75l150 150l-150 150l75 75l75 -75v250zM250 650v-200l150 100zM250 350v-200l150 100z" /> + <glyph glyph-name="1f" unicode="" +d="M0 800h500c110 0 200 -90 200 -200c0 -47 -17 -91 -44 -125c85 -40 144 -125 144 -225c0 -138 -112 -250 -250 -250h-550v100c55 0 100 45 100 100v400c0 55 -45 100 -100 100v100zM300 700v-200h100c55 0 100 45 100 100s-45 100 -100 100h-100zM300 400v-300h150 +c83 0 150 67 150 150s-67 150 -150 150h-150z" /> + <glyph glyph-name="20" unicode="" horiz-adv-x="600" +d="M300 800v-300h200l-300 -500v300h-200z" /> + <glyph glyph-name="21" unicode="" +d="M100 800h300v-300l100 100l100 -100v300h50c28 0 50 -22 50 -50v-550h-550c-28 0 -50 -22 -50 -50s22 -50 50 -50h550v-100h-550c-83 0 -150 67 -150 150v550l3 19c8 39 39 70 78 78z" /> + <glyph glyph-name="22" unicode="" horiz-adv-x="400" +d="M0 800h400v-800l-200 200l-200 -200v800z" /> + <glyph glyph-name="23" unicode="" +d="M0 800h800v-100h-800v100zM0 600h300v-103h203v103h297v-591c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v591z" /> + <glyph glyph-name="24" unicode="" +d="M300 800h200c55 0 100 -46 100 -100v-100h191c6 0 9 -3 9 -9v-241c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v241c0 6 3 9 9 9h191v100c0 54 45 100 100 100zM300 700v-100h200v100h-200zM0 209c16 -5 32 -9 50 -9h700c18 0 34 4 50 9v-200c0 -6 -3 -9 -9 -9h-782 +c-6 0 -9 3 -9 9v200z" /> + <glyph glyph-name="25" unicode="" horiz-adv-x="600" +d="M300 800c58 0 110 -16 147 -53s53 -89 53 -147h-100c0 39 -11 61 -25 75s-36 25 -75 25c-35 0 -55 -10 -72 -31s-28 -55 -28 -94c0 -51 20 -107 28 -175h172v-100h-178c-14 -60 -49 -127 -113 -200h491v-100h-600v122l16 12c69 69 95 121 106 166h-122v100h125 +c-8 50 -25 106 -25 175c0 58 16 113 50 156s88 69 150 69z" /> + <glyph glyph-name="26" unicode="" +d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-700c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v700v2c0 20 15 42 34 48zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50zM350 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h300c28 0 50 22 50 50 +s-22 50 -50 50h-300zM100 400v-400h600v400h-600z" /> + <glyph glyph-name="27" unicode="" +d="M744 797l9 -3l41 -41c4 -4 3 -11 0 -15l-266 -375c-3 -5 -10 -11 -15 -13l-25 -12c-23 72 -78 127 -150 150l12 25l13 15l375 266zM266 400c74 0 134 -61 134 -134c0 -148 -118 -266 -266 -266c-48 0 -94 15 -134 38c80 46 134 129 134 228c0 73 59 134 132 134z" /> + <glyph glyph-name="28" unicode="" +d="M9 451c0 23 19 50 46 50c8 0 19 -3 26 -7l131 -66l29 22c-79 81 -1 250 118 250s197 -167 119 -250l28 -22l131 66c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-115 -56c9 -16 19 -33 25 -50h68c28 0 50 -22 50 -50s-22 -50 -50 -50h-50 +c0 -23 -2 -45 -6 -66l78 -40c21 -5 37 -28 37 -49c0 -28 -22 -50 -50 -50c-10 0 -23 5 -31 11l-65 35c-24 -46 -59 -83 -100 -107c-35 19 -63 42 -63 69v135v4v5v6v5v5v87c0 28 -22 50 -50 50c-25 0 -46 -17 -50 -40c1 -3 1 -8 1 -11s0 -8 -1 -11v-82v-4v-5v-144 +c0 -28 -27 -53 -62 -72c-41 25 -76 64 -100 110l-66 -35c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49l78 40c-4 21 -6 43 -6 66h-50h-5c-28 0 -50 22 -50 50c0 26 22 50 50 50h5h69c6 17 16 34 25 50l-116 56c-16 7 -28 27 -28 45z" /> + <glyph glyph-name="29" unicode="" +d="M610 700h81c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-91v597zM210 503l290 147v-500l-250 125v-3c-14 0 -25 -11 -28 -25l72 -178c11 -25 0 -55 -25 -66s-55 0 -66 25l-103 272h-91c-6 0 -9 3 -9 9v182c0 6 3 9 9 9h182z" /> + <glyph glyph-name="2a" unicode="" +d="M9 800h682c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM100 700v-200h500v200h-500zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-300h100v300h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" /> + <glyph glyph-name="2b" unicode="" +d="M0 800h700v-200h-700v200zM0 500h700v-491c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v491zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100z" /> + <glyph glyph-name="2c" unicode="" +d="M409 800h182c6 0 10 -4 12 -9l94 -182c2 -5 6 -9 12 -9h82c6 0 9 -3 9 -9v-582c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v441c0 83 67 150 150 150h141c6 0 10 4 12 9l94 182c2 5 6 9 12 9zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z +M500 500c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM500 400c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" /> + <glyph glyph-name="2d" unicode="" +d="M0 600h800l-400 -400z" /> + <glyph glyph-name="2e" unicode="" horiz-adv-x="400" +d="M400 800v-800l-400 400z" /> + <glyph glyph-name="2f" unicode="" horiz-adv-x="400" +d="M0 800l400 -400l-400 -400v800z" /> + <glyph glyph-name="30" unicode="" +d="M400 600l400 -400h-800z" /> + <glyph glyph-name="31" unicode="" +d="M0 550c0 23 20 50 46 50h3h5h4h200c17 0 37 -13 44 -28l38 -72h444c14 0 19 -10 15 -22l-81 -253c-4 -13 -21 -25 -35 -25h-350c-14 0 -30 12 -34 25c-27 83 -54 167 -81 250l-10 25h-150c-2 0 -5 -1 -7 -1c-28 0 -51 23 -51 51zM358 100c28 0 50 -22 50 -50 +s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM658 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" /> + <glyph glyph-name="32" unicode="" +d="M0 700h500v-100h-300v-300h-100l-100 -100v500zM300 500h500v-500l-100 100h-400v400z" /> + <glyph glyph-name="33" unicode="" +d="M641 700l140 -141c-137 -143 -279 -280 -418 -421l-72 -69c-76 71 -148 146 -222 219l-69 71l141 141c51 -48 101 -97 150 -147c117 116 231 234 350 347z" /> + <glyph glyph-name="34" unicode="" +d="M150 600l250 -250l250 250l150 -150l-400 -400l-400 400z" /> + <glyph glyph-name="35" unicode="" horiz-adv-x="600" +d="M400 800l150 -150l-250 -250l250 -250l-150 -150l-400 400z" /> + <glyph glyph-name="36" unicode="" horiz-adv-x="600" +d="M150 800l400 -400l-400 -400l-150 150l250 250l-250 250z" /> + <glyph glyph-name="37" unicode="" +d="M400 600l400 -400l-150 -150l-250 250l-250 -250l-150 150z" /> + <glyph glyph-name="38" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM600 622l-250 -250l-100 100l-72 -72l172 -172l322 322z" /> + <glyph glyph-name="39" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM250 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" /> + <glyph glyph-name="3a" unicode="" +d="M350 800c28 0 50 -22 50 -50v-50h75c14 0 25 -11 25 -25v-75h-300v75c0 14 11 25 25 25h75v50c0 28 22 50 50 50zM25 700h75v-200h500v200h75c14 0 25 -11 25 -25v-650c0 -14 -11 -25 -25 -25h-650c-14 0 -25 11 -25 25v650c0 14 11 25 25 25z" /> + <glyph glyph-name="3b" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM350 600h100v-181c23 -24 47 -47 72 -69l-72 -72c-27 30 -55 59 -84 88l-16 12 +v222z" /> + <glyph glyph-name="3c" unicode="" +d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-191v50c0 83 -67 150 -150 150s-150 -67 -150 -150v-50h-272c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM434 400h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1 +v-150h150l-200 -200l-200 200h150v150v2c0 20 15 42 34 48z" /> + <glyph glyph-name="3d" unicode="" +d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -18 -4 -34 -9 -50h-141l-200 200l-200 -200h-222c-17 30 -28 63 -28 100c0 110 90 200 200 200c23 114 129 200 250 200zM450 350l250 -250h-200v-50c0 -28 -22 -50 -50 -50s-50 22 -50 50v50h-200z" /> + <glyph glyph-name="3e" unicode="" +d="M450 700c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200s90 200 200 200c23 114 129 200 250 200z" /> + <glyph glyph-name="3f" unicode="" +d="M250 800c82 0 154 -40 200 -100c-143 -1 -270 -84 -325 -209c-37 -9 -70 -26 -100 -47c-16 32 -25 67 -25 106c0 138 112 250 250 250zM450 600c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -83 -67 -150 -150 -150h-450c-110 0 -200 90 -200 200 +s90 200 200 200c23 114 129 200 250 200z" /> + <glyph glyph-name="40" unicode="" +d="M500 700h100l-300 -600h-100zM100 600h100l-100 -200l100 -200h-100l-100 200zM600 600h100l100 -200l-100 -200h-100l100 200z" /> + <glyph glyph-name="41" unicode="" +d="M350 800h100l50 -119l28 -12l119 50l69 -72l-47 -119l12 -28l119 -50v-100l-119 -50l-12 -28l50 -119l-72 -72l-119 50l-28 -12l-50 -119h-100l-50 119l-28 12l-119 -50l-72 72l50 116l-12 31l-119 50v100l119 50l12 28l-50 119l72 72l119 -50l28 12zM400 550 +c-83 0 -150 -67 -150 -150s67 -150 150 -150s150 67 150 150s-67 150 -150 150z" /> + <glyph glyph-name="42" unicode="" +d="M0 800h800v-200h-800v200zM200 500h400l-200 -200zM0 100h800v-100h-800v100z" /> + <glyph glyph-name="43" unicode="" +d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM500 600v-400l-200 200z" /> + <glyph glyph-name="44" unicode="" +d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM300 600l200 -200l-200 -200v400z" /> + <glyph glyph-name="45" unicode="" +d="M0 800h800v-100h-800v100zM400 500l200 -200h-400zM0 200h800v-200h-800v200z" /> + <glyph glyph-name="46" unicode="" +d="M150 700c83 0 150 -67 150 -150v-50h100v50c0 83 67 150 150 150s150 -67 150 -150s-67 -150 -150 -150h-50v-100h50c83 0 150 -67 150 -150s-67 -150 -150 -150s-150 67 -150 150v50h-100v-50c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150h50v100h-50 +c-83 0 -150 67 -150 150s67 150 150 150zM150 600c-28 0 -50 -22 -50 -50s22 -50 50 -50h50v50c0 28 -22 50 -50 50zM550 600c-28 0 -50 -22 -50 -50v-50h50c28 0 50 22 50 50s-22 50 -50 50zM300 400v-100h100v100h-100zM150 200c-28 0 -50 -22 -50 -50s22 -50 50 -50 +s50 22 50 50v50h-50zM500 200v-50c0 -28 22 -50 50 -50s50 22 50 50s-22 50 -50 50h-50z" /> + <glyph glyph-name="47" unicode="" +d="M0 791c0 6 3 9 9 9h782c6 0 9 -4 9 -10v-790l-200 200h-591c-6 0 -9 3 -9 9v582z" /> + <glyph glyph-name="48" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM600 600l-100 -300l-300 -100l100 300zM400 450c-28 0 -50 -22 -50 -50 +s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="49" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700v-600c166 0 300 134 300 300s-134 300 -300 300z" /> + <glyph glyph-name="4a" unicode="" +d="M0 800h800v-100h-800v100zM0 600h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100zM750 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" /> + <glyph glyph-name="4b" unicode="" +d="M25 700h750c14 0 25 -11 25 -25v-75h-800v75c0 14 11 25 25 25zM0 500h800v-375c0 -14 -11 -25 -25 -25h-750c-14 0 -25 11 -25 25v375zM100 300v-100h100v100h-100zM300 300v-100h100v100h-100z" /> + <glyph glyph-name="4c" unicode="" +d="M100 800h100v-100h450l100 100l50 -50l-100 -100v-450h100v-100h-100v-100h-100v100h-500v500h-100v100h100v100zM200 600v-350l350 350h-350zM600 550l-350 -350h350v350z" /> + <glyph glyph-name="4d" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z +M200 452c0 20 15 42 34 48h3h3h8c12 0 28 -7 36 -16l91 -90l25 6c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100l6 25l-90 91c-9 8 -16 24 -16 36zM550 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" /> + <glyph glyph-name="4e" unicode="" +d="M300 800h200v-300h200l-300 -300l-300 300h200v300zM0 100h800v-100h-800v100z" /> + <glyph glyph-name="4f" unicode="" +d="M0 800h800v-100h-800v100zM400 600l300 -300h-200v-300h-200v300h-200z" /> + <glyph glyph-name="50" unicode="" +d="M200 700h600v-600h-600l-200 300zM350 622l-72 -72l150 -150l-150 -150l72 -72l150 150l150 -150l72 72l-150 150l150 150l-72 72l-150 -150z" /> + <glyph glyph-name="51" unicode="" +d="M400 700c220 0 400 -180 400 -400h-100c0 166 -134 300 -300 300s-300 -134 -300 -300h-100c0 220 180 400 400 400zM341 491l59 -88l59 88c82 -25 141 -101 141 -191c0 -110 -90 -200 -200 -200s-200 90 -200 200c0 90 59 166 141 191z" /> + <glyph glyph-name="52" unicode="" +d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300zM100 600v-100h100v100h-100zM100 400v-100h100v100h-100zM100 200v-100h400v100h-400z" /> + <glyph glyph-name="53" unicode="" horiz-adv-x="600" +d="M200 700h100v-100h75c30 0 58 -6 81 -22c24 -15 44 -44 44 -78v-100h-100v94c-4 3 -13 6 -25 6h-250c-13 0 -25 -12 -25 -25v-50c0 -14 20 -40 34 -44l257 -65c66 -16 109 -73 109 -141v-50c0 -69 -56 -125 -125 -125h-75v-100h-100v100h-75c-30 0 -58 6 -81 22 +c-24 15 -44 44 -44 78v100h100v-94c4 -3 13 -6 25 -6h250c13 0 25 12 25 25v50c0 14 -20 40 -34 44l-257 65c-66 16 -109 73 -109 141v50c0 69 56 125 125 125h75v100z" /> + <glyph glyph-name="54" unicode="" +d="M0 700h300v-300l-300 -300v600zM500 700h300v-300l-300 -300v600z" /> + <glyph glyph-name="55" unicode="" +d="M300 700v-600h-300v300zM800 700v-600h-300v300z" /> + <glyph glyph-name="56" unicode="" +d="M300 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300zM800 700v-100c-111 0 -200 -89 -200 -200h200v-300h-300v300c0 165 135 300 300 300z" /> + <glyph glyph-name="57" unicode="" +d="M0 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300zM500 700h300v-300c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-200v300z" /> + <glyph glyph-name="58" unicode="" horiz-adv-x="600" +d="M300 800l34 -34c11 -11 266 -269 266 -488c0 -165 -135 -300 -300 -300s-300 135 -300 300c0 219 255 477 266 488zM150 328c-28 0 -50 -22 -50 -50c0 -110 90 -200 200 -200c28 0 50 22 50 50s-22 50 -50 50c-55 0 -100 45 -100 100c0 28 -22 50 -50 50z" /> + <glyph glyph-name="59" unicode="" +d="M400 800l400 -500h-800zM0 200h800v-200h-800v200z" /> + <glyph glyph-name="5a" unicode="" horiz-adv-x="600" +d="M300 800l300 -300h-600zM0 300h600l-300 -300z" /> + <glyph glyph-name="5b" unicode="" +d="M0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200z" /> + <glyph glyph-name="5c" unicode="" +d="M0 700h800v-100l-400 -200l-400 200v100zM0 500l400 -200l400 200v-400h-800v400z" /> + <glyph glyph-name="5d" unicode="" +d="M400 800l400 -200v-600h-800v600zM400 688l-300 -150v-188l300 -150l300 150v188zM200 500h400v-100l-200 -100l-200 100v100z" /> + <glyph glyph-name="5e" unicode="" +d="M600 700c69 0 134 -19 191 -50l-16 -106c-49 35 -109 56 -175 56c-131 0 -240 -84 -281 -200h331l-16 -100h-334c0 -36 8 -68 19 -100h297l-16 -100h-222c55 -61 133 -100 222 -100c78 0 147 30 200 78v-122c-59 -35 -127 -56 -200 -56c-147 0 -274 82 -344 200h-256 +l19 100h197c-8 32 -16 66 -16 100h-200l25 100h191c45 172 198 300 384 300z" /> + <glyph glyph-name="5f" unicode="" +d="M0 700h700v-100h-700v100zM0 500h500v-100h-500v100zM0 300h800v-100h-800v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100z" /> + <glyph glyph-name="60" unicode="" +d="M0 800h800v-100h-800v100zM200 600h400l-200 -200zM0 200h800v-200h-800v200z" /> + <glyph glyph-name="61" unicode="" +d="M0 800h100v-800h-100v800zM600 800h200v-800h-200v800zM200 600l200 -200l-200 -200v400z" /> + <glyph glyph-name="62" unicode="" +d="M0 800h200v-800h-200v800zM700 800h100v-800h-100v800zM600 600v-400l-200 200z" /> + <glyph glyph-name="63" unicode="" +d="M0 800h800v-200h-800v200zM400 400l200 -200h-400zM0 100h800v-100h-800v100z" /> + <glyph glyph-name="64" unicode="" +d="M0 800h200v-100h-100v-600h600v100h100v-200h-800v800zM400 800h400v-400l-150 150l-250 -250l-100 100l250 250z" /> + <glyph glyph-name="65" unicode="" +d="M403 700c247 0 397 -300 397 -300s-150 -300 -397 -300c-253 0 -403 300 -403 300s150 300 403 300zM400 600c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200zM400 500c10 0 19 -3 28 -6c-16 -8 -28 -24 -28 -44c0 -28 22 -50 50 -50 +c20 0 36 12 44 28c3 -9 6 -18 6 -28c0 -55 -45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" /> + <glyph glyph-name="66" unicode="" horiz-adv-x="900" +d="M331 700h3h3c3 1 7 1 10 1c12 0 29 -8 37 -17l94 -93l66 65c57 57 156 57 212 0c59 -58 59 -154 0 -212l-65 -66l93 -94c10 -8 18 -25 18 -38c0 -28 -22 -50 -50 -50c-13 0 -32 9 -40 20l-62 65l-366 -365l-12 -16h-272v272l375 381l-63 63c-9 8 -16 24 -16 36 +c0 20 16 42 35 48zM447 481l-313 -315l128 -132l316 316z" /> + <glyph glyph-name="67" unicode="" +d="M0 800h300v-400h400v-400h-700v800zM400 800l300 -300h-300v300z" /> + <glyph glyph-name="68" unicode="" +d="M200 800c0 0 200 -100 200 -300s-298 -302 -200 -500c0 0 -200 100 -200 300s300 300 200 500zM500 500c0 0 200 -100 200 -300c0 -150 -60 -200 -100 -200h-300c0 200 300 300 200 500z" /> + <glyph glyph-name="69" unicode="" +d="M0 800h100v-800h-100v800zM200 800h300v-100h300l-200 -203l200 -197h-400v100h-200v400z" /> + <glyph glyph-name="6a" unicode="" horiz-adv-x="400" +d="M150 800h150l-100 -200h200l-150 -300h150l-300 -300l-100 300h134l66 200h-200z" /> + <glyph glyph-name="6b" unicode="" +d="M0 800h300v-100h500v-100h-800v200zM0 500h800v-450c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v450z" /> + <glyph glyph-name="6c" unicode="" +d="M150 800c83 0 150 -67 150 -150c0 -66 -41 -121 -100 -141v-118c15 5 33 9 50 9h200c28 0 50 22 50 50v59c-59 20 -100 75 -100 141c0 83 67 150 150 150s150 -67 150 -150c0 -66 -41 -121 -100 -141v-59c0 -82 -68 -150 -150 -150h-200c-14 0 -25 -7 -34 -16 +c50 -24 84 -74 84 -134c0 -83 -67 -150 -150 -150s-150 67 -150 150c0 66 41 121 100 141v218c-59 20 -100 75 -100 141c0 83 67 150 150 150z" /> + <glyph glyph-name="6d" unicode="" +d="M0 800h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400zM500 400l150 -150l150 150v-400h-400l150 150l-150 150z" /> + <glyph glyph-name="6e" unicode="" +d="M100 800l150 -150l150 150v-400h-400l150 150l-150 150zM400 400h400l-150 -150l150 -150l-100 -100l-150 150l-150 -150v400z" /> + <glyph glyph-name="6f" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM400 700c-56 0 -108 -17 -153 -44l22 -19c33 -18 13 -48 -13 -59c-29 -13 -77 10 -65 -41c14 -55 -27 -3 -47 -15c-43 -25 49 -152 31 -156c-14 -3 -40 34 -59 34 +c-8 0 -13 -5 -16 -10c1 -30 10 -57 19 -84c28 -10 74 1 97 -22c47 -28 100 -118 78 -162c33 -13 68 -22 106 -22c100 0 189 49 244 125c3 24 -5 44 -47 44c-69 0 -156 14 -153 97c2 46 101 108 66 143c-30 31 12 39 12 66c0 37 -65 32 -69 50s20 36 41 56 +c-30 10 -61 19 -94 19zM631 591c-38 -11 -95 -35 -87 -53c5 -15 55 -2 68 -13c11 -10 15 -58 44 -31l19 22c-12 27 -26 53 -44 75z" /> + <glyph glyph-name="70" unicode="" +d="M703 800l97 -100l-400 -400l-100 100l-200 -203l-100 100l300 303l100 -100zM0 100h800v-100h-800v100z" /> + <glyph glyph-name="71" unicode="" +d="M0 700h100v-100h-100v100zM200 700h100v-100h-100v100zM400 700h100v-100h-100v100zM600 700h100v-100h-100v100zM0 500h100v-100h-100v100zM200 500h100v-100h-100v100zM400 500h100v-100h-100v100zM600 500h100v-100h-100v100zM0 300h100v-100h-100v100zM200 300h100 +v-100h-100v100zM400 300h100v-100h-100v100zM600 300h100v-100h-100v100zM0 100h100v-100h-100v100zM200 100h100v-100h-100v100zM400 100h100v-100h-100v100zM600 100h100v-100h-100v100z" /> + <glyph glyph-name="72" unicode="" +d="M0 800h200v-200h-200v200zM300 800h200v-200h-200v200zM600 800h200v-200h-200v200zM0 500h200v-200h-200v200zM300 500h200v-200h-200v200zM600 500h200v-200h-200v200zM0 200h200v-200h-200v200zM300 200h200v-200h-200v200zM600 200h200v-200h-200v200z" /> + <glyph glyph-name="73" unicode="" +d="M0 800h300v-300h-300v300zM500 800h300v-300h-300v300zM0 300h300v-300h-300v300zM500 300h300v-300h-300v300z" /> + <glyph glyph-name="74" unicode="" +d="M19 800h662c11 0 19 -8 19 -19v-331c0 -28 -22 -50 -50 -50h-600c-28 0 -50 22 -50 50v331c0 11 8 19 19 19zM0 309c16 -5 32 -9 50 -9h600c18 0 34 4 50 9v-290c0 -11 -8 -19 -19 -19h-662c-11 0 -19 8 -19 19v290zM550 200c-28 0 -50 -22 -50 -50s22 -50 50 -50 +s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="75" unicode="" +d="M0 700h300v-100h-50c-28 0 -50 -22 -50 -50v-150h300v150c0 28 -22 50 -50 50h-50v100h300v-100h-50c-28 0 -50 -22 -50 -50v-400c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50v150h-300v-150c0 -28 22 -50 50 -50h50v-100h-300v100h50c28 0 50 22 50 50 +v400c0 28 -22 50 -50 50h-50v100z" /> + <glyph glyph-name="76" unicode="" +d="M400 700c165 0 300 -135 300 -300v-100h50c28 0 50 -22 50 -50v-200c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v350c0 111 -89 200 -200 200s-200 -89 -200 -200v-350c0 -28 -22 -50 -50 -50h-100c-28 0 -50 22 -50 50v200c0 28 22 50 50 50h50v100 +c0 165 135 300 300 300z" /> + <glyph glyph-name="77" unicode="" +d="M0 500c0 109 91 200 200 200s200 -91 200 -200c0 109 91 200 200 200s200 -91 200 -200c0 -55 -22 -104 -59 -141l-341 -343l-341 343c-37 36 -59 86 -59 141z" /> + <glyph glyph-name="78" unicode="" +d="M400 700l400 -300l-100 3v-403h-200v200h-200v-200h-200v400h-100z" /> + <glyph glyph-name="79" unicode="" +d="M0 800h800v-800h-800v800zM100 700v-300l100 100l400 -400h100v100l-200 200l100 100l100 -100v300h-600z" /> + <glyph glyph-name="7a" unicode="" +d="M19 800h762c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-762c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 600v-300h100l100 -100h200l100 100h100v300h-600z" /> + <glyph glyph-name="7b" unicode="" +d="M200 600c79 0 143 -56 200 -122c58 66 119 122 200 122c131 0 200 -101 200 -200s-69 -200 -200 -200c-81 0 -142 56 -200 122c-58 -66 -121 -122 -200 -122c-131 0 -200 101 -200 200s69 200 200 200zM200 500c-74 0 -100 -54 -100 -100s26 -100 100 -100 +c42 0 88 47 134 100c-46 53 -92 100 -134 100zM600 500c-43 0 -89 -47 -134 -100c45 -53 91 -100 134 -100c74 0 100 54 100 100s-26 100 -100 100z" /> + <glyph glyph-name="7c" unicode="" horiz-adv-x="400" +d="M300 800c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100zM150 550c83 0 150 -69 150 -150c0 -66 -100 -214 -100 -250c0 -28 22 -50 50 -50s50 22 50 50h100c0 -83 -67 -150 -150 -150s-150 64 -150 150s100 222 100 250s-22 50 -50 50 +s-50 -22 -50 -50h-100c0 83 67 150 150 150z" /> + <glyph glyph-name="7d" unicode="" +d="M200 800h500v-100h-122c-77 -197 -156 -392 -234 -588l-6 -12h162v-100h-500v100h122c77 197 156 392 234 588l7 12h-163v100z" /> + <glyph glyph-name="7e" unicode="" +d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM100 100h600v-100h-600v100z" /> + <glyph glyph-name="7f" unicode="" +d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM0 100h600v-100h-600v100z" /> + <glyph glyph-name="80" unicode="" +d="M0 700h800v-100h-800v100zM0 500h800v-100h-800v100zM0 300h800v-100h-800v100zM200 100h600v-100h-600v100z" /> + <glyph glyph-name="81" unicode="" +d="M550 800c138 0 250 -112 250 -250s-112 -250 -250 -250c-16 0 -30 3 -44 6l-6 -6v-100h-200v-200h-300v200l306 306c-3 14 -6 28 -6 44c0 138 112 250 250 250zM600 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" /> + <glyph glyph-name="82" unicode="" +d="M134 600h3h4h4h5h500c28 0 50 -22 50 -50v-350h100v-150c0 -28 -22 -50 -50 -50h-700c-28 0 -50 22 -50 50v150h100v350v2c0 20 15 42 34 48zM200 500v-300h100v-100h200v100h100v300h-400z" /> + <glyph glyph-name="83" unicode="" +d="M0 800h400v-400h-400v400zM500 600h100v-400h-400v100h300v300zM700 400h100v-400h-400v100h300v300z" /> + <glyph glyph-name="84" unicode="" horiz-adv-x="600" +d="M337 694c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-300 -150c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50c0 21 16 44 37 49zM437 544c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-400 -200c-8 -6 -21 -11 -31 -11c-28 0 -50 22 -50 50 +c0 21 16 44 37 49zM437 344c6 4 12 7 21 7c28 0 50 -22 50 -50c0 -17 -12 -37 -27 -45l-106 -56c24 -4 43 -26 43 -50c0 -28 -23 -51 -51 -51c-2 0 -6 1 -8 1h-200c-26 1 -48 24 -48 50c0 16 12 36 26 44zM151 -50c0 23 20 50 46 50h3h4h5h100c28 0 50 -22 50 -50 +s-22 -50 -50 -50h-100c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" /> + <glyph glyph-name="85" unicode="" +d="M199 800h100v-200h-200v100h100v100zM587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 10 35 23 43l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0l-43 -44c-8 -13 -28 -24 -43 -24 +c-28 0 -50 22 -50 50c0 15 11 35 24 43l44 44c33 33 72 53 128 56zM209 490c4 5 13 16 21 16h4c2 0 6 1 8 1c28 0 50 -22 50 -50c0 -11 -7 -27 -15 -35l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0l44 44c8 13 28 24 43 24c28 0 50 -22 50 -50 +c0 -15 -11 -35 -24 -43l-44 -44c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281zM499 200h200v-100h-100v-100h-100v200z" /> + <glyph glyph-name="86" unicode="" +d="M587 797c18 1 38 1 56 -3c36 -8 69 -26 97 -54c78 -77 78 -203 0 -281l-150 -150c-62 -62 -131 -81 -181 -78s-70 17 -85 25s-26 27 -26 44c0 28 22 51 50 51c8 0 19 -3 26 -7c0 0 15 -11 41 -13c26 -1 63 4 106 47l150 150c40 40 40 105 0 144c-40 40 -110 34 -144 0 +c-8 -13 -27 -24 -42 -24c-28 0 -50 22 -50 50c0 15 11 35 24 43c33 33 72 53 128 56zM387 566c50 -2 63 -17 84 -22s38 -28 38 -49c0 -28 -22 -50 -50 -50c-10 0 -24 5 -32 11c0 0 -19 9 -47 10s-63 -4 -103 -44l-150 -150c-40 -40 -40 -105 0 -144c40 -40 110 -34 144 0 +c8 13 28 24 43 24c28 0 50 -22 50 -50c0 -15 -11 -35 -24 -43c-22 -22 -48 -37 -75 -47c-71 -25 -150 -9 -206 47c-78 77 -78 203 0 281l150 150c60 60 128 78 178 76z" /> + <glyph glyph-name="87" unicode="" +d="M0 700h300v-300h-300v300zM400 700h400v-100h-400v100zM400 500h300v-100h-300v100zM0 300h300v-300h-300v300zM400 300h400v-100h-400v100zM400 100h300v-100h-300v100z" /> + <glyph glyph-name="88" unicode="" +d="M50 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 700h600v-100h-600v100zM50 500c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 500h600v-100h-600v100zM50 300c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50 +s22 50 50 50zM200 300h600v-100h-600v100zM50 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM200 100h600v-100h-600v100z" /> + <glyph glyph-name="89" unicode="" +d="M800 800l-400 -800l-100 300l-300 100z" /> + <glyph glyph-name="8a" unicode="" horiz-adv-x="600" +d="M300 700c110 0 200 -90 200 -200v-100h100v-400h-600v400h100v100c0 110 90 200 200 200zM300 600c-56 0 -100 -44 -100 -100v-100h200v100c0 56 -44 100 -100 100z" /> + <glyph glyph-name="8b" unicode="" horiz-adv-x="600" +d="M300 800c110 0 200 -90 200 -200v-200h100v-400h-600v400h400v200c0 56 -44 100 -100 100s-100 -44 -100 -100h-100c0 110 90 200 200 200z" /> + <glyph glyph-name="8c" unicode="" +d="M400 700v-100c-111 0 -200 -89 -200 -200h100l-150 -200l-150 200h100c0 165 135 300 300 300zM650 600l150 -200h-100c0 -165 -135 -300 -300 -300v100c111 0 200 89 200 200h-100z" /> + <glyph glyph-name="8d" unicode="" +d="M100 800h600v-300h100l-150 -250l-150 250h100v200h-400v-100h-100v200zM150 550l150 -250h-100v-200h400v100h100v-200h-600v300h-100z" /> + <glyph glyph-name="8e" unicode="" +d="M600 700l200 -150l-200 -150v100h-500v-100h-100v100c0 55 45 100 100 100h500v100zM200 300v-100h500v100h100v-100c0 -54 -46 -100 -100 -100h-500v-100l-200 150z" /> + <glyph glyph-name="8f" unicode="" horiz-adv-x="900" +d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c5 -3 12 -8 16 -12l100 -100c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-100 100c-4 3 -9 9 -12 13c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 200 +c142 0 250 108 250 250c0 139 -111 250 -250 250s-250 -111 -250 -250s111 -250 250 -250z" /> + <glyph glyph-name="90" unicode="" horiz-adv-x="600" +d="M300 800c166 0 300 -134 300 -300c0 -200 -300 -500 -300 -500s-300 300 -300 500c0 166 134 300 300 300zM300 700c-110 0 -200 -90 -200 -200s90 -200 200 -200s200 90 200 200s-90 200 -200 200z" /> + <glyph glyph-name="91" unicode="" horiz-adv-x="900" +d="M0 800h800v-541c1 -3 1 -8 1 -11s0 -7 -1 -10v-238h-800v800zM495 250c0 26 22 50 50 50h5h150v400h-600v-600h600v100h-150h-5c-28 0 -50 22 -50 50zM350 600c83 0 150 -67 150 -150c0 -100 -150 -250 -150 -250s-150 150 -150 250c0 83 67 150 150 150zM350 500 +c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="92" unicode="" horiz-adv-x="600" +d="M0 700h200v-600h-200v600zM400 700h200v-600h-200v600z" /> + <glyph glyph-name="93" unicode="" horiz-adv-x="600" +d="M0 700l600 -300l-600 -300v600z" /> + <glyph glyph-name="94" unicode="" horiz-adv-x="600" +d="M300 700c166 0 300 -134 300 -300s-134 -300 -300 -300s-300 134 -300 300s134 300 300 300z" /> + <glyph glyph-name="95" unicode="" +d="M400 700v-600l-400 300zM400 400l400 300v-600z" /> + <glyph glyph-name="96" unicode="" +d="M0 700l400 -300l-400 -300v600zM400 100v600l400 -300z" /> + <glyph glyph-name="97" unicode="" +d="M0 700h200v-600h-200v600zM200 400l500 300v-600z" /> + <glyph glyph-name="98" unicode="" +d="M0 700l500 -300l-500 -300v600zM500 100v600h200v-600h-200z" /> + <glyph glyph-name="99" unicode="" horiz-adv-x="600" +d="M0 700h600v-600h-600v600z" /> + <glyph glyph-name="9a" unicode="" +d="M200 800h400v-200h200v-400h-200v-200h-400v200h-200v400h200v200z" /> + <glyph glyph-name="9b" unicode="" +d="M0 700h800v-100h-800v100zM0 403h800v-100h-800v100zM0 103h800v-100h-800v100z" /> + <glyph glyph-name="9c" unicode="" horiz-adv-x="600" +d="M278 700c7 2 13 4 22 4c55 0 100 -45 100 -100v-4v-200c0 -55 -45 -100 -100 -100s-100 45 -100 100v200v2c0 44 35 88 78 98zM34 500h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-50c0 -111 89 -200 200 -200s200 89 200 200v50c0 28 22 50 50 50s50 -22 50 -50v-50 +c0 -148 -109 -270 -250 -294v-106h50c55 0 100 -45 100 -100h-400c0 55 45 100 100 100h50v106c-141 24 -250 146 -250 294v50v2c0 20 15 42 34 48z" /> + <glyph glyph-name="9d" unicode="" +d="M0 500h800v-200h-800v200z" /> + <glyph glyph-name="9e" unicode="" +d="M34 700h4h3h4h5h700c28 0 50 -22 50 -50v-500c0 -28 -22 -50 -50 -50h-250v-100h100c55 0 100 -45 100 -100h-600c0 55 45 100 100 100h100v100h-250c-28 0 -50 22 -50 50v500v2c0 20 15 42 34 48zM100 600v-400h600v400h-600z" /> + <glyph glyph-name="9f" unicode="" +d="M272 700c-14 -40 -22 -84 -22 -128c0 -221 179 -400 400 -400c45 0 88 11 128 25c-53 -158 -202 -275 -378 -275c-221 0 -400 179 -400 400c0 176 114 325 272 378z" /> + <glyph glyph-name="a0" unicode="" +d="M350 700l150 -150h-100v-150h150v100l150 -150l-150 -150v100h-150v-150h100l-150 -150l-150 150h100v150h-150v-100l-150 150l150 150v-100h150v150h-100z" /> + <glyph glyph-name="a1" unicode="" +d="M800 800v-550c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v206c-201 -6 -327 -27 -400 -50v-397c0 -83 -67 -150 -150 -150s-150 67 -150 150s67 150 150 150c17 0 35 -4 50 -9v409s100 100 600 100z" /> + <glyph glyph-name="a2" unicode="" horiz-adv-x="700" +d="M499 700c51 0 102 -20 141 -59c78 -77 78 -203 0 -281l-250 -244c-48 -48 -127 -48 -175 0s-48 127 0 175c32 32 64 65 96 97l69 -69l-59 -63l-38 -34c-10 -10 -10 -28 0 -38s28 -10 38 0l250 247c38 40 39 103 0 141c-40 40 -105 40 -144 0v-3l-278 -272 +c-67 -69 -68 -179 0 -247c69 -69 181 -69 250 0c39 44 83 84 125 125l69 -69l-125 -125c-107 -107 -281 -107 -388 0s-107 281 0 388l278 272c38 39 90 59 141 59z" /> + <glyph glyph-name="a3" unicode="" +d="M600 800l200 -200l-100 -100l-200 200zM400 600l200 -200l-400 -400h-200v200z" /> + <glyph glyph-name="a4" unicode="" +d="M550 800c83 0 150 -90 150 -200s-67 -200 -150 -200c-22 0 -41 5 -59 16c6 27 9 55 9 84c0 85 -27 158 -72 212c27 52 71 88 122 88zM250 700c83 0 150 -90 150 -200s-67 -200 -150 -200s-150 90 -150 200s67 200 150 200zM725 384c44 -22 75 -66 75 -118v-166h-200v66 +c0 50 -17 96 -44 134c67 2 126 33 169 84zM75 284c44 -53 106 -84 175 -84s131 31 175 84c44 -22 75 -66 75 -118v-166h-500v166c0 52 31 96 75 118z" /> + <glyph glyph-name="a5" unicode="" +d="M400 800c110 0 200 -112 200 -250s-90 -250 -200 -250s-200 112 -200 250s90 250 200 250zM191 300c54 -61 128 -100 209 -100s155 39 209 100c107 -4 191 -92 191 -200v-100h-800v100c0 108 84 196 191 200z" /> + <glyph glyph-name="a6" unicode="" horiz-adv-x="600" +d="M19 800h462c11 0 19 -8 19 -19v-762c0 -11 -8 -19 -19 -19h-462c-11 0 -19 8 -19 19v762c0 11 8 19 19 19zM100 700v-500h300v500h-300zM250 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="a7" unicode="" +d="M350 800c17 0 34 0 50 -3v-397l-297 297c63 64 150 103 247 103zM500 694c169 -24 300 -168 300 -344c0 -193 -157 -350 -350 -350c-85 0 -162 31 -222 81l272 272v341zM91 562l237 -234l-216 -212c-69 54 -112 139 -112 234c0 84 36 158 91 212z" /> + <glyph glyph-name="a8" unicode="" +d="M92 650c0 23 20 50 46 50h3h4h5h400c28 0 50 -22 50 -50s-22 -50 -50 -50h-50v-200h100c55 0 100 -45 100 -100h-300v-300l-56 -100l-44 100v300h-300c0 55 45 100 100 100h100v200h-50c-2 0 -6 -1 -8 -1c-28 0 -50 23 -50 51z" /> + <glyph glyph-name="a9" unicode="" +d="M400 800c221 0 400 -179 400 -400s-179 -400 -400 -400s-400 179 -400 400s179 400 400 400zM300 600v-400l300 200z" /> + <glyph glyph-name="aa" unicode="" +d="M300 800h200v-300h300v-200h-300v-300h-200v300h-300v200h300v300z" /> + <glyph glyph-name="ab" unicode="" +d="M300 800h100v-400h-100v400zM172 656l62 -78l-40 -31c-58 -46 -94 -117 -94 -197c0 -139 111 -250 250 -250s250 111 250 250c0 80 -39 151 -97 197l-37 31l62 78l38 -31c82 -64 134 -163 134 -275c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 112 54 211 134 275z +" /> + <glyph glyph-name="ac" unicode="" +d="M200 800h400v-200h-400v200zM9 500h782c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-91v200h-600v-200h-91c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM200 300h400v-300h-400v300z" /> + <glyph glyph-name="ad" unicode="" +d="M0 700h100v-700h-100v700zM700 700h100v-700h-100v700zM200 600h200v-100h-200v100zM300 400h200v-100h-200v100zM400 200h200v-100h-200v100z" /> + <glyph glyph-name="ae" unicode="" +d="M325 700c42 -141 87 -280 131 -419c29 74 59 148 88 222c29 -58 57 -116 87 -172h169v-100h-231l-13 28c-37 -92 -74 -184 -112 -275c-39 127 -79 255 -119 382c-41 -133 -83 -265 -125 -397c-28 88 -56 175 -84 262h-116v100h188l9 -34l3 -6c42 137 83 273 125 409z" /> + <glyph glyph-name="af" unicode="" +d="M200 700c0 58 42 100 100 100s100 -42 100 -100c0 -28 -20 -48 -31 -72c-2 -6 -3 -16 -3 -28h234v-234c12 0 22 3 28 6c24 10 44 28 72 28c58 0 100 -42 100 -100s-42 -100 -100 -100c-28 0 -48 20 -72 31c-6 2 -16 3 -28 3v-234h-234c0 12 3 22 6 28c10 24 28 44 28 72 +c0 58 -42 100 -100 100s-100 -42 -100 -100c0 -28 20 -48 31 -72c2 -6 3 -16 3 -28h-234v600h234c0 12 -3 22 -6 28c-10 24 -28 44 -28 72z" /> + <glyph glyph-name="b0" unicode="" horiz-adv-x="500" +d="M247 700c83 0 147 -20 190 -59s60 -93 60 -141c0 -117 -66 -181 -116 -225s-84 -67 -84 -150v-25h-100v25c0 117 69 181 119 225s81 67 81 150c0 25 -8 48 -28 66s-56 34 -122 34s-97 -18 -116 -37s-27 -43 -31 -69l-100 12c5 38 19 88 59 128s103 66 188 66zM197 0h100 +v-100h-100v100z" /> + <glyph glyph-name="b1" unicode="" +d="M450 800c138 0 250 -112 250 -250v-50c58 -21 100 -85 100 -150c0 -69 -48 -127 -112 -144c-22 55 -75 94 -138 94c-20 0 -39 -5 -56 -12c-17 64 -75 112 -144 112s-127 -48 -144 -112c-17 7 -36 12 -56 12c-37 0 -71 -16 -97 -38c-33 36 -53 86 -53 138 +c0 110 90 200 200 200c23 114 129 200 250 200zM334 300h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-200c0 -28 -22 -50 -50 -50s-50 22 -50 50v200v2c0 20 15 42 34 48zM134 200h4h3c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2 +c0 20 15 42 34 48zM534 200h3h4c3 0 6 1 9 1c28 0 50 -22 50 -50v-1v-100c0 -28 -22 -50 -50 -50s-50 22 -50 50v100v2c0 20 15 42 34 48z" /> + <glyph glyph-name="b2" unicode="" +d="M600 700l200 -150l-200 -150v100h-53v-3l-150 -187l175 -207v-3h28v100l200 -150l-200 -150v100h-25c-35 0 -57 10 -78 34v4l-163 190l-153 -190c-21 -26 -46 -38 -81 -38h-100v100h103v3l163 203l-163 191v3h-103v100h100c35 0 57 -10 78 -34v-4l150 -174l141 174 +c21 26 46 38 81 38h50v100z" /> + <glyph glyph-name="b3" unicode="" +d="M400 700c109 0 208 -47 281 -119l119 119v-300h-300l109 110c-55 55 -126 90 -209 90c-166 0 -300 -134 -300 -300s134 -300 300 -300c84 0 158 33 212 88l69 -69c-72 -73 -171 -119 -281 -119c-220 0 -400 180 -400 400s180 400 400 400z" /> + <glyph glyph-name="b4" unicode="" +d="M400 800h400v-400l-166 166l-400 -400l166 -166h-400v400l166 -166l400 400z" /> + <glyph glyph-name="b5" unicode="" horiz-adv-x="600" +d="M250 800l250 -300h-200v-200h200l-250 -300l-250 300h200v200h-200z" /> + <glyph glyph-name="b6" unicode="" +d="M300 600v-200h200v200l300 -250l-300 -250v200h-200v-200l-300 250z" /> + <glyph glyph-name="b7" unicode="" +d="M0 800c441 0 800 -359 800 -800h-200c0 333 -267 600 -600 600v200zM0 500c275 0 500 -225 500 -500h-200c0 167 -133 300 -300 300v200zM0 200c111 0 200 -89 200 -200h-200v200z" /> + <glyph glyph-name="b8" unicode="" +d="M100 800c386 0 700 -314 700 -700h-100c0 332 -268 600 -600 600v100zM100 600c276 0 500 -224 500 -500h-100c0 222 -178 400 -400 400v100zM100 400c165 0 300 -135 300 -300h-100c0 111 -89 200 -200 200v100zM100 200c55 0 100 -45 100 -100s-45 -100 -100 -100 +s-100 45 -100 100s45 100 100 100z" /> + <glyph glyph-name="b9" unicode="" +d="M300 800h400c55 0 100 -45 100 -100v-200h-400v150c0 28 -22 50 -50 50s-50 -22 -50 -50v-250h400v-300c0 -55 -45 -100 -100 -100h-500c-55 0 -100 45 -100 100v200h100v-150c0 -28 22 -50 50 -50s50 22 50 50v550c0 55 45 100 100 100z" /> + <glyph glyph-name="ba" unicode="" +d="M75 700h225v-100h-200v-500h400v100h100v-125c0 -40 -35 -75 -75 -75h-450c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM600 700l200 -200l-200 -200v100h-200c-94 0 -173 -65 -194 -153c23 199 189 353 394 353v100z" /> + <glyph glyph-name="bb" unicode="" +d="M500 700l300 -284l-300 -316v200h-100c-200 0 -348 -102 -400 -300c0 295 100 500 500 500v200z" /> + <glyph glyph-name="bc" unicode="" +d="M381 791l19 9l19 -9c127 -53 253 -108 381 -160v-31c0 -166 -67 -313 -147 -419c-40 -53 -83 -97 -125 -128s-82 -53 -128 -53s-86 22 -128 53s-85 75 -125 128c-80 107 -147 253 -147 419v31c128 52 254 107 381 160zM400 100v591l-294 -122c8 -126 58 -243 122 -328 +c35 -46 73 -86 106 -110s62 -31 66 -31z" /> + <glyph glyph-name="bd" unicode="" +d="M600 800h100v-800h-100v800zM400 700h100v-700h-100v700zM200 500h100v-500h-100v500zM0 300h100v-300h-100v300z" /> + <glyph glyph-name="be" unicode="" +d="M300 800h100v-200h200l100 -100l-100 -100h-200v-400h-100v500h-200l-100 100l100 100h200v100z" /> + <glyph glyph-name="bf" unicode="" +d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h200v-100h-200v100zM400 600h300v-100h-300v100zM400 400h400v-100h-400v100z" /> + <glyph glyph-name="c0" unicode="" +d="M200 800h100v-600h200l-250 -200l-250 200h200v600zM400 800h400v-100h-400v100zM400 600h300v-100h-300v100zM400 400h200v-100h-200v100z" /> + <glyph glyph-name="c1" unicode="" +d="M75 700h650c40 0 75 -35 75 -75v-550c0 -40 -35 -75 -75 -75h-650c-40 0 -75 35 -75 75v550c0 40 35 75 75 75zM100 600v-100h100v100h-100zM300 600v-100h400v100h-400zM100 400v-100h100v100h-100zM300 400v-100h400v100h-400zM100 200v-100h100v100h-100zM300 200 +v-100h400v100h-400z" /> + <glyph glyph-name="c2" unicode="" +d="M400 800l100 -300h300l-250 -200l100 -300l-250 200l-250 -200l100 300l-250 200h300z" /> + <glyph glyph-name="c3" unicode="" +d="M400 800c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM650 700c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 600c110 0 200 -90 200 -200 +s-90 -200 -200 -200s-200 90 -200 200s90 200 200 200zM50 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM750 450c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM150 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50 +s22 50 50 50zM650 200c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50zM400 100c28 0 50 -22 50 -50s-22 -50 -50 -50s-50 22 -50 50s22 50 50 50z" /> + <glyph glyph-name="c4" unicode="" +d="M34 800h632c18 0 34 -16 34 -34v-732c0 -18 -16 -34 -34 -34h-632c-18 0 -34 16 -34 34v732c0 18 16 34 34 34zM100 700v-500h500v500h-500zM350 150c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="c5" unicode="" +d="M0 800h300l500 -500l-300 -300l-500 500v300zM200 700c-55 0 -100 -45 -100 -100s45 -100 100 -100s100 45 100 100s-45 100 -100 100z" /> + <glyph glyph-name="c6" unicode="" +d="M0 600h200l300 -300l-200 -200l-300 300v200zM340 600h160l300 -300l-200 -200l-78 78l119 122zM150 500c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="c7" unicode="" +d="M400 800c220 0 400 -180 400 -400s-180 -400 -400 -400s-400 180 -400 400s180 400 400 400zM400 700c-166 0 -300 -134 -300 -300s134 -300 300 -300s300 134 300 300s-134 300 -300 300zM400 600c110 0 200 -90 200 -200s-90 -200 -200 -200s-200 90 -200 200 +s90 200 200 200zM400 500c-56 0 -100 -44 -100 -100s44 -100 100 -100s100 44 100 100s-44 100 -100 100z" /> + <glyph glyph-name="c8" unicode="" +d="M0 700h559l-100 -100h-359v-500h500v159l100 100v-359h-700v700zM700 700l100 -100l-400 -400l-200 200l100 100l100 -100z" /> + <glyph glyph-name="c9" unicode="" +d="M9 800h782c6 0 9 -3 9 -9v-782c0 -6 -3 -9 -9 -9h-782c-6 0 -9 3 -9 9v782c0 6 3 9 9 9zM150 722l-72 -72l100 -100l-100 -100l72 -72l172 172zM400 500v-100h300v100h-300z" /> + <glyph glyph-name="ca" unicode="" +d="M0 800h800v-200h-50c0 55 -45 100 -100 100h-150v-550c0 -28 22 -50 50 -50h50v-100h-400v100h50c28 0 50 22 50 50v550h-150c-55 0 -100 -45 -100 -100h-50v200z" /> + <glyph glyph-name="cb" unicode="" +d="M0 700h100v-400h-100v400zM200 700h350c21 0 39 -13 47 -31c0 0 103 -291 103 -319s-22 -50 -50 -50h-150c-28 0 -50 -25 -50 -50s39 -158 47 -184c8 -27 -5 -55 -31 -63c-27 -8 -53 5 -66 31s-110 219 -128 238c-19 18 -44 28 -72 28v400z" /> + <glyph glyph-name="cc" unicode="" +d="M444 700l22 -3c26 -8 39 -36 31 -63s-47 -159 -47 -184s22 -50 50 -50h150c28 0 50 -22 50 -50s-103 -319 -103 -319c-8 -18 -26 -31 -47 -31h-350v400c28 0 53 10 72 28c18 19 115 212 128 238c10 20 25 32 44 34zM0 400h100v-400h-100v400z" /> + <glyph glyph-name="cd" unicode="" +d="M200 700h300v-100h-100v-6c25 -4 50 -8 72 -16l-35 -94c-29 10 -57 16 -87 16c-139 0 -250 -111 -250 -250s111 -250 250 -250s250 111 250 250c0 31 -5 61 -16 91l94 34c13 -38 22 -80 22 -125c0 -193 -157 -350 -350 -350s-350 157 -350 350c0 176 130 323 300 347v3 +h-100v100zM700 588c0 0 -296 -353 -316 -372c-20 -20 -52 -20 -72 0s-20 52 0 72s388 300 388 300z" /> + <glyph glyph-name="ce" unicode="" +d="M600 700l200 -150l-200 -150v100h-600v100h600v100zM200 300v-100h600v-100h-600v-100l-200 150z" /> + <glyph glyph-name="cf" unicode="" +d="M300 800h100c55 0 100 -45 100 -100h100c55 0 100 -45 100 -100h-700c0 55 45 100 100 100h100c0 55 45 100 100 100zM100 500h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-350c0 -28 22 -50 50 -50s50 22 50 50v350h100v-481c0 -11 -8 -19 -19 -19h-462 +c-11 0 -19 8 -19 19v481z" /> + <glyph glyph-name="d0" unicode="" +d="M100 800h200v-400c0 -55 45 -100 100 -100s100 45 100 100v400h100v-400c0 -110 -90 -200 -200 -200h-50c-138 0 -250 90 -250 200v400zM0 100h700v-100h-700v100z" /> + <glyph glyph-name="d1" unicode="" +d="M9 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM609 700h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282 +c0 6 3 9 9 9zM0 100h800v-100h-800v100z" /> + <glyph glyph-name="d2" unicode="" +d="M10 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM610 700h181c6 0 9 -3 9 -9v-191h-200v191c0 6 4 9 10 9zM310 600h181c6 0 9 -3 9 -9v-91h-200v91c0 6 4 9 10 9zM0 400h800v-100h-800v100zM0 200h200v-191c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v191zM300 200 +h200v-91c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v91zM600 200h200v-191c0 -6 -3 -9 -9 -9h-181c-6 0 -10 3 -10 9v191z" /> + <glyph glyph-name="d3" unicode="" +d="M0 700h800v-100h-800v100zM9 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v482c0 6 3 9 9 9zM309 500h182c6 0 9 -3 9 -9v-282c0 -6 -3 -9 -9 -9h-182c-6 0 -9 3 -9 9v282c0 6 3 9 9 9zM609 500h182c6 0 9 -3 9 -9v-482c0 -6 -3 -9 -9 -9h-182 +c-6 0 -9 3 -9 9v482c0 6 3 9 9 9z" /> + <glyph glyph-name="d4" unicode="" +d="M50 600h500c28 0 50 -22 50 -50v-150l100 100h100v-300h-100l-100 100v-150c0 -28 -22 -50 -50 -50h-500c-28 0 -50 22 -50 50v400c0 28 22 50 50 50z" /> + <glyph glyph-name="d5" unicode="" +d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 600v100c26 0 52 -4 75 -10c130 -32 225 -150 225 -290s-95 -259 -225 -291c-23 -6 -49 -9 -75 -9v100c16 0 33 2 50 6c86 22 150 100 150 194s-64 172 -150 194h-3c-16 4 -32 6 -47 6zM500 500l22 -3h3v-3 +c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" /> + <glyph glyph-name="d6" unicode="" horiz-adv-x="600" +d="M334 800h66v-800h-66l-134 200h-200v400h200zM500 500l22 -3h3v-3c42 -12 75 -49 75 -94c0 -46 -32 -85 -75 -97l-25 -3v200z" /> + <glyph glyph-name="d7" unicode="" horiz-adv-x="400" +d="M334 800h66v-800h-66l-134 200h-200v400h200z" /> + <glyph glyph-name="d8" unicode="" +d="M309 800h82c6 0 10 -4 12 -9l294 -682l3 -19v-81c0 -6 -3 -9 -9 -9h-682c-6 0 -9 3 -9 9v81l3 19l294 682c2 5 6 9 12 9zM300 500v-200h100v200h-100zM300 200v-100h100v100h-100z" /> + <glyph glyph-name="d9" unicode="" +d="M375 700c138 0 269 -39 378 -109l-53 -82c-93 60 -205 91 -325 91c-119 0 -229 -35 -322 -94l-53 88c109 69 238 106 375 106zM378 400c79 0 151 -23 213 -62l-53 -85c-46 29 -101 47 -160 47c-60 0 -114 -17 -162 -47l-54 85c62 40 136 62 216 62zM375 100 +c55 0 100 -45 100 -100s-45 -100 -100 -100s-100 45 -100 100s45 100 100 100z" /> + <glyph glyph-name="da" unicode="" horiz-adv-x="900" +d="M551 700c16 0 30 -3 44 -6l-94 -94v-200h200l94 94c3 -14 6 -28 6 -44c0 -138 -112 -250 -250 -250c-32 0 -62 6 -90 16l-288 -288c-20 -19 -46 -28 -72 -28s-52 11 -72 31c-39 39 -39 102 0 141l291 287c-11 28 -19 59 -19 91c0 138 112 250 250 250zM101 50 +c-28 0 -50 -22 -50 -50s22 -50 50 -50s50 22 50 50s-22 50 -50 50z" /> + <glyph glyph-name="db" unicode="" +d="M141 700c85 -80 167 -164 250 -247c83 82 165 167 250 247l140 -141c-80 -85 -164 -167 -247 -250c82 -83 167 -165 247 -250l-140 -140c-85 80 -167 164 -250 247c-83 -82 -165 -167 -250 -247l-141 140c80 85 164 167 247 250c-82 83 -167 165 -247 250z" /> + <glyph glyph-name="dc" unicode="" +d="M0 800h100l231 -300h38l231 300h100l-225 -300h225v-100h-300v-100h300v-100h-300v-200h-100v200h-300v100h300v100h-300v100h225z" /> + <glyph glyph-name="dd" unicode="" horiz-adv-x="900" +d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700 +c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM300 600h100v-100h100v-100h-100v-100h-100v100h-100v100h100v100z" /> + <glyph glyph-name="de" unicode="" horiz-adv-x="900" +d="M350 800c193 0 350 -157 350 -350c0 -60 -17 -117 -44 -166c4 -2 10 -6 13 -9l103 -103c16 -16 30 -49 30 -72c0 -56 -46 -102 -102 -102c-23 0 -56 14 -72 30l-103 103c-3 2 -7 7 -9 10c-49 -26 -107 -41 -166 -41c-193 0 -350 157 -350 350s157 350 350 350zM350 700 +c-139 0 -250 -111 -250 -250s111 -250 250 -250c60 0 120 22 160 59c8 12 21 26 34 32l3 3c34 43 53 97 53 156c0 139 -111 250 -250 250zM200 500h300v-100h-300v100z" /> + </font> +</defs></svg> diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf Binary files differnew file mode 100644 index 000000000..0f94acd1e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff Binary files differnew file mode 100644 index 000000000..793176af4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css new file mode 100644 index 000000000..c9ab2c74f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css @@ -0,0 +1,228 @@ +/* Start of reset */ + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +body { + margin: 0; +} + + +/** + * Define consistent border, margin, and padding. + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; +} + +/** End of reset */ + + +body { + font-family: 'Roboto', sans-serif; + font-size: 14px; + line-height: 22px; + font-weight: 300; +} +h1 { + font-size: 42px; + line-height: 44px; + padding-bottom: 5px; + color: #b10610; + margin-top: 10px; + margin-bottom: 30px; +} +h2 { + color: #333333; + font-size: 28px; + line-height: 44px; + font-weight: 300; +} +h3 { + font-size: 21px; + margin-top: 20px; + margin-bottom: 10px; +} +a { + color: #31a1cd; +} +h1 a { + text-decoration: none; +} +h2 a { + color: #333333; +} +a:visited { + color: #6098a2; +} +h2 a:visited { + color: #333333; +} +a:hover { + color: #b10610; +} +hr { + border: none; + border-top: 1px dashed #c9ea75; + margin-top: 30px; + margin-bottom: 30px; +} +header { + background: #eeeeee; +} +header a { + font-size: 28px; + font-weight: 500; + color: #333; + text-decoration: none; +} +.logo { + padding: 5px 10px; +} +.logo img { + vertical-align: middle; + border: 0; +} +input, button { + font: inherit; + color: inherit; +} + +input[type=text] { + border: 1px solid #bbbbbb; + line-height: 22px; + padding: 5px 10px; + border-radius: 3px; +} + +nav { + padding: 5px; +} + +.btn, button, input[type=submit] { + display: inline-block; + color: white; + background: #4fa3ac; + padding: 9px 15px; + border-radius: 2px; + border: 0; + text-decoration: none; +} +a.btn:visited { + color: white; +} + +.btn.disabled { + background: #eeeeee; + color: #bbbbbb; +} +section { + margin: 40px 10px; +} + +section table { + height: 40px; +} + +.nodeTable tr { + border-bottom: 3px solid white; +} + +.nodeTable td { + padding: 10px 10px 10px 10px; + +} + +.nodeTable a { + text-decoration: none; +} + +.nodeTable .nameColumn { + font-weight: bold; + padding: 10px 20px; + background: #ebf5f6; + min-width: 200px; +} +.nodeTable .oi { + color: #b10610; +} + +.propTable tr { + height: 40px; +} + +.propTable th { + background: #f6f6f6; + padding: 0 10px; + text-align: left; +} + +.propTable td { + padding: 0 10px; + background: #eeeeee; +} + +.propTable pre { + font-size: 80%; + background: #f8f8f8; +} + +.actions { + border: 1px dotted #76baa6; + padding: 20px; + margin-bottom: 20px; + +} + +.actions h3 { + margin-top: 10px; + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: 1px solid #eeeeee; +} + +.actions label { + width: 150px; + display: inline-block; + line-height: 40px; +} + +.actions input[type=text] { + width: 450px; +} + +.actions input[type=submit] { + display: inline-block; + margin-left: 153px; +} + +footer { + padding: 50px 0; + font-size: 80%; + text-align: center; +} + +ul.tree { + list-style: none; + margin: 0; + padding: 0; +} + +ul.tree ul { + list-style: none; + padding-left: 10px; + border-left: 4px solid #ccc; +} diff --git a/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png Binary files differnew file mode 100644 index 000000000..48a97398a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png diff --git a/vendor/sabre/dav/lib/DAV/Client.php b/vendor/sabre/dav/lib/DAV/Client.php new file mode 100644 index 000000000..d46b397b6 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Client.php @@ -0,0 +1,448 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +/** + * SabreDAV DAV client + * + * This client wraps around Curl to provide a convenient API to a WebDAV + * server. + * + * NOTE: This class is experimental, it's api will likely change in the future. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Client extends HTTP\Client { + + /** + * The xml service. + * + * Uset this service to configure the property and namespace maps. + * + * @var mixed + */ + public $xml; + + /** + * The elementMap + * + * This property is linked via reference to $this->xml->elementMap. + * It's deprecated as of version 3.0.0, and should no longer be used. + * + * @deprecated + * @var array + */ + public $propertyMap = []; + + /** + * Base URI + * + * This URI will be used to resolve relative urls. + * + * @var string + */ + protected $baseUri; + + /** + * Basic authentication + */ + const AUTH_BASIC = 1; + + /** + * Digest authentication + */ + const AUTH_DIGEST = 2; + + /** + * NTLM authentication + */ + const AUTH_NTLM = 4; + + /** + * Identity encoding, which basically does not nothing. + */ + const ENCODING_IDENTITY = 1; + + /** + * Deflate encoding + */ + const ENCODING_DEFLATE = 2; + + /** + * Gzip encoding + */ + const ENCODING_GZIP = 4; + + /** + * Sends all encoding headers. + */ + const ENCODING_ALL = 7; + + /** + * Content-encoding + * + * @var int + */ + protected $encoding = self::ENCODING_IDENTITY; + + /** + * Constructor + * + * Settings are provided through the 'settings' argument. The following + * settings are supported: + * + * * baseUri + * * userName (optional) + * * password (optional) + * * proxy (optional) + * * authType (optional) + * * encoding (optional) + * + * authType must be a bitmap, using self::AUTH_BASIC, self::AUTH_DIGEST + * and self::AUTH_NTLM. If you know which authentication method will be + * used, it's recommended to set it, as it will save a great deal of + * requests to 'discover' this information. + * + * Encoding is a bitmap with one of the ENCODING constants. + * + * @param array $settings + */ + function __construct(array $settings) { + + if (!isset($settings['baseUri'])) { + throw new \InvalidArgumentException('A baseUri must be provided'); + } + + parent::__construct(); + + $this->baseUri = $settings['baseUri']; + + if (isset($settings['proxy'])) { + $this->addCurlSetting(CURLOPT_PROXY, $settings['proxy']); + } + + if (isset($settings['userName'])) { + $userName = $settings['userName']; + $password = isset($settings['password']) ? $settings['password'] : ''; + + if (isset($settings['authType'])) { + $curlType = 0; + if ($settings['authType'] & self::AUTH_BASIC) { + $curlType |= CURLAUTH_BASIC; + } + if ($settings['authType'] & self::AUTH_DIGEST) { + $curlType |= CURLAUTH_DIGEST; + } + if ($settings['authType'] & self::AUTH_NTLM) { + $curlType |= CURLAUTH_NTLM; + } + } else { + $curlType = CURLAUTH_BASIC | CURLAUTH_DIGEST; + } + + $this->addCurlSetting(CURLOPT_HTTPAUTH, $curlType); + $this->addCurlSetting(CURLOPT_USERPWD, $userName . ':' . $password); + + } + + if (isset($settings['encoding'])) { + $encoding = $settings['encoding']; + + $encodings = []; + if ($encoding & self::ENCODING_IDENTITY) { + $encodings[] = 'identity'; + } + if ($encoding & self::ENCODING_DEFLATE) { + $encodings[] = 'deflate'; + } + if ($encoding & self::ENCODING_GZIP) { + $encodings[] = 'gzip'; + } + $this->addCurlSetting(CURLOPT_ENCODING, implode(',', $encodings)); + } + + $this->addCurlSetting(CURLOPT_USERAGENT, 'sabre-dav/' . Version::VERSION . ' (http://sabre.io/)'); + + $this->xml = new Xml\Service(); + // BC + $this->propertyMap = & $this->xml->elementMap; + + } + + /** + * Does a PROPFIND request + * + * The list of requested properties must be specified as an array, in clark + * notation. + * + * The returned array will contain a list of filenames as keys, and + * properties as values. + * + * The properties array will contain the list of properties. Only properties + * that are actually returned from the server (without error) will be + * returned, anything else is discarded. + * + * Depth should be either 0 or 1. A depth of 1 will cause a request to be + * made to the server to also return all child resources. + * + * @param string $url + * @param array $properties + * @param int $depth + * @return array + */ + function propFind($url, array $properties, $depth = 0) { + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $root = $dom->createElementNS('DAV:', 'd:propfind'); + $prop = $dom->createElement('d:prop'); + + foreach ($properties as $property) { + + list( + $namespace, + $elementName + ) = \Sabre\Xml\Service::parseClarkNotation($property); + + if ($namespace === 'DAV:') { + $element = $dom->createElement('d:' . $elementName); + } else { + $element = $dom->createElementNS($namespace, 'x:' . $elementName); + } + + $prop->appendChild($element); + } + + $dom->appendChild($root)->appendChild($prop); + $body = $dom->saveXML(); + + $url = $this->getAbsoluteUrl($url); + + $request = new HTTP\Request('PROPFIND', $url, [ + 'Depth' => $depth, + 'Content-Type' => 'application/xml' + ], $body); + + $response = $this->send($request); + + if ((int)$response->getStatus() >= 400) { + throw new \Sabre\HTTP\ClientHttpException($response); + } + + $result = $this->parseMultiStatus($response->getBodyAsString()); + + // If depth was 0, we only return the top item + if ($depth === 0) { + reset($result); + $result = current($result); + return isset($result[200]) ? $result[200] : []; + } + + $newResult = []; + foreach ($result as $href => $statusList) { + + $newResult[$href] = isset($statusList[200]) ? $statusList[200] : []; + + } + + return $newResult; + + } + + /** + * Updates a list of properties on the server + * + * The list of properties must have clark-notation properties for the keys, + * and the actual (string) value for the value. If the value is null, an + * attempt is made to delete the property. + * + * @param string $url + * @param array $properties + * @return bool + */ + function propPatch($url, array $properties) { + + $propPatch = new Xml\Request\PropPatch(); + $propPatch->properties = $properties; + $xml = $this->xml->write( + '{DAV:}propertyupdate', + $propPatch + ); + + $url = $this->getAbsoluteUrl($url); + $request = new HTTP\Request('PROPPATCH', $url, [ + 'Content-Type' => 'application/xml', + ], $xml); + $response = $this->send($request); + + if ($response->getStatus() >= 400) { + throw new \Sabre\HTTP\ClientHttpException($response); + } + + if ($response->getStatus() === 207) { + // If it's a 207, the request could still have failed, but the + // information is hidden in the response body. + $result = $this->parseMultiStatus($response->getBodyAsString()); + + $errorProperties = []; + foreach ($result as $href => $statusList) { + foreach ($statusList as $status => $properties) { + + if ($status >= 400) { + foreach ($properties as $propName => $propValue) { + $errorProperties[] = $propName . ' (' . $status . ')'; + } + } + + } + } + if ($errorProperties) { + + throw new \Sabre\HTTP\ClientException('PROPPATCH failed. The following properties errored: ' . implode(', ', $errorProperties)); + } + } + return true; + + } + + /** + * Performs an HTTP options request + * + * This method returns all the features from the 'DAV:' header as an array. + * If there was no DAV header, or no contents this method will return an + * empty array. + * + * @return array + */ + function options() { + + $request = new HTTP\Request('OPTIONS', $this->getAbsoluteUrl('')); + $response = $this->send($request); + + $dav = $response->getHeader('Dav'); + if (!$dav) { + return []; + } + + $features = explode(',', $dav); + foreach ($features as &$v) { + $v = trim($v); + } + return $features; + + } + + /** + * Performs an actual HTTP request, and returns the result. + * + * If the specified url is relative, it will be expanded based on the base + * url. + * + * The returned array contains 3 keys: + * * body - the response body + * * httpCode - a HTTP code (200, 404, etc) + * * headers - a list of response http headers. The header names have + * been lowercased. + * + * For large uploads, it's highly recommended to specify body as a stream + * resource. You can easily do this by simply passing the result of + * fopen(..., 'r'). + * + * This method will throw an exception if an HTTP error was received. Any + * HTTP status code above 399 is considered an error. + * + * Note that it is no longer recommended to use this method, use the send() + * method instead. + * + * @param string $method + * @param string $url + * @param string|resource|null $body + * @param array $headers + * @throws ClientException, in case a curl error occurred. + * @return array + */ + function request($method, $url = '', $body = null, array $headers = []) { + + $url = $this->getAbsoluteUrl($url); + + $response = $this->send(new HTTP\Request($method, $url, $headers, $body)); + return [ + 'body' => $response->getBodyAsString(), + 'statusCode' => (int)$response->getStatus(), + 'headers' => array_change_key_case($response->getHeaders()), + ]; + + } + + /** + * Returns the full url based on the given url (which may be relative). All + * urls are expanded based on the base url as given by the server. + * + * @param string $url + * @return string + */ + 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; + + } + + /** + * Parses a WebDAV multistatus response body + * + * This method returns an array with the following structure + * + * [ + * 'url/to/resource' => [ + * '200' => [ + * '{DAV:}property1' => 'value1', + * '{DAV:}property2' => 'value2', + * ], + * '404' => [ + * '{DAV:}property1' => null, + * '{DAV:}property2' => null, + * ], + * ], + * 'url/to/resource2' => [ + * .. etc .. + * ] + * ] + * + * + * @param string $body xml body + * @return array + */ + function parseMultiStatus($body) { + + $multistatus = $this->xml->expect('{DAV:}multistatus', $body); + + $result = []; + + foreach ($multistatus->getResponses() as $response) { + + $result[$response->getHref()] = $response->getResponseProperties(); + + } + + return $result; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Collection.php b/vendor/sabre/dav/lib/DAV/Collection.php new file mode 100644 index 000000000..a46bcc342 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Collection.php @@ -0,0 +1,109 @@ +<?php + +namespace Sabre\DAV; + +/** + * Collection class + * + * This is a helper class, that should aid in getting collections classes setup. + * Most of its methods are implemented, and throw permission denied exceptions + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Collection extends Node implements ICollection { + + /** + * Returns a child object, by its name. + * + * This method makes use of the getChildren method to grab all the child + * nodes, and compares the name. + * Generally its wise to override this, as this can usually be optimized + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws Exception\NotFound + * @return INode + */ + function getChild($name) { + + foreach ($this->getChildren() as $child) { + + if ($child->getName() === $name) return $child; + + } + throw new Exception\NotFound('File not found: ' . $name); + + } + + /** + * Checks is a child-node exists. + * + * It is generally a good idea to try and override this. Usually it can be optimized. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + try { + + $this->getChild($name); + return true; + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * 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 + * @return null|string + */ + function createFile($name, $data = null) { + + throw new Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @throws Exception\Forbidden + * @return void + */ + function createDirectory($name) { + + throw new Exception\Forbidden('Permission denied to create directory'); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/CorePlugin.php b/vendor/sabre/dav/lib/DAV/CorePlugin.php new file mode 100644 index 000000000..3a70b2a7e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/CorePlugin.php @@ -0,0 +1,927 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\Xml\ParseException; + +/** + * The core plugin provides all the basic features for a WebDAV server. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class CorePlugin extends ServerPlugin { + + /** + * Reference to server object. + * + * @var Server + */ + protected $server; + + /** + * Sets up the plugin + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + $server->on('method:GET', [$this, 'httpGet']); + $server->on('method:OPTIONS', [$this, 'httpOptions']); + $server->on('method:HEAD', [$this, 'httpHead']); + $server->on('method:DELETE', [$this, 'httpDelete']); + $server->on('method:PROPFIND', [$this, 'httpPropFind']); + $server->on('method:PROPPATCH', [$this, 'httpPropPatch']); + $server->on('method:PUT', [$this, 'httpPut']); + $server->on('method:MKCOL', [$this, 'httpMkcol']); + $server->on('method:MOVE', [$this, 'httpMove']); + $server->on('method:COPY', [$this, 'httpCopy']); + $server->on('method:REPORT', [$this, 'httpReport']); + + $server->on('propPatch', [$this, 'propPatchProtectedPropertyCheck'], 90); + $server->on('propPatch', [$this, 'propPatchNodeUpdate'], 200); + $server->on('propFind', [$this, 'propFind']); + $server->on('propFind', [$this, 'propFindNode'], 120); + $server->on('propFind', [$this, 'propFindLate'], 200); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'core'; + + } + + /** + * This is the default implementation for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = $this->server->tree->getNodeForPath($path); + + if (!$node instanceof IFile) return; + + $body = $node->get(); + + // Converting string into stream, if needed. + if (is_string($body)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $body); + rewind($stream); + $body = $stream; + } + + /* + * TODO: getetag, getlastmodified, getsize should also be used using + * this method + */ + $httpHeaders = $this->server->getHTTPHeaders($path); + + /* ContentType needs to get a default, because many webservers will otherwise + * default to text/html, and we don't want this for security reasons. + */ + if (!isset($httpHeaders['Content-Type'])) { + $httpHeaders['Content-Type'] = 'application/octet-stream'; + } + + + if (isset($httpHeaders['Content-Length'])) { + + $nodeSize = $httpHeaders['Content-Length']; + + // Need to unset Content-Length, because we'll handle that during figuring out the range + unset($httpHeaders['Content-Length']); + + } else { + $nodeSize = null; + } + + $response->addHeaders($httpHeaders); + + $range = $this->server->getHTTPRange(); + $ifRange = $request->getHeader('If-Range'); + $ignoreRangeHeader = false; + + // If ifRange is set, and range is specified, we first need to check + // the precondition. + if ($nodeSize && $range && $ifRange) { + + // if IfRange is parsable as a date we'll treat it as a DateTime + // otherwise, we must treat it as an etag. + try { + $ifRangeDate = new \DateTime($ifRange); + + // It's a date. We must check if the entity is modified since + // the specified date. + if (!isset($httpHeaders['Last-Modified'])) $ignoreRangeHeader = true; + else { + $modified = new \DateTime($httpHeaders['Last-Modified']); + if ($modified > $ifRangeDate) $ignoreRangeHeader = true; + } + + } catch (\Exception $e) { + + // It's an entity. We can do a simple comparison. + if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; + elseif ($httpHeaders['ETag'] !== $ifRange) $ignoreRangeHeader = true; + } + } + + // We're only going to support HTTP ranges if the backend provided a filesize + if (!$ignoreRangeHeader && $nodeSize && $range) { + + // Determining the exact byte offsets + if (!is_null($range[0])) { + + $start = $range[0]; + $end = $range[1] ? $range[1] : $nodeSize - 1; + if ($start >= $nodeSize) + throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); + + if ($end < $start) throw new Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); + if ($end >= $nodeSize) $end = $nodeSize - 1; + + } else { + + $start = $nodeSize - $range[1]; + $end = $nodeSize - 1; + + if ($start < 0) $start = 0; + + } + + // Streams may advertise themselves as seekable, but still not + // actually allow fseek. We'll manually go forward in the stream + // if fseek failed. + if (!stream_get_meta_data($body)['seekable'] || fseek($body, $start, SEEK_SET) === -1) { + $consumeBlock = 8192; + for ($consumed = 0; $start - $consumed > 0;){ + if (feof($body)) throw new Exception\RequestedRangeNotSatisfiable('The start offset (' . $start . ') exceeded the size of the entity (' . $consumed . ')'); + $consumed += strlen(fread($body, min($start - $consumed, $consumeBlock))); + } + } + + $response->setHeader('Content-Length', $end - $start + 1); + $response->setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $nodeSize); + $response->setStatus(206); + $response->setBody($body); + + } else { + + if ($nodeSize) $response->setHeader('Content-Length', $nodeSize); + $response->setStatus(200); + $response->setBody($body); + + } + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP OPTIONS + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpOptions(RequestInterface $request, ResponseInterface $response) { + + $methods = $this->server->getAllowedMethods($request->getPath()); + + $response->setHeader('Allow', strtoupper(implode(', ', $methods))); + $features = ['1', '3', 'extended-mkcol']; + + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + + $response->setHeader('DAV', implode(', ', $features)); + $response->setHeader('MS-Author-Via', 'DAV'); + $response->setHeader('Accept-Ranges', 'bytes'); + $response->setHeader('Content-Length', '0'); + $response->setStatus(200); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP HEAD + * + * This method is normally used to take a peak at a url, and only get the + * HTTP response headers, without the body. This is used by clients to + * determine if a remote file was changed, so they can use a local cached + * version, instead of downloading it again + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpHead(RequestInterface $request, ResponseInterface $response) { + + // This is implemented by changing the HEAD request to a GET request, + // and dropping the response body. + $subRequest = clone $request; + $subRequest->setMethod('GET'); + + try { + $this->server->invokeMethod($subRequest, $response, false); + $response->setBody(''); + } catch (Exception\NotImplemented $e) { + // Some clients may do HEAD requests on collections, however, GET + // requests and HEAD requests _may_ not be defined on a collection, + // which would trigger a 501. + // This breaks some clients though, so we're transforming these + // 501s into 200s. + $response->setStatus(200); + $response->setBody(''); + $response->setHeader('Content-Type', 'text/plain'); + $response->setHeader('X-Sabre-Real-Status', $e->getHTTPCode()); + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP Delete + * + * The HTTP delete method, deletes a given uri + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpDelete(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + if (!$this->server->emit('beforeUnbind', [$path])) return false; + $this->server->tree->delete($path); + $this->server->emit('afterUnbind', [$path]); + + $response->setStatus(204); + $response->setHeader('Content-Length', '0'); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPFIND + * + * This WebDAV method requests information about an uri resource, or a list of resources + * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value + * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) + * + * The request body contains an XML data structure that has a list of properties the client understands + * The response body is also an xml document, containing information about every uri resource and the requested properties + * + * It has to return a HTTP 207 Multi-status status code + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPropFind(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $requestBody = $request->getBodyAsString(); + if (strlen($requestBody)) { + try { + $propFindXml = $this->server->xml->expect('{DAV:}propfind', $requestBody); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + } else { + $propFindXml = new Xml\Request\PropFind(); + $propFindXml->allProp = true; + $propFindXml->properties = []; + } + + $depth = $this->server->getHTTPDepth(1); + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $newProperties = $this->server->getPropertiesForPath($path, $propFindXml->properties, $depth); + + // This is a multi-status response + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Vary', 'Brief,Prefer'); + + // Normally this header is only needed for OPTIONS responses, however.. + // iCal seems to also depend on these being set for PROPFIND. Since + // this is not harmful, we'll add it. + $features = ['1', '3', 'extended-mkcol']; + foreach ($this->server->getPlugins() as $plugin) { + $features = array_merge($features, $plugin->getFeatures()); + } + $response->setHeader('DAV', implode(', ', $features)); + + $prefer = $this->server->getHTTPPrefer(); + $minimal = $prefer['return'] === 'minimal'; + + $data = $this->server->generateMultiStatus($newProperties, $minimal); + $response->setBody($data); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV PROPPATCH + * + * This method is called to update properties on a Node. The request is an XML body with all the mutations. + * In this XML body it is specified which properties should be set/updated and/or deleted + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPropPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + try { + $propPatch = $this->server->xml->expect('{DAV:}propertyupdate', $request->getBody()); + } catch (ParseException $e) { + throw new BadRequest($e->getMessage(), null, $e); + } + $newProperties = $propPatch->properties; + + $result = $this->server->updateProperties($path, $newProperties); + + $prefer = $this->server->getHTTPPrefer(); + $response->setHeader('Vary', 'Brief,Prefer'); + + if ($prefer['return'] === 'minimal') { + + // If return-minimal is specified, we only have to check if the + // request was succesful, and don't need to return the + // multi-status. + $ok = true; + foreach ($result as $prop => $code) { + if ((int)$code > 299) { + $ok = false; + } + } + + if ($ok) { + + $response->setStatus(204); + return false; + + } + + } + + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + + // Reorganizing the result for generateMultiStatus + $multiStatus = []; + foreach ($result as $propertyName => $code) { + if (isset($multiStatus[$code])) { + $multiStatus[$code][$propertyName] = null; + } else { + $multiStatus[$code] = [$propertyName => null]; + } + } + $multiStatus['href'] = $path; + + $response->setBody( + $this->server->generateMultiStatus([$multiStatus]) + ); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * HTTP PUT method + * + * This HTTP method updates a file, or creates a new one. + * + * If a new resource was created, a 201 Created status code should be returned. If an existing resource is updated, it's a 204 No Content + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $response) { + + $body = $request->getBodyAsStream(); + $path = $request->getPath(); + + // Intercepting Content-Range + if ($request->getHeader('Content-Range')) { + /* + An origin server that allows PUT on a given target resource MUST send + a 400 (Bad Request) response to a PUT request that contains a + Content-Range header field. + + Reference: http://tools.ietf.org/html/rfc7231#section-4.3.4 + */ + throw new Exception\BadRequest('Content-Range on PUT requests are forbidden.'); + } + + // Intercepting the Finder problem + if (($expected = $request->getHeader('X-Expected-Entity-Length')) && $expected > 0) { + + /* + Many webservers will not cooperate well with Finder PUT requests, + because it uses 'Chunked' transfer encoding for the request body. + + The symptom of this problem is that Finder sends files to the + server, but they arrive as 0-length files in PHP. + + If we don't do anything, the user might think they are uploading + files successfully, but they end up empty on the server. Instead, + we throw back an error if we detect this. + + The reason Finder uses Chunked, is because it thinks the files + might change as it's being uploaded, and therefore the + Content-Length can vary. + + Instead it sends the X-Expected-Entity-Length header with the size + of the file at the very start of the request. If this header is set, + but we don't get a request body we will fail the request to + protect the end-user. + */ + + // Only reading first byte + $firstByte = fread($body, 1); + if (strlen($firstByte) !== 1) { + throw new Exception\Forbidden('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.'); + } + + // The body needs to stay intact, so we copy everything to a + // temporary stream. + + $newBody = fopen('php://temp', 'r+'); + fwrite($newBody, $firstByte); + stream_copy_to_stream($body, $newBody); + rewind($newBody); + + $body = $newBody; + + } + + if ($this->server->tree->nodeExists($path)) { + + $node = $this->server->tree->getNodeForPath($path); + + // If the node is a collection, we'll deny it + if (!($node instanceof IFile)) throw new Exception\Conflict('PUT is not allowed on non-files.'); + + if (!$this->server->updateFile($path, $body, $etag)) { + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + } else { + + $etag = null; + // If we got here, the resource didn't exist yet. + if (!$this->server->createFile($path, $body, $etag)) { + // For one reason or another the file was not created. + return false; + } + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(201); + + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + + /** + * WebDAV MKCOL + * + * The MKCOL method is used to create a new collection (directory) on the server + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMkcol(RequestInterface $request, ResponseInterface $response) { + + $requestBody = $request->getBodyAsString(); + $path = $request->getPath(); + + if ($requestBody) { + + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) { + + // We must throw 415 for unsupported mkcol bodies + throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); + + } + + try { + $mkcol = $this->server->xml->expect('{DAV:}mkcol', $requestBody); + } catch (\Sabre\Xml\ParseException $e) { + throw new Exception\BadRequest($e->getMessage(), null, $e); + } + + $properties = $mkcol->getProperties(); + + if (!isset($properties['{DAV:}resourcetype'])) + throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property'); + + $resourceType = $properties['{DAV:}resourcetype']->getValue(); + unset($properties['{DAV:}resourcetype']); + + } else { + + $properties = []; + $resourceType = ['{DAV:}collection']; + + } + + $mkcol = new MkCol($resourceType, $properties); + + $result = $this->server->createCollection($path, $mkcol); + + if (is_array($result)) { + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $response->setBody( + $this->server->generateMultiStatus([$result]) + ); + + } else { + $response->setHeader('Content-Length', '0'); + $response->setStatus(201); + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP MOVE method + * + * This method moves one uri to a different uri. A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpMove(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $moveInfo = $this->server->getCopyAndMoveInfo($request); + + if ($moveInfo['destinationExists']) { + + if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) return false; + + } + if (!$this->server->emit('beforeUnbind', [$path])) return false; + if (!$this->server->emit('beforeBind', [$moveInfo['destination']])) return false; + if (!$this->server->emit('beforeMove', [$path, $moveInfo['destination']])) return false; + + if ($moveInfo['destinationExists']) { + + $this->server->tree->delete($moveInfo['destination']); + $this->server->emit('afterUnbind', [$moveInfo['destination']]); + + } + + $this->server->tree->move($path, $moveInfo['destination']); + + // Its important afterMove is called before afterUnbind, because it + // allows systems to transfer data from one path to another. + // PropertyStorage uses this. If afterUnbind was first, it would clean + // up all the properties before it has a chance. + $this->server->emit('afterMove', [$path, $moveInfo['destination']]); + $this->server->emit('afterUnbind', [$path]); + $this->server->emit('afterBind', [$moveInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($moveInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * WebDAV HTTP COPY method + * + * This method copies one uri to a different uri, and works much like the MOVE request + * A lot of the actual request processing is done in getCopyMoveInfo + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpCopy(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $copyInfo = $this->server->getCopyAndMoveInfo($request); + + if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false; + if ($copyInfo['destinationExists']) { + if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false; + $this->server->tree->delete($copyInfo['destination']); + } + + $this->server->tree->copy($path, $copyInfo['destination']); + $this->server->emit('afterBind', [$copyInfo['destination']]); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $response->setHeader('Content-Length', '0'); + $response->setStatus($copyInfo['destinationExists'] ? 204 : 201); + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + + } + + /** + * HTTP REPORT method implementation + * + * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253) + * It's used in a lot of extensions, so it made sense to implement it into the core. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpReport(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + $result = $this->server->xml->parse( + $request->getBody(), + $request->getUrl(), + $rootElementName + ); + + if ($this->server->emit('report', [$rootElementName, $result, $path])) { + + // If emit returned true, it means the report was not supported + throw new Exception\ReportNotSupported(); + + } + + // Sending back false will interupt the event chain and tell the server + // we've handled this method. + return false; + + } + + /** + * This method is called during property updates. + * + * Here we check if a user attempted to update a protected property and + * ensure that the process fails if this is the case. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchProtectedPropertyCheck($path, PropPatch $propPatch) { + + // Comparing the mutation list to the list of propetected properties. + $mutations = $propPatch->getMutations(); + + $protected = array_intersect( + $this->server->protectedProperties, + array_keys($mutations) + ); + + if ($protected) { + $propPatch->setResultCode($protected, 403); + } + + } + + /** + * This method is called during property updates. + * + * Here we check if a node implements IProperties and let the node handle + * updating of (some) properties. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatchNodeUpdate($path, PropPatch $propPatch) { + + // This should trigger a 404 if the node doesn't exist. + $node = $this->server->tree->getNodeForPath($path); + + if ($node instanceof IProperties) { + $node->propPatch($propPatch); + } + + } + + /** + * This method is called when properties are retrieved. + * + * Here we add all the default properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $propFind->handle('{DAV:}getlastmodified', function() use ($node) { + $lm = $node->getLastModified(); + if ($lm) { + return new Xml\Property\GetLastModified($lm); + } + }); + + if ($node instanceof IFile) { + $propFind->handle('{DAV:}getcontentlength', [$node, 'getSize']); + $propFind->handle('{DAV:}getetag', [$node, 'getETag']); + $propFind->handle('{DAV:}getcontenttype', [$node, 'getContentType']); + } + + if ($node instanceof IQuota) { + $quotaInfo = null; + $propFind->handle('{DAV:}quota-used-bytes', function() use (&$quotaInfo, $node) { + $quotaInfo = $node->getQuotaInfo(); + return $quotaInfo[0]; + }); + $propFind->handle('{DAV:}quota-available-bytes', function() use (&$quotaInfo, $node) { + if (!$quotaInfo) { + $quotaInfo = $node->getQuotaInfo(); + } + return $quotaInfo[1]; + }); + } + + $propFind->handle('{DAV:}supported-report-set', function() use ($propFind) { + $reports = []; + foreach ($this->server->getPlugins() as $plugin) { + $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->getPath())); + } + return new Xml\Property\SupportedReportSet($reports); + }); + $propFind->handle('{DAV:}resourcetype', function() use ($node) { + return new Xml\Property\ResourceType($this->server->getResourceTypeForNode($node)); + }); + $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) { + return new Xml\Property\SupportedMethodSet( + $this->server->getAllowedMethods($propFind->getPath()) + ); + }); + + } + + /** + * Fetches properties for a node. + * + * This event is called a bit later, so plugins have a chance first to + * populate the result. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindNode(PropFind $propFind, INode $node) { + + 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); + } + } + + } + + } + + /** + * This method is called when properties are retrieved. + * + * This specific handler is called very late in the process, because we + * want other systems to first have a chance to handle the properties. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFindLate(PropFind $propFind, INode $node) { + + $propFind->handle('{http://calendarserver.org/ns/}getctag', function() use ($propFind) { + + // If we already have a sync-token from the current propFind + // request, we can re-use that. + $val = $propFind->get('{http://sabredav.org/ns}sync-token'); + if ($val) return $val; + + $val = $propFind->get('{DAV:}sync-token'); + if ($val && is_scalar($val)) { + return $val; + } + if ($val && $val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + + // If we got here, the earlier two properties may simply not have + // been part of the earlier request. We're going to fetch them. + $result = $this->server->getProperties($propFind->getPath(), [ + '{http://sabredav.org/ns}sync-token', + '{DAV:}sync-token', + ]); + + if (isset($result['{http://sabredav.org/ns}sync-token'])) { + return $result['{http://sabredav.org/ns}sync-token']; + } + if (isset($result['{DAV:}sync-token'])) { + $val = $result['{DAV:}sync-token']; + if (is_scalar($val)) { + return $val; + } elseif ($val instanceof Xml\Property\Href) { + return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); + } + } + + }); + + } + + /** + * 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' => 'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.', + 'link' => null, + ]; + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Exception.php b/vendor/sabre/dav/lib/DAV/Exception.php new file mode 100644 index 000000000..14f5bab2a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\DAV; + +/** + * Main Exception class. + * + * This class defines a getHTTPCode method, which should return the appropriate HTTP code for the Exception occurred. + * The default for this is 500. + * + * This class also allows you to generate custom xml data for your exceptions. This will be displayed + * in the 'error' element in the failing response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Exception extends \Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 500; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(Server $server, \DOMElement $errorNode) { + + + } + + /** + * This method allows the exception to return any extra HTTP response headers. + * + * The headers must be returned as an array. + * + * @param Server $server + * @return array + */ + function getHTTPHeaders(Server $server) { + + return []; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/BadRequest.php b/vendor/sabre/dav/lib/DAV/Exception/BadRequest.php new file mode 100644 index 000000000..c21f493da --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/BadRequest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * BadRequest + * + * The BadRequest is thrown when the user submitted an invalid HTTP request + * BadRequest + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class BadRequest extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 400; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/Conflict.php b/vendor/sabre/dav/lib/DAV/Exception/Conflict.php new file mode 100644 index 000000000..4190e6082 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/Conflict.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * Conflict + * + * A 409 Conflict is thrown when a user tried to make a directory over an existing + * file or in a parent directory that doesn't exist. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Conflict extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 409; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php b/vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php new file mode 100644 index 000000000..b2d2746c5 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * ConflictingLock + * + * Similar to the Locked exception, this exception thrown when a LOCK request + * was made, on a resource which was already locked + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ConflictingLock extends Locked { + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:no-conflicting-lock'); + $errorNode->appendChild($error); + $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:', 'd:href', $this->lock->uri)); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php b/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php new file mode 100644 index 000000000..77df7ca9e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * Forbidden + * + * This exception is thrown whenever a user tries to do an operation he's not allowed to + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Forbidden extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 403; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php b/vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php new file mode 100644 index 000000000..96e1acc50 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * InsufficientStorage + * + * This Exception can be thrown, when for example a harddisk is full or a quota is exceeded + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class InsufficientStorage extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 507; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php b/vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php new file mode 100644 index 000000000..505fe5c10 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php @@ -0,0 +1,33 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * InvalidResourceType + * + * This exception is thrown when the user tried to create a new collection, with + * a special resourcetype value that was not recognized by the server. + * + * See RFC5689 section 3.3 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class InvalidResourceType extends Forbidden { + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(\Sabre\DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php b/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php new file mode 100644 index 000000000..51a253b29 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php @@ -0,0 +1,38 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * InvalidSyncToken + * + * This exception is emited for the {DAV:}valid-sync-token pre-condition, as + * defined in rfc6578, section 3.2. + * + * http://tools.ietf.org/html/rfc6578#section-3.2 + * + * This is emitted in cases where the the sync-token, supplied by a client is + * either completely unknown, or has expired. + * + * @author Evert Pot (http://evertpot.com/) + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class InvalidSyncToken extends Forbidden { + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:valid-sync-token'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php b/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php new file mode 100644 index 000000000..989718558 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * LengthRequired + * + * This exception is thrown when a request was made that required a + * Content-Length header, but did not contain one. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LengthRequired extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 411; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php b/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php new file mode 100644 index 000000000..5f8c31868 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * LockTokenMatchesRequestUri + * + * This exception is thrown by UNLOCK if a supplied lock-token is invalid + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LockTokenMatchesRequestUri extends Conflict { + + /** + * Creates the exception + */ + function __construct() { + + $this->message = 'The locktoken supplied does not match any locks on this entity'; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-matches-request-uri'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/Locked.php b/vendor/sabre/dav/lib/DAV/Exception/Locked.php new file mode 100644 index 000000000..8176db46e --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/Locked.php @@ -0,0 +1,72 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * Locked + * + * The 423 is thrown when a client tried to access a resource that was locked, without supplying a valid lock token + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Locked extends DAV\Exception { + + /** + * Lock information + * + * @var Sabre\DAV\Locks\LockInfo + */ + protected $lock; + + /** + * Creates the exception + * + * A LockInfo object should be passed if the user should be informed + * which lock actually has the file locked. + * + * @param DAV\Locks\LockInfo $lock + */ + function __construct(DAV\Locks\LockInfo $lock = null) { + + $this->lock = $lock; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 423; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:lock-token-submitted'); + $errorNode->appendChild($error); + + $href = $errorNode->ownerDocument->createElementNS('DAV:', 'd:href'); + $href->appendChild($errorNode->ownerDocument->createTextNode($this->lock->uri)); + $error->appendChild( + $href + ); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php b/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php new file mode 100644 index 000000000..30c1c2553 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php @@ -0,0 +1,47 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * MethodNotAllowed + * + * The 405 is thrown when a client tried to create a directory on an already existing directory + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MethodNotAllowed extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 405; + + } + + /** + * This method allows the exception to return any extra HTTP response headers. + * + * The headers must be returned as an array. + * + * @param \Sabre\DAV\Server $server + * @return array + */ + function getHTTPHeaders(\Sabre\DAV\Server $server) { + + $methods = $server->getAllowedMethods($server->getRequestUri()); + + return [ + 'Allow' => strtoupper(implode(', ', $methods)), + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php b/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php new file mode 100644 index 000000000..e69a60c75 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * NotAuthenticated + * + * This exception is thrown when the client did not provide valid + * authentication credentials. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class NotAuthenticated extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 401; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/NotFound.php b/vendor/sabre/dav/lib/DAV/Exception/NotFound.php new file mode 100644 index 000000000..b0397446d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/NotFound.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * NotFound + * + * This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class NotFound extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 404; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php b/vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php new file mode 100644 index 000000000..68f309222 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * NotImplemented + * + * This exception is thrown when the client tried to call an unsupported HTTP method or other feature + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class NotImplemented extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 501; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.php b/vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.php new file mode 100644 index 000000000..43e17344a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/PaymentRequired.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * Payment Required + * + * The PaymentRequired exception may be thrown in a case where a user must pay + * to access a certain resource or operation. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PaymentRequired extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 402; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php b/vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php new file mode 100644 index 000000000..550360e5a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php @@ -0,0 +1,71 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * PreconditionFailed + * + * This exception is normally thrown when a client submitted a conditional request, + * like for example an If, If-None-Match or If-Match header, which caused the HTTP + * request to not execute (the condition of the header failed) + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PreconditionFailed extends DAV\Exception { + + /** + * When this exception is thrown, the header-name might be set. + * + * This allows the exception-catching code to determine which HTTP header + * caused the exception. + * + * @var string + */ + public $header = null; + + /** + * Create the exception + * + * @param string $message + * @param string $header + */ + function __construct($message, $header = null) { + + parent::__construct($message); + $this->header = $header; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 412; + + } + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->header) { + $prop = $errorNode->ownerDocument->createElement('s:header'); + $prop->nodeValue = $this->header; + $errorNode->appendChild($prop); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php b/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php new file mode 100644 index 000000000..a83695627 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * ReportNotSupported + * + * This exception is thrown when the client requested an unknown report through the REPORT method + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ReportNotSupported extends UnsupportedMediaType { + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:supported-report'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php b/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php new file mode 100644 index 000000000..c8ccfc062 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * RequestedRangeNotSatisfiable + * + * This exception is normally thrown when the user + * request a range that is out of the entity bounds. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class RequestedRangeNotSatisfiable extends DAV\Exception { + + /** + * returns the http statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 416; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php b/vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php new file mode 100644 index 000000000..31df606e2 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/ServiceUnavailable.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * ServiceUnavailable + * + * This exception is thrown in case the service + * is currently not available (e.g. down for maintenance). + * + * @author Thomas Müller <thomas.mueller@tmit.eu> + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ServiceUnavailable extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 503; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php b/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php new file mode 100644 index 000000000..d0f0f84e8 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php @@ -0,0 +1,38 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * TooManyMatches + * + * This exception is emited for the {DAV:}number-of-matches-within-limits + * post-condition, as defined in rfc6578, section 3.2. + * + * http://tools.ietf.org/html/rfc6578#section-3.2 + * + * This is emitted in cases where the response to a {DAV:}sync-collection would + * generate more results than the implementation is willing to send back. + * + * @author Evert Pot (http://evertpot.com/) + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class TooManyMatches extends Forbidden { + + /** + * This method allows the exception to include additional information into the WebDAV error response + * + * @param DAV\Server $server + * @param \DOMElement $errorNode + * @return void + */ + function serialize(DAV\Server $server, \DOMElement $errorNode) { + + $error = $errorNode->ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits'); + $errorNode->appendChild($error); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php b/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php new file mode 100644 index 000000000..f3d92842d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV\Exception; + +use Sabre\DAV; + +/** + * UnSupportedMediaType + * + * The 415 Unsupported Media Type status code is generally sent back when the client + * tried to call an HTTP method, with a body the server didn't understand + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class UnsupportedMediaType extends DAV\Exception { + + /** + * returns the http statuscode for this exception + * + * @return int + */ + function getHTTPCode() { + + return 415; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FS/Directory.php b/vendor/sabre/dav/lib/DAV/FS/Directory.php new file mode 100644 index 000000000..963e5554c --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FS/Directory.php @@ -0,0 +1,151 @@ +<?php + +namespace Sabre\DAV\FS; + +use Sabre\DAV; + +/** + * Directory class + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Directory extends Node implements DAV\ICollection, 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 successful 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 + * @return null|string + */ + function createFile($name, $data = null) { + + $newPath = $this->path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + foreach ($iterator as $entry) { + + $nodes[] = $this->getChild($entry->getFilename()); + + } + return $nodes; + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + */ + function delete() { + + foreach ($this->getChildren() as $child) $child->delete(); + rmdir($this->path); + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + + return [ + disk_total_space($this->path) - disk_free_space($this->path), + disk_free_space($this->path) + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FS/File.php b/vendor/sabre/dav/lib/DAV/FS/File.php new file mode 100644 index 000000000..4fc5af057 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FS/File.php @@ -0,0 +1,95 @@ +<?php + +namespace Sabre\DAV\FS; + +use Sabre\DAV; + +/** + * File class + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends Node implements DAV\IFile { + + /** + * Updates the data + * + * @param resource $data + * @return void + */ + function put($data) { + + file_put_contents($this->path, $data); + clearstatcache(true, $this->path); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return void + */ + function delete() { + + unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + + /** + * 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 + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + function getContentType() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FS/Node.php b/vendor/sabre/dav/lib/DAV/FS/Node.php new file mode 100644 index 000000000..831c11911 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FS/Node.php @@ -0,0 +1,80 @@ +<?php + +namespace Sabre\DAV\FS; + +use Sabre\DAV; +use Sabre\HTTP\URLUtil; + +/** + * Base node-class + * + * The node class implements the method used by both the File and the Directory classes + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Node implements DAV\INode { + + /** + * The path to the current node + * + * @var string + */ + protected $path; + + /** + * Sets up the node, expects a full path name + * + * @param string $path + */ + function __construct($path) { + + $this->path = $path; + + } + + + + /** + * Returns the name of the node + * + * @return string + */ + function getName() { + + list(, $name) = URLUtil::splitPath($this->path); + return $name; + + } + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + function setName($name) { + + list($parentPath, ) = URLUtil::splitPath($this->path); + list(, $newName) = URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + rename($this->path, $newPath); + + $this->path = $newPath; + + } + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified() { + + return filemtime($this->path); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FSExt/Directory.php b/vendor/sabre/dav/lib/DAV/FSExt/Directory.php new file mode 100644 index 000000000..648079e26 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FSExt/Directory.php @@ -0,0 +1,219 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; +use Sabre\DAV\FS\Node; + +/** + * Directory class + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Directory extends Node implements DAV\ICollection, DAV\IQuota, DAV\IMoveTarget { + + /** + * 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 successful 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 + * @return null|string + */ + function createFile($name, $data = null) { + + // We're not allowing dots + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + $newPath = $this->path . '/' . $name; + file_put_contents($newPath, $data); + clearstatcache(true, $newPath); + + return '"' . sha1( + fileinode($newPath) . + filesize($newPath) . + filemtime($newPath) + ) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + // We're not allowing dots + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + $newPath = $this->path . '/' . $name; + mkdir($newPath); + clearstatcache(true, $newPath); + + } + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws DAV\Exception\NotFound + * @return DAV\INode + */ + function getChild($name) { + + $path = $this->path . '/' . $name; + + if (!file_exists($path)) throw new DAV\Exception\NotFound('File could not be located'); + if ($name == '.' || $name == '..') throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + if (is_dir($path)) { + + return new self($path); + + } else { + + return new File($path); + + } + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + function childExists($name) { + + if ($name == '.' || $name == '..') + throw new DAV\Exception\Forbidden('Permission denied to . and ..'); + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren() { + + $nodes = []; + $iterator = new \FilesystemIterator( + $this->path, + \FilesystemIterator::CURRENT_AS_SELF + | \FilesystemIterator::SKIP_DOTS + ); + + foreach ($iterator as $entry) { + + $node = $entry->getFilename(); + + if ($node === '.sabredav') + continue; + + $nodes[] = $this->getChild($node); + + } + return $nodes; + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return bool + */ + function delete() { + + // Deleting all children + foreach ($this->getChildren() as $child) $child->delete(); + + // Removing resource info, if its still around + if (file_exists($this->path . '/.sabredav')) unlink($this->path . '/.sabredav'); + + // Removing the directory itself + rmdir($this->path); + + return true; + + } + + /** + * Returns available diskspace information + * + * @return array + */ + function getQuotaInfo() { + + $total = disk_total_space(realpath($this->path)); + $free = disk_free_space(realpath($this->path)); + + return [ + $total - $free, + $free + ]; + } + + /** + * Moves a node into this collection. + * + * It is up to the implementors to: + * 1. Create the new resource. + * 2. Remove the old resource. + * 3. Transfer any properties or other data. + * + * Generally you should make very sure that your collection can easily move + * the move. + * + * If you don't, just return false, which will trigger sabre/dav to handle + * the move itself. If you return true from this function, the assumption + * is that the move was successful. + * + * @param string $targetName New local file/collection name. + * @param string $sourcePath Full path to source node + * @param DAV\INode $sourceNode Source node itself + * @return bool + */ + function moveInto($targetName, $sourcePath, DAV\INode $sourceNode) { + + // We only support FSExt\Directory or FSExt\File objects, so + // anything else we want to quickly reject. + if (!$sourceNode instanceof self && !$sourceNode instanceof File) { + return false; + } + + // PHP allows us to access protected properties from other objects, as + // long as they are defined in a class that has a shared inheritence + // with the current class. + rename($sourceNode->path, $this->path . '/' . $targetName); + + return true; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/FSExt/File.php b/vendor/sabre/dav/lib/DAV/FSExt/File.php new file mode 100644 index 000000000..eb5ae19fe --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/FSExt/File.php @@ -0,0 +1,152 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; +use Sabre\DAV\FS\Node; + +/** + * File class + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends Node implements DAV\PartialUpdate\IPatchSupport { + + /** + * Updates the data + * + * Data is a readable stream resource. + * + * @param resource|string $data + * @return string + */ + function put($data) { + + file_put_contents($this->path, $data); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Updates the file based on a range specification. + * + * The first argument is the data, which is either a readable stream + * resource or a string. + * + * The second argument is the type of update we're doing. + * This is either: + * * 1. append + * * 2. update based on a start byte + * * 3. update based on an end byte + *; + * The third argument is the start or end byte. + * + * 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. + * + * @param resource|string $data + * @param int $rangeType + * @param int $offset + * @return string|null + */ + function patch($data, $rangeType, $offset = null) { + + switch ($rangeType) { + case 1 : + $f = fopen($this->path, 'a'); + break; + case 2 : + $f = fopen($this->path, 'c'); + fseek($f, $offset); + break; + case 3 : + $f = fopen($this->path, 'c'); + fseek($f, $offset, SEEK_END); + break; + } + if (is_string($data)) { + fwrite($f, $data); + } else { + stream_copy_to_stream($data, $f); + } + fclose($f); + clearstatcache(true, $this->path); + return $this->getETag(); + + } + + /** + * Returns the data + * + * @return resource + */ + function get() { + + return fopen($this->path, 'r'); + + } + + /** + * Delete the current file + * + * @return bool + */ + function delete() { + + return unlink($this->path); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string|null + */ + function getETag() { + + return '"' . sha1( + fileinode($this->path) . + filesize($this->path) . + filemtime($this->path) + ) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType() { + + return null; + + } + + /** + * Returns the size of the file, in bytes + * + * @return int + */ + function getSize() { + + return filesize($this->path); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/File.php b/vendor/sabre/dav/lib/DAV/File.php new file mode 100644 index 000000000..e0a0391db --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/File.php @@ -0,0 +1,84 @@ +<?php + +namespace Sabre\DAV; + +/** + * File class + * + * This is a helper class, that should aid in getting file classes setup. + * Most of its methods are implemented, and throw permission denied exceptions + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class File extends Node implements IFile { + + /** + * Updates the data + * + * data is a readable stream resource. + * + * @param resource $data + * @return void + */ + function put($data) { + + throw new Exception\Forbidden('Permission denied to change data'); + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + throw new Exception\Forbidden('Permission denied to read this file'); + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + function getSize() { + + return 0; + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * + * @return string|null + */ + function getETag() { + + return null; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType() { + + return null; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/ICollection.php b/vendor/sabre/dav/lib/DAV/ICollection.php new file mode 100644 index 000000000..390d9b741 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/ICollection.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\DAV; + +/** + * The ICollection Interface + * + * This interface should be implemented by each class that represents a collection + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface ICollection extends INode { + + /** + * 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 successful 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 + * @return null|string + */ + function createFile($name, $data = null); + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name); + + /** + * Returns a specific child node, referenced by its name + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @return DAV\INode + */ + function getChild($name); + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + function getChildren(); + + /** + * Checks if a child-node with the specified name exists + * + * @param string $name + * @return bool + */ + function childExists($name); + +} diff --git a/vendor/sabre/dav/lib/DAV/IExtendedCollection.php b/vendor/sabre/dav/lib/DAV/IExtendedCollection.php new file mode 100644 index 000000000..c561d7072 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IExtendedCollection.php @@ -0,0 +1,43 @@ +<?php + +namespace Sabre\DAV; + +/** + * The IExtendedCollection interface. + * + * This interface can be used to create special-type of collection-resources + * as defined by RFC 5689. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IExtendedCollection extends ICollection { + + /** + * Creates a new collection. + * + * This method will receive a MkCol object with all the information about + * the new collection that's being created. + * + * The MkCol object contains information about the resourceType of the new + * collection. If you don't support the specified resourceType, you should + * throw Exception\InvalidResourceType. + * + * The object also contains a list of WebDAV properties for the new + * collection. + * + * You should call the handle() method on this object to specify exactly + * which properties you are storing. This allows the system to figure out + * exactly which properties you didn't store, which in turn allows other + * plugins (such as the propertystorage plugin) to handle storing the + * property for you. + * + * @param string $name + * @param MkCol $mkCol + * @throws Exception\InvalidResourceType + * @return void + */ + function createExtendedCollection($name, MkCol $mkCol); + +} diff --git a/vendor/sabre/dav/lib/DAV/IFile.php b/vendor/sabre/dav/lib/DAV/IFile.php new file mode 100644 index 000000000..e16a3a58a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IFile.php @@ -0,0 +1,81 @@ +<?php + +namespace Sabre\DAV; + +/** + * This interface represents a file in the directory tree + * + * A file is a bit of a broad definition. In general it implies that on + * this specific node a PUT or GET method may be performed, to either update, + * or retrieve the contents of the file. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IFile extends INode { + + /** + * Replaces the contents of the file. + * + * The data argument is a readable stream resource. + * + * 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 resource $data + * @return string|null + */ + function put($data); + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get(); + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + function getContentType(); + + /** + * 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. + * + * Return null if the ETag can not effectively be determined. + * + * The ETag must be surrounded by double-quotes, so something like this + * would make a valid ETag: + * + * return '"someetag"'; + * + * @return string|null + */ + function getETag(); + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize(); + +} diff --git a/vendor/sabre/dav/lib/DAV/IMoveTarget.php b/vendor/sabre/dav/lib/DAV/IMoveTarget.php new file mode 100644 index 000000000..f0f67bc26 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IMoveTarget.php @@ -0,0 +1,44 @@ +<?php + +namespace Sabre\DAV; + +/** + * By implementing this interface, a collection can effectively say "other + * nodes may be moved into this collection". + * + * The benefit of this, is that sabre/dav will by default perform a move, by + * tranfersing an entire directory tree, copying every collection, and deleting + * every item. + * + * If a backend supports a better optimized move operation, this can trigger + * some huge speed gains. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IMoveTarget extends ICollection { + + /** + * Moves a node into this collection. + * + * It is up to the implementors to: + * 1. Create the new resource. + * 2. Remove the old resource. + * 3. Transfer any properties or other data. + * + * Generally you should make very sure that your collection can easily move + * the move. + * + * If you don't, just return false, which will trigger sabre/dav to handle + * the move itself. If you return true from this function, the assumption + * is that the move was successful. + * + * @param string $targetName New local file/collection name. + * @param string $sourcePath Full path to source node + * @param INode $sourceNode Source node itself + * @return bool + */ + function moveInto($targetName, $sourcePath, INode $sourceNode); + +} diff --git a/vendor/sabre/dav/lib/DAV/IMultiGet.php b/vendor/sabre/dav/lib/DAV/IMultiGet.php new file mode 100644 index 000000000..e26b457ef --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IMultiGet.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\DAV; + +/** + * IMultiGet + * + * This interface adds a tiny bit of functionality to collections. + * + * There a certain situations, in particular in relation to WebDAV-Sync, CalDAV + * and CardDAV, where information for a list of items will be requested. + * + * Because the getChild() call is the main abstraction method, this can in + * reality result in many database calls, which could potentially be + * optimized. + * + * The MultiGet interface is used by the server in these cases. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IMultiGet extends ICollection { + + /** + * This method receives a list of paths in it's first argument. + * It must return an array with Node objects. + * + * If any children are not found, you do not have to return them. + * + * @param string[] $paths + * @return array + */ + function getMultipleChildren(array $paths); + +} diff --git a/vendor/sabre/dav/lib/DAV/INode.php b/vendor/sabre/dav/lib/DAV/INode.php new file mode 100644 index 000000000..b5e6cb9ef --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/INode.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\DAV; + +/** + * The INode interface is the base interface, and the parent class of both ICollection and IFile + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface INode { + + /** + * Deleted the current node + * + * @return void + */ + function delete(); + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName(); + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + function setName($name); + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + function getLastModified(); + +} diff --git a/vendor/sabre/dav/lib/DAV/IProperties.php b/vendor/sabre/dav/lib/DAV/IProperties.php new file mode 100644 index 000000000..00969c2c4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IProperties.php @@ -0,0 +1,47 @@ +<?php + +namespace Sabre\DAV; + +/** + * IProperties interface + * + * Implement this interface to support custom WebDAV properties requested and sent from clients. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IProperties extends INode { + + /** + * Updates properties on this node. + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * To update specific properties, call the 'handle' method on this object. + * Read the PropPatch documentation for more information. + * + * @param PropPatch $propPatch + * @return void + */ + function propPatch(PropPatch $propPatch); + + /** + * Returns a list of properties for this nodes. + * + * The properties list is a list of propertynames the client requested, + * encoded in clark-notation {xmlnamespace}tagname + * + * If the array is empty, it means 'all properties' were requested. + * + * Note that it's fine to liberally give properties back, instead of + * conforming to the list of requested properties. + * The Server class will filter out the extra. + * + * @param array $properties + * @return array + */ + function getProperties($properties); + +} diff --git a/vendor/sabre/dav/lib/DAV/IQuota.php b/vendor/sabre/dav/lib/DAV/IQuota.php new file mode 100644 index 000000000..e16f386b9 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/IQuota.php @@ -0,0 +1,26 @@ +<?php + +namespace Sabre\DAV; + +/** + * IQuota interface + * + * Implement this interface to add the ability to return quota information. The ObjectTree + * will check for quota information on any given node. If the information is not available it will + * attempt to fetch the information from the root node. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IQuota extends ICollection { + + /** + * Returns the quota information + * + * This method MUST return an array with 2 values, the first being the total used space, + * the second the available space (in bytes) + */ + function getQuotaInfo(); + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php new file mode 100644 index 000000000..044316cdb --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php @@ -0,0 +1,18 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +/** + * This is an Abstract clas for lock backends. + * + * Currently this backend has no function, but it exists for consistency, and + * to ensure that if default code is required in the backend, there will be a + * non-bc-breaking way to do so. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractBackend implements BackendInterface { + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php new file mode 100644 index 000000000..a2d2fe89c --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php @@ -0,0 +1,50 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks; + +/** + * If you are defining your own Locks backend, you must implement this + * interface. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface BackendInterface { + + /** + * 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 + */ + function getLocks($uri, $returnChildLocks); + + /** + * Locks a uri + * + * @param string $uri + * @param Locks\LockInfo $lockInfo + * @return bool + */ + function lock($uri, Locks\LockInfo $lockInfo); + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param Locks\LockInfo $lockInfo + * @return bool + */ + function unlock($uri, Locks\LockInfo $lockInfo); + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php new file mode 100644 index 000000000..849539bee --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php @@ -0,0 +1,185 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks\LockInfo; + +/** + * This Locks backend stores all locking information in a single file. + * + * Note that this is not nearly as robust as a database. If you are considering + * using this backend, keep in mind that the PDO backend can work with SqLite, + * which is designed to be a good file-based database. + * + * It literally solves the problem this class solves as well, but much better. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends AbstractBackend { + + /** + * The storage file + * + * @var string + */ + private $locksFile; + + /** + * Constructor + * + * @param string $locksFile path to file + */ + function __construct($locksFile) { + + $this->locksFile = $locksFile; + + } + + /** + * 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 + */ + function getLocks($uri, $returnChildLocks) { + + $newLocks = []; + + $locks = $this->getData(); + + foreach ($locks as $lock) { + + if ($lock->uri === $uri || + //deep locks on parents + ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) || + + // locks on children + ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) { + + $newLocks[] = $lock; + + } + + } + + // Checking if we can remove any of these locks + foreach ($newLocks as $k => $lock) { + if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]); + } + return $newLocks; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 1800; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getData(); + + foreach ($locks as $k => $lock) { + if ( + ($lock->token == $lockInfo->token) || + (time() > $lock->timeout + $lock->created) + ) { + unset($locks[$k]); + } + } + $locks[] = $lockInfo; + $this->putData($locks); + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $locks = $this->getData(); + foreach ($locks as $k => $lock) { + + if ($lock->token == $lockInfo->token) { + + unset($locks[$k]); + $this->putData($locks); + return true; + + } + } + return false; + + } + + /** + * Loads the lockdata from the filesystem. + * + * @return array + */ + protected function getData() { + + if (!file_exists($this->locksFile)) return []; + + // opening up the file, and creating a shared lock + $handle = fopen($this->locksFile, 'r'); + flock($handle, LOCK_SH); + + // Reading data until the eof + $data = stream_get_contents($handle); + + // We're all good + flock($handle, LOCK_UN); + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!$data) return []; + return $data; + + } + + /** + * Saves the lockdata + * + * @param array $newData + * @return void + */ + protected function putData(array $newData) { + + // opening up the file, and creating an exclusive lock + $handle = fopen($this->locksFile, 'a+'); + flock($handle, LOCK_EX); + + // We can only truncate and rewind once the lock is acquired. + ftruncate($handle, 0); + rewind($handle); + + fwrite($handle, serialize($newData)); + flock($handle, LOCK_UN); + fclose($handle); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php new file mode 100644 index 000000000..a01d9bae4 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php @@ -0,0 +1,180 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks\LockInfo; + +/** + * The Lock manager allows you to handle all file-locks centrally. + * + * This Lock Manager stores all its data in a database. You must pass a PDO + * connection object in the constructor. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PDO extends AbstractBackend { + + /** + * The PDO tablename this backend uses. + * + * @var string + */ + public $tableName = 'locks'; + + /** + * The PDO connection object + * + * @var pdo + */ + protected $pdo; + + /** + * Constructor + * + * @param PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * 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 + */ + function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatenation prevents us + // from doing this though. + $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM ' . $this->tableName . ' WHERE (created > (? - timeout)) AND ((uri = ?)'; + $params = [time(),$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; + + $query .= ' OR (depth!=0 AND uri = ?)'; + $params[] = $currentPath; + + } + + if ($returnChildLocks) { + + $query .= ' OR (uri LIKE ?)'; + $params[] = $uri . '/%'; + + } + $query .= ')'; + + $stmt = $this->pdo->prepare($query); + $stmt->execute($params); + $result = $stmt->fetchAll(); + + $lockList = []; + foreach ($result as $row) { + + $lockInfo = new 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 LockInfo $lockInfo + * @return bool + */ + function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 30 * 60; + $lockInfo->created = time(); + $lockInfo->uri = $uri; + + $locks = $this->getLocks($uri, false); + $exists = false; + foreach ($locks as $lock) { + if ($lock->token == $lockInfo->token) $exists = true; + } + + if ($exists) { + $stmt = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET owner = ?, timeout = ?, scope = ?, depth = ?, uri = ?, created = ? WHERE token = ?'); + $stmt->execute([ + $lockInfo->owner, + $lockInfo->timeout, + $lockInfo->scope, + $lockInfo->depth, + $uri, + $lockInfo->created, + $lockInfo->token + ]); + } else { + $stmt = $this->pdo->prepare('INSERT INTO ' . $this->tableName . ' (owner,timeout,scope,depth,uri,created,token) VALUES (?,?,?,?,?,?,?)'); + $stmt->execute([ + $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 LockInfo $lockInfo + * @return bool + */ + function unlock($uri, LockInfo $lockInfo) { + + $stmt = $this->pdo->prepare('DELETE FROM ' . $this->tableName . ' WHERE uri = ? AND token = ?'); + $stmt->execute([$uri, $lockInfo->token]); + + return $stmt->rowCount() === 1; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php new file mode 100644 index 000000000..2c8cca0fe --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php @@ -0,0 +1,80 @@ +<?php + +namespace Sabre\DAV\Locks; + +/** + * LockInfo class + * + * An object of the LockInfo class holds all the information relevant to a + * single lock. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LockInfo { + + /** + * A shared lock + */ + const SHARED = 1; + + /** + * An exclusive lock + */ + const EXCLUSIVE = 2; + + /** + * A never expiring timeout + */ + const TIMEOUT_INFINITE = -1; + + /** + * The owner of the lock + * + * @var string + */ + public $owner; + + /** + * The locktoken + * + * @var string + */ + public $token; + + /** + * How long till the lock is expiring + * + * @var int + */ + public $timeout; + + /** + * UNIX Timestamp of when this lock was created + * + * @var int + */ + public $created; + + /** + * Exclusive or shared lock + * + * @var int + */ + public $scope = self::EXCLUSIVE; + + /** + * Depth of lock, can be 0 or Sabre\DAV\Server::DEPTH_INFINITY + */ + public $depth = 0; + + /** + * The uri this lock locks + * + * TODO: This value is not always set + * @var mixed + */ + public $uri; + +} diff --git a/vendor/sabre/dav/lib/DAV/Locks/Plugin.php b/vendor/sabre/dav/lib/DAV/Locks/Plugin.php new file mode 100644 index 000000000..4855b7076 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Locks/Plugin.php @@ -0,0 +1,589 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * Locking plugin + * + * This plugin provides locking support to a WebDAV server. + * The easiest way to get started, is by hooking it up as such: + * + * $lockBackend = new Sabre\DAV\Locks\Backend\File('./mylockdb'); + * $lockPlugin = new Sabre\DAV\Locks\Plugin($lockBackend); + * $server->addPlugin($lockPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * locksBackend + * + * @var Backend\Backend\Interface + */ + protected $locksBackend; + + /** + * server + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * __construct + * + * @param Backend\BackendInterface $locksBackend + */ + function __construct(Backend\BackendInterface $locksBackend) { + + $this->locksBackend = $locksBackend; + + } + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + + $this->server->xml->elementMap['{DAV:}lockinfo'] = 'Sabre\\DAV\\Xml\\Request\\Lock'; + + $server->on('method:LOCK', [$this, 'httpLock']); + $server->on('method:UNLOCK', [$this, 'httpUnlock']); + $server->on('validateTokens', [$this, 'validateTokens']); + $server->on('propFind', [$this, 'propFind']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * 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 'locks'; + + } + + /** + * This method is called after most properties have been found + * it allows us to add in any Lock-related properties + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}supportedlock', function() { + return new DAV\Xml\Property\SupportedLock(); + }); + $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { + return new DAV\Xml\Property\LockDiscovery( + $this->getLocks($propFind->getPath()) + ); + }); + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + return ['LOCK','UNLOCK']; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * In this case this is only the number 2. The 2 in the Dav: header + * indicates the server supports locks. + * + * @return array + */ + function getFeatures() { + + return [2]; + + } + + /** + * Returns all lock information on a particular uri + * + * This function should return an array with Sabre\DAV\Locks\LockInfo objects. If there are no locks on a file, return an empty array. + * + * Additionally there is also the possibility of locks on parent nodes, so we'll need to traverse every part of the tree + * If the $returnChildLocks argument is set to true, we'll also traverse all the children of the object + * for any possible locks and return those as well. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + function getLocks($uri, $returnChildLocks = false) { + + return $this->locksBackend->getLocks($uri, $returnChildLocks); + + } + + /** + * Locks an uri + * + * The WebDAV lock request can be operated to either create a new lock on a file, or to refresh an existing lock + * If a new lock is created, a full XML body should be supplied, containing information about the lock such as the type + * of lock (shared or exclusive) and the owner of the lock + * + * If a lock is to be refreshed, no body should be supplied and there should be a valid If header containing the lock + * + * Additionally, a lock can be requested for a non-existent file. In these case we're obligated to create an empty file as per RFC4918:S7.3 + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpLock(RequestInterface $request, ResponseInterface $response) { + + $uri = $request->getPath(); + + $existingLocks = $this->getLocks($uri); + + if ($body = $request->getBodyAsString()) { + // This is a new lock request + + $existingLock = null; + // Checking if there's already non-shared locks on the uri. + foreach ($existingLocks as $existingLock) { + if ($existingLock->scope === LockInfo::EXCLUSIVE) { + throw new DAV\Exception\ConflictingLock($existingLock); + } + } + + $lockInfo = $this->parseLockRequest($body); + $lockInfo->depth = $this->server->getHTTPDepth(); + $lockInfo->uri = $uri; + if ($existingLock && $lockInfo->scope != LockInfo::SHARED) + throw new DAV\Exception\ConflictingLock($existingLock); + + } else { + + // Gonna check if this was a lock refresh. + $existingLocks = $this->getLocks($uri); + $conditions = $this->server->getIfConditions($request); + $found = null; + + foreach ($existingLocks as $existingLock) { + foreach ($conditions as $condition) { + foreach ($condition['tokens'] as $token) { + if ($token['token'] === 'opaquelocktoken:' . $existingLock->token) { + $found = $existingLock; + break 3; + } + } + } + } + + // If none were found, this request is in error. + if (is_null($found)) { + if ($existingLocks) { + throw new DAV\Exception\Locked(reset($existingLocks)); + } else { + throw new DAV\Exception\BadRequest('An xml body is required for lock requests'); + } + + } + + // This must have been a lock refresh + $lockInfo = $found; + + // The resource could have been locked through another uri. + if ($uri != $lockInfo->uri) $uri = $lockInfo->uri; + + } + + if ($timeout = $this->getTimeoutHeader()) $lockInfo->timeout = $timeout; + + $newFile = false; + + // If we got this far.. we should go check if this node actually exists. If this is not the case, we need to create it first + try { + $this->server->tree->getNodeForPath($uri); + + // We need to call the beforeWriteContent event for RFC3744 + // Edit: looks like this is not used, and causing problems now. + // + // See Issue 222 + // $this->server->emit('beforeWriteContent',array($uri)); + + } catch (DAV\Exception\NotFound $e) { + + // It didn't, lets create it + $this->server->createFile($uri, fopen('php://memory', 'r')); + $newFile = true; + + } + + $this->lockNode($uri, $lockInfo); + + $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $response->setHeader('Lock-Token', '<opaquelocktoken:' . $lockInfo->token . '>'); + $response->setStatus($newFile ? 201 : 200); + $response->setBody($this->generateLockResponse($lockInfo)); + + // Returning false will interupt the event chain and mark this method + // as 'handled'. + return false; + + } + + /** + * Unlocks a uri + * + * This WebDAV method allows you to remove a lock from a node. The client should provide a valid locktoken through the Lock-token http header + * The server should return 204 (No content) on success + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpUnlock(RequestInterface $request, ResponseInterface $response) { + + $lockToken = $request->getHeader('Lock-Token'); + + // If the locktoken header is not supplied, we need to throw a bad request exception + if (!$lockToken) throw new DAV\Exception\BadRequest('No lock token was supplied'); + + $path = $request->getPath(); + $locks = $this->getLocks($path); + + // Windows sometimes forgets to include < and > in the Lock-Token + // header + if ($lockToken[0] !== '<') $lockToken = '<' . $lockToken . '>'; + + foreach ($locks as $lock) { + + if ('<opaquelocktoken:' . $lock->token . '>' == $lockToken) { + + $this->unlockNode($path, $lock); + $response->setHeader('Content-Length', '0'); + $response->setStatus(204); + + // Returning false will break the method chain, and mark the + // method as 'handled'. + return false; + + } + + } + + // If we got here, it means the locktoken was invalid + throw new DAV\Exception\LockTokenMatchesRequestUri(); + + } + + /** + * This method is called after a node is deleted. + * + * We use this event to clean up any locks that still exist on the node. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $locks = $this->getLocks($path, $includeChildren = true); + foreach ($locks as $lock) { + $this->unlockNode($path, $lock); + } + + } + + /** + * Locks a uri + * + * All the locking information is supplied in the lockInfo object. The object has a suggested timeout, but this can be safely ignored + * It is important that if the existing timeout is ignored, the property is overwritten, as this needs to be sent back to the client + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function lockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeLock', [$uri, $lockInfo])) return; + return $this->locksBackend->lock($uri, $lockInfo); + + } + + /** + * Unlocks a uri + * + * This method removes a lock from a uri. It is assumed all the supplied information is correct and verified + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + function unlockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->emit('beforeUnlock', [$uri, $lockInfo])) return; + return $this->locksBackend->unlock($uri, $lockInfo); + + } + + + /** + * Returns the contents of the HTTP Timeout header. + * + * The method formats the header into an integer. + * + * @return int + */ + function getTimeoutHeader() { + + $header = $this->server->httpRequest->getHeader('Timeout'); + + if ($header) { + + if (stripos($header, 'second-') === 0) $header = (int)(substr($header, 7)); + elseif (stripos($header, 'infinite') === 0) $header = LockInfo::TIMEOUT_INFINITE; + else throw new DAV\Exception\BadRequest('Invalid HTTP timeout header'); + + } else { + + $header = 0; + + } + + return $header; + + } + + /** + * Generates the response for successful LOCK requests + * + * @param LockInfo $lockInfo + * @return string + */ + protected function generateLockResponse(LockInfo $lockInfo) { + + return $this->server->xml->write('{DAV:}prop', [ + '{DAV:}lockdiscovery' => + new DAV\Xml\Property\LockDiscovery([$lockInfo]) + ]); + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * In addition, it will also ensure that it checks any missing lokens that + * must be present in the request, and reject requests without the proper + * tokens. + * + * @param RequestInterface $request + * @param mixed $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + // First we need to gather a list of locks that must be satisfied. + $mustLocks = []; + $method = $request->getMethod(); + + // Methods not in that list are operations that doesn't alter any + // resources, and we don't need to check the lock-states for. + switch ($method) { + + case 'DELETE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + break; + case 'MKCOL' : + case 'MKCALENDAR' : + case 'PROPPATCH' : + case 'PUT' : + case 'PATCH' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + false + )); + break; + case 'MOVE' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $request->getPath(), + true + )); + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'COPY' : + $mustLocks = array_merge($mustLocks, $this->getLocks( + $this->server->calculateUri($request->getHeader('Destination')), + false + )); + break; + case 'LOCK' : + //Temporary measure.. figure out later why this is needed + // Here we basically ignore all incoming tokens... + foreach ($conditions as $ii => $condition) { + foreach ($condition['tokens'] as $jj => $token) { + $conditions[$ii]['tokens'][$jj]['validToken'] = true; + } + } + return; + + } + + // It's possible that there's identical locks, because of shared + // parents. We're removing the duplicates here. + $tmp = []; + foreach ($mustLocks as $lock) $tmp[$lock->token] = $lock; + $mustLocks = array_values($tmp); + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Lock tokens always start with opaquelocktoken: + if (substr($token['token'], 0, 16) !== 'opaquelocktoken:') { + continue; + } + + $checkToken = substr($token['token'], 16); + // Looping through our list with locks. + foreach ($mustLocks as $jj => $mustLock) { + + if ($mustLock->token == $checkToken) { + + // We have a match! + // Removing this one from mustlocks + unset($mustLocks[$jj]); + + // Marking the condition as valid. + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + + // Advancing to the next token + continue 2; + + } + + } + + // If we got here, it means that there was a + // lock-token, but it was not in 'mustLocks'. + // + // This is an edge-case, as it could mean that token + // was specified with a url that was not 'required' to + // check. So we're doing one extra lookup to make sure + // we really don't know this token. + // + // This also gets triggered when the user specified a + // lock-token that was expired. + $oddLocks = $this->getLocks($condition['uri']); + foreach ($oddLocks as $oddLock) { + + if ($oddLock->token === $checkToken) { + + // We have a hit! + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + continue 2; + + } + } + + // If we get all the way here, the lock-token was + // really unknown. + + + } + + } + + // If there's any locks left in the 'mustLocks' array, it means that + // the resource was locked and we must block it. + if ($mustLocks) { + + throw new DAV\Exception\Locked(reset($mustLocks)); + + } + + } + + /** + * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object + * + * @param string $body + * @return LockInfo + */ + protected function parseLockRequest($body) { + + $result = $this->server->xml->expect( + '{DAV:}lockinfo', + $body + ); + + $lockInfo = new LockInfo(); + + $lockInfo->owner = $result->owner; + $lockInfo->token = DAV\UUIDUtil::getUUID(); + $lockInfo->scope = $result->scope; + + return $lockInfo; + + } + + /** + * 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' => 'The locks plugin turns this server into a class-2 WebDAV server and adds support for LOCK and UNLOCK', + 'link' => 'http://sabre.io/dav/locks/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/MkCol.php b/vendor/sabre/dav/lib/DAV/MkCol.php new file mode 100644 index 000000000..c79055418 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/MkCol.php @@ -0,0 +1,71 @@ +<?php + +namespace Sabre\DAV; + +/** + * This class represents a MKCOL operation. + * + * MKCOL creates a new collection. MKCOL comes in two flavours: + * + * 1. MKCOL with no body, signifies the creation of a simple collection. + * 2. MKCOL with a request body. This can create a collection with a specific + * resource type, and a set of properties that should be set on the new + * collection. This can be used to create caldav calendars, carddav address + * books, etc. + * + * Property updates must always be atomic. This means that a property update + * must either completely succeed, or completely fail. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MkCol extends PropPatch { + + /** + * A list of resource-types in clark-notation. + * + * @var array + */ + protected $resourceType; + + /** + * Creates the MKCOL object. + * + * @param string[] $resourceType List of resourcetype values. + * @param array $mutations List of new properties values. + */ + function __construct(array $resourceType, array $mutations) { + + $this->resourceType = $resourceType; + parent::__construct($mutations); + + } + + /** + * Returns the resourcetype of the new collection. + * + * @return string[] + */ + function getResourceType() { + + return $this->resourceType; + + } + + /** + * Returns true or false if the MKCOL operation has at least the specified + * resource type. + * + * If the resourcetype is specified as an array, all resourcetypes are + * checked. + * + * @param string|string[] $resourceType + */ + function hasResourceType($resourceType) { + + return count(array_diff((array)$resourceType, $this->resourceType)) === 0; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Mount/Plugin.php b/vendor/sabre/dav/lib/DAV/Mount/Plugin.php new file mode 100644 index 000000000..8e06acb9f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Mount/Plugin.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\DAV\Mount; + +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * This plugin provides support for RFC4709: Mounting WebDAV servers + * + * Simply append ?mount to any collection to generate the davmount response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * Reference to Server class + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * Initializes the plugin and registers event handles + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->on('method:GET', [$this, 'httpGet'], 90); + + } + + /** + * 'beforeMethod' event handles. This event handles intercepts GET requests ending + * with ?mount + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $response) { + + $queryParams = $request->getQueryParameters(); + if (!array_key_exists('mount', $queryParams)) return; + + $currentUri = $request->getAbsoluteUrl(); + + // Stripping off everything after the ? + list($currentUri) = explode('?', $currentUri); + + $this->davMount($response, $currentUri); + + // Returning false to break the event chain + return false; + + } + + /** + * Generates the davmount response + * + * @param ResponseInterface $response + * @param string $uri absolute uri + * @return void + */ + function davMount(ResponseInterface $response, $uri) { + + $response->setStatus(200); + $response->setHeader('Content-Type', 'application/davmount+xml'); + ob_start(); + echo '<?xml version="1.0"?>', "\n"; + echo "<dm:mount xmlns:dm=\"http://purl.org/NET/webdav/mount\">\n"; + echo " <dm:url>", htmlspecialchars($uri, ENT_NOQUOTES, 'UTF-8'), "</dm:url>\n"; + echo "</dm:mount>"; + $response->setBody(ob_get_clean()); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/Node.php b/vendor/sabre/dav/lib/DAV/Node.php new file mode 100644 index 000000000..ba270e8f9 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Node.php @@ -0,0 +1,54 @@ +<?php + +namespace Sabre\DAV; + +/** + * Node class + * + * This is a helper class, that should aid in getting nodes setup. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Node implements INode { + + /** + * Returns the last modification time as a unix timestamp. + * + * If the information is not available, return null. + * + * @return int + */ + function getLastModified() { + + return null; + + } + + /** + * Deletes the current node + * + * @throws Sabre\DAV\Exception\Forbidden + * @return void + */ + function delete() { + + throw new Exception\Forbidden('Permission denied to delete node'); + + } + + /** + * Renames the node + * + * @throws Sabre\DAV\Exception\Forbidden + * @param string $name The new name + * @return void + */ + function setName($name) { + + throw new Exception\Forbidden('Permission denied to rename file'); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php b/vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php new file mode 100644 index 000000000..97d24f9cb --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php @@ -0,0 +1,47 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; + +/** + * This interface provides a way to modify only part of a target resource + * It may be used to update a file chunk, upload big a file into smaller + * chunks or resume an upload + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface IPatchSupport extends DAV\IFile { + + /** + * Updates the file based on a range specification. + * + * The first argument is the data, which is either a readable stream + * resource or a string. + * + * The second argument is the type of update we're doing. + * This is either: + * * 1. append + * * 2. update based on a start byte + * * 3. update based on an end byte + *; + * The third argument is the start or end byte. + * + * 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. + * + * @param resource|string $data + * @param int $rangeType + * @param int $offset + * @return string|null + */ + function patch($data, $rangeType, $offset = null); + +} diff --git a/vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php b/vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php new file mode 100644 index 000000000..24ba970b1 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php @@ -0,0 +1,215 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * Partial update plugin (Patch method) + * + * This plugin provides a way to modify only part of a target resource + * It may bu used to update a file chunk, upload big a file into smaller + * chunks or resume an upload. + * + * $patchPlugin = new \Sabre\DAV\PartialUpdate\Plugin(); + * $server->addPlugin($patchPlugin); + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + const RANGE_APPEND = 1; + const RANGE_START = 2; + const RANGE_END = 3; + + /** + * Reference to server + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $server->on('method:PATCH', [$this, 'httpPatch']); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + + return 'partialupdate'; + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * We claim to support PATCH method (partirl update) if and only if + * - the node exist + * - the node implements our partial update interface + * + * @param string $uri + * @return array + */ + function getHTTPMethods($uri) { + + $tree = $this->server->tree; + + if ($tree->nodeExists($uri)) { + $node = $tree->getNodeForPath($uri); + if ($node instanceof IPatchSupport) { + return ['PATCH']; + } + } + return []; + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * @return array + */ + function getFeatures() { + + return ['sabredav-partialupdate']; + + } + + /** + * Patch an uri + * + * The WebDAV patch request can be used to modify only a part of an + * existing resource. If the resource does not exist yet and the first + * offset is not 0, the request fails + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return void + */ + function httpPatch(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Get the node. Will throw a 404 if not found + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof IPatchSupport) { + throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.'); + } + + $range = $this->getHTTPUpdateRange($request); + + if (!$range) { + throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers'); + } + + $contentType = strtolower( + $request->getHeader('Content-Type') + ); + + if ($contentType != 'application/x-sabredav-partialupdate') { + throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"'); + } + + $len = $this->server->httpRequest->getHeader('Content-Length'); + if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required'); + + switch ($range[0]) { + case self::RANGE_START : + // Calculate the end-range if it doesn't exist. + if (!$range[2]) { + $range[2] = $range[1] + $len - 1; + } else { + if ($range[2] < $range[1]) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')'); + } + if ($range[2] - $range[1] + 1 != $len) { + throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets'); + } + } + break; + } + + if (!$this->server->emit('beforeWriteContent', [$path, $node, null])) + return; + + $body = $this->server->httpRequest->getBody(); + + + $etag = $node->patch($body, $range[0], isset($range[1]) ? $range[1] : null); + + $this->server->emit('afterWriteContent', [$path, $node]); + + $response->setHeader('Content-Length', '0'); + if ($etag) $response->setHeader('ETag', $etag); + $response->setStatus(204); + + // Breaks the event chain + return false; + + } + + /** + * Returns the HTTP custom range update header + * + * This method returns null if there is no well-formed HTTP range request + * header. It returns array(1) if it was an append request, array(2, + * $start, $end) if it's a start and end range, lastly it's array(3, + * $endoffset) if the offset was negative, and should be calculated from + * the end of the file. + * + * Examples: + * + * null - invalid + * [1] - append + * [2,10,15] - update bytes 10, 11, 12, 13, 14, 15 + * [2,10,null] - update bytes 10 until the end of the patch body + * [3,-5] - update from 5 bytes from the end of the file. + * + * @param RequestInterface $request + * @return array|null + */ + function getHTTPUpdateRange(RequestInterface $request) { + + $range = $request->getHeader('X-Update-Range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i', $range, $matches)) return null; + + if ($matches[1] === 'append') { + return [self::RANGE_APPEND]; + } elseif (strlen($matches[2]) > 0) { + return [self::RANGE_START, $matches[2], $matches[3] ?: null]; + } else { + return [self::RANGE_END, $matches[4]]; + } + + } +} diff --git a/vendor/sabre/dav/lib/DAV/PropFind.php b/vendor/sabre/dav/lib/DAV/PropFind.php new file mode 100644 index 000000000..8ae6b6cfd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropFind.php @@ -0,0 +1,347 @@ +<?php + +namespace Sabre\DAV; + +/** + * This class holds all the information about a PROPFIND request. + * + * It contains the type of PROPFIND request, which properties were requested + * and also the returned items. + */ +class PropFind { + + /** + * A normal propfind + */ + const NORMAL = 0; + + /** + * An allprops request. + * + * While this was originally intended for instructing the server to really + * fetch every property, because it was used so often and it's so heavy + * this turned into a small list of default properties after a while. + * + * So 'all properties' now means a hardcoded list. + */ + const ALLPROPS = 1; + + /** + * A propname request. This just returns a list of properties that are + * defined on a node, without their values. + */ + const PROPNAME = 2; + + /** + * Creates the PROPFIND object + * + * @param string $path + * @param array $properties + * @param int $depth + * @param int $requestType + */ + function __construct($path, array $properties, $depth = 0, $requestType = self::NORMAL) { + + $this->path = $path; + $this->properties = $properties; + $this->depth = $depth; + $this->requestType = $requestType; + + if ($requestType === self::ALLPROPS) { + $this->properties = [ + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}resourcetype', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}getetag', + '{DAV:}getcontenttype', + ]; + } + + foreach ($this->properties as $propertyName) { + + // Seeding properties with 404's. + $this->result[$propertyName] = [404, null]; + + } + $this->itemsLeft = count($this->result); + + } + + /** + * Handles a specific property. + * + * This method checks wether the specified property was requested in this + * PROPFIND request, and if so, it will call the callback and use the + * return value for it's value. + * + * Example: + * + * $propFind->handle('{DAV:}displayname', function() { + * return 'hello'; + * }); + * + * Note that handle will only work the first time. If null is returned, the + * value is ignored. + * + * It's also possible to not pass a callback, but immediately pass a value + * + * @param string $propertyName + * @param mixed $valueOrCallBack + * @return void + */ + function handle($propertyName, $valueOrCallBack) { + + if ($this->itemsLeft && isset($this->result[$propertyName]) && $this->result[$propertyName][0] === 404) { + if (is_callable($valueOrCallBack)) { + $value = $valueOrCallBack(); + } else { + $value = $valueOrCallBack; + } + if (!is_null($value)) { + $this->itemsLeft--; + $this->result[$propertyName] = [200, $value]; + } + } + + } + + /** + * Sets the value of the property + * + * If status is not supplied, the status will default to 200 for non-null + * properties, and 404 for null properties. + * + * @param string $propertyName + * @param mixed $value + * @param int $status + * @return void + */ + function set($propertyName, $value, $status = null) { + + if (is_null($status)) { + $status = is_null($value) ? 404 : 200; + } + // If this is an ALLPROPS request and the property is + // unknown, add it to the result; else ignore it: + if (!isset($this->result[$propertyName])) { + if ($this->requestType === self::ALLPROPS) { + $this->result[$propertyName] = [$status, $value]; + } + return; + } + if ($status !== 404 && $this->result[$propertyName][0] === 404) { + $this->itemsLeft--; + } elseif ($status === 404 && $this->result[$propertyName][0] !== 404) { + $this->itemsLeft++; + } + $this->result[$propertyName] = [$status, $value]; + + } + + /** + * Returns the current value for a property. + * + * @param string $propertyName + * @return mixed + */ + function get($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][1] : null; + + } + + /** + * Returns the current status code for a property name. + * + * If the property does not appear in the list of requested properties, + * null will be returned. + * + * @param string $propertyName + * @return int|null + */ + function getStatus($propertyName) { + + return isset($this->result[$propertyName]) ? $this->result[$propertyName][0] : null; + + } + + /** + * Updates the path for this PROPFIND. + * + * @param string $path + * @return void + */ + function setPath($path) { + + $this->path = $path; + + } + + /** + * Returns the path this PROPFIND request is for. + * + * @return string + */ + function getPath() { + + return $this->path; + + } + + /** + * Returns the depth of this propfind request. + * + * @return int + */ + function getDepth() { + + return $this->depth; + + } + + /** + * Updates the depth of this propfind request. + * + * @param int $depth + * @return void + */ + function setDepth($depth) { + + $this->depth = $depth; + + } + + /** + * Returns all propertynames that have a 404 status, and thus don't have a + * value yet. + * + * @return array + */ + function get404Properties() { + + if ($this->itemsLeft === 0) { + return []; + } + $result = []; + foreach ($this->result as $propertyName => $stuff) { + if ($stuff[0] === 404) { + $result[] = $propertyName; + } + } + return $result; + + } + + /** + * Returns the full list of requested properties. + * + * This returns just their names, not a status or value. + * + * @return array + */ + function getRequestedProperties() { + + return $this->properties; + + } + + /** + * Returns true if this was an '{DAV:}allprops' request. + * + * @return bool + */ + function isAllProps() { + + return $this->requestType === self::ALLPROPS; + + } + + /** + * Returns a result array that's often used in multistatus responses. + * + * The array uses status codes as keys, and property names and value pairs + * as the value of the top array.. such as : + * + * [ + * 200 => [ '{DAV:}displayname' => 'foo' ], + * ] + * + * @return array + */ + function getResultForMultiStatus() { + + $r = [ + 200 => [], + 404 => [], + ]; + foreach ($this->result as $propertyName => $info) { + if (!isset($r[$info[0]])) { + $r[$info[0]] = [$propertyName => $info[1]]; + } else { + $r[$info[0]][$propertyName] = $info[1]; + } + } + // Removing the 404's for multi-status requests. + if ($this->requestType === self::ALLPROPS) unset($r[404]); + return $r; + + } + + /** + * The path that we're fetching properties for. + * + * @var string + */ + protected $path; + + /** + * The Depth of the request. + * + * 0 means only the current item. 1 means the current item + its children. + * It can also be DEPTH_INFINITY if this is enabled in the server. + * + * @var int + */ + protected $depth = 0; + + /** + * The type of request. See the TYPE constants + */ + protected $requestType; + + /** + * A list of requested properties + * + * @var array + */ + protected $properties = []; + + /** + * The result of the operation. + * + * The keys in this array are property names. + * The values are an array with two elements: the http status code and then + * optionally a value. + * + * Example: + * + * [ + * "{DAV:}owner" : [404], + * "{DAV:}displayname" : [200, "Admin"] + * ] + * + * @var array + */ + protected $result = []; + + /** + * This is used as an internal counter for the number of properties that do + * not yet have a value. + * + * @var int + */ + protected $itemsLeft; + +} diff --git a/vendor/sabre/dav/lib/DAV/PropPatch.php b/vendor/sabre/dav/lib/DAV/PropPatch.php new file mode 100644 index 000000000..6d599dacc --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropPatch.php @@ -0,0 +1,373 @@ +<?php + +namespace Sabre\DAV; + +use UnexpectedValueException; + +/** + * This class represents a set of properties that are going to be updated. + * + * Usually this is simply a PROPPATCH request, but it can also be used for + * internal updates. + * + * Property updates must always be atomic. This means that a property update + * must either completely succeed, or completely fail. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropPatch { + + /** + * Properties that are being updated. + * + * This is a key-value list. If the value is null, the property is supposed + * to be deleted. + * + * @var array + */ + protected $mutations; + + /** + * A list of properties and the result of the update. The result is in the + * form of a HTTP status code. + * + * @var array + */ + protected $result = []; + + /** + * This is the list of callbacks when we're performing the actual update. + * + * @var array + */ + protected $propertyUpdateCallbacks = []; + + /** + * This property will be set to true if the operation failed. + * + * @var bool + */ + protected $failed = false; + + /** + * Constructor + * + * @param array $mutations A list of updates + */ + function __construct(array $mutations) { + + $this->mutations = $mutations; + + } + + /** + * Call this function if you wish to handle updating certain properties. + * For instance, your class may be responsible for handling updates for the + * {DAV:}displayname property. + * + * In that case, call this method with the first argument + * "{DAV:}displayname" and a second argument that's a method that does the + * actual updating. + * + * It's possible to specify more than one property as an array. + * + * The callback must return a boolean or an it. If the result is true, the + * operation was considered successful. If it's false, it's consided + * failed. + * + * If the result is an integer, we'll use that integer as the http status + * code associated with the operation. + * + * @param string|string[] $properties + * @param callable $callback + * @return void + */ + function handle($properties, callable $callback) { + + $usedProperties = []; + foreach ((array)$properties as $propertyName) { + + if (array_key_exists($propertyName, $this->mutations) && !isset($this->result[$propertyName])) { + + $usedProperties[] = $propertyName; + // HTTP Accepted + $this->result[$propertyName] = 202; + } + + } + + // Only registering if there's any unhandled properties. + if (!$usedProperties) { + return; + } + $this->propertyUpdateCallbacks[] = [ + // If the original argument to this method was a string, we need + // to also make sure that it stays that way, so the commit function + // knows how to format the arguments to the callback. + is_string($properties) ? $properties : $usedProperties, + $callback + ]; + + } + + /** + * Call this function if you wish to handle _all_ properties that haven't + * been handled by anything else yet. Note that you effectively claim with + * this that you promise to process _all_ properties that are coming in. + * + * @param callable $callback + * @return void + */ + function handleRemaining(callable $callback) { + + $properties = $this->getRemainingMutations(); + if (!$properties) { + // Nothing to do, don't register callback + return; + } + + foreach ($properties as $propertyName) { + // HTTP Accepted + $this->result[$propertyName] = 202; + + $this->propertyUpdateCallbacks[] = [ + $properties, + $callback + ]; + } + + } + + /** + * Sets the result code for one or more properties. + * + * @param string|string[] $properties + * @param int $resultCode + * @return void + */ + function setResultCode($properties, $resultCode) { + + foreach ((array)$properties as $propertyName) { + $this->result[$propertyName] = $resultCode; + } + + if ($resultCode >= 400) { + $this->failed = true; + } + + } + + /** + * Sets the result code for all properties that did not have a result yet. + * + * @param int $resultCode + * @return void + */ + function setRemainingResultCode($resultCode) { + + $this->setResultCode( + $this->getRemainingMutations(), + $resultCode + ); + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns a list of property names, but not its values. + * + * @return string[] + */ + function getRemainingMutations() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[] = $propertyName; + } + } + + return $remaining; + + } + + /** + * Returns the list of properties that don't have a result code yet. + * + * This method returns list of properties and their values. + * + * @return array + */ + function getRemainingValues() { + + $remaining = []; + foreach ($this->mutations as $propertyName => $propValue) { + if (!isset($this->result[$propertyName])) { + $remaining[$propertyName] = $propValue; + } + } + + return $remaining; + + } + + /** + * Performs the actual update, and calls all callbacks. + * + * This method returns true or false depending on if the operation was + * successful. + * + * @return bool + */ + function commit() { + + // First we validate if every property has a handler + foreach ($this->mutations as $propertyName => $value) { + + if (!isset($this->result[$propertyName])) { + $this->failed = true; + $this->result[$propertyName] = 403; + } + + } + + foreach ($this->propertyUpdateCallbacks as $callbackInfo) { + + if ($this->failed) { + break; + } + if (is_string($callbackInfo[0])) { + $this->doCallbackSingleProp($callbackInfo[0], $callbackInfo[1]); + } else { + $this->doCallbackMultiProp($callbackInfo[0], $callbackInfo[1]); + } + + } + + /** + * If anywhere in this operation updating a property failed, we must + * update all other properties accordingly. + */ + if ($this->failed) { + + foreach ($this->result as $propertyName => $status) { + if ($status === 202) { + // Failed dependency + $this->result[$propertyName] = 424; + } + } + + } + + return !$this->failed; + + } + + /** + * Executes a property callback with the single-property syntax. + * + * @param string $propertyName + * @param callable $callback + * @return void + */ + private function doCallBackSingleProp($propertyName, callable $callback) { + + $result = $callback($this->mutations[$propertyName]); + if (is_bool($result)) { + if ($result) { + if (is_null($this->mutations[$propertyName])) { + // Delete + $result = 204; + } else { + // Update + $result = 200; + } + } else { + // Fail + $result = 403; + } + } + if (!is_int($result)) { + throw new UnexpectedValueException('A callback sent to handle() did not return an int or a bool'); + } + $this->result[$propertyName] = $result; + if ($result >= 400) { + $this->failed = true; + } + + } + + /** + * Executes a property callback with the multi-property syntax. + * + * @param array $propertyList + * @param callable $callback + * @return void + */ + private function doCallBackMultiProp(array $propertyList, callable $callback) { + + $argument = []; + foreach ($propertyList as $propertyName) { + $argument[$propertyName] = $this->mutations[$propertyName]; + } + + $result = $callback($argument); + + if (is_array($result)) { + foreach ($propertyList as $propertyName) { + if (!isset($result[$propertyName])) { + $resultCode = 500; + } else { + $resultCode = $result[$propertyName]; + } + if ($resultCode >= 400) { + $this->failed = true; + } + $this->result[$propertyName] = $resultCode; + + } + } elseif ($result === true) { + + // Success + foreach ($argument as $propertyName => $propertyValue) { + $this->result[$propertyName] = is_null($propertyValue) ? 204 : 200; + } + + } elseif ($result === false) { + // Fail :( + $this->failed = true; + foreach ($propertyList as $propertyName) { + $this->result[$propertyName] = 403; + } + } else { + throw new UnexpectedValueException('A callback sent to handle() did not return an array or a bool'); + } + + } + + /** + * Returns the result of the operation. + * + * @return array + */ + function getResult() { + + return $this->result; + + } + + /** + * Returns the full list of mutations + * + * @return array + */ + function getMutations() { + + return $this->mutations; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php new file mode 100644 index 000000000..31ecafdb2 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php @@ -0,0 +1,80 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; + +/** + * Propertystorage backend interface. + * + * Propertystorage backends must implement this interface to be used by the + * propertystorage plugin. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface BackendInterface { + + /** + * Fetches properties for a path. + * + * This method received a PropFind object, which contains all the + * information about the properties that need to be fetched. + * + * Ususually you would just want to call 'get404Properties' on this object, + * as this will give you the _exact_ list of properties that need to be + * fetched, and haven't yet. + * + * However, you can also support the 'allprops' property here. In that + * case, you should check for $propFind->isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind); + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch); + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path); + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination); + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php new file mode 100644 index 000000000..910e4979d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php @@ -0,0 +1,217 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Xml\Property\Complex; + +/** + * PropertyStorage PDO backend. + * + * This backend class uses a PDO-enabled database to store webdav properties. + * Both sqlite and mysql have been tested. + * + * The database structure can be found in the examples/sql/ directory. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PDO implements BackendInterface { + + /** + * Value is stored as string. + */ + const VT_STRING = 1; + + /** + * Value is stored as XML fragment. + */ + const VT_XML = 2; + + /** + * Value is stored as a property object. + */ + const VT_OBJECT = 3; + + /** + * PDO + * + * @var \PDO + */ + protected $pdo; + + /** + * PDO table name we'll be using + * + * @var string + */ + public $tableName = 'propertystorage'; + + /** + * Creates the PDO property storage engine + * + * @param \PDO $pdo + */ + function __construct(\PDO $pdo) { + + $this->pdo = $pdo; + + } + + /** + * Fetches properties for a path. + * + * This method received a PropFind object, which contains all the + * information about the properties that need to be fetched. + * + * Ususually you would just want to call 'get404Properties' on this object, + * as this will give you the _exact_ list of properties that need to be + * fetched, and haven't yet. + * + * However, you can also support the 'allprops' property here. In that + * case, you should check for $propFind->isAllProps(). + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + function propFind($path, PropFind $propFind) { + + if (!$propFind->isAllProps() && count($propFind->get404Properties()) === 0) { + return; + } + + $query = 'SELECT name, value, valuetype FROM ' . $this->tableName . ' WHERE path = ?'; + $stmt = $this->pdo->prepare($query); + $stmt->execute([$path]); + + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + switch ($row['valuetype']) { + case null : + case self::VT_STRING : + $propFind->set($row['name'], $row['value']); + break; + case self::VT_XML : + $propFind->set($row['name'], new Complex($row['value'])); + break; + case self::VT_OBJECT : + $propFind->set($row['name'], unserialize($row['value'])); + break; + } + } + + } + + /** + * Updates properties for a path + * + * This method received a PropPatch object, which contains all the + * information about the update. + * + * Usually you would want to call 'handleRemaining' on this object, to get; + * a list of all properties that need to be stored. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $propPatch->handleRemaining(function($properties) use ($path) { + + $updateStmt = $this->pdo->prepare("REPLACE INTO " . $this->tableName . " (path, name, valuetype, value) VALUES (?, ?, ?, ?)"); + $deleteStmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? AND name = ?"); + + foreach ($properties as $name => $value) { + + if (!is_null($value)) { + if (is_scalar($value)) { + $valueType = self::VT_STRING; + } elseif ($value instanceof Complex) { + $valueType = self::VT_XML; + $value = $value->getXml(); + } else { + $valueType = self::VT_OBJECT; + $value = serialize($value); + } + $updateStmt->execute([$path, $name, $valueType, $value]); + } else { + $deleteStmt->execute([$path, $name]); + } + + } + + return true; + + }); + + } + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + * + * The delete method will get called once for the deletion of an entire + * tree. + * + * @param string $path + * @return void + */ + function delete($path) { + + $stmt = $this->pdo->prepare("DELETE FROM " . $this->tableName . " WHERE path = ? OR path LIKE ? ESCAPE '='"); + $childPath = strtr( + $path, + [ + '=' => '==', + '%' => '=%', + '_' => '=_' + ] + ) . '/%'; + + $stmt->execute([$path, $childPath]); + + } + + /** + * This method is called after a successful MOVE + * + * This should be used to migrate all properties from one path to another. + * Note that entire collections may be moved, so ensure that all properties + * for children are also moved along. + * + * @param string $source + * @param string $destination + * @return void + */ + function move($source, $destination) { + + // I don't know a way to write this all in a single sql query that's + // also compatible across db engines, so we're letting PHP do all the + // updates. Much slower, but it should still be pretty fast in most + // cases. + $select = $this->pdo->prepare('SELECT id, path FROM ' . $this->tableName . ' WHERE path = ? OR path LIKE ?'); + $select->execute([$source, $source . '/%']); + + $update = $this->pdo->prepare('UPDATE ' . $this->tableName . ' SET path = ? WHERE id = ?'); + while ($row = $select->fetch(\PDO::FETCH_ASSOC)) { + + // Sanity check. SQL may select too many records, such as records + // with different cases. + if ($row['path'] !== $source && strpos($row['path'], $source . '/') !== 0) continue; + + $trailingPart = substr($row['path'], strlen($source) + 1); + $newPath = $destination; + if ($trailingPart) { + $newPath .= '/' . $trailingPart; + } + $update->execute([$newPath, $row['id']]); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php b/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php new file mode 100644 index 000000000..0c28b7882 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php @@ -0,0 +1,180 @@ +<?php + +namespace Sabre\DAV\PropertyStorage; + +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\PropPatch; +use Sabre\DAV\PropFind; +use Sabre\DAV\INode; + +/** + * PropertyStorage Plugin. + * + * Adding this plugin to your server allows clients to store any arbitrary + * WebDAV property. + * + * See: + * http://sabre.io/dav/property-storage/ + * + * for more information. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends ServerPlugin { + + /** + * If you only want this plugin to store properties for a limited set of + * paths, you can use a pathFilter to do this. + * + * The pathFilter should be a callable. The callable retrieves a path as + * its argument, and should return true or false wether it allows + * properties to be stored. + * + * @var callable + */ + public $pathFilter; + + /** + * Creates the plugin + * + * @param Backend\BackendInterface $backend + */ + function __construct(Backend\BackendInterface $backend) { + + $this->backend = $backend; + + } + + /** + * 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) { + + $server->on('propFind', [$this, 'propFind'], 130); + $server->on('propPatch', [$this, 'propPatch'], 300); + $server->on('afterMove', [$this, 'afterMove']); + $server->on('afterUnbind', [$this, 'afterUnbind']); + + } + + /** + * Called during PROPFIND operations. + * + * If there's any requested properties that don't have a value yet, this + * plugin will look in the property storage backend to find them. + * + * @param PropFind $propFind + * @param INode $node + * @return void + */ + function propFind(PropFind $propFind, INode $node) { + + $path = $propFind->getPath(); + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propFind($propFind->getPath(), $propFind); + + } + + /** + * Called during PROPPATCH operations + * + * If there's any updated properties that haven't been stored, the + * propertystorage backend can handle it. + * + * @param string $path + * @param PropPatch $propPatch + * @return void + */ + function propPatch($path, PropPatch $propPatch) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->propPatch($path, $propPatch); + + } + + /** + * Called after a node is deleted. + * + * This allows the backend to clean up any properties still in the + * database. + * + * @param string $path + * @return void + */ + function afterUnbind($path) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($path)) return; + $this->backend->delete($path); + + } + + /** + * Called after a node is moved. + * + * This allows the backend to move all the associated properties. + * + * @param string $source + * @param string $destination + * @return void + */ + function afterMove($source, $destination) { + + $pathFilter = $this->pathFilter; + if ($pathFilter && !$pathFilter($source)) return; + // If the destination is filtered, afterUnbind will handle cleaning up + // the properties. + if ($pathFilter && !$pathFilter($destination)) return; + + $this->backend->move($source, $destination); + + } + + /** + * 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 'property-storage'; + + } + + /** + * 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 allows any arbitrary WebDAV property to be set on any resource.', + 'link' => 'http://sabre.io/dav/property-storage/', + ]; + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Server.php b/vendor/sabre/dav/lib/DAV/Server.php new file mode 100644 index 000000000..b37652812 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Server.php @@ -0,0 +1,1627 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\Event\EventEmitter; +use Sabre\HTTP; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\HTTP\URLUtil; +use Sabre\Uri; + +/** + * Main DAV server class + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Server extends EventEmitter { + + /** + * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree + */ + const DEPTH_INFINITY = -1; + + /** + * XML namespace for all SabreDAV related elements + */ + const NS_SABREDAV = 'http://sabredav.org/ns'; + + /** + * The tree object + * + * @var Sabre\DAV\Tree + */ + public $tree; + + /** + * The base uri + * + * @var string + */ + protected $baseUri = null; + + /** + * httpResponse + * + * @var Sabre\HTTP\Response + */ + public $httpResponse; + + /** + * httpRequest + * + * @var Sabre\HTTP\Request + */ + public $httpRequest; + + /** + * PHP HTTP Sapi + * + * @var Sabre\HTTP\Sapi + */ + public $sapi; + + /** + * The list of plugins + * + * @var array + */ + protected $plugins = []; + + /** + * This property will be filled with a unique string that describes the + * transaction. This is useful for performance measuring and logging + * purposes. + * + * By default it will just fill it with a lowercased HTTP method name, but + * plugins override this. For example, the WebDAV-Sync sync-collection + * report will set this to 'report-sync-collection'. + * + * @var string + */ + public $transactionType; + + /** + * This is a list of properties that are always server-controlled, and + * must not get modified with PROPPATCH. + * + * Plugins may add to this list. + * + * @var string[] + */ + public $protectedProperties = [ + + // RFC4918 + '{DAV:}getcontentlength', + '{DAV:}getetag', + '{DAV:}getlastmodified', + '{DAV:}lockdiscovery', + '{DAV:}supportedlock', + + // RFC4331 + '{DAV:}quota-available-bytes', + '{DAV:}quota-used-bytes', + + // RFC3744 + '{DAV:}supported-privilege-set', + '{DAV:}current-user-privilege-set', + '{DAV:}acl', + '{DAV:}acl-restrictions', + '{DAV:}inherited-acl-set', + + // RFC3253 + '{DAV:}supported-method-set', + '{DAV:}supported-report-set', + + // RFC6578 + '{DAV:}sync-token', + + // calendarserver.org extensions + '{http://calendarserver.org/ns/}ctag', + + // sabredav extensions + '{http://sabredav.org/ns}sync-token', + + ]; + + /** + * This is a flag that allow or not showing file, line and code + * of the exception in the returned XML + * + * @var bool + */ + public $debugExceptions = false; + + /** + * This property allows you to automatically add the 'resourcetype' value + * based on a node's classname or interface. + * + * The preset ensures that {DAV:}collection is automatically added for nodes + * implementing Sabre\DAV\ICollection. + * + * @var array + */ + public $resourceTypeMapping = [ + 'Sabre\\DAV\\ICollection' => '{DAV:}collection', + ]; + + /** + * This property allows the usage of Depth: infinity on PROPFIND requests. + * + * By default Depth: infinity is treated as Depth: 1. Allowing Depth: + * infinity is potentially risky, as it allows a single client to do a full + * index of the webdav server, which is an easy DoS attack vector. + * + * Only turn this on if you know what you're doing. + * + * @var bool + */ + public $enablePropfindDepthInfinity = false; + + /** + * Reference to the XML utility object. + * + * @var Xml\Service + */ + public $xml; + + /** + * If this setting is turned off, SabreDAV's version number will be hidden + * from various places. + * + * Some people feel this is a good security measure. + * + * @var bool + */ + static $exposeVersion = true; + + /** + * Sets up the server + * + * If a Sabre\DAV\Tree object is passed as an argument, it will + * use it as the directory tree. If a Sabre\DAV\INode is passed, it + * will create a Sabre\DAV\Tree and use the node as the root. + * + * If nothing is passed, a Sabre\DAV\SimpleCollection is created in + * a Sabre\DAV\Tree. + * + * If an array is passed, we automatically create a root node, and use + * the nodes in the array as top-level children. + * + * @param Tree|INode|array|null $treeOrNode The tree object + */ + function __construct($treeOrNode = null) { + + if ($treeOrNode instanceof Tree) { + $this->tree = $treeOrNode; + } elseif ($treeOrNode instanceof INode) { + $this->tree = new Tree($treeOrNode); + } elseif (is_array($treeOrNode)) { + + // If it's an array, a list of nodes was passed, and we need to + // create the root node. + foreach ($treeOrNode as $node) { + if (!($node instanceof INode)) { + throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode'); + } + } + + $root = new SimpleCollection('root', $treeOrNode); + $this->tree = new Tree($root); + + } elseif (is_null($treeOrNode)) { + $root = new SimpleCollection('root'); + $this->tree = new Tree($root); + } else { + throw new Exception('Invalid argument passed to constructor. Argument must either be an instance of Sabre\\DAV\\Tree, Sabre\\DAV\\INode, an array or null'); + } + + $this->xml = new Xml\Service(); + $this->sapi = new HTTP\Sapi(); + $this->httpResponse = new HTTP\Response(); + $this->httpRequest = $this->sapi->getRequest(); + $this->addPlugin(new CorePlugin()); + + } + + /** + * Starts the DAV Server + * + * @return void + */ + function exec() { + + try { + + // If nginx (pre-1.2) is used as a proxy server, and SabreDAV as an + // origin, we must make sure we send back HTTP/1.0 if this was + // requested. + // This is mainly because nginx doesn't support Chunked Transfer + // Encoding, and this forces the webserver SabreDAV is running on, + // to buffer entire responses to calculate Content-Length. + $this->httpResponse->setHTTPVersion($this->httpRequest->getHTTPVersion()); + + // Setting the base url + $this->httpRequest->setBaseUrl($this->getBaseUri()); + $this->invokeMethod($this->httpRequest, $this->httpResponse); + + } catch (\Exception $e) { + + try { + $this->emit('exception', [$e]); + } catch (\Exception $ignore) { + } + $DOM = new \DOMDocument('1.0', 'utf-8'); + $DOM->formatOutput = true; + + $error = $DOM->createElementNS('DAV:', 'd:error'); + $error->setAttribute('xmlns:s', self::NS_SABREDAV); + $DOM->appendChild($error); + + $h = function($v) { + + return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); + + }; + + if (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version', $h(Version::VERSION))); + } + + $error->appendChild($DOM->createElement('s:exception', $h(get_class($e)))); + $error->appendChild($DOM->createElement('s:message', $h($e->getMessage()))); + if ($this->debugExceptions) { + $error->appendChild($DOM->createElement('s:file', $h($e->getFile()))); + $error->appendChild($DOM->createElement('s:line', $h($e->getLine()))); + $error->appendChild($DOM->createElement('s:code', $h($e->getCode()))); + $error->appendChild($DOM->createElement('s:stacktrace', $h($e->getTraceAsString()))); + } + + if ($this->debugExceptions) { + $previous = $e; + while ($previous = $previous->getPrevious()) { + $xPrevious = $DOM->createElement('s:previous-exception'); + $xPrevious->appendChild($DOM->createElement('s:exception', $h(get_class($previous)))); + $xPrevious->appendChild($DOM->createElement('s:message', $h($previous->getMessage()))); + $xPrevious->appendChild($DOM->createElement('s:file', $h($previous->getFile()))); + $xPrevious->appendChild($DOM->createElement('s:line', $h($previous->getLine()))); + $xPrevious->appendChild($DOM->createElement('s:code', $h($previous->getCode()))); + $xPrevious->appendChild($DOM->createElement('s:stacktrace', $h($previous->getTraceAsString()))); + $error->appendChild($xPrevious); + } + } + + + if ($e instanceof Exception) { + + $httpCode = $e->getHTTPCode(); + $e->serialize($this, $error); + $headers = $e->getHTTPHeaders($this); + + } else { + + $httpCode = 500; + $headers = []; + + } + $headers['Content-Type'] = 'application/xml; charset=utf-8'; + + $this->httpResponse->setStatus($httpCode); + $this->httpResponse->setHeaders($headers); + $this->httpResponse->setBody($DOM->saveXML()); + $this->sapi->sendResponse($this->httpResponse); + + } + + } + + /** + * Sets the base server uri + * + * @param string $uri + * @return void + */ + function setBaseUri($uri) { + + // If the baseUri does not end with a slash, we must add it + if ($uri[strlen($uri) - 1] !== '/') + $uri .= '/'; + + $this->baseUri = $uri; + + } + + /** + * Returns the base responding uri + * + * @return string + */ + function getBaseUri() { + + if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri(); + return $this->baseUri; + + } + + /** + * This method attempts to detect the base uri. + * Only the PATH_INFO variable is considered. + * + * If this variable is not set, the root (/) is assumed. + * + * @return string + */ + function guessBaseUri() { + + $pathInfo = $this->httpRequest->getRawServerValue('PATH_INFO'); + $uri = $this->httpRequest->getRawServerValue('REQUEST_URI'); + + // If PATH_INFO is found, we can assume it's accurate. + if (!empty($pathInfo)) { + + // We need to make sure we ignore the QUERY_STRING part + if ($pos = strpos($uri, '?')) + $uri = substr($uri, 0, $pos); + + // PATH_INFO is only set for urls, such as: /example.php/path + // in that case PATH_INFO contains '/path'. + // Note that REQUEST_URI is percent encoded, while PATH_INFO is + // not, Therefore they are only comparable if we first decode + // REQUEST_INFO as well. + $decodedUri = URLUtil::decodePath($uri); + + // A simple sanity check: + if (substr($decodedUri, strlen($decodedUri) - strlen($pathInfo)) === $pathInfo) { + $baseUri = substr($decodedUri, 0, strlen($decodedUri) - strlen($pathInfo)); + return rtrim($baseUri, '/') . '/'; + } + + throw new Exception('The REQUEST_URI (' . $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); + + } + + // The last fallback is that we're just going to assume the server root. + return '/'; + + } + + /** + * Adds a plugin to the server + * + * For more information, console the documentation of Sabre\DAV\ServerPlugin + * + * @param ServerPlugin $plugin + * @return void + */ + function addPlugin(ServerPlugin $plugin) { + + $this->plugins[$plugin->getPluginName()] = $plugin; + $plugin->initialize($this); + + } + + /** + * Returns an initialized plugin by it's name. + * + * This function returns null if the plugin was not found. + * + * @param string $name + * @return ServerPlugin + */ + function getPlugin($name) { + + if (isset($this->plugins[$name])) + return $this->plugins[$name]; + + return null; + + } + + /** + * Returns all plugins + * + * @return array + */ + function getPlugins() { + + return $this->plugins; + + } + + /** + * Handles a http request, and execute a method based on its name + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param $sendResponse Whether to send the HTTP response to the DAV client. + * @return void + */ + function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) { + + $method = $request->getMethod(); + + if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return; + if (!$this->emit('beforeMethod', [$request, $response])) return; + + if (self::$exposeVersion) { + $response->setHeader('X-Sabre-Version', Version::VERSION); + } + + $this->transactionType = strtolower($method); + + if (!$this->checkPreconditions($request, $response)) { + $this->sapi->sendResponse($response); + return; + } + + if ($this->emit('method:' . $method, [$request, $response])) { + if ($this->emit('method', [$request, $response])) { + $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method."; + if ($method === "GET") { + $exMessage .= " Enable the Browser plugin to get a better result here."; + } + + // Unsupported method + throw new Exception\NotImplemented($exMessage); + } + } + + if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; + if (!$this->emit('afterMethod', [$request, $response])) return; + + if ($response->getStatus() === null) { + throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.'); + } + if ($sendResponse) { + $this->sapi->sendResponse($response); + $this->emit('afterResponse', [$request, $response]); + } + + } + + // {{{ HTTP/WebDAV protocol helpers + + /** + * Returns an array with all the supported HTTP methods for a specific uri. + * + * @param string $path + * @return array + */ + function getAllowedMethods($path) { + + $methods = [ + 'OPTIONS', + 'GET', + 'HEAD', + 'DELETE', + 'PROPFIND', + 'PUT', + 'PROPPATCH', + 'COPY', + 'MOVE', + 'REPORT' + ]; + + // The MKCOL is only allowed on an unmapped uri + try { + $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $methods[] = 'MKCOL'; + } + + // We're also checking if any of the plugins register any new methods + foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path)); + array_unique($methods); + + return $methods; + + } + + /** + * Gets the uri for the request, keeping the base uri into consideration + * + * @return string + */ + function getRequestUri() { + + return $this->calculateUri($this->httpRequest->getUrl()); + + } + + /** + * Turns a URI such as the REQUEST_URI into a local path. + * + * This method: + * * strips off the base path + * * normalizes the path + * * uri-decodes the path + * + * @param string $uri + * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri + * @return string + */ + function calculateUri($uri) { + + if ($uri[0] != '/' && strpos($uri, '://')) { + + $uri = parse_url($uri, PHP_URL_PATH); + + } + + $uri = Uri\normalize(str_replace('//', '/', $uri)); + $baseUri = Uri\normalize($this->getBaseUri()); + + if (strpos($uri, $baseUri) === 0) { + + return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/'); + + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri . '/' === $baseUri) { + + return ''; + + } else { + + throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')'); + + } + + } + + /** + * Returns the HTTP depth header + * + * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object + * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent + * + * @param mixed $default + * @return int + */ + function getHTTPDepth($default = self::DEPTH_INFINITY) { + + // If its not set, we'll grab the default + $depth = $this->httpRequest->getHeader('Depth'); + + if (is_null($depth)) return $default; + + if ($depth == 'infinity') return self::DEPTH_INFINITY; + + + // If its an unknown value. we'll grab the default + if (!ctype_digit($depth)) return $default; + + return (int)$depth; + + } + + /** + * Returns the HTTP range header + * + * This method returns null if there is no well-formed HTTP range request + * header or array($start, $end). + * + * The first number is the offset of the first byte in the range. + * The second number is the offset of the last byte in the range. + * + * If the second offset is null, it should be treated as the offset of the last byte of the entity + * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity + * + * @return array|null + */ + function getHTTPRange() { + + $range = $this->httpRequest->getHeader('range'); + if (is_null($range)) return null; + + // Matching "Range: bytes=1234-5678: both numbers are optional + + if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) return null; + + if ($matches[1] === '' && $matches[2] === '') return null; + + return [ + $matches[1] !== '' ? $matches[1] : null, + $matches[2] !== '' ? $matches[2] : null, + ]; + + } + + /** + * Returns the HTTP Prefer header information. + * + * The prefer header is defined in: + * http://tools.ietf.org/html/draft-snell-http-prefer-14 + * + * This method will return an array with options. + * + * Currently, the following options may be returned: + * [ + * 'return-asynch' => true, + * 'return-minimal' => true, + * 'return-representation' => true, + * 'wait' => 30, + * 'strict' => true, + * 'lenient' => true, + * ] + * + * This method also supports the Brief header, and will also return + * 'return-minimal' if the brief header was set to 't'. + * + * For the boolean options, false will be returned if the headers are not + * specified. For the integer options it will be 'null'. + * + * @return array + */ + function getHTTPPrefer() { + + $result = [ + // can be true or false + 'respond-async' => false, + // Could be set to 'representation' or 'minimal'. + 'return' => null, + // Used as a timeout, is usually a number. + 'wait' => null, + // can be 'strict' or 'lenient'. + 'handling' => false, + ]; + + if ($prefer = $this->httpRequest->getHeader('Prefer')) { + + $result = array_merge( + $result, + \Sabre\HTTP\parsePrefer($prefer) + ); + + } elseif ($this->httpRequest->getHeader('Brief') == 't') { + $result['return'] = 'minimal'; + } + + return $result; + + } + + + /** + * Returns information about Copy and Move requests + * + * This function is created to help getting information about the source and the destination for the + * WebDAV MOVE and COPY HTTP request. It also validates a lot of information and throws proper exceptions + * + * The returned value is an array with the following keys: + * * destination - Destination path + * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) + * + * @param RequestInterface $request + * @throws Exception\BadRequest upon missing or broken request headers + * @throws Exception\UnsupportedMediaType when trying to copy into a + * non-collection. + * @throws Exception\PreconditionFailed If overwrite is set to false, but + * the destination exists. + * @throws Exception\Forbidden when source and destination paths are + * identical. + * @throws Exception\Conflict When trying to copy a node into its own + * subtree. + * @return array + */ + function getCopyAndMoveInfo(RequestInterface $request) { + + // Collecting the relevant HTTP headers + if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); + $destination = $this->calculateUri($request->getHeader('Destination')); + $overwrite = $request->getHeader('Overwrite'); + if (!$overwrite) $overwrite = 'T'; + if (strtoupper($overwrite) == 'T') $overwrite = true; + elseif (strtoupper($overwrite) == 'F') $overwrite = false; + // We need to throw a bad request exception, if the header was invalid + else throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F'); + + list($destinationDir) = URLUtil::splitPath($destination); + + try { + $destinationParent = $this->tree->getNodeForPath($destinationDir); + if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection'); + } catch (Exception\NotFound $e) { + + // If the destination parent node is not found, we throw a 409 + throw new Exception\Conflict('The destination node is not found'); + } + + try { + + $destinationNode = $this->tree->getNodeForPath($destination); + + // If this succeeded, it means the destination already exists + // we'll need to throw precondition failed in case overwrite is false + if (!$overwrite) throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite'); + + } catch (Exception\NotFound $e) { + + // Destination didn't exist, we're all good + $destinationNode = false; + + } + + $requestPath = $request->getPath(); + if ($destination === $requestPath) { + throw new Exception\Forbidden('Source and destination uri are identical.'); + } + if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') { + throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.'); + } + + // These are the three relevant properties we need to return + return [ + 'destination' => $destination, + 'destinationExists' => !!$destinationNode, + 'destinationNode' => $destinationNode, + ]; + + } + + /** + * Returns a list of properties for a path + * + * This is a simplified version getPropertiesForPath. If you aren't + * interested in status codes, but you just want to have a flat list of + * properties, use this method. + * + * Please note though that any problems related to retrieving properties, + * such as permission issues will just result in an empty array being + * returned. + * + * @param string $path + * @param array $propertyNames + */ + function getProperties($path, $propertyNames) { + + $result = $this->getPropertiesForPath($path, $propertyNames, 0); + if (isset($result[0][200])) { + return $result[0][200]; + } else { + return []; + } + + } + + /** + * A kid-friendly way to fetch properties for a node's children. + * + * The returned array will be indexed by the path of the of child node. + * Only properties that are actually found will be returned. + * + * The parent node will not be returned. + * + * @param string $path + * @param array $propertyNames + * @return array + */ + function getPropertiesForChildren($path, $propertyNames) { + + $result = []; + foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) { + + // Skipping the parent path + if ($k === 0) continue; + + $result[$row['href']] = $row[200]; + + } + return $result; + + } + + /** + * Returns a list of HTTP headers for a particular resource + * + * The generated http headers are based on properties provided by the + * resource. The method basically provides a simple mapping between + * DAV property and HTTP header. + * + * The headers are intended to be used for HEAD and GET requests. + * + * @param string $path + * @return array + */ + function getHTTPHeaders($path) { + + $propertyMap = [ + '{DAV:}getcontenttype' => 'Content-Type', + '{DAV:}getcontentlength' => 'Content-Length', + '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getetag' => 'ETag', + ]; + + $properties = $this->getProperties($path, array_keys($propertyMap)); + + $headers = []; + foreach ($propertyMap as $property => $header) { + if (!isset($properties[$property])) continue; + + if (is_scalar($properties[$property])) { + $headers[$header] = $properties[$property]; + + // GetLastModified gets special cased + } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) { + $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); + } + + } + + return $headers; + + } + + /** + * Small helper to support PROPFIND with DEPTH_INFINITY. + * + * @param array[] $propFindRequests + * @param PropFind $propFind + * @return void + */ + private function addPathNodesRecursively(&$propFindRequests, PropFind $propFind) { + + $newDepth = $propFind->getDepth(); + $path = $propFind->getPath(); + + if ($newDepth !== self::DEPTH_INFINITY) { + $newDepth--; + } + + foreach ($this->tree->getChildren($path) as $childNode) { + $subPropFind = clone $propFind; + $subPropFind->setDepth($newDepth); + if ($path !== '') { + $subPath = $path . '/' . $childNode->getName(); + } else { + $subPath = $childNode->getName(); + } + $subPropFind->setPath($subPath); + + $propFindRequests[] = [ + $subPropFind, + $childNode + ]; + + if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) { + $this->addPathNodesRecursively($propFindRequests, $subPropFind); + } + + } + } + + /** + * Returns a list of properties for a given path + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * If a depth of 1 is requested child elements will also be returned. + * + * @param string $path + * @param array $propertyNames + * @param int $depth + * @return array + */ + function getPropertiesForPath($path, $propertyNames = [], $depth = 0) { + + // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled + if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + + $path = trim($path, '/'); + + $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS; + $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType); + + $parentNode = $this->tree->getNodeForPath($path); + + $propFindRequests = [[ + $propFind, + $parentNode + ]]; + + if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) { + $this->addPathNodesRecursively($propFindRequests, $propFind); + } + + $returnPropertyList = []; + + foreach ($propFindRequests as $propFindRequest) { + + list($propFind, $node) = $propFindRequest; + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result = $propFind->getResultForMultiStatus(); + $result['href'] = $propFind->getPath(); + + // WebDAV recommends adding a slash to the path, if the path is + // a collection. + // Furthermore, iCal also demands this to be the case for + // principals. This is non-standard, but we support it. + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result['href'] .= '/'; + } + $returnPropertyList[] = $result; + } + + } + + return $returnPropertyList; + + } + + /** + * Returns a list of properties for a list of paths. + * + * The path that should be supplied should have the baseUrl stripped out + * The list of properties should be supplied in Clark notation. If the list is empty + * 'allprops' is assumed. + * + * The result is returned as an array, with paths for it's keys. + * The result may be returned out of order. + * + * @param array $paths + * @param array $propertyNames + * @return array + */ + function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) { + + $result = [ + ]; + + $nodes = $this->tree->getMultipleNodes($paths); + + foreach ($nodes as $path => $node) { + + $propFind = new PropFind($path, $propertyNames); + $r = $this->getPropertiesByNode($propFind, $node); + if ($r) { + $result[$path] = $propFind->getResultForMultiStatus(); + $result[$path]['href'] = $path; + + $resourceType = $this->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result[$path]['href'] .= '/'; + } + } + + } + + return $result; + + } + + + /** + * Determines all properties for a node. + * + * This method tries to grab all properties for a node. This method is used + * internally getPropertiesForPath and a few others. + * + * It could be useful to call this, if you already have an instance of your + * target node and simply want to run through the system to get a correct + * list of properties. + * + * @param PropFind $propFind + * @param INode $node + * @return bool + */ + function getPropertiesByNode(PropFind $propFind, INode $node) { + + return $this->emit('propFind', [$propFind, $node]); + + } + + /** + * This method is invoked by sub-systems creating a new file. + * + * Currently this is done by HTTP PUT and HTTP LOCK (in the Locks_Plugin). + * It was important to get this done through a centralized function, + * allowing plugins to intercept this using the beforeCreateFile event. + * + * This method will return true if the file was actually created + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function createFile($uri, $data, &$etag = null) { + + list($dir, $name) = URLUtil::splitPath($uri); + + if (!$this->emit('beforeBind', [$uri])) return false; + + $parent = $this->tree->getNodeForPath($dir); + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Files can only be created as children of collections'); + } + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false; + + $etag = $parent->createFile($name, $data); + + if ($modified) $etag = null; + + $this->tree->markDirty($dir . '/' . $name); + + $this->emit('afterBind', [$uri]); + $this->emit('afterCreateFile', [$uri, $parent]); + + return true; + } + + /** + * This method is invoked by sub-systems updating a file. + * + * This method will return true if the file was actually updated + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool + */ + function updateFile($uri, $data, &$etag = null) { + + $node = $this->tree->getNodeForPath($uri); + + // It is possible for an event handler to modify the content of the + // body, before it gets written. If this is the case, $modified + // should be set to true. + // + // If $modified is true, we must not send back an ETag. + $modified = false; + if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false; + + $etag = $node->put($data); + if ($modified) $etag = null; + $this->emit('afterWriteContent', [$uri, $node]); + + return true; + } + + + + /** + * This method is invoked by sub-systems creating a new directory. + * + * @param string $uri + * @return void + */ + function createDirectory($uri) { + + $this->createCollection($uri, new MkCol(['{DAV:}collection'], [])); + + } + + /** + * Use this method to create a new collection + * + * @param string $uri The new uri + * @param MkCol $mkCol + * @return array|null + */ + function createCollection($uri, MkCol $mkCol) { + + list($parentUri, $newName) = URLUtil::splitPath($uri); + + // Making sure the parent exists + try { + $parent = $this->tree->getNodeForPath($parentUri); + + } catch (Exception\NotFound $e) { + throw new Exception\Conflict('Parent node does not exist'); + + } + + // Making sure the parent is a collection + if (!$parent instanceof ICollection) { + throw new Exception\Conflict('Parent node is not a collection'); + } + + // Making sure the child does not already exist + try { + $parent->getChild($newName); + + // If we got here.. it means there's already a node on that url, and we need to throw a 405 + throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); + + } catch (Exception\NotFound $e) { + // NotFound is the expected behavior. + } + + + if (!$this->emit('beforeBind', [$uri])) return; + + if ($parent instanceof IExtendedCollection) { + + /** + * If the parent is an instance of IExtendedCollection, it means that + * we can pass the MkCol object directly as it may be able to store + * properties immediately. + */ + $parent->createExtendedCollection($newName, $mkCol); + + } else { + + /** + * If the parent is a standard ICollection, it means only + * 'standard' collections can be created, so we should fail any + * MKCOL operation that carries extra resourcetypes. + */ + if (count($mkCol->getResourceType()) > 1) { + throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); + } + + $parent->createDirectory($newName); + + } + + // If there are any properties that have not been handled/stored, + // we ask the 'propPatch' event to handle them. This will allow for + // example the propertyStorage system to store properties upon MKCOL. + if ($mkCol->getRemainingMutations()) { + $this->emit('propPatch', [$uri, $mkCol]); + } + $success = $mkCol->commit(); + + if (!$success) { + $result = $mkCol->getResult(); + // generateMkCol needs the href key to exist. + $result['href'] = $uri; + return $result; + } + + $this->tree->markDirty($parentUri); + $this->emit('afterBind', [$uri]); + + } + + /** + * This method updates a resource's properties + * + * The properties array must be a list of properties. Array-keys are + * property names in clarknotation, array-values are it's values. + * If a property must be deleted, the value should be null. + * + * Note that this request should either completely succeed, or + * completely fail. + * + * The response is an array with properties for keys, and http status codes + * as their values. + * + * @param string $path + * @param array $properties + * @return array + */ + function updateProperties($path, array $properties) { + + $propPatch = new PropPatch($properties); + $this->emit('propPatch', [$path, $propPatch]); + $propPatch->commit(); + + return $propPatch->getResult(); + + } + + /** + * This method checks the main HTTP preconditions. + * + * Currently these are: + * * If-Match + * * If-None-Match + * * If-Modified-Since + * * If-Unmodified-Since + * + * The method will return true if all preconditions are met + * The method will return false, or throw an exception if preconditions + * failed. If false is returned the operation should be aborted, and + * the appropriate HTTP response headers are already set. + * + * Normally this method will throw 412 Precondition Failed for failures + * related to If-None-Match, If-Match and If-Unmodified Since. It will + * set the status to 304 Not Modified for If-Modified_since. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function checkPreconditions(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + $node = null; + $lastMod = null; + $etag = null; + + if ($ifMatch = $request->getHeader('If-Match')) { + + // If-Match contains an entity tag. Only if the entity-tag + // matches we are allowed to make the request succeed. + // If the entity-tag is '*' we are only allowed to make the + // request succeed if a resource exists at that url. + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + throw new Exception\PreconditionFailed('An If-Match header was specified and the resource did not exist', 'If-Match'); + } + + // Only need to check entity tags if they are not * + if ($ifMatch !== '*') { + + // There can be multiple ETags + $ifMatch = explode(',', $ifMatch); + $haveMatch = false; + foreach ($ifMatch as $ifMatchItem) { + + // Stripping any extra spaces + $ifMatchItem = trim($ifMatchItem, ' '); + + $etag = $node instanceof IFile ? $node->getETag() : null; + if ($etag === $ifMatchItem) { + $haveMatch = true; + } else { + // Evolution has a bug where it sometimes prepends the " + // with a \. This is our workaround. + if (str_replace('\\"', '"', $ifMatchItem) === $etag) { + $haveMatch = true; + } + } + + } + if (!$haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match'); + } + } + } + + if ($ifNoneMatch = $request->getHeader('If-None-Match')) { + + // The If-None-Match header contains an ETag. + // Only if the ETag does not match the current ETag, the request will succeed + // The header can also contain *, in which case the request + // will only succeed if the entity does not exist at all. + $nodeExists = true; + if (!$node) { + try { + $node = $this->tree->getNodeForPath($path); + } catch (Exception\NotFound $e) { + $nodeExists = false; + } + } + if ($nodeExists) { + $haveMatch = false; + if ($ifNoneMatch === '*') $haveMatch = true; + else { + + // There might be multiple ETags + $ifNoneMatch = explode(',', $ifNoneMatch); + $etag = $node instanceof IFile ? $node->getETag() : null; + + foreach ($ifNoneMatch as $ifNoneMatchItem) { + + // Stripping any extra spaces + $ifNoneMatchItem = trim($ifNoneMatchItem, ' '); + + if ($etag === $ifNoneMatchItem) $haveMatch = true; + + } + + } + + if ($haveMatch) { + if ($etag) $response->setHeader('ETag', $etag); + if ($request->getMethod() === 'GET') { + $response->setStatus(304); + return false; + } else { + throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match'); + } + } + } + + } + + if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) { + + // The If-Modified-Since header contains a date. We + // will only return the entity if it has been changed since + // that date. If it hasn't been changed, we return a 304 + // header + // Note that this header only has to be checked if there was no If-None-Match header + // as per the HTTP spec. + $date = HTTP\Util::parseHTTPDate($ifModifiedSince); + + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod <= $date) { + $response->setStatus(304); + $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); + return false; + } + } + } + } + + if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) { + + // The If-Unmodified-Since will allow allow the request if the + // entity has not changed since the specified date. + $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); + + // We must only check the date if it's valid + if ($date) { + if (is_null($node)) { + $node = $this->tree->getNodeForPath($path); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod > $date) { + throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since'); + } + } + } + + } + + // Now the hardest, the If: header. The If: header can contain multiple + // urls, ETags and so-called 'state tokens'. + // + // Examples of state tokens include lock-tokens (as defined in rfc4918) + // and sync-tokens (as defined in rfc6578). + // + // The only proper way to deal with these, is to emit events, that a + // Sync and Lock plugin can pick up. + $ifConditions = $this->getIfConditions($request); + + foreach ($ifConditions as $kk => $ifCondition) { + foreach ($ifCondition['tokens'] as $ii => $token) { + $ifConditions[$kk]['tokens'][$ii]['validToken'] = false; + } + } + + // Plugins are responsible for validating all the tokens. + // If a plugin deemed a token 'valid', it will set 'validToken' to + // true. + $this->emit('validateTokens', [ $request, &$ifConditions ]); + + // Now we're going to analyze the result. + + // Every ifCondition needs to validate to true, so we exit as soon as + // we have an invalid condition. + foreach ($ifConditions as $ifCondition) { + + $uri = $ifCondition['uri']; + $tokens = $ifCondition['tokens']; + + // We only need 1 valid token for the condition to succeed. + foreach ($tokens as $token) { + + $tokenValid = $token['validToken'] || !$token['token']; + + $etagValid = false; + if (!$token['etag']) { + $etagValid = true; + } + // Checking the ETag, only if the token was already deamed + // valid and there is one. + if ($token['etag'] && $tokenValid) { + + // The token was valid, and there was an ETag. We must + // grab the current ETag and check it. + $node = $this->tree->getNodeForPath($uri); + $etagValid = $node instanceof IFile && $node->getETag() == $token['etag']; + + } + + + if (($tokenValid && $etagValid) ^ $token['negate']) { + // Both were valid, so we can go to the next condition. + continue 2; + } + + + } + + // If we ended here, it means there was no valid ETag + token + // combination found for the current condition. This means we fail! + throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If'); + + } + + return true; + + } + + /** + * This method is created to extract information from the WebDAV HTTP 'If:' header + * + * The If header can be quite complex, and has a bunch of features. We're using a regex to extract all relevant information + * The function will return an array, containing structs with the following keys + * + * * uri - the uri the condition applies to. + * * tokens - The lock token. another 2 dimensional array containing 3 elements + * + * Example 1: + * + * If: (<opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>) + * + * Would result in: + * + * [ + * [ + * 'uri' => '/request/uri', + * 'tokens' => [ + * [ + * [ + * 'negate' => false, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => "" + * ] + * ] + * ], + * ] + * ] + * + * Example 2: + * + * If: </path/> (Not <opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2> ["Im An ETag"]) (["Another ETag"]) </path2/> (Not ["Path2 ETag"]) + * + * Would result in: + * + * [ + * [ + * 'uri' => 'path', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => 'opaquelocktoken:181d4fae-7d8c-11d0-a765-00a0c91e6bf2', + * 'etag' => '"Im An ETag"' + * ], + * [ + * 'negate' => false, + * 'token' => '', + * 'etag' => '"Another ETag"' + * ] + * ] + * ], + * ], + * [ + * 'uri' => 'path2', + * 'tokens' => [ + * [ + * [ + * 'negate' => true, + * 'token' => '', + * 'etag' => '"Path2 ETag"' + * ] + * ] + * ], + * ], + * ] + * + * @param RequestInterface $request + * @return array + */ + function getIfConditions(RequestInterface $request) { + + $header = $request->getHeader('If'); + if (!$header) return []; + + $matches = []; + + $regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im'; + preg_match_all($regex, $header, $matches, PREG_SET_ORDER); + + $conditions = []; + + foreach ($matches as $match) { + + // If there was no uri specified in this match, and there were + // already conditions parsed, we add the condition to the list of + // conditions for the previous uri. + if (!$match['uri'] && count($conditions)) { + $conditions[count($conditions) - 1]['tokens'][] = [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ]; + } else { + + if (!$match['uri']) { + $realUri = $request->getPath(); + } else { + $realUri = $this->calculateUri($match['uri']); + } + + $conditions[] = [ + 'uri' => $realUri, + 'tokens' => [ + [ + 'negate' => $match['not'] ? true : false, + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '' + ] + ], + + ]; + } + + } + + return $conditions; + + } + + /** + * Returns an array with resourcetypes for a node. + * + * @param INode $node + * @return array + */ + function getResourceTypeForNode(INode $node) { + + $result = []; + foreach ($this->resourceTypeMapping as $className => $resourceType) { + if ($node instanceof $className) $result[] = $resourceType; + } + return $result; + + } + + // }}} + // {{{ XML Readers & Writers + + + /** + * Generates a WebDAV propfind response body based on a list of nodes. + * + * If 'strip404s' is set to true, all 404 responses will be removed. + * + * @param array $fileProperties The list with nodes + * @param bool strip404s + * @return string + */ + function generateMultiStatus(array $fileProperties, $strip404s = false) { + + $xml = []; + + foreach ($fileProperties as $entry) { + + $href = $entry['href']; + unset($entry['href']); + if ($strip404s) { + unset($entry[404]); + } + $response = new Xml\Element\Response( + ltrim($href, '/'), + $entry + ); + $xml[] = [ + 'name' => '{DAV:}response', + 'value' => $response + ]; + + } + return $this->xml->write('{DAV:}multistatus', $xml, $this->baseUri); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/ServerPlugin.php b/vendor/sabre/dav/lib/DAV/ServerPlugin.php new file mode 100644 index 000000000..b2c468ab3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/ServerPlugin.php @@ -0,0 +1,110 @@ +<?php + +namespace Sabre\DAV; + +/** + * The baseclass for all server plugins. + * + * Plugins can modify or extend the servers behaviour. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class ServerPlugin { + + /** + * 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 + */ + abstract function initialize(Server $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 []; + + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * @param string $path + * @return array + */ + function getHTTPMethods($path) { + + return []; + + } + + /** + * 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 get_class($this); + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + return []; + + } + + /** + * 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' => null, + 'link' => null, + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/SimpleCollection.php b/vendor/sabre/dav/lib/DAV/SimpleCollection.php new file mode 100644 index 000000000..998cfcbff --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/SimpleCollection.php @@ -0,0 +1,107 @@ +<?php + +namespace Sabre\DAV; + +/** + * SimpleCollection + * + * The SimpleCollection is used to quickly setup static directory structures. + * Just create the object with a proper name, and add children to use it. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SimpleCollection extends Collection { + + /** + * List of childnodes + * + * @var INode[] + */ + protected $children = []; + + /** + * Name of this resource + * + * @var string + */ + protected $name; + + /** + * Creates this node + * + * The name of the node must be passed, child nodes can also be passed. + * This nodes must be instances of INode + * + * @param string $name + * @param INode[] $children + */ + function __construct($name, array $children = []) { + + $this->name = $name; + foreach ($children as $child) { + + if (!($child instanceof INode)) throw new Exception('Only instances of Sabre\DAV\INode are allowed to be passed in the children argument'); + $this->addChild($child); + + } + + } + + /** + * Adds a new childnode to this collection + * + * @param INode $child + * @return void + */ + function addChild(INode $child) { + + $this->children[$child->getName()] = $child; + + } + + /** + * Returns the name of the collection + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns a child object, by its name. + * + * This method makes use of the getChildren method to grab all the child nodes, and compares the name. + * Generally its wise to override this, as this can usually be optimized + * + * This method must throw Sabre\DAV\Exception\NotFound if the node does not + * exist. + * + * @param string $name + * @throws Exception\NotFound + * @return INode + */ + function getChild($name) { + + if (isset($this->children[$name])) return $this->children[$name]; + throw new Exception\NotFound('File not found: ' . $name . ' in \'' . $this->getName() . '\''); + + } + + /** + * Returns a list of children for this collection + * + * @return INode[] + */ + function getChildren() { + + return array_values($this->children); + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/SimpleFile.php b/vendor/sabre/dav/lib/DAV/SimpleFile.php new file mode 100644 index 000000000..bcad786f3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/SimpleFile.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAV; + +/** + * SimpleFile + * + * The 'SimpleFile' class is used to easily add read-only immutable files to + * the directory structure. One usecase would be to add a 'readme.txt' to a + * root of a webserver with some standard content. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SimpleFile extends File { + + /** + * File contents + * + * @var string + */ + protected $contents = []; + + /** + * Name of this resource + * + * @var string + */ + protected $name; + + /** + * A mimetype, such as 'text/plain' or 'text/html' + * + * @var string + */ + protected $mimeType; + + /** + * Creates this node + * + * The name of the node must be passed, as well as the contents of the + * file. + * + * @param string $name + * @param string $contents + * @param string|null $mimeType + */ + function __construct($name, $contents, $mimeType = null) { + + $this->name = $name; + $this->contents = $contents; + $this->mimeType = $mimeType; + + } + + /** + * Returns the node name for this file. + * + * This name is used to construct the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $this->contents; + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + function getSize() { + + return strlen($this->contents); + + } + + /** + * Returns the ETag for a file + * + * An ETag is a unique identifier representing the current version of the file. If the file changes, the ETag MUST change. + * The ETag is an arbitrary string, but MUST be surrounded by double-quotes. + * + * Return null if the ETag can not effectively be determined + * @return string + */ + function getETag() { + + return '"' . sha1($this->contents) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * @return string + */ + function getContentType() { + + return $this->mimeType; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/StringUtil.php b/vendor/sabre/dav/lib/DAV/StringUtil.php new file mode 100644 index 000000000..10eecebfd --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/StringUtil.php @@ -0,0 +1,91 @@ +<?php + +namespace Sabre\DAV; + +/** + * String utility + * + * This class is mainly used to implement the 'text-match' filter, used by both + * the CalDAV calendar-query REPORT, and CardDAV addressbook-query REPORT. + * Because they both need it, it was decided to put it in Sabre\DAV instead. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class StringUtil { + + /** + * Checks if a needle occurs in a haystack ;) + * + * @param string $haystack + * @param string $needle + * @param string $collation + * @param string $matchType + * @return bool + */ + static function textMatch($haystack, $needle, $collation, $matchType = 'contains') { + + switch ($collation) { + + case 'i;ascii-casemap' : + // default strtolower takes locale into consideration + // we don't want this. + $haystack = str_replace(range('a', 'z'), range('A', 'Z'), $haystack); + $needle = str_replace(range('a', 'z'), range('A', 'Z'), $needle); + break; + + case 'i;octet' : + // Do nothing + break; + + case 'i;unicode-casemap' : + $haystack = mb_strtoupper($haystack, 'UTF-8'); + $needle = mb_strtoupper($needle, 'UTF-8'); + break; + + default : + throw new Exception\BadRequest('Collation type: ' . $collation . ' is not supported'); + + } + + switch ($matchType) { + + case 'contains' : + return strpos($haystack, $needle) !== false; + case 'equals' : + return $haystack === $needle; + case 'starts-with' : + return strpos($haystack, $needle) === 0; + case 'ends-with' : + return strrpos($haystack, $needle) === strlen($haystack) - strlen($needle); + default : + throw new Exception\BadRequest('Match-type: ' . $matchType . ' is not supported'); + + } + + } + + /** + * This method takes an input string, checks if it's not valid UTF-8 and + * attempts to convert it to UTF-8 if it's not. + * + * Note that currently this can only convert ISO-8559-1 to UTF-8 (latin-1), + * anything else will likely fail. + * + * @param string $input + * @return string + */ + static function ensureUTF8($input) { + + $encoding = mb_detect_encoding($input, ['UTF-8', 'ISO-8859-1'], true); + + if ($encoding === 'ISO-8859-1') { + return utf8_encode($input); + } else { + return $input; + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php b/vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php new file mode 100644 index 000000000..d3dc28a80 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php @@ -0,0 +1,88 @@ +<?php + +namespace Sabre\DAV\Sync; + +use Sabre\DAV; + +/** + * If a class extends ISyncCollection, it supports WebDAV-sync. + * + * You are responsible for maintaining a changelist for this collection. This + * means that if any child nodes in this collection was created, modified or + * deleted in any way, you should maintain an updated changelist. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface ISyncCollection extends DAV\ICollection { + + /** + * This method returns the current sync-token for this collection. + * This can be any string. + * + * If null is returned from this function, the plugin assumes there's no + * sync information available. + * + * @return string|null + */ + function getSyncToken(); + + /** + * The getChanges method returns all the changes that have happened, since + * the specified syncToken and the current collection. + * + * This function should return an array, such as the following: + * + * [ + * 'syncToken' => 'The current synctoken', + * 'added' => [ + * 'new.txt', + * ], + * 'modified' => [ + * 'modified.txt', + * ], + * 'deleted' => array( + * 'foo.php.bak', + * 'old.txt' + * ) + * ]; + * + * The syncToken property should reflect the *current* syncToken of the + * collection, as reported getSyncToken(). This is needed here too, to + * ensure the operation is atomic. + * + * If the syncToken is specified as null, this is an initial sync, and all + * members should be reported. + * + * The modified property is an array of nodenames that have changed since + * the last token. + * + * The deleted property is an array with nodenames, that have been deleted + * from collection. + * + * The second argument is basically the 'depth' of the report. If it's 1, + * you only have to report changes that happened only directly in immediate + * descendants. If it's 2, it should also include changes from the nodes + * below the child collections. (grandchildren) + * + * The third (optional) argument allows a client to specify how many + * results should be returned at most. If the limit is not specified, it + * should be treated as infinite. + * + * If the limit (infinite or not) is higher than you're willing to return, + * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. + * + * If the syncToken is expired (due to data cleanup) or unknown, you must + * return null. + * + * The limit is 'suggestive'. You are free to ignore it. + * + * @param string $syncToken + * @param int $syncLevel + * @param int $limit + * @return array + */ + function getChanges($syncToken, $syncLevel, $limit = null); + +} diff --git a/vendor/sabre/dav/lib/DAV/Sync/Plugin.php b/vendor/sabre/dav/lib/DAV/Sync/Plugin.php new file mode 100644 index 000000000..4a141c72b --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Sync/Plugin.php @@ -0,0 +1,277 @@ +<?php + +namespace Sabre\DAV\Sync; + +use Sabre\DAV; +use Sabre\HTTP\RequestInterface; +use Sabre\DAV\Xml\Request\SyncCollectionReport; + +/** + * This plugin all WebDAV-sync capabilities to the Server. + * + * WebDAV-sync is defined by rfc6578 + * + * The sync capabilities only work with collections that implement + * Sabre\DAV\Sync\ISyncCollection. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * Reference to server object + * + * @var DAV\Server + */ + protected $server; + + const SYNCTOKEN_PREFIX = 'http://sabre.io/ns/sync/'; + + /** + * 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 'sync'; + + } + + /** + * Initializes the plugin. + * + * This is when the plugin registers it's hooks. + * + * @param DAV\Server $server + * @return void + */ + function initialize(DAV\Server $server) { + + $this->server = $server; + $server->xml->elementMap['{DAV:}sync-collection'] = 'Sabre\\DAV\\Xml\\Request\\SyncCollectionReport'; + + $self = $this; + + $server->on('report', function($reportName, $dom, $uri) use ($self) { + + if ($reportName === '{DAV:}sync-collection') { + $this->server->transactionType = 'report-sync-collection'; + $self->syncCollection($uri, $dom); + return false; + } + + }); + + $server->on('propFind', [$this, 'propFind']); + $server->on('validateTokens', [$this, 'validateTokens']); + + } + + /** + * Returns a list of reports this plugin supports. + * + * This will be used in the {DAV:}supported-report-set property. + * Note that you still need to subscribe to the 'report' event to actually + * implement them + * + * @param string $uri + * @return array + */ + function getSupportedReportSet($uri) { + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof ISyncCollection && $node->getSyncToken()) { + return [ + '{DAV:}sync-collection', + ]; + } + + return []; + + } + + + /** + * This method handles the {DAV:}sync-collection HTTP REPORT. + * + * @param string $uri + * @param SyncCollectionReport $report + * @return void + */ + function syncCollection($uri, SyncCollectionReport $report) { + + // Getting the data + $node = $this->server->tree->getNodeForPath($uri); + if (!$node instanceof ISyncCollection) { + throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); + } + $token = $node->getSyncToken(); + if (!$token) { + throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); + } + + $syncToken = $report->syncToken; + if (!is_null($syncToken)) { + // Sync-token must start with our prefix + if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + } + + $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); + + } + $changeInfo = $node->getChanges($syncToken, $report->syncLevel, $report->limit); + + if (is_null($changeInfo)) { + + throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); + + } + + // Encoding the response + $this->sendSyncCollectionResponse( + $changeInfo['syncToken'], + $uri, + $changeInfo['added'], + $changeInfo['modified'], + $changeInfo['deleted'], + $report->properties + ); + + } + + /** + * Sends the response to a sync-collection request. + * + * @param string $syncToken + * @param string $collectionUrl + * @param array $added + * @param array $modified + * @param array $deleted + * @param array $properties + * @return void + */ + protected function sendSyncCollectionResponse($syncToken, $collectionUrl, array $added, array $modified, array $deleted, array $properties) { + + + $fullPaths = []; + + // Pre-fetching children, if this is possible. + foreach (array_merge($added, $modified) as $item) { + $fullPath = $collectionUrl . '/' . $item; + $fullPaths[] = $fullPath; + } + + $responses = []; + foreach ($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { + + // The 'Property_Response' class is responsible for generating a + // single {DAV:}response xml element. + $responses[] = new DAV\Xml\Element\Response($fullPath, $props); + + } + + + + // Deleted items also show up as 'responses'. They have no properties, + // and a single {DAV:}status element set as 'HTTP/1.1 404 Not Found'. + foreach ($deleted as $item) { + + $fullPath = $collectionUrl . '/' . $item; + $responses[] = new DAV\Xml\Element\Response($fullPath, [], 404); + + } + $multiStatus = new DAV\Xml\Response\MultiStatus($responses, self::SYNCTOKEN_PREFIX . $syncToken); + + $this->server->httpResponse->setStatus(207); + $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); + $this->server->httpResponse->setBody( + $this->server->xml->write('{DAV:}multistatus', $multiStatus, $this->server->getBaseUri()) + ); + + } + + /** + * This method is triggered whenever properties are requested for a node. + * We intercept this to see if we must return a {DAV:}sync-token. + * + * @param DAV\PropFind $propFind + * @param DAV\INode $node + * @return void + */ + function propFind(DAV\PropFind $propFind, DAV\INode $node) { + + $propFind->handle('{DAV:}sync-token', function() use ($node) { + if (!$node instanceof ISyncCollection || !$token = $node->getSyncToken()) { + return; + } + return self::SYNCTOKEN_PREFIX . $token; + }); + + } + + /** + * The validateTokens event is triggered before every request. + * + * It's a moment where this plugin can check all the supplied lock tokens + * in the If: header, and check if they are valid. + * + * @param RequestInterface $request + * @param array $conditions + * @return void + */ + function validateTokens(RequestInterface $request, &$conditions) { + + foreach ($conditions as $kk => $condition) { + + foreach ($condition['tokens'] as $ii => $token) { + + // Sync-tokens must always start with our designated prefix. + if (substr($token['token'], 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { + continue; + } + + // Checking if the token is a match. + $node = $this->server->tree->getNodeForPath($condition['uri']); + + if ( + $node instanceof ISyncCollection && + $node->getSyncToken() == substr($token['token'], strlen(self::SYNCTOKEN_PREFIX)) + ) { + $conditions[$kk]['tokens'][$ii]['validToken'] = true; + } + + } + + } + + } + + /** + * 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' => 'Adds support for WebDAV Collection Sync (rfc6578)', + 'link' => 'http://sabre.io/dav/sync/', + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php b/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php new file mode 100644 index 000000000..c5b8aa1ca --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php @@ -0,0 +1,297 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\HTTP\URLUtil; + +/** + * Temporary File Filter Plugin + * + * The purpose of this filter is to intercept some of the garbage files + * operation systems and applications tend to generate when mounting + * a WebDAV share as a disk. + * + * It will intercept these files and place them in a separate directory. + * these files are not deleted automatically, so it is adviceable to + * delete these after they are not accessed for 24 hours. + * + * Currently it supports: + * * OS/X style resource forks and .DS_Store + * * desktop.ini and Thumbs.db (windows) + * * .*.swp (vim temporary files) + * * .dat.* (smultron temporary files) + * + * Additional patterns can be added, by adding on to the + * temporaryFilePatterns property. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class TemporaryFileFilterPlugin extends ServerPlugin { + + /** + * This is the list of patterns we intercept. + * If new patterns are added, they must be valid patterns for preg_match. + * + * @var array + */ + public $temporaryFilePatterns = [ + '/^\._(.*)$/', // OS/X resource forks + '/^.DS_Store$/', // OS/X custom folder settings + '/^desktop.ini$/', // Windows custom folder settings + '/^Thumbs.db$/', // Windows thumbnail cache + '/^.(.*).swp$/', // ViM temporary files + '/^\.dat(.*)$/', // Smultron seems to create these + '/^~lock.(.*)#$/', // Windows 7 lockfiles + ]; + + /** + * A reference to the main Server class + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * This is the directory where this plugin + * will store it's files. + * + * @var string + */ + private $dataDir; + + /** + * Creates the plugin. + * + * Make sure you specify a directory for your files. If you don't, we + * will use PHP's directory for session-storage instead, and you might + * not want that. + * + * @param string|null $dataDir + */ + function __construct($dataDir = null) { + + if (!$dataDir) $dataDir = ini_get('session.save_path') . '/sabredav/'; + if (!is_dir($dataDir)) mkdir($dataDir); + $this->dataDir = $dataDir; + + } + + /** + * Initialize the plugin + * + * This is called automatically be the Server class after this plugin is + * added with Sabre\DAV\Server::addPlugin() + * + * @param Server $server + * @return void + */ + function initialize(Server $server) { + + $this->server = $server; + $server->on('beforeMethod', [$this, 'beforeMethod']); + $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); + + } + + /** + * This method is called before any HTTP method handler + * + * This method intercepts any GET, DELETE, PUT and PROPFIND calls to + * filenames that are known to match the 'temporary file' regex. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return bool + */ + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + if (!$tempLocation = $this->isTempFile($request->getPath())) + return; + + switch ($request->getMethod()) { + case 'GET' : + return $this->httpGet($request, $response, $tempLocation); + case 'PUT' : + return $this->httpPut($request, $response, $tempLocation); + case 'PROPFIND' : + return $this->httpPropfind($request, $response, $tempLocation); + case 'DELETE' : + return $this->httpDelete($request, $response, $tempLocation); + } + return; + + } + + /** + * This method is invoked if some subsystem creates a new file. + * + * This is used to deal with HTTP LOCK requests which create a new + * file. + * + * @param string $uri + * @param resource $data + * @param DAV\ICollection $parentNode + * @param bool $modified Should be set to true, if this event handler + * changed &$data. + * @return bool + */ + function beforeCreateFile($uri, $data, $parent, $modified) { + + if ($tempPath = $this->isTempFile($uri)) { + + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp', 'true'); + file_put_contents($tempPath, $data); + return false; + } + return; + + } + + /** + * This method will check if the url matches the temporary file pattern + * if it does, it will return an path based on $this->dataDir for the + * temporary file storage. + * + * @param string $path + * @return bool|string + */ + protected function isTempFile($path) { + + // We're only interested in the basename. + list(, $tempPath) = URLUtil::splitPath($path); + + foreach ($this->temporaryFilePatterns as $tempFile) { + + if (preg_match($tempFile, $tempPath)) { + return $this->getDataDir() . '/sabredav_' . md5($path) . '.tempfile'; + } + + } + + return false; + + } + + + /** + * This method handles the GET method for temporary files. + * If the file doesn't exist, it will return false which will kick in + * the regular system for the GET method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('Content-Type', 'application/octet-stream'); + $hR->setHeader('Content-Length', filesize($tempLocation)); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(200); + $hR->setBody(fopen($tempLocation, 'r')); + return false; + + } + + /** + * This method handles the PUT method. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPut(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + $hR->setHeader('X-Sabre-Temp', 'true'); + + $newFile = !file_exists($tempLocation); + + if (!$newFile && ($this->server->httpRequest->getHeader('If-None-Match'))) { + throw new Exception\PreconditionFailed('The resource already exists, and an If-None-Match header was supplied'); + } + + file_put_contents($tempLocation, $this->server->httpRequest->getBody()); + $hR->setStatus($newFile ? 201 : 200); + return false; + + } + + /** + * This method handles the DELETE method. + * + * If the file didn't exist, it will return false, which will make the + * standard HTTP DELETE handler kick in. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpDelete(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + unlink($tempLocation); + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(204); + return false; + + } + + /** + * This method handles the PROPFIND method. + * + * It's a very lazy method, it won't bother checking the request body + * for which properties were requested, and just sends back a default + * set of properties. + * + * @param RequestInterface $request + * @param ResponseInterface $hR + * @param string $tempLocation + * @return bool + */ + function httpPropfind(RequestInterface $request, ResponseInterface $hR, $tempLocation) { + + if (!file_exists($tempLocation)) return; + + $hR->setHeader('X-Sabre-Temp', 'true'); + $hR->setStatus(207); + $hR->setHeader('Content-Type', 'application/xml; charset=utf-8'); + + $properties = [ + 'href' => $request->getPath(), + 200 => [ + '{DAV:}getlastmodified' => new Xml\Property\GetLastModified(filemtime($tempLocation)), + '{DAV:}getcontentlength' => filesize($tempLocation), + '{DAV:}resourcetype' => new Xml\Property\ResourceType(null), + '{' . Server::NS_SABREDAV . '}tempFile' => true, + + ], + ]; + + $data = $this->server->generateMultiStatus([$properties]); + $hR->setBody($data); + return false; + + } + + + /** + * This method returns the directory where the temporary files should be stored. + * + * @return string + */ + protected function getDataDir() + { + return $this->dataDir; + } +} diff --git a/vendor/sabre/dav/lib/DAV/Tree.php b/vendor/sabre/dav/lib/DAV/Tree.php new file mode 100644 index 000000000..4563f7c72 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Tree.php @@ -0,0 +1,340 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\URLUtil; + +/** + * The tree object is responsible for basic tree operations. + * + * It allows for fetching nodes by path, facilitates deleting, copying and + * moving. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Tree { + + /** + * The root node + * + * @var ICollection + */ + protected $rootNode; + + /** + * This is the node cache. Accessed nodes are stored here. + * Arrays keys are path names, values are the actual nodes. + * + * @var array + */ + protected $cache = []; + + /** + * Creates the object + * + * This method expects the rootObject to be passed as a parameter + * + * @param ICollection $rootNode + */ + function __construct(ICollection $rootNode) { + + $this->rootNode = $rootNode; + + } + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @return INode + */ + 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; + } + + // Attempting to fetch its parent + list($parentName, $baseName) = URLUtil::splitPath($path); + + // If there was no parent, we must simply ask it from the root node. + if ($parentName === "") { + $node = $this->rootNode->getChild($baseName); + } else { + // Otherwise, we recursively grab the parent and ask him/her. + $parent = $this->getNodeForPath($parentName); + + if (!($parent instanceof ICollection)) + throw new Exception\NotFound('Could not find node at path: ' . $path); + + $node = $parent->getChild($baseName); + + } + + $this->cache[$path] = $node; + return $node; + + } + + /** + * This function allows you to check if a node exists. + * + * Implementors of this class should override this method to make + * it cheaper. + * + * @param string $path + * @return bool + */ + function nodeExists($path) { + + try { + + // The root always exists + if ($path === '') return true; + + list($parent, $base) = URLUtil::splitPath($path); + + $parentNode = $this->getNodeForPath($parent); + if (!$parentNode instanceof ICollection) return false; + return $parentNode->childExists($base); + + } catch (Exception\NotFound $e) { + + return false; + + } + + } + + /** + * Copies a file from path to another + * + * @param string $sourcePath The source location + * @param string $destinationPath The full destination path + * @return void + */ + function copy($sourcePath, $destinationPath) { + + $sourceNode = $this->getNodeForPath($sourcePath); + + // grab the dirname and basename components + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + $destinationParent = $this->getNodeForPath($destinationDir); + $this->copyNode($sourceNode, $destinationParent, $destinationName); + + $this->markDirty($destinationDir); + + } + + /** + * 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 + * @return int + */ + function move($sourcePath, $destinationPath) { + + list($sourceDir) = URLUtil::splitPath($sourcePath); + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + if ($sourceDir === $destinationDir) { + // If this is a 'local' rename, it means we can just trigger a rename. + $sourceNode = $this->getNodeForPath($sourcePath); + $sourceNode->setName($destinationName); + } else { + $newParentNode = $this->getNodeForPath($destinationDir); + $moveSuccess = false; + if ($newParentNode instanceof IMoveTarget) { + // The target collection may be able to handle the move + $sourceNode = $this->getNodeForPath($sourcePath); + $moveSuccess = $newParentNode->moveInto($destinationName, $sourcePath, $sourceNode); + } + if (!$moveSuccess) { + $this->copy($sourcePath, $destinationPath); + $this->getNodeForPath($sourcePath)->delete(); + } + } + $this->markDirty($sourceDir); + $this->markDirty($destinationDir); + + } + + /** + * Deletes a node from the tree + * + * @param string $path + * @return void + */ + function delete($path) { + + $node = $this->getNodeForPath($path); + $node->delete(); + + list($parent) = URLUtil::splitPath($path); + $this->markDirty($parent); + + } + + /** + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array + */ + function getChildren($path) { + + $node = $this->getNodeForPath($path); + $children = $node->getChildren(); + $basePath = trim($path, '/'); + if ($basePath !== '') $basePath .= '/'; + + foreach ($children as $child) { + + $this->cache[$basePath . $child->getName()] = $child; + + } + return $children; + + } + + /** + * This method is called with every tree update + * + * Examples of tree updates are: + * * node deletions + * * node creations + * * copy + * * move + * * renaming nodes + * + * If Tree classes implement a form of caching, this will allow + * them to make sure caches will be expired. + * + * If a path is passed, it is assumed that the entire subtree is dirty + * + * @param string $path + * @return void + */ + function markDirty($path) { + + // We don't care enough about sub-paths + // flushing the entire cache + $path = trim($path, '/'); + foreach ($this->cache as $nodePath => $node) { + if ($nodePath == $path || strpos($nodePath, $path . '/') === 0) + unset($this->cache[$nodePath]); + + } + + } + + /** + * This method tells the tree system to pre-fetch and cache a list of + * children of a single parent. + * + * There are a bunch of operations in the WebDAV stack that request many + * children (based on uris), and sometimes fetching many at once can + * optimize this. + * + * This method returns an array with the found nodes. It's keys are the + * original paths. The result may be out of order. + * + * @param array $paths List of nodes that must be fetched. + * @return array + */ + function getMultipleNodes($paths) { + + // Finding common parents + $parents = []; + foreach ($paths as $path) { + list($parent, $node) = URLUtil::splitPath($path); + if (!isset($parents[$parent])) { + $parents[$parent] = [$node]; + } else { + $parents[$parent][] = $node; + } + } + + $result = []; + + foreach ($parents as $parent => $children) { + + $parentNode = $this->getNodeForPath($parent); + if ($parentNode instanceof IMultiGet) { + foreach ($parentNode->getMultipleChildren($children) as $childNode) { + $fullPath = $parent . '/' . $childNode->getName(); + $result[$fullPath] = $childNode; + $this->cache[$fullPath] = $childNode; + } + } else { + foreach ($children as $child) { + $fullPath = $parent . '/' . $child; + $result[$fullPath] = $this->getNodeForPath($fullPath); + } + } + + } + + return $result; + + } + + + /** + * copyNode + * + * @param INode $source + * @param ICollection $destinationParent + * @param string $destinationName + * @return void + */ + protected function copyNode(INode $source, ICollection $destinationParent, $destinationName = null) { + + if (!$destinationName) $destinationName = $source->getName(); + + if ($source instanceof IFile) { + + $data = $source->get(); + + // If the body was a string, we need to convert it to a stream + if (is_string($data)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $data); + rewind($stream); + $data = $stream; + } + $destinationParent->createFile($destinationName, $data); + $destination = $destinationParent->getChild($destinationName); + + } elseif ($source instanceof ICollection) { + + $destinationParent->createDirectory($destinationName); + + $destination = $destinationParent->getChild($destinationName); + foreach ($source->getChildren() as $child) { + + $this->copyNode($child, $destination); + + } + + } + if ($source instanceof IProperties && $destination instanceof IProperties) { + + $props = $source->getProperties([]); + $propPatch = new PropPatch($props); + $destination->propPatch($propPatch); + $propPatch->commit(); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/UUIDUtil.php b/vendor/sabre/dav/lib/DAV/UUIDUtil.php new file mode 100644 index 000000000..177adafd3 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/UUIDUtil.php @@ -0,0 +1,64 @@ +<?php + +namespace Sabre\DAV; + +/** + * UUID Utility + * + * This class has static methods to generate and validate UUID's. + * UUIDs are used a decent amount within various *DAV standards, so it made + * sense to include it. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class UUIDUtil { + + /** + * Returns a pseudo-random v4 UUID + * + * This function is based on a comment by Andrew Moore on php.net + * + * @see http://www.php.net/manual/en/function.uniqid.php#94959 + * @return string + */ + static function getUUID() { + + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + + // 16 bits for "time_mid" + mt_rand(0, 0xffff), + + // 16 bits for "time_hi_and_version", + // four most significant bits holds version number 4 + mt_rand(0, 0x0fff) | 0x4000, + + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + mt_rand(0, 0x3fff) | 0x8000, + + // 48 bits for "node" + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Checks if a string is a valid UUID. + * + * @param string $uuid + * @return bool + */ + static function validateUUID($uuid) { + + return preg_match( + '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', + $uuid + ) !== 0; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Version.php b/vendor/sabre/dav/lib/DAV/Version.php new file mode 100644 index 000000000..f9331943a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\DAV; + +/** + * This class contains the SabreDAV version constants. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '3.1.3'; + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php b/vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php new file mode 100644 index 000000000..db5332c50 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php @@ -0,0 +1,116 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV\Xml\Property\Complex; +use Sabre\Xml\XmlDeserializable; +use Sabre\Xml\Reader; + +/** + * This class is responsible for decoding the {DAV:}prop element as it appears + * in {DAV:}property-update. + * + * This class doesn't return an instance of itself. It just returns a + * key->value array. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Prop implements XmlDeserializable { + + /** + * 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) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + + $values = []; + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT) { + + $clark = $reader->getClark(); + $values[$clark] = self::parseCurrentElement($reader)['value']; + + } else { + $reader->read(); + } + + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $values; + + } + + /** + * This function behaves similar to Sabre\Xml\Reader::parseCurrentElement, + * but instead of creating deep xml array structures, it will turn any + * top-level element it doesn't recognize into either a string, or an + * XmlFragment class. + * + * This method returns arn array with 2 properties: + * * name - A clark-notation XML element name. + * * value - The parsed value. + * + * @param Reader $reader + * @return array + */ + private static function parseCurrentElement(Reader $reader) { + + $name = $reader->getClark(); + + if (array_key_exists($name, $reader->elementMap)) { + $deserializer = $reader->elementMap[$name]; + if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) { + $value = call_user_func([ $deserializer, 'xmlDeserialize' ], $reader); + } elseif (is_callable($deserializer)) { + $value = call_user_func($deserializer, $reader); + } else { + $type = gettype($deserializer); + if ($type === 'string') { + $type .= ' (' . $deserializer . ')'; + } elseif ($type === 'object') { + $type .= ' (' . get_class($deserializer) . ')'; + } + throw new \LogicException('Could not use this type as a deserializer: ' . $type); + } + } else { + $value = Complex::xmlDeserialize($reader); + } + + return [ + 'name' => $name, + 'value' => $value, + ]; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php b/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php new file mode 100644 index 000000000..97a2bb59f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php @@ -0,0 +1,253 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * WebDAV {DAV:}response parser + * + * This class parses the {DAV:}response element, as defined in: + * + * https://tools.ietf.org/html/rfc4918#section-14.24 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Response implements Element { + + /** + * Url for the response + * + * @var string + */ + protected $href; + + /** + * Propertylist, ordered by HTTP status code + * + * @var array + */ + protected $responseProperties; + + /** + * The HTTP status for an entire response. + * + * This is currently only used in WebDAV-Sync + * + * @var string + */ + protected $httpStatus; + + /** + * The href argument is a url relative to the root of the server. This + * class will calculate the full path. + * + * The responseProperties argument is a list of properties + * within an array with keys representing HTTP status codes + * + * Besides specific properties, the entire {DAV:}response element may also + * have a http status code. + * In most cases you don't need it. + * + * This is currently used by the Sync extension to indicate that a node is + * deleted. + * + * @param string $href + * @param array $responseProperties + * @param string $httpStatus + */ + function __construct($href, array $responseProperties, $httpStatus = null) { + + $this->href = $href; + $this->responseProperties = $responseProperties; + $this->httpStatus = $httpStatus; + + } + + /** + * Returns the url + * + * @return string + */ + function getHref() { + + return $this->href; + + } + + /** + * Returns the httpStatus value + * + * @return string + */ + function getHttpStatus() { + + return $this->httpStatus; + + } + + /** + * Returns the property list + * + * @return array + */ + function getResponseProperties() { + + return $this->responseProperties; + + } + + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + if ($status = $this->getHTTPStatus()) { + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + } + $writer->writeElement('{DAV:}href', $writer->contextUri . \Sabre\HTTP\encodePath($this->getHref())); + + $empty = true; + + foreach ($this->getResponseProperties() as $status => $properties) { + + // Skipping empty lists + if (!$properties || (!ctype_digit($status) && !is_int($status))) { + continue; + } + $empty = false; + $writer->startElement('{DAV:}propstat'); + $writer->writeElement('{DAV:}prop', $properties); + $writer->writeElement('{DAV:}status', 'HTTP/1.1 ' . $status . ' ' . \Sabre\HTTP\Response::$statusCodes[$status]); + $writer->endElement(); // {DAV:}propstat + + } + if ($empty) { + /* + * The WebDAV spec _requires_ at least one DAV:propstat to appear for + * every DAV:response. In some circumstances however, there are no + * properties to encode. + * + * In those cases we MUST specify at least one DAV:propstat anyway, with + * no properties. + */ + $writer->writeElement('{DAV:}propstat', [ + '{DAV:}prop' => [], + '{DAV:}status' => 'HTTP/1.1 418 ' . \Sabre\HTTP\Response::$statusCodes[418] + ]); + + } + + } + + /** + * 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) { + + $reader->pushContext(); + + $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue'; + + // We are overriding the parser for {DAV:}prop. This deserializer is + // almost identical to the one for Sabre\Xml\Element\KeyValue. + // + // The difference is that if there are any child-elements inside of + // {DAV:}prop, that have no value, normally any deserializers are + // called. But we don't want this, because a singular element without + // child-elements implies 'no value' in {DAV:}prop, so we want to skip + // deserializers and just set null for those. + $reader->elementMap['{DAV:}prop'] = function(Reader $reader) { + + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + $values = []; + $reader->read(); + do { + if ($reader->nodeType === Reader::ELEMENT) { + $clark = $reader->getClark(); + + if ($reader->isEmptyElement) { + $values[$clark] = null; + $reader->next(); + } else { + $values[$clark] = $reader->parseCurrentElement()['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + $reader->read(); + return $values; + + }; + $elems = $reader->parseInnerTree(); + $reader->popContext(); + + $href = null; + $propertyLists = []; + $statusCode = null; + + foreach ($elems as $elem) { + + switch ($elem['name']) { + + case '{DAV:}href' : + $href = $elem['value']; + break; + case '{DAV:}propstat' : + $status = $elem['value']['{DAV:}status']; + list(, $status, ) = explode(' ', $status, 3); + $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : []; + if ($properties) $propertyLists[$status] = $properties; + break; + case '{DAV:}status' : + list(, $statusCode, ) = explode(' ', $elem['value'], 3); + break; + + } + + } + + return new self($href, $propertyLists, $statusCode); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php new file mode 100644 index 000000000..1d9202082 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php @@ -0,0 +1,89 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\Xml\Element\XmlFragment; +use Sabre\Xml\Reader; + +/** + * This class represents a 'complex' property that didn't have a default + * decoder. + * + * It's basically a container for an xml snippet. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Complex extends XmlFragment { + + /** + * 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) { + + $xml = $reader->readInnerXml(); + + if ($reader->nodeType === Reader::ELEMENT && $reader->isEmptyElement) { + // Easy! + $reader->next(); + return null; + } + // Now we have a copy of the inner xml, we need to traverse it to get + // all the strings. If there's no non-string data, we just return the + // string, otherwise we return an instance of this class. + $reader->read(); + + $nonText = false; + $text = ''; + + while (true) { + + switch ($reader->nodeType) { + case Reader::ELEMENT : + $nonText = true; + $reader->next(); + continue 2; + case Reader::TEXT : + case Reader::CDATA : + $text .= $reader->value; + break; + case Reader::END_ELEMENT : + break 2; + } + $reader->read(); + + } + + // Make sure we advance the cursor one step further. + $reader->read(); + + if ($nonText) { + $new = new self($xml); + return $new; + } else { + return $text; + } + + } + + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php b/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php new file mode 100644 index 000000000..2db47269f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php @@ -0,0 +1,110 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; +use Sabre\HTTP; +use DateTime; +use DateTimeZone; + +/** + * This property represents the {DAV:}getlastmodified property. + * + * Defined in: + * http://tools.ietf.org/html/rfc4918#section-15.7 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class GetLastModified implements Element { + + /** + * time + * + * @var DateTime + */ + public $time; + + /** + * Constructor + * + * @param int|DateTime $time + */ + function __construct($time) { + + if ($time instanceof DateTime) { + $this->time = clone $time; + } else { + $this->time = new DateTime('@' . $time); + } + + // Setting timezone to UTC + $this->time->setTimezone(new DateTimeZone('UTC')); + + } + + /** + * getTime + * + * @return DateTime + */ + function getTime() { + + return $this->time; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + $writer->write( + HTTP\Util::toHTTPDate($this->time) + ); + + } + + /** + * 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. + * + * Important note 2: 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) { + + return + new self(new DateTime($reader->parseInnerTree())); + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php new file mode 100644 index 000000000..538e98d0f --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php @@ -0,0 +1,176 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Browser\HtmlOutput; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * Href property + * + * This class represents any WebDAV property that contains a {DAV:}href + * element, and there are many. + * + * It can support either 1 or more hrefs. If while unserializing no valid + * {DAV:}href elements were found, this property will unserialize itself as + * null. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Href implements Element, HtmlOutput { + + /** + * List of uris + * + * @var array + */ + 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. + * + * 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 + * @param bool $autoPrefix + */ + function __construct($hrefs, $autoPrefix = true) { + + if (is_string($hrefs)) { + $hrefs = [$hrefs]; + } + $this->hrefs = $hrefs; + $this->autoPrefix = $autoPrefix; + + + } + + /** + * Returns the first Href. + * + * @return string + */ + function getHref() { + + return $this->hrefs[0]; + + } + + /** + * Returns the hrefs as an array + * + * @return array + */ + function getHrefs() { + + return $this->hrefs; + + } + + /** + * The xmlSerialize metod 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->getHrefs() as $href) { + if ($this->autoPrefix) { + $href = $writer->contextUri . \Sabre\HTTP\encodePath($href); + } + $writer->writeElement('{DAV:}href', $href); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + $links = []; + foreach ($this->getHrefs() as $href) { + $links[] = $html->link($href); + } + return implode('<br />', $links); + + } + + /** + * 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) { + + $hrefs = []; + foreach ((array)$reader->parseInnerTree() as $elem) { + if ($elem['name'] !== '{DAV:}href') + continue; + + $hrefs[] = $elem['value']; + + } + if ($hrefs) { + return new self($hrefs, false); + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php b/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php new file mode 100644 index 000000000..f4b692219 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php @@ -0,0 +1,106 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Locks\LockInfo; +use Sabre\Xml\Element\XmlFragment; +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +/** + * Represents {DAV:}lockdiscovery property. + * + * This property is defined here: + * http://tools.ietf.org/html/rfc4918#section-15.8 + * + * This property contains all the open locks on a given resource + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LockDiscovery implements XmlSerializable { + + /** + * locks + * + * @var LockInfo[] + */ + public $locks; + + /** + * Hides the {DAV:}lockroot element from the response. + * + * It was reported that showing the lockroot in the response can break + * Office 2000 compatibility. + * + * @var bool + */ + static $hideLockRoot = false; + + /** + * __construct + * + * @param LockInfo[] $locks + */ + function __construct($locks) { + + $this->locks = $locks; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->locks as $lock) { + + $writer->startElement('{DAV:}activelock'); + + $writer->startElement('{DAV:}lockscope'); + if ($lock->scope === LockInfo::SHARED) { + $writer->writeElement('{DAV:}shared'); + } else { + $writer->writeElement('{DAV:}exclusive'); + } + + $writer->endElement(); // {DAV:}lockscope + + $writer->startElement('{DAV:}locktype'); + $writer->writeElement('{DAV:}write'); + $writer->endElement(); // {DAV:}locktype + + if (!self::$hideLockRoot) { + $writer->startElement('{DAV:}lockroot'); + $writer->writeElement('{DAV:}href', $writer->contextUri . $lock->uri); + $writer->endElement(); // {DAV:}lockroot + } + $writer->writeElement('{DAV:}depth', ($lock->depth == DAV\Server::DEPTH_INFINITY ? 'infinity' : $lock->depth)); + $writer->writeElement('{DAV:}timeout', 'Second-' . $lock->timeout); + + $writer->startElement('{DAV:}locktoken'); + $writer->writeElement('{DAV:}href', 'opaquelocktoken:' . $lock->token); + $writer->endElement(); // {DAV:}locktoken + + $writer->writeElement('{DAV:}owner', new XmlFragment($lock->owner)); + $writer->endElement(); // {DAV:}activelock + + } + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php b/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php new file mode 100644 index 000000000..302888321 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php @@ -0,0 +1,128 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Browser\HtmlOutput; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\Xml\Element; +use Sabre\Xml\Reader; + +/** + * {DAV:}resourcetype property + * + * This class represents the {DAV:}resourcetype property, as defined in: + * + * https://tools.ietf.org/html/rfc4918#section-15.9 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ResourceType extends Element\Elements implements HtmlOutput { + + /** + * Constructor + * + * You can either pass null (for no resourcetype), a string (for a single + * resourcetype) or an array (for multiple). + * + * The resourcetype must be specified in clark-notation + * + * @param array|string|null $resourceType + */ + function __construct($resourceTypes = null) { + + parent::__construct((array)$resourceTypes); + + } + + /** + * Returns the values in clark-notation + * + * For example array('{DAV:}collection') + * + * @return array + */ + function getValue() { + + return $this->value; + + } + + /** + * Checks if the principal contains a certain value + * + * @param string $type + * @return bool + */ + function is($type) { + + return in_array($type, $this->value); + + } + + /** + * Adds a resourcetype value to this property + * + * @param string $type + * @return void + */ + function add($type) { + + $this->value[] = $type; + $this->value = array_unique($this->value); + + } + + /** + * 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. + * + * Important note 2: 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) { + + return + new self(parent::xmlDeserialize($reader)); + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php new file mode 100644 index 000000000..f6d01aa37 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php @@ -0,0 +1,54 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +/** + * This class represents the {DAV:}supportedlock property. + * + * This property is defined here: + * http://tools.ietf.org/html/rfc4918#section-15.10 + * + * This property contains information about what kind of locks + * this server supports. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SupportedLock implements XmlSerializable { + + /** + * The xmlSerialize metod 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->writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}exclusive' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + $writer->writeElement('{DAV:}lockentry', [ + '{DAV:}lockscope' => ['{DAV:}shared' => null], + '{DAV:}locktype' => ['{DAV:}write' => null], + ]); + + } +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php new file mode 100644 index 000000000..56b418db6 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php @@ -0,0 +1,126 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Browser\HtmlOutput; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +/** + * supported-method-set property. + * + * This property is defined in RFC3253, but since it's + * so common in other webdav-related specs, it is part of the core server. + * + * This property is defined here: + * http://tools.ietf.org/html/rfc3253#section-3.1.3 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SupportedMethodSet implements XmlSerializable, HtmlOutput { + + /** + * List of methods + * + * @var string[] + */ + protected $methods = []; + + /** + * Creates the property + * + * Any reports passed in the constructor + * should be valid report-types in clark-notation. + * + * Either a string or an array of strings must be passed. + * + * @param string|string[] $methods + */ + function __construct($methods = null) { + + $this->methods = (array)$methods; + + } + + /** + * Returns the list of supported http methods. + * + * @return string[] + */ + function getValue() { + + return $this->methods; + + } + + /** + * Returns true or false if the property contains a specific method. + * + * @param string $methodName + * @return bool + */ + function has($methodName) { + + return in_array( + $methodName, + $this->methods + ); + + } + + /** + * The xmlSerialize metod 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->getValue() as $val) { + $writer->startElement('{DAV:}supported-method'); + $writer->writeAttribute('name', $val); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'h'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php new file mode 100644 index 000000000..ebf27300d --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php @@ -0,0 +1,154 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutput; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +/** + * supported-report-set property. + * + * This property is defined in RFC3253, but since it's + * so common in other webdav-related specs, it is part of the core server. + * + * This property is defined here: + * http://tools.ietf.org/html/rfc3253#section-3.1.5 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SupportedReportSet implements XmlSerializable, HtmlOutput { + + /** + * List of reports + * + * @var array + */ + protected $reports = []; + + /** + * Creates the property + * + * Any reports passed in the constructor + * should be valid report-types in clark-notation. + * + * Either a string or an array of strings must be passed. + * + * @param string|string[] $reports + */ + function __construct($reports = null) { + + if (!is_null($reports)) + $this->addReport($reports); + + } + + /** + * Adds a report to this property + * + * The report must be a string in clark-notation. + * Multiple reports can be specified as an array. + * + * @param mixed $report + * @return void + */ + function addReport($report) { + + $report = (array)$report; + + foreach ($report as $r) { + + if (!preg_match('/^{([^}]*)}(.*)$/', $r)) + throw new DAV\Exception('Reportname must be in clark-notation'); + + $this->reports[] = $r; + + } + + } + + /** + * Returns the list of supported reports + * + * @return string[] + */ + function getValue() { + + return $this->reports; + + } + + /** + * Returns true or false if the property contains a specific report. + * + * @param string $reportName + * @return bool + */ + function has($reportName) { + + return in_array( + $reportName, + $this->reports + ); + + } + + /** + * The xmlSerialize metod 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->getValue() as $val) { + $writer->startElement('{DAV:}supported-report'); + $writer->startElement('{DAV:}report'); + $writer->writeElement($val); + $writer->endElement(); + $writer->endElement(); + } + + } + + /** + * Generate html representation for this value. + * + * The html output is 100% trusted, and no effort is being made to sanitize + * it. It's up to the implementor to sanitize user provided values. + * + * The output must be in UTF-8. + * + * The baseUri parameter is a url to the root of the application, and can + * be used to construct local links. + * + * @param HtmlOutputHelper $html + * @return string + */ + function toHtml(HtmlOutputHelper $html) { + + return implode( + ', ', + array_map([$html, 'xmlName'], $this->getValue()) + ); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php b/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php new file mode 100644 index 000000000..76df98d13 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php @@ -0,0 +1,81 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Locks\LockInfo; +use Sabre\Xml\Element\KeyValue; +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +/** + * WebDAV LOCK request parser. + * + * This class parses the {DAV:}lockinfo request, as defined in: + * + * http://tools.ietf.org/html/rfc4918#section-9.10 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Lock implements XmlDeserializable { + + /** + * Owner of the lock + * + * @var string + */ + public $owner; + + /** + * Scope of the lock. + * + * Either LockInfo::SHARED or LockInfo::EXCLUSIVE + * @var int + */ + public $scope; + + /** + * 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) { + + $reader->pushContext(); + $reader->elementMap['{DAV:}owner'] = 'Sabre\\Xml\\Element\\XmlFragment'; + + $values = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $new = new self(); + $new->owner = !empty($values['{DAV:}owner']) ? $values['{DAV:}owner']->getXml() : null; + $new->scope = LockInfo::SHARED; + + if (isset($values['{DAV:}lockscope'])) { + foreach ($values['{DAV:}lockscope'] as $elem) { + if ($elem['name'] === '{DAV:}exclusive') $new->scope = LockInfo::EXCLUSIVE; + } + } + return $new; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php b/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php new file mode 100644 index 000000000..5db239061 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php @@ -0,0 +1,82 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +/** + * WebDAV Extended MKCOL request parser. + * + * This class parses the {DAV:}mkol request, as defined in: + * + * https://tools.ietf.org/html/rfc5689#section-5.1 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MkCol implements XmlDeserializable { + + /** + * The list of properties that will be set. + * + * @var array + */ + protected $properties = []; + + /** + * Returns a key=>value array with properties that are supposed to get set + * during creation of the new collection. + * + * @return array + */ + function getProperties() { + + return $this->properties; + + } + + /** + * 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) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php b/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php new file mode 100644 index 000000000..ad3ad7c43 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php @@ -0,0 +1,83 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\Xml\Element\KeyValue; +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +/** + * WebDAV PROPFIND request parser. + * + * This class parses the {DAV:}propfind request, as defined in: + * + * https://tools.ietf.org/html/rfc4918#section-14.20 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropFind implements XmlDeserializable { + + /** + * If this is set to true, this was an 'allprop' request. + * + * @var bool + */ + public $allProp = false; + + /** + * The property list + * + * @var null|array + */ + public $properties; + + /** + * 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) { + + $self = new self(); + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + + foreach (KeyValue::xmlDeserialize($reader) as $k => $v) { + + switch ($k) { + case '{DAV:}prop' : + $self->properties = $v; + break; + case '{DAV:}allprop' : + $self->allProp = true; + + } + + } + + $reader->popContext(); + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php b/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php new file mode 100644 index 000000000..07a05f887 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php @@ -0,0 +1,118 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * WebDAV PROPPATCH request parser. + * + * This class parses the {DAV:}propertyupdate request, as defined in: + * + * https://tools.ietf.org/html/rfc4918#section-14.20 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropPatch implements Element { + + /** + * The list of properties that will be updated and removed. + * + * If a property will be removed, it's value will be set to null. + * + * @var array + */ + public $properties = []; + + /** + * The xmlSerialize metod 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->properties as $propertyName => $propertyValue) { + + if (is_null($propertyValue)) { + $writer->startElement("{DAV:}remove"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } else { + $writer->startElement("{DAV:}set"); + $writer->write(['{DAV:}prop' => [$propertyName => $propertyValue]]); + $writer->endElement(); + } + + } + + } + + /** + * 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) { + + $self = new self(); + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\DAV\Xml\Element\Prop'; + $elementMap['{DAV:}set'] = 'Sabre\Xml\Element\KeyValue'; + $elementMap['{DAV:}remove'] = 'Sabre\Xml\Element\KeyValue'; + + $elems = $reader->parseInnerTree($elementMap); + + foreach ($elems as $elem) { + if ($elem['name'] === '{DAV:}set') { + $self->properties = array_merge($self->properties, $elem['value']['{DAV:}prop']); + } + if ($elem['name'] === '{DAV:}remove') { + + // Ensuring there are no values. + foreach ($elem['value']['{DAV:}prop'] as $remove => $value) { + $self->properties[$remove] = null; + } + + } + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php b/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php new file mode 100644 index 000000000..3092ada47 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; +use Sabre\Xml\Element\KeyValue; +use Sabre\DAV\Exception\BadRequest; + +/** + * SyncCollection request parser. + * + * This class parses the {DAV:}sync-collection reprot, as defined in: + * + * http://tools.ietf.org/html/rfc6578#section-3.2 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SyncCollectionReport implements XmlDeserializable { + + /** + * The sync-token the client supplied for the report. + * + * @var string|null + */ + public $syncToken; + + /** + * The 'depth' of the sync the client is interested in. + * + * @var int + */ + public $syncLevel; + + /** + * Maximum amount of items returned. + * + * @var int|null + */ + public $limit; + + /** + * The list of properties that are being requested for every change. + * + * @var null|array + */ + public $properties; + + /** + * 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) { + + $self = new self(); + + $reader->pushContext(); + + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + $elems = KeyValue::xmlDeserialize($reader); + + $reader->popContext(); + + $required = [ + '{DAV:}sync-token', + '{DAV:}prop', + ]; + + foreach ($required as $elem) { + if (!array_key_exists($elem, $elems)) { + throw new BadRequest('The ' . $elem . ' element in the {DAV:}sync-collection report is required'); + } + } + + + $self->properties = $elems['{DAV:}prop']; + $self->syncToken = $elems['{DAV:}sync-token']; + + if (isset($elems['{DAV:}limit'])) { + $nresults = null; + foreach ($elems['{DAV:}limit'] as $child) { + if ($child['name'] === '{DAV:}nresults') { + $nresults = (int)$child['value']; + } + } + $self->limit = $nresults; + } + + if (isset($elems['{DAV:}sync-level'])) { + + $value = $elems['{DAV:}sync-level']; + if ($value === 'infinity') { + $value = \Sabre\DAV\Server::DEPTH_INFINITY; + } + $self->syncLevel = $value; + + } + + return $self; + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php b/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php new file mode 100644 index 000000000..16a3d4a68 --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php @@ -0,0 +1,142 @@ +<?php + +namespace Sabre\DAV\Xml\Response; + +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * WebDAV MultiStatus parser + * + * This class parses the {DAV:}multistatus response, as defined in: + * https://tools.ietf.org/html/rfc4918#section-14.16 + * + * And it also adds the {DAV:}synctoken change from: + * http://tools.ietf.org/html/rfc6578#section-6.4 + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MultiStatus implements Element { + + /** + * The responses + * + * @var \Sabre\DAV\Xml\Element\Response[] + */ + protected $responses; + + /** + * A sync token (from RFC6578). + * + * @var string + */ + protected $syncToken; + + /** + * Constructor + * + * @param \Sabre\DAV\Xml\Element\Response[] $responses + * @param string $syncToken + */ + function __construct(array $responses, $syncToken = null) { + + $this->responses = $responses; + $this->syncToken = $syncToken; + + } + + /** + * Returns the response list. + * + * @return \Sabre\DAV\Xml\Element\Response[] + */ + function getResponses() { + + return $this->responses; + + } + + /** + * Returns the sync-token, if available. + * + * @return string|null + */ + function getSyncToken() { + + return $this->syncToken; + + } + + /** + * The serialize method is called during xml writing. + * + * It should use the $writer argument to encode this object into XML. + * + * Important note: it is not needed to create the parent element. The + * parent element is already created, and we only have to worry about + * attributes, child elements and text (if any). + * + * Important note 2: If you are writing any new elements, you are also + * responsible for closing them. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + + foreach ($this->getResponses() as $response) { + $writer->writeElement('{DAV:}response', $response); + } + if ($syncToken = $this->getSyncToken()) { + $writer->writeElement('{DAV:}sync-token', $syncToken); + } + + } + + /** + * 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) { + + $elementMap = $reader->elementMap; + $elementMap['{DAV:}prop'] = 'Sabre\\DAV\\Xml\\Element\\Prop'; + $elements = $reader->parseInnerTree($elementMap); + + $responses = []; + $syncToken = null; + + if ($elements) foreach ($elements as $elem) { + if ($elem['name'] === '{DAV:}response') { + $responses[] = $elem['value']; + } + if ($elem['name'] === '{DAV:}sync-token') { + $syncToken = $elem['value']; + } + } + + return new self($responses, $syncToken); + + } + +} diff --git a/vendor/sabre/dav/lib/DAV/Xml/Service.php b/vendor/sabre/dav/lib/DAV/Xml/Service.php new file mode 100644 index 000000000..f41ed984a --- /dev/null +++ b/vendor/sabre/dav/lib/DAV/Xml/Service.php @@ -0,0 +1,47 @@ +<?php + +namespace Sabre\DAV\Xml; + +/** + * XML service for WebDAV + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Service extends \Sabre\Xml\Service { + + /** + * This is a list of XML elements that we automatically map to PHP classes. + * + * For instance, this list may contain an entry `{DAV:}propfind` that would + * be mapped to Sabre\DAV\Xml\Request\PropFind + */ + public $elementMap = [ + '{DAV:}multistatus' => 'Sabre\\DAV\\Xml\\Response\\MultiStatus', + '{DAV:}response' => 'Sabre\\DAV\\Xml\\Element\\Response', + + // Requests + '{DAV:}propfind' => 'Sabre\\DAV\\Xml\\Request\\PropFind', + '{DAV:}propertyupdate' => 'Sabre\\DAV\\Xml\\Request\\PropPatch', + '{DAV:}mkcol' => 'Sabre\\DAV\\Xml\\Request\\MkCol', + + // Properties + '{DAV:}resourcetype' => 'Sabre\\DAV\\Xml\\Property\\ResourceType', + + ]; + + /** + * This is a default list of namespaces. + * + * If you are defining your own custom namespace, add it here to reduce + * bandwidth and improve legibility of xml bodies. + * + * @var array + */ + public $namespaceMap = [ + 'DAV:' => 'd', + 'http://sabredav.org/ns' => 's', + ]; + +} |