diff options
Diffstat (limited to 'vendor/sabre/dav/lib/Sabre/DAV')
88 files changed, 10286 insertions, 0 deletions
diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractBasic.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractBasic.php new file mode 100644 index 000000000..daa8bd8ad --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractBasic.php @@ -0,0 +1,87 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\DAV; +use Sabre\HTTP; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author James David Low (http://jameslow.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class AbstractBasic implements BackendInterface { + + /** + * This variable holds the currently logged in username. + * + * @var string|null + */ + protected $currentUser; + + /** + * 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); + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + public function getCurrentUser() { + return $this->currentUser; + } + + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param DAV\Server $server + * @param string $realm + * @throws DAV\Exception\NotAuthenticated + * @return bool + */ + public function authenticate(DAV\Server $server, $realm) { + + $auth = new HTTP\BasicAuth(); + $auth->setHTTPRequest($server->httpRequest); + $auth->setHTTPResponse($server->httpResponse); + $auth->setRealm($realm); + $userpass = $auth->getUserPass(); + if (!$userpass) { + $auth->requireLogin(); + throw new DAV\Exception\NotAuthenticated('No basic authentication headers were found'); + } + + // Authenticates the user + if (!$this->validateUserPass($userpass[0],$userpass[1])) { + $auth->requireLogin(); + throw new DAV\Exception\NotAuthenticated('Username or password does not match'); + } + $this->currentUser = $userpass[0]; + return true; + } + + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php new file mode 100644 index 000000000..14993a014 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/AbstractDigest.php @@ -0,0 +1,101 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +use Sabre\HTTP; +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class AbstractDigest implements BackendInterface { + + /** + * This variable holds the currently logged in username. + * + * @var array|null + */ + protected $currentUser; + + /** + * 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 public function getDigestHash($realm, $username); + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param DAV\Server $server + * @param string $realm + * @throws DAV\Exception\NotAuthenticated + * @return bool + */ + public function authenticate(DAV\Server $server, $realm) { + + $digest = new HTTP\DigestAuth(); + + // Hooking up request and response objects + $digest->setHTTPRequest($server->httpRequest); + $digest->setHTTPResponse($server->httpResponse); + + $digest->setRealm($realm); + $digest->init(); + + $username = $digest->getUsername(); + + // No username was given + if (!$username) { + $digest->requireLogin(); + throw new DAV\Exception\NotAuthenticated('No digest authentication headers were found'); + } + + $hash = $this->getDigestHash($realm, $username); + // If this was false, the user account didn't exist + if ($hash===false || is_null($hash)) { + $digest->requireLogin(); + throw new DAV\Exception\NotAuthenticated('The supplied username was not on file'); + } + 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)) { + $digest->requireLogin(); + throw new DAV\Exception\NotAuthenticated('Incorrect username'); + } + + $this->currentUser = $username; + return true; + + } + + /** + * Returns the currently logged in username. + * + * @return string|null + */ + public function getCurrentUser() { + + return $this->currentUser; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php new file mode 100644 index 000000000..bdde16716 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/Apache.php @@ -0,0 +1,63 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Apache implements BackendInterface { + + /** + * Current apache user + * + * @var string + */ + protected $remoteUser; + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param DAV\Server $server + * @param string $realm + * @return bool + */ + public function authenticate(DAV\Server $server, $realm) { + + $remoteUser = $server->httpRequest->getRawServerValue('REMOTE_USER'); + if (is_null($remoteUser)) { + throw new DAV\Exception('We did not receive the $_SERVER[REMOTE_USER] property. This means that apache might have been misconfigured'); + } + + $this->remoteUser = $remoteUser; + return true; + + } + + /** + * Returns information about the currently logged in user. + * + * If nobody is currently logged in, this method should return null. + * + * @return array|null + */ + public function getCurrentUser() { + + return $this->remoteUser; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php new file mode 100644 index 000000000..140adaa2e --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/BackendInterface.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +/** + * This is the base class for any authentication object. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface BackendInterface { + + /** + * Authenticates the user based on the current request. + * + * If authentication is successful, true must be returned. + * If authentication fails, an exception must be thrown. + * + * @param \Sabre\DAV\Server $server + * @param string $realm + * @return bool + */ + function authenticate(\Sabre\DAV\Server $server,$realm); + + /** + * Returns information about the currently logged in username. + * + * If nobody is currently logged in, this method should return null. + * + * @return string|null + */ + function getCurrentUser(); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/File.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/File.php new file mode 100644 index 000000000..c325b1e5b --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class File extends AbstractDigest { + + /** + * List of users + * + * @var array + */ + protected $users = array(); + + /** + * Creates the backend object. + * + * If the filename argument is passed in, it will parse out the specified file fist. + * + * @param string|null $filename + */ + public 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 + */ + public 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 + */ + public function getDigestHash($realm, $username) { + + return isset($this->users[$realm . ':' . $username])?$this->users[$realm . ':' . $username]:false; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php new file mode 100644 index 000000000..1bc6699b7 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Backend/PDO.php @@ -0,0 +1,65 @@ +<?php + +namespace Sabre\DAV\Auth\Backend; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + protected $tableName; + + + /** + * Creates the backend object. + * + * If the filename argument is passed in, it will parse out the specified file fist. + * + * @param PDO $pdo + * @param string $tableName The PDO table name to use + */ + public function __construct(\PDO $pdo, $tableName = 'users') { + + $this->pdo = $pdo; + $this->tableName = $tableName; + + } + + /** + * Returns the digest hash for a user. + * + * @param string $realm + * @param string $username + * @return string|null + */ + public function getDigestHash($realm,$username) { + + $stmt = $this->pdo->prepare('SELECT username, digesta1 FROM '.$this->tableName.' WHERE username = ?'); + $stmt->execute(array($username)); + $result = $stmt->fetchAll(); + + if (!count($result)) return; + + return $result[0]['digesta1']; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php new file mode 100644 index 000000000..fccbcc22f --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Auth/Plugin.php @@ -0,0 +1,112 @@ +<?php + +namespace Sabre\DAV\Auth; +use Sabre\DAV; + +/** + * This plugin provides Authentication for a WebDAV server. + * + * It relies on a Backend object, which provides user information. + * + * Additionally, it provides support for: + * * {DAV:}current-user-principal property from RFC5397 + * * {DAV:}principal-collection-set property from RFC3744 + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * Reference to main server object + * + * @var Sabre\DAV\Server + */ + protected $server; + + /** + * Authentication backend + * + * @var Backend\BackendInterface + */ + protected $authBackend; + + /** + * The authentication realm. + * + * @var string + */ + private $realm; + + /** + * __construct + * + * @param Backend\BackendInterface $authBackend + * @param string $realm + */ + public function __construct(Backend\BackendInterface $authBackend, $realm) { + + $this->authBackend = $authBackend; + $this->realm = $realm; + + } + + /** + * Initializes the plugin. This function is automatically called by the server + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod',array($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 + */ + public function getPluginName() { + + return 'auth'; + + } + + /** + * Returns the current users' principal uri. + * + * If nobody is logged in, this will return null. + * + * @return string|null + */ + public function getCurrentUser() { + + $userInfo = $this->authBackend->getCurrentUser(); + if (!$userInfo) return null; + + return $userInfo; + + } + + /** + * This method is called before any HTTP method and forces users to be authenticated + * + * @param string $method + * @param string $uri + * @throws Sabre\DAV\Exception\NotAuthenticated + * @return bool + */ + public function beforeMethod($method, $uri) { + + $this->authBackend->authenticate($this->server,$this->realm); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php new file mode 100644 index 000000000..36c63f515 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/GuessContentType.php @@ -0,0 +1,99 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class GuessContentType extends DAV\ServerPlugin { + + /** + * List of recognized file extensions + * + * Feel free to add more + * + * @var array + */ + public $extensionMap = array( + + // images + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + + // groupware + 'ics' => 'text/calendar', + 'vcf' => 'text/x-vcard', + + // text + 'txt' => 'text/plain', + + ); + + /** + * Initializes the plugin + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) { + + // Using a relatively low priority (200) to allow other extensions + // to set the content-type first. + $server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties'),200); + + } + + /** + * Handler for teh afterGetProperties event + * + * @param string $path + * @param array $properties + * @return void + */ + public function afterGetProperties($path, &$properties) { + + if (array_key_exists('{DAV:}getcontenttype', $properties[404])) { + + list(, $fileName) = DAV\URLUtil::splitPath($path); + $contentType = $this->getContentType($fileName); + + if ($contentType) { + $properties[200]['{DAV:}getcontenttype'] = $contentType; + unset($properties[404]['{DAV:}getcontenttype']); + } + + } + + } + + /** + * 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]; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php new file mode 100644 index 000000000..a429e8f88 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/MapGetToPropFind.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); + } + + /** + * This method intercepts GET requests to non-files, and changes it into an HTTP PROPFIND request + * + * @param string $method + * @param string $uri + * @return bool + */ + public function httpGetInterceptor($method, $uri) { + + if ($method!='GET') return true; + + $node = $this->server->tree->getNodeForPath($uri); + if ($node instanceof DAV\IFile) return; + + $this->server->invokeMethod('PROPFIND',$uri); + return false; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php new file mode 100644 index 000000000..5fefc7ee2 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/Plugin.php @@ -0,0 +1,491 @@ +<?php + +namespace Sabre\DAV\Browser; + +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * List of default icons for nodes. + * + * This is an array with class / interface names as keys, and asset names + * as values. + * + * The evaluation order is reversed. The last item in the list gets + * precendence. + * + * @var array + */ + public $iconMap = array( + 'Sabre\\DAV\\IFile' => 'icons/file', + 'Sabre\\DAV\\ICollection' => 'icons/collection', + 'Sabre\\DAVACL\\IPrincipal' => 'icons/principal', + 'Sabre\\CalDAV\\ICalendar' => 'icons/calendar', + 'Sabre\\CardDAV\\IAddressBook' => 'icons/addressbook', + 'Sabre\\CardDAV\\ICard' => 'icons/card', + ); + + /** + * The file extension used for all icons + * + * @var string + */ + public $iconExtension = '.png'; + + /** + * 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; + + /** + * By default the browser plugin will generate a favicon and other images. + * To turn this off, set this property to false. + * + * @var bool + */ + protected $enableAssets = true; + + /** + * Creates the object. + * + * By default it will allow file creation and uploads. + * Specify the first argument as false to disable this + * + * @param bool $enablePost + * @param bool $enableAssets + */ + public function __construct($enablePost=true, $enableAssets = true) { + + $this->enablePost = $enablePost; + $this->enableAssets = $enableAssets; + + } + + /** + * Initializes the plugin and subscribes to events + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod',array($this,'httpGetInterceptor')); + $this->server->subscribeEvent('onHTMLActionsPanel', array($this, 'htmlActionsPanel'),200); + if ($this->enablePost) $this->server->subscribeEvent('unknownMethod',array($this,'httpPOSTHandler')); + } + + /** + * This method intercepts GET requests to collections and returns the html + * + * @param string $method + * @param string $uri + * @return bool + */ + public function httpGetInterceptor($method, $uri) { + + if ($method !== 'GET') return true; + + // We're not using straight-up $_GET, because we want everything to be + // unit testable. + $getVars = array(); + parse_str($this->server->httpRequest->getQueryString(), $getVars); + + if (isset($getVars['sabreAction']) && $getVars['sabreAction'] === 'asset' && isset($getVars['assetName'])) { + $this->serveAsset($getVars['assetName']); + return false; + } + + try { + $node = $this->server->tree->getNodeForPath($uri); + } catch (DAV\Exception\NotFound $e) { + // We're simply stopping when the file isn't found to not interfere + // with other plugins. + return; + } + if ($node instanceof DAV\IFile) + return; + + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->setHeader('Content-Type','text/html; charset=utf-8'); + + $this->server->httpResponse->sendBody( + $this->generateDirectoryIndex($uri) + ); + + return false; + + } + + /** + * Handles POST requests for tree operations. + * + * @param string $method + * @param string $uri + * @return bool + */ + public function httpPOSTHandler($method, $uri) { + + if ($method!='POST') return; + $contentType = $this->server->httpRequest->getHeader('Content-Type'); + list($contentType) = explode(';', $contentType); + if ($contentType !== 'application/x-www-form-urlencoded' && + $contentType !== 'multipart/form-data') { + return; + } + $postVars = $this->server->httpRequest->getPostVars(); + + if (!isset($postVars['sabreAction'])) + return; + + if ($this->server->broadcastEvent('onBrowserPostAction', array($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) = DAV\URLUtil::splitPath(trim($postVars['name'])); + $this->server->createDirectory($uri . '/' . $folderName); + } + break; + case 'put' : + if ($_FILES) $file = current($_FILES); + else break; + + list(, $newName) = DAV\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) = DAV\URLUtil::splitPath($newName); + + if (is_uploaded_file($file['tmp_name'])) { + $this->server->createFile($uri . '/' . $newName, fopen($file['tmp_name'],'r')); + } + break; + + } + + } + $this->server->httpResponse->setHeader('Location',$this->server->httpRequest->getUri()); + $this->server->httpResponse->sendStatus(302); + return false; + + } + + /** + * Escapes a string for html. + * + * @param string $value + * @return string + */ + public function escapeHTML($value) { + + return htmlspecialchars($value,ENT_QUOTES,'UTF-8'); + + } + + /** + * Generates the html directory index for a given url + * + * @param string $path + * @return string + */ + public function generateDirectoryIndex($path) { + + $version = ''; + if (DAV\Server::$exposeVersion) { + $version = DAV\Version::VERSION ."-". DAV\Version::STABILITY; + } + + $html = "<html> +<head> + <title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title> + <style type=\"text/css\"> + body { Font-family: arial} + h1 { font-size: 150% } + </style> + "; + + if ($this->enableAssets) { + $html.='<link rel="shortcut icon" href="'.$this->getAssetUrl('favicon.ico').'" type="image/vnd.microsoft.icon" />'; + } + + $html .= "</head> +<body> + <h1>Index for " . $this->escapeHTML($path) . "/</h1> + <table> + <tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr> + <tr><td colspan=\"5\"><hr /></td></tr>"; + + $files = $this->server->getPropertiesForPath($path,array( + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ),1); + + $parent = $this->server->tree->getNodeForPath($path); + + + if ($path) { + + list($parentUri) = DAV\URLUtil::splitPath($path); + $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); + + $icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':''; + $html.= "<tr> + <td>$icon</td> + <td><a href=\"{$fullPath}\">..</a></td> + <td>[parent]</td> + <td></td> + <td></td> + </tr>"; + + } + + foreach($files as $file) { + + // This is the current directory, we can skip it + if (rtrim($file['href'],'/')==$path) continue; + + list(, $name) = DAV\URLUtil::splitPath($file['href']); + + $type = null; + + + if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); + + // resourcetype can have multiple values + if (!is_array($type)) $type = array($type); + + foreach($type as $k=>$v) { + + // Some name mapping is preferred + switch($v) { + case '{DAV:}collection' : + $type[$k] = 'Collection'; + break; + case '{DAV:}principal' : + $type[$k] = 'Principal'; + break; + case '{urn:ietf:params:xml:ns:carddav}addressbook' : + $type[$k] = 'Addressbook'; + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $type[$k] = 'Calendar'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : + $type[$k] = 'Schedule Inbox'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : + $type[$k] = 'Schedule Outbox'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-read' : + $type[$k] = 'Proxy-Read'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-write' : + $type[$k] = 'Proxy-Write'; + break; + } + + } + $type = implode(', ', $type); + } + + // If no resourcetype was found, we attempt to use + // the contenttype property + if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + $type = $file[200]['{DAV:}getcontenttype']; + } + if (!$type) $type = 'Unknown'; + + $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; + $lastmodified = isset($file[200]['{DAV:}getlastmodified'])?$file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM):''; + + $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); + + $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; + + $displayName = $this->escapeHTML($displayName); + $type = $this->escapeHTML($type); + + $icon = ''; + + if ($this->enableAssets) { + $node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name); + foreach(array_reverse($this->iconMap) as $class=>$iconName) { + + if ($node instanceof $class) { + $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>'; + break; + } + + + } + + } + + $html.= "<tr> + <td>$icon</td> + <td><a href=\"{$fullPath}\">{$displayName}</a></td> + <td>{$type}</td> + <td>{$size}</td> + <td>{$lastmodified}</td> + </tr>"; + + } + + $html.= "<tr><td colspan=\"5\"><hr /></td></tr>"; + + $output = ''; + + if ($this->enablePost) { + $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); + } + + $html.=$output; + + $html.= "</table> + <address>Generated by SabreDAV " . $version . " (c)2007-2013 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address> + </body> + </html>"; + + return $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 + */ + public 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; + + $output.= '<tr><td colspan="2"><form method="post" action=""> + <h3>Create new folder</h3> + <input type="hidden" name="sabreAction" value="mkcol" /> + Name: <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" /> + Name (optional): <input type="text" name="name" /><br /> + File: <input type="file" name="file" /><br /> + <input type="submit" value="upload" /> + </form> + </td></tr>'; + + } + + /** + * 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 + */ + protected function getLocalAssetPath($assetName) { + + $assetDir = __DIR__ . '/assets/'; + $path = $assetDir . $assetName; + + // Making sure people aren't trying to escape from the base path. + if (strpos(realpath($path), realpath($assetDir)) === 0) { + return $path; + } + throw new DAV\Exception\Forbidden('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); + if (!file_exists($assetPath)) { + throw new DAV\Exception\NotFound('Could not find an asset with this name'); + } + // Rudimentary mime type detection + switch(strtolower(substr($assetPath,strpos($assetPath,'.')+1))) { + + case 'ico' : + $mime = 'image/vnd.microsoft.icon'; + break; + + case 'png' : + $mime = 'image/png'; + break; + + default: + $mime = 'application/octet-stream'; + break; + + } + + $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->sendStatus(200); + $this->server->httpResponse->sendBody(fopen($assetPath,'r')); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png Binary files differnew file mode 100644 index 000000000..c9acc8417 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/addressbook.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png Binary files differnew file mode 100644 index 000000000..3ecd6a800 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/calendar.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png Binary files differnew file mode 100644 index 000000000..2ce954866 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/card.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png Binary files differnew file mode 100644 index 000000000..156fa64fd --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/collection.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png Binary files differnew file mode 100644 index 000000000..3b98551ce --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/file.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png Binary files differnew file mode 100644 index 000000000..156fa64fd --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/parent.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png Binary files differnew file mode 100644 index 000000000..f8988f828 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Browser/assets/icons/principal.png diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Client.php b/vendor/sabre/dav/lib/Sabre/DAV/Client.php new file mode 100644 index 000000000..b9cf043a2 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Client.php @@ -0,0 +1,566 @@ +<?php + +namespace Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Client { + + /** + * The propertyMap is a key-value array. + * + * If you use the propertyMap, any {DAV:}multistatus responses with the + * proeprties listed in this array, will automatically be mapped to a + * respective class. + * + * The {DAV:}resourcetype property is automatically added. This maps to + * Sabre\DAV\Property\ResourceType + * + * @var array + */ + public $propertyMap = array(); + + protected $baseUri; + protected $userName; + protected $password; + protected $proxy; + protected $trustedCertificates; + + /** + * Basic authentication + */ + const AUTH_BASIC = 1; + + /** + * Digest authentication + */ + const AUTH_DIGEST = 2; + + /** + * The authentication type we're using. + * + * This is a bitmask of AUTH_BASIC and AUTH_DIGEST. + * + * If DIGEST is used, the client makes 1 extra request per request, to get + * the authentication tokens. + * + * @var int + */ + protected $authType; + + /** + * Indicates if SSL verification is enabled or not. + * + * @var boolean + */ + protected $verifyPeer; + + /** + * Constructor + * + * Settings are provided through the 'settings' argument. The following + * settings are supported: + * + * * baseUri + * * userName (optional) + * * password (optional) + * * proxy (optional) + * + * @param array $settings + */ + public function __construct(array $settings) { + + if (!isset($settings['baseUri'])) { + throw new \InvalidArgumentException('A baseUri must be provided'); + } + + $validSettings = array( + 'baseUri', + 'userName', + 'password', + 'proxy', + ); + + foreach($validSettings as $validSetting) { + if (isset($settings[$validSetting])) { + $this->$validSetting = $settings[$validSetting]; + } + } + + if (isset($settings['authType'])) { + $this->authType = $settings['authType']; + } else { + $this->authType = self::AUTH_BASIC | self::AUTH_DIGEST; + } + + $this->propertyMap['{DAV:}resourcetype'] = 'Sabre\\DAV\\Property\\ResourceType'; + + } + + /** + * Add trusted root certificates to the webdav client. + * + * The parameter certificates should be a absolute path to a file + * which contains all trusted certificates + * + * @param string $certificates + */ + public function addTrustedCertificates($certificates) { + $this->trustedCertificates = $certificates; + } + + /** + * Enables/disables SSL peer verification + * + * @param boolean $value + */ + public function setVerifyPeer($value) { + $this->verifyPeer = $value; + } + + /** + * 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 + */ + public function propFind($url, array $properties, $depth = 0) { + + $body = '<?xml version="1.0"?>' . "\n"; + $body.= '<d:propfind xmlns:d="DAV:">' . "\n"; + $body.= ' <d:prop>' . "\n"; + + foreach($properties as $property) { + + list( + $namespace, + $elementName + ) = XMLUtil::parseClarkNotation($property); + + if ($namespace === 'DAV:') { + $body.=' <d:' . $elementName . ' />' . "\n"; + } else { + $body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n"; + } + + } + + $body.= ' </d:prop>' . "\n"; + $body.= '</d:propfind>'; + + $response = $this->request('PROPFIND', $url, $body, array( + 'Depth' => $depth, + 'Content-Type' => 'application/xml' + )); + + $result = $this->parseMultiStatus($response['body']); + + // If depth was 0, we only return the top item + if ($depth===0) { + reset($result); + $result = current($result); + return isset($result[200])?$result[200]:array(); + } + + $newResult = array(); + foreach($result as $href => $statusList) { + + $newResult[$href] = isset($statusList[200])?$statusList[200]:array(); + + } + + 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. + * + * @todo Must be building the request using the DOM, and does not yet + * support complex properties. + * @param string $url + * @param array $properties + * @return void + */ + public function propPatch($url, array $properties) { + + $body = '<?xml version="1.0"?>' . "\n"; + $body.= '<d:propertyupdate xmlns:d="DAV:">' . "\n"; + + foreach($properties as $propName => $propValue) { + + list( + $namespace, + $elementName + ) = XMLUtil::parseClarkNotation($propName); + + if ($propValue === null) { + + $body.="<d:remove><d:prop>\n"; + + if ($namespace === 'DAV:') { + $body.=' <d:' . $elementName . ' />' . "\n"; + } else { + $body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\"/>\n"; + } + + $body.="</d:prop></d:remove>\n"; + + } else { + + $body.="<d:set><d:prop>\n"; + if ($namespace === 'DAV:') { + $body.=' <d:' . $elementName . '>'; + } else { + $body.=" <x:" . $elementName . " xmlns:x=\"" . $namespace . "\">"; + } + // Shitty.. i know + $body.=htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8'); + if ($namespace === 'DAV:') { + $body.='</d:' . $elementName . '>' . "\n"; + } else { + $body.="</x:" . $elementName . ">\n"; + } + $body.="</d:prop></d:set>\n"; + + } + + } + + $body.= '</d:propertyupdate>'; + + $this->request('PROPPATCH', $url, $body, array( + 'Content-Type' => 'application/xml' + )); + + } + + /** + * 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 + */ + public function options() { + + $result = $this->request('OPTIONS'); + if (!isset($result['headers']['dav'])) { + return array(); + } + + $features = explode(',', $result['headers']['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. + * + * @param string $method + * @param string $url + * @param string $body + * @param array $headers + * @return array + */ + public function request($method, $url = '', $body = null, $headers = array()) { + + $url = $this->getAbsoluteUrl($url); + + $curlSettings = array( + CURLOPT_RETURNTRANSFER => true, + // Return headers as part of the response + CURLOPT_HEADER => true, + CURLOPT_POSTFIELDS => $body, + // Automatically follow redirects + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + ); + + if($this->verifyPeer !== null) { + $curlSettings[CURLOPT_SSL_VERIFYPEER] = $this->verifyPeer; + } + + if($this->trustedCertificates) { + $curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates; + } + + switch ($method) { + case 'HEAD' : + + // do not read body with HEAD requests (this is necessary because cURL does not ignore the body with HEAD + // requests when the Content-Length header is given - which in turn is perfectly valid according to HTTP + // specs...) cURL does unfortunately return an error in this case ("transfer closed transfer closed with + // ... bytes remaining to read") this can be circumvented by explicitly telling cURL to ignore the + // response body + $curlSettings[CURLOPT_NOBODY] = true; + $curlSettings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; + break; + + default: + $curlSettings[CURLOPT_CUSTOMREQUEST] = $method; + break; + + } + + // Adding HTTP headers + $nHeaders = array(); + foreach($headers as $key=>$value) { + + $nHeaders[] = $key . ': ' . $value; + + } + $curlSettings[CURLOPT_HTTPHEADER] = $nHeaders; + + if ($this->proxy) { + $curlSettings[CURLOPT_PROXY] = $this->proxy; + } + + if ($this->userName && $this->authType) { + $curlType = 0; + if ($this->authType & self::AUTH_BASIC) { + $curlType |= CURLAUTH_BASIC; + } + if ($this->authType & self::AUTH_DIGEST) { + $curlType |= CURLAUTH_DIGEST; + } + $curlSettings[CURLOPT_HTTPAUTH] = $curlType; + $curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password; + } + + list( + $response, + $curlInfo, + $curlErrNo, + $curlError + ) = $this->curlRequest($url, $curlSettings); + + $headerBlob = substr($response, 0, $curlInfo['header_size']); + $response = substr($response, $curlInfo['header_size']); + + // In the case of 100 Continue, or redirects we'll have multiple lists + // of headers for each separate HTTP response. We can easily split this + // because they are separated by \r\n\r\n + $headerBlob = explode("\r\n\r\n", trim($headerBlob, "\r\n")); + + // We only care about the last set of headers + $headerBlob = $headerBlob[count($headerBlob)-1]; + + // Splitting headers + $headerBlob = explode("\r\n", $headerBlob); + + $headers = array(); + foreach($headerBlob as $header) { + $parts = explode(':', $header, 2); + if (count($parts)==2) { + $headers[strtolower(trim($parts[0]))] = trim($parts[1]); + } + } + + $response = array( + 'body' => $response, + 'statusCode' => $curlInfo['http_code'], + 'headers' => $headers + ); + + if ($curlErrNo) { + throw new Exception('[CURL] Error while making request: ' . $curlError . ' (error code: ' . $curlErrNo . ')'); + } + + if ($response['statusCode']>=400) { + switch ($response['statusCode']) { + case 400 : + throw new Exception\BadRequest('Bad request'); + case 401 : + throw new Exception\NotAuthenticated('Not authenticated'); + case 402 : + throw new Exception\PaymentRequired('Payment required'); + case 403 : + throw new Exception\Forbidden('Forbidden'); + case 404: + throw new Exception\NotFound('Resource not found.'); + case 405 : + throw new Exception\MethodNotAllowed('Method not allowed'); + case 409 : + throw new Exception\Conflict('Conflict'); + case 412 : + throw new Exception\PreconditionFailed('Precondition failed'); + case 416 : + throw new Exception\RequestedRangeNotSatisfiable('Requested Range Not Satisfiable'); + case 500 : + throw new Exception('Internal server error'); + case 501 : + throw new Exception\NotImplemented('Not Implemented'); + case 507 : + throw new Exception\InsufficientStorage('Insufficient storage'); + default: + throw new Exception('HTTP error response. (errorcode ' . $response['statusCode'] . ')'); + } + } + + return $response; + + } + + /** + * Wrapper for all curl functions. + * + * The only reason this was split out in a separate method, is so it + * becomes easier to unittest. + * + * @param string $url + * @param array $settings + * @return array + */ + // @codeCoverageIgnoreStart + protected function curlRequest($url, $settings) { + + $curl = curl_init($url); + curl_setopt_array($curl, $settings); + + return array( + curl_exec($curl), + curl_getinfo($curl), + curl_errno($curl), + curl_error($curl) + ); + + } + // @codeCoverageIgnoreEnd + + /** + * 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 + */ + protected 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 + * + * array( + * 'url/to/resource' => array( + * '200' => array( + * '{DAV:}property1' => 'value1', + * '{DAV:}property2' => 'value2', + * ), + * '404' => array( + * '{DAV:}property1' => null, + * '{DAV:}property2' => null, + * ), + * ) + * 'url/to/resource2' => array( + * .. etc .. + * ) + * ) + * + * + * @param string $body xml body + * @return array + */ + public function parseMultiStatus($body) { + + $body = XMLUtil::convertDAVNamespace($body); + + $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA); + if ($responseXML===false) { + throw new \InvalidArgumentException('The passed data is not valid XML'); + } + + $responseXML->registerXPathNamespace('d', 'urn:DAV'); + + $propResult = array(); + + foreach($responseXML->xpath('d:response') as $response) { + $response->registerXPathNamespace('d', 'urn:DAV'); + $href = $response->xpath('d:href'); + $href = (string)$href[0]; + + $properties = array(); + + foreach($response->xpath('d:propstat') as $propStat) { + + $propStat->registerXPathNamespace('d', 'urn:DAV'); + $status = $propStat->xpath('d:status'); + list($httpVersion, $statusCode, $message) = explode(' ', (string)$status[0],3); + + // Only using the propertymap for results with status 200. + $propertyMap = $statusCode==='200' ? $this->propertyMap : array(); + + $properties[$statusCode] = XMLUtil::parseProperties(dom_import_simplexml($propStat), $propertyMap); + + } + + $propResult[$href] = $properties; + + } + + return $propResult; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Collection.php b/vendor/sabre/dav/lib/Sabre/DAV/Collection.php new file mode 100644 index 000000000..d8d5265b9 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Collection.php @@ -0,0 +1,110 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function createDirectory($name) { + + throw new Exception\Forbidden('Permission denied to create directory'); + + } + + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception.php new file mode 100644 index 000000000..9c7a985b4 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception.php @@ -0,0 +1,64 @@ +<?php + +/** + * SabreDAV base exception + * + * This is SabreDAV's base exception file, use this to implement your own exception. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ + +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. + */ +class Exception extends \Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public 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 + */ + public 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 + */ + public function getHTTPHeaders(Server $server) { + + return array(); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/BadRequest.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/BadRequest.php new file mode 100644 index 000000000..bcf4c8eb4 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/BadRequest.php @@ -0,0 +1,28 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * BadRequest + * + * The BadRequest is thrown when the user submitted an invalid HTTP request + * BadRequest + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class BadRequest extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 400; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/Conflict.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Conflict.php new file mode 100644 index 000000000..2c8fa8167 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Conflict.php @@ -0,0 +1,28 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Conflict extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 409; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/ConflictingLock.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/ConflictingLock.php new file mode 100644 index 000000000..ba66f243d --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/ConflictingLock.php @@ -0,0 +1,37 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function serialize(DAV\Server $server, \DOMElement $errorNode) { + + if ($this->lock) { + $error = $errorNode->ownerDocument->createElementNS('DAV:','d:no-conflicting-lock'); + $errorNode->appendChild($error); + if (!is_object($this->lock)) var_dump($this->lock); + $error->appendChild($errorNode->ownerDocument->createElementNS('DAV:','d:href',$this->lock->uri)); + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php new file mode 100644 index 000000000..da700ebb0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/FileNotFound.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * FileNotFound + * + * Deprecated: Warning, this class is deprecated and will be removed in a + * future version of SabreDAV. Please use Sabre\DAV\Exception\NotFound instead. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @deprecated Use Sabre\DAV\Exception\NotFound instead + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class FileNotFound extends NotFound { + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/Forbidden.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Forbidden.php new file mode 100644 index 000000000..3c9a46677 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Forbidden.php @@ -0,0 +1,27 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * Forbidden + * + * This exception is thrown whenever a user tries to do an operation he's not allowed to + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Forbidden extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 403; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/InsufficientStorage.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/InsufficientStorage.php new file mode 100644 index 000000000..8505ce17f --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/InsufficientStorage.php @@ -0,0 +1,27 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * InsufficientStorage + * + * This Exception can be thrown, when for example a harddisk is full or a quota is exceeded + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class InsufficientStorage extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 507; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/InvalidResourceType.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/InvalidResourceType.php new file mode 100644 index 000000000..ce64f2ee0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public 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/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php new file mode 100644 index 000000000..a8f8407f0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class LockTokenMatchesRequestUri extends Conflict { + + /** + * Creates the exception + */ + public 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 + */ + public 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/Sabre/DAV/Exception/Locked.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php new file mode 100644 index 000000000..2f8460be2 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/Locked.php @@ -0,0 +1,73 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function __construct(DAV\Locks\LockInfo $lock = null) { + + $this->lock = $lock; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public 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 + */ + public 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/Sabre/DAV/Exception/MethodNotAllowed.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php new file mode 100644 index 000000000..09aeca4c8 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/MethodNotAllowed.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * MethodNotAllowed + * + * The 405 is thrown when a client tried to create a directory on an already existing directory + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class MethodNotAllowed extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public 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 + */ + public function getHTTPHeaders(\Sabre\DAV\Server $server) { + + $methods = $server->getAllowedMethods($server->getRequestUri()); + + return array( + 'Allow' => strtoupper(implode(', ',$methods)), + ); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotAuthenticated.php new file mode 100644 index 000000000..695edda15 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class NotAuthenticated extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 401; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotFound.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotFound.php new file mode 100644 index 000000000..605c650a4 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotFound.php @@ -0,0 +1,28 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * NotFound + * + * This Exception is thrown when a Node couldn't be found. It returns HTTP error code 404 + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class NotFound extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 404; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotImplemented.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotImplemented.php new file mode 100644 index 000000000..6934a53e5 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/NotImplemented.php @@ -0,0 +1,27 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * NotImplemented + * + * This exception is thrown when the client tried to call an unsupported HTTP method or other feature + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class NotImplemented extends \Sabre\DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 501; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/PaymentRequired.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/PaymentRequired.php new file mode 100644 index 000000000..ad053fd79 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class PaymentRequired extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 402; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/PreconditionFailed.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/PreconditionFailed.php new file mode 100644 index 000000000..225b99a36 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function __construct($message, $header=null) { + + parent::__construct($message); + $this->header = $header; + + } + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public 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 + */ + public 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/Sabre/DAV/Exception/ReportNotSupported.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/ReportNotSupported.php new file mode 100644 index 000000000..d55c99bd0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class ReportNotSupported 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 + */ + public 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/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php new file mode 100644 index 000000000..0a8a85808 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -0,0 +1,31 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class RequestedRangeNotSatisfiable extends DAV\Exception { + + /** + * returns the http statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 416; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/ServiceUnavailable.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/ServiceUnavailable.php new file mode 100644 index 000000000..dca764899 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class ServiceUnavailable extends DAV\Exception { + + /** + * Returns the HTTP statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 503; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php b/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php new file mode 100644 index 000000000..38f5a9fac --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Exception/UnsupportedMediaType.php @@ -0,0 +1,28 @@ +<?php + +namespace Sabre\DAV\Exception; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class UnsupportedMediaType extends \Sabre\DAV\Exception { + + /** + * returns the http statuscode for this exception + * + * @return int + */ + public function getHTTPCode() { + + return 415; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FS/Directory.php b/vendor/sabre/dav/lib/Sabre/DAV/FS/Directory.php new file mode 100644 index 000000000..333492d17 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FS/Directory.php @@ -0,0 +1,140 @@ +<?php + +namespace Sabre\DAV\FS; +use Sabre\DAV; + +/** + * Directory class + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function createFile($name, $data = null) { + + $newPath = $this->path . '/' . $name; + file_put_contents($newPath,$data); + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + public function createDirectory($name) { + + $newPath = $this->path . '/' . $name; + mkdir($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 + */ + public 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 Directory($path); + + } else { + + return new File($path); + + } + + } + + /** + * Returns an array with all the child nodes + * + * @return DAV\INode[] + */ + public function getChildren() { + + $nodes = array(); + foreach(scandir($this->path) as $node) if($node!='.' && $node!='..') $nodes[] = $this->getChild($node); + return $nodes; + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + public function childExists($name) { + + $path = $this->path . '/' . $name; + return file_exists($path); + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return void + */ + public function delete() { + + foreach($this->getChildren() as $child) $child->delete(); + rmdir($this->path); + + } + + /** + * Returns available diskspace information + * + * @return array + */ + public function getQuotaInfo() { + + return array( + disk_total_space($this->path)-disk_free_space($this->path), + disk_free_space($this->path) + ); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php b/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php new file mode 100644 index 000000000..437a4dd78 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FS/File.php @@ -0,0 +1,91 @@ +<?php + +namespace Sabre\DAV\FS; + +use Sabre\DAV; + +/** + * File class + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class File extends Node implements DAV\IFile { + + /** + * Updates the data + * + * @param resource $data + * @return void + */ + public function put($data) { + + file_put_contents($this->path,$data); + + } + + /** + * Returns the data + * + * @return string + */ + public function get() { + + return fopen($this->path,'r'); + + } + + /** + * Delete the current file + * + * @return void + */ + public function delete() { + + unlink($this->path); + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + public 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 + */ + public function getETag() { + + return null; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return mixed + */ + public function getContentType() { + + return null; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php new file mode 100644 index 000000000..4f52e1683 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FS/Node.php @@ -0,0 +1,82 @@ +<?php + +namespace Sabre\DAV\FS; + +use Sabre\DAV; + +/** + * Base node-class + * + * The node class implements the method used by both the File and the Directory classes + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function __construct($path) { + + $this->path = $path; + + } + + + + /** + * Returns the name of the node + * + * @return string + */ + public function getName() { + + list(, $name) = DAV\URLUtil::splitPath($this->path); + return $name; + + } + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + public function setName($name) { + + list($parentPath, ) = DAV\URLUtil::splitPath($this->path); + list(, $newName) = DAV\URLUtil::splitPath($name); + + $newPath = $parentPath . '/' . $newName; + rename($this->path,$newPath); + + $this->path = $newPath; + + } + + + + /** + * Returns the last modification time, as a unix timestamp + * + * @return int + */ + public function getLastModified() { + + return filemtime($this->path); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php new file mode 100644 index 000000000..c27ad5ba5 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Directory.php @@ -0,0 +1,159 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; + +/** + * Directory class + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public 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); + + return '"' . md5_file($newPath) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + public 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); + + } + + /** + * 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 + */ + public 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 Directory($path); + + } else { + + return new File($path); + + } + + } + + /** + * Checks if a child exists. + * + * @param string $name + * @return bool + */ + public 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[] + */ + public function getChildren() { + + $nodes = array(); + foreach(scandir($this->path) as $node) if($node!='.' && $node!='..' && $node!='.sabredav') $nodes[] = $this->getChild($node); + return $nodes; + + } + + /** + * Deletes all files in this directory, and then itself + * + * @return bool + */ + public 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 parent::delete(); + + } + + /** + * Returns available diskspace information + * + * @return array + */ + public function getQuotaInfo() { + + return array( + disk_total_space($this->path)-disk_free_space($this->path), + disk_free_space($this->path) + ); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php new file mode 100644 index 000000000..402195757 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/File.php @@ -0,0 +1,118 @@ +<?php + +namespace Sabre\DAV\FSExt; +use Sabre\DAV; + +/** + * File class + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class File extends Node implements DAV\PartialUpdate\IFile { + + /** + * Updates the data + * + * data is a readable stream resource. + * + * @param resource|string $data + * @return string + */ + public function put($data) { + + file_put_contents($this->path,$data); + return '"' . md5_file($this->path) . '"'; + + } + + /** + * Updates the data at a given offset + * + * The data argument is a readable stream resource. + * The offset argument is a 0-based offset where the data should be + * written. + * + * param resource|string $data + * @return void + */ + public function putRange($data, $offset) { + + $f = fopen($this->path, 'c'); + fseek($f,$offset-1); + if (is_string($data)) { + fwrite($f, $data); + } else { + stream_copy_to_stream($data,$f); + } + fclose($f); + return '"' . md5_file($this->path) . '"'; + + } + + /** + * Returns the data + * + * @return resource + */ + public function get() { + + return fopen($this->path,'r'); + + } + + /** + * Delete the current file + * + * @return bool + */ + public function delete() { + + unlink($this->path); + return parent::delete(); + + } + + /** + * 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 + */ + public function getETag() { + + return '"' . md5_file($this->path). '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + public function getContentType() { + + return null; + + } + + /** + * Returns the size of the file, in bytes + * + * @return int + */ + public function getSize() { + + return filesize($this->path); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php new file mode 100644 index 000000000..4fb6c9a9a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/FSExt/Node.php @@ -0,0 +1,214 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; + +/** + * Base node-class + * + * The node class implements the method used by both the File and the Directory classes + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class Node extends DAV\FS\Node implements DAV\IProperties { + + /** + * Updates properties on this node, + * + * @param array $properties + * @see Sabre\DAV\IProperties::updateProperties + * @return bool|array + */ + public function updateProperties($properties) { + + $resourceData = $this->getResourceData(); + + foreach($properties as $propertyName=>$propertyValue) { + + // If it was null, we need to delete the property + if (is_null($propertyValue)) { + if (isset($resourceData['properties'][$propertyName])) { + unset($resourceData['properties'][$propertyName]); + } + } else { + $resourceData['properties'][$propertyName] = $propertyValue; + } + + } + + $this->putResourceData($resourceData); + return true; + } + + /** + * Returns a list of properties for this nodes.; + * + * The properties list is a list of propertynames the client requested, encoded as xmlnamespace#tagName, for example: http://www.example.org/namespace#author + * If the array is empty, all properties should be returned + * + * @param array $properties + * @return array + */ + function getProperties($properties) { + + $resourceData = $this->getResourceData(); + + // if the array was empty, we need to return everything + if (!$properties) return $resourceData['properties']; + + $props = array(); + foreach($properties as $property) { + if (isset($resourceData['properties'][$property])) $props[$property] = $resourceData['properties'][$property]; + } + + return $props; + + } + + /** + * Returns the path to the resource file + * + * @return string + */ + protected function getResourceInfoPath() { + + list($parentDir) = DAV\URLUtil::splitPath($this->path); + return $parentDir . '/.sabredav'; + + } + + /** + * Returns all the stored resource information + * + * @return array + */ + protected function getResourceData() { + + $path = $this->getResourceInfoPath(); + if (!file_exists($path)) return array('properties' => array()); + + // opening up the file, and creating a shared lock + $handle = fopen($path,'r'); + flock($handle,LOCK_SH); + $data = ''; + + // Reading data until the eof + while(!feof($handle)) { + $data.=fread($handle,8192); + } + + // We're all good + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!isset($data[$this->getName()])) { + return array('properties' => array()); + } + + $data = $data[$this->getName()]; + if (!isset($data['properties'])) $data['properties'] = array(); + return $data; + + } + + /** + * Updates the resource information + * + * @param array $newData + * @return void + */ + protected function putResourceData(array $newData) { + + $path = $this->getResourceInfoPath(); + + // opening up the file, and creating a shared lock + $handle = fopen($path,'a+'); + flock($handle,LOCK_EX); + $data = ''; + + rewind($handle); + + // Reading data until the eof + while(!feof($handle)) { + $data.=fread($handle,8192); + } + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + $data[$this->getName()] = $newData; + ftruncate($handle,0); + rewind($handle); + + fwrite($handle,serialize($data)); + fclose($handle); + + } + + /** + * Renames the node + * + * @param string $name The new name + * @return void + */ + public function setName($name) { + + list($parentPath, ) = DAV\URLUtil::splitPath($this->path); + list(, $newName) = DAV\URLUtil::splitPath($name); + $newPath = $parentPath . '/' . $newName; + + // We're deleting the existing resourcedata, and recreating it + // for the new path. + $resourceData = $this->getResourceData(); + $this->deleteResourceData(); + + rename($this->path,$newPath); + $this->path = $newPath; + $this->putResourceData($resourceData); + + + } + + /** + * @return bool + */ + public function deleteResourceData() { + + // When we're deleting this node, we also need to delete any resource information + $path = $this->getResourceInfoPath(); + if (!file_exists($path)) return true; + + // opening up the file, and creating a shared lock + $handle = fopen($path,'a+'); + flock($handle,LOCK_EX); + $data = ''; + + rewind($handle); + + // Reading data until the eof + while(!feof($handle)) { + $data.=fread($handle,8192); + } + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (isset($data[$this->getName()])) unset($data[$this->getName()]); + ftruncate($handle,0); + rewind($handle); + fwrite($handle,serialize($data)); + fclose($handle); + + return true; + } + + public function delete() { + + return $this->deleteResourceData(); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/File.php b/vendor/sabre/dav/lib/Sabre/DAV/File.php new file mode 100644 index 000000000..10f56c540 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/File.php @@ -0,0 +1,85 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public 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 + */ + public function get() { + + throw new Exception\Forbidden('Permission denied to read this file'); + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + public 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 + */ + public function getETag() { + + return null; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * + * @return string|null + */ + public function getContentType() { + + return null; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/ICollection.php b/vendor/sabre/dav/lib/Sabre/DAV/ICollection.php new file mode 100644 index 000000000..3f26c1245 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/ICollection.php @@ -0,0 +1,77 @@ +<?php + +namespace Sabre\DAV; + +/** + * The ICollection Interface + * + * This interface should be implemented by each class that represents a collection + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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/Sabre/DAV/IExtendedCollection.php b/vendor/sabre/dav/lib/Sabre/DAV/IExtendedCollection.php new file mode 100644 index 000000000..50ae3f4d2 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/IExtendedCollection.php @@ -0,0 +1,28 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface IExtendedCollection extends ICollection { + + /** + * Creates a new collection + * + * @param string $name + * @param array $resourceType + * @param array $properties + * @return void + */ + function createExtendedCollection($name, array $resourceType, array $properties); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/IFile.php b/vendor/sabre/dav/lib/Sabre/DAV/IFile.php new file mode 100644 index 000000000..025b0213d --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/IFile.php @@ -0,0 +1,77 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface IFile extends INode { + + /** + * Updates the data + * + * 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 + * + * @return void + */ + function getETag(); + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize(); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/INode.php b/vendor/sabre/dav/lib/Sabre/DAV/INode.php new file mode 100644 index 000000000..c3f3c4abd --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/INode.php @@ -0,0 +1,46 @@ +<?php + +namespace Sabre\DAV; + +/** + * The INode interface is the base interface, and the parent class of both ICollection and IFile + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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/Sabre/DAV/IProperties.php b/vendor/sabre/dav/lib/Sabre/DAV/IProperties.php new file mode 100644 index 000000000..5e0124549 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/IProperties.php @@ -0,0 +1,71 @@ +<?php + +namespace Sabre\DAV; + +/** + * IProperties interface + * + * Implement this interface to support custom WebDAV properties requested and sent from clients. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface IProperties extends INode { + + /** + * Updates properties on this node, + * + * The properties array uses the propertyName in clark-notation as key, + * and the array value for the property value. In the case a property + * should be deleted, the property value will be null. + * + * This method must be atomic. If one property cannot be changed, the + * entire operation must fail. + * + * If the operation was successful, true can be returned. + * If the operation failed, false can be returned. + * + * Deletion of a non-existent property is always successful. + * + * Lastly, it is optional to return detailed information about any + * failures. In this case an array should be returned with the following + * structure: + * + * array( + * 403 => array( + * '{DAV:}displayname' => null, + * ), + * 424 => array( + * '{DAV:}owner' => null, + * ) + * ) + * + * In this example it was forbidden to update {DAV:}displayname. + * (403 Forbidden), which in turn also caused {DAV:}owner to fail + * (424 Failed Dependency) because the request needs to be atomic. + * + * @param array $mutations + * @return bool|array + */ + function updateProperties($mutations); + + /** + * 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 void + */ + function getProperties($properties); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php b/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php new file mode 100644 index 000000000..463c0f0db --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/IQuota.php @@ -0,0 +1,27 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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/Sabre/DAV/Locks/Backend/AbstractBackend.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/AbstractBackend.php new file mode 100644 index 000000000..78fa8aa9a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/AbstractBackend.php @@ -0,0 +1,21 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class AbstractBackend implements BackendInterface { + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/BackendInterface.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/BackendInterface.php new file mode 100644 index 000000000..7665a10b5 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/BackendInterface.php @@ -0,0 +1,51 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function getLocks($uri, $returnChildLocks); + + /** + * Locks a uri + * + * @param string $uri + * @param Locks\LockInfo $lockInfo + * @return bool + */ + public function lock($uri,Locks\LockInfo $lockInfo); + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param Locks\LockInfo $lockInfo + * @return bool + */ + public function unlock($uri,Locks\LockInfo $lockInfo); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/FS.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/FS.php new file mode 100644 index 000000000..b77d8b5af --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/FS.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks\LockInfo; + +/** + * This Lock Backend stores all its data in the filesystem in separate file per + * node. + * + * This Lock Manager is now deprecated. It has a bug that allows parent + * collections to be deletes when children deeper in the tree are locked. + * + * This also means that using this backend means you will not pass the Neon + * Litmus test. + * + * You are recommended to use either the PDO or the File backend instead. + * + * @deprecated + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class FS extends AbstractBackend { + + /** + * The default data directory + * + * @var string + */ + private $dataDir; + + public function __construct($dataDir) { + + $this->dataDir = $dataDir; + + } + + protected function getFileNameForUri($uri) { + + return $this->dataDir . '/sabredav_' . md5($uri) . '.locks'; + + } + + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + public function getLocks($uri, $returnChildLocks) { + + $lockList = array(); + $currentPath = ''; + + foreach(explode('/',$uri) as $uriPart) { + + // weird algorithm that can probably be improved, but we're traversing the path top down + if ($currentPath) $currentPath.='/'; + $currentPath.=$uriPart; + + $uriLocks = $this->getData($currentPath); + + foreach($uriLocks as $uriLock) { + + // Unless we're on the leaf of the uri-tree we should ignore locks with depth 0 + if($uri==$currentPath || $uriLock->depth!=0) { + $uriLock->uri = $currentPath; + $lockList[] = $uriLock; + } + + } + + } + + // Checking if we can remove any of these locks + foreach($lockList as $k=>$lock) { + if (time() > $lock->timeout + $lock->created) unset($lockList[$k]); + } + return $lockList; + + } + + /** + * Locks a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + public function lock($uri, LockInfo $lockInfo) { + + // We're making the lock timeout 30 minutes + $lockInfo->timeout = 1800; + $lockInfo->created = time(); + + $locks = $this->getLocks($uri,false); + foreach($locks as $k=>$lock) { + if ($lock->token == $lockInfo->token) unset($locks[$k]); + } + $locks[] = $lockInfo; + $this->putData($uri,$locks); + return true; + + } + + /** + * Removes a lock from a uri + * + * @param string $uri + * @param LockInfo $lockInfo + * @return bool + */ + public function unlock($uri, LockInfo $lockInfo) { + + $locks = $this->getLocks($uri,false); + foreach($locks as $k=>$lock) { + + if ($lock->token == $lockInfo->token) { + + unset($locks[$k]); + $this->putData($uri,$locks); + return true; + + } + } + return false; + + } + + /** + * Returns the stored data for a uri + * + * @param string $uri + * @return array + */ + protected function getData($uri) { + + $path = $this->getFilenameForUri($uri); + if (!file_exists($path)) return array(); + + // opening up the file, and creating a shared lock + $handle = fopen($path,'r'); + flock($handle,LOCK_SH); + $data = ''; + + // Reading data until the eof + while(!feof($handle)) { + $data.=fread($handle,8192); + } + + // We're all good + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!$data) return array(); + return $data; + + } + + /** + * Updates the lock information + * + * @param string $uri + * @param array $newData + * @return void + */ + protected function putData($uri,array $newData) { + + $path = $this->getFileNameForUri($uri); + + // opening up the file, and creating a shared lock + $handle = fopen($path,'a+'); + flock($handle,LOCK_EX); + ftruncate($handle,0); + rewind($handle); + + fwrite($handle,serialize($newData)); + fclose($handle); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php new file mode 100644 index 000000000..22d31e347 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/File.php @@ -0,0 +1,183 @@ +<?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 single file. + * + * Note that this is not nearly as robust as a database, you are encouraged + * to use the PDO backend instead. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class File extends AbstractBackend { + + /** + * The storage file + * + * @var string + */ + private $locksFile; + + /** + * Constructor + * + * @param string $locksFile path to file + */ + public 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 + */ + public function getLocks($uri, $returnChildLocks) { + + $newLocks = array(); + + $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 + */ + public 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 + */ + public 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 array(); + + // 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 + fclose($handle); + + // Unserializing and checking if the resource file contains data for this file + $data = unserialize($data); + if (!$data) return array(); + 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)); + fclose($handle); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php new file mode 100644 index 000000000..cb9f633cc --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Backend/PDO.php @@ -0,0 +1,167 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class PDO extends AbstractBackend { + + /** + * The PDO connection object + * + * @var pdo + */ + private $pdo; + + /** + * The PDO tablename this backend uses. + * + * @var string + */ + protected $tableName; + + /** + * Constructor + * + * @param PDO $pdo + * @param string $tableName + */ + public function __construct(\PDO $pdo, $tableName = 'locks') { + + $this->pdo = $pdo; + $this->tableName = $tableName; + + } + + /** + * Returns a list of Sabre\DAV\Locks\LockInfo objects + * + * This method should return all the locks for a particular uri, including + * locks that might be set on a parent uri. + * + * If returnChildLocks is set to true, this method should also look for + * any locks in the subtree of the uri for locks. + * + * @param string $uri + * @param bool $returnChildLocks + * @return array + */ + public function getLocks($uri, $returnChildLocks) { + + // NOTE: the following 10 lines or so could be easily replaced by + // pure sql. MySQL's non-standard string concatenation prevents us + // from doing this though. + $query = 'SELECT owner, token, timeout, created, scope, depth, uri FROM '.$this->tableName.' WHERE ((created + timeout) > CAST(? AS UNSIGNED INTEGER)) AND ((uri = ?)'; + $params = array(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 = array(); + 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 + */ + public 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(array($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(array($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 + */ + public function unlock($uri, LockInfo $lockInfo) { + + $stmt = $this->pdo->prepare('DELETE FROM '.$this->tableName.' WHERE uri = ? AND token = ?'); + $stmt->execute(array($uri,$lockInfo->token)); + + return $stmt->rowCount()===1; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php new file mode 100644 index 000000000..f7178a819 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/LockInfo.php @@ -0,0 +1,81 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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/Sabre/DAV/Locks/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Plugin.php new file mode 100644 index 000000000..ece363e1b --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Locks/Plugin.php @@ -0,0 +1,642 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function __construct(Backend\BackendInterface $locksBackend = null) { + + $this->locksBackend = $locksBackend; + + } + + /** + * Initializes the plugin + * + * This method is automatically called by the Server class after addPlugin. + * + * @param DAV\Server $server + * @return void + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); + $server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),50); + $server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties')); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + public function getPluginName() { + + return 'locks'; + + } + + /** + * This method is called by the Server if the user used an HTTP method + * the server didn't recognize. + * + * This plugin intercepts the LOCK and UNLOCK methods. + * + * @param string $method + * @param string $uri + * @return bool + */ + public function unknownMethod($method, $uri) { + + switch($method) { + + case 'LOCK' : $this->httpLock($uri); return false; + case 'UNLOCK' : $this->httpUnlock($uri); return false; + + } + + } + + /** + * This method is called after most properties have been found + * it allows us to add in any Lock-related properties + * + * @param string $path + * @param array $newProperties + * @return bool + */ + public function afterGetProperties($path, &$newProperties) { + + foreach($newProperties[404] as $propName=>$discard) { + + switch($propName) { + + case '{DAV:}supportedlock' : + $val = false; + if ($this->locksBackend) $val = true; + $newProperties[200][$propName] = new DAV\Property\SupportedLock($val); + unset($newProperties[404][$propName]); + break; + + case '{DAV:}lockdiscovery' : + $newProperties[200][$propName] = new DAV\Property\LockDiscovery($this->getLocks($path)); + unset($newProperties[404][$propName]); + break; + + } + + + } + return true; + + } + + + /** + * This method is called before the logic for any HTTP method is + * handled. + * + * This plugin uses that feature to intercept access to locked resources. + * + * @param string $method + * @param string $uri + * @return bool + */ + public function beforeMethod($method, $uri) { + + switch($method) { + + case 'DELETE' : + $lastLock = null; + if (!$this->validateLock($uri,$lastLock, true)) + throw new DAV\Exception\Locked($lastLock); + break; + case 'MKCOL' : + case 'PROPPATCH' : + case 'PUT' : + case 'PATCH' : + $lastLock = null; + if (!$this->validateLock($uri,$lastLock)) + throw new DAV\Exception\Locked($lastLock); + break; + case 'MOVE' : + $lastLock = null; + if (!$this->validateLock(array( + $uri, + $this->server->calculateUri($this->server->httpRequest->getHeader('Destination')), + ),$lastLock, true)) + throw new DAV\Exception\Locked($lastLock); + break; + case 'COPY' : + $lastLock = null; + if (!$this->validateLock( + $this->server->calculateUri($this->server->httpRequest->getHeader('Destination')), + $lastLock, true)) + throw new DAV\Exception\Locked($lastLock); + break; + } + + return true; + + } + + /** + * 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 + */ + public function getHTTPMethods($uri) { + + if ($this->locksBackend) + return array('LOCK','UNLOCK'); + + return array(); + + } + + /** + * 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 + */ + public function getFeatures() { + + return array(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 + */ + public function getLocks($uri, $returnChildLocks = false) { + + $lockList = array(); + + if ($this->locksBackend) + $lockList = array_merge($lockList,$this->locksBackend->getLocks($uri, $returnChildLocks)); + + return $lockList; + + } + + /** + * 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 string $uri + * @return void + */ + protected function httpLock($uri) { + + $lastLock = null; + if (!$this->validateLock($uri,$lastLock)) { + + // If the existing lock was an exclusive lock, we need to fail + if (!$lastLock || $lastLock->scope == LockInfo::EXCLUSIVE) { + //var_dump($lastLock); + throw new DAV\Exception\ConflictingLock($lastLock); + } + + } + + if ($body = $this->server->httpRequest->getBody(true)) { + // This is a new lock request + $lockInfo = $this->parseLockRequest($body); + $lockInfo->depth = $this->server->getHTTPDepth(); + $lockInfo->uri = $uri; + if($lastLock && $lockInfo->scope != LockInfo::SHARED) throw new DAV\Exception\ConflictingLock($lastLock); + + } elseif ($lastLock) { + + // This must have been a lock refresh + $lockInfo = $lastLock; + + // The resource could have been locked through another uri. + if ($uri!=$lockInfo->uri) $uri = $lockInfo->uri; + + } else { + + // There was neither a lock refresh nor a new lock request + throw new DAV\Exception\BadRequest('An xml body is required for lock requests'); + + } + + 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->broadcastEvent('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); + + $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->server->httpResponse->setHeader('Lock-Token','<opaquelocktoken:' . $lockInfo->token . '>'); + $this->server->httpResponse->sendStatus($newFile?201:200); + $this->server->httpResponse->sendBody($this->generateLockResponse($lockInfo)); + + } + + /** + * 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 string $uri + * @return void + */ + protected function httpUnlock($uri) { + + $lockToken = $this->server->httpRequest->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'); + + $locks = $this->getLocks($uri); + + // 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($uri,$lock); + $this->server->httpResponse->setHeader('Content-Length','0'); + $this->server->httpResponse->sendStatus(204); + return; + + } + + } + + // If we got here, it means the locktoken was invalid + throw new DAV\Exception\LockTokenMatchesRequestUri(); + + } + + /** + * 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 + */ + public function lockNode($uri,LockInfo $lockInfo) { + + if (!$this->server->broadcastEvent('beforeLock',array($uri,$lockInfo))) return; + + if ($this->locksBackend) return $this->locksBackend->lock($uri,$lockInfo); + throw new DAV\Exception\MethodNotAllowed('Locking support is not enabled for this resource. No Locking backend was found so if you didn\'t expect this error, please check your configuration.'); + + } + + /** + * 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 + */ + public function unlockNode($uri, LockInfo $lockInfo) { + + if (!$this->server->broadcastEvent('beforeUnlock',array($uri,$lockInfo))) return; + if ($this->locksBackend) return $this->locksBackend->unlock($uri,$lockInfo); + + } + + + /** + * Returns the contents of the HTTP Timeout header. + * + * The method formats the header into an integer. + * + * @return int + */ + public function getTimeoutHeader() { + + $header = $this->server->httpRequest->getHeader('Timeout'); + + if ($header) { + + if (stripos($header,'second-')===0) $header = (int)(substr($header,7)); + else if (strtolower($header)=='infinite') $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) { + + $dom = new \DOMDocument('1.0','utf-8'); + $dom->formatOutput = true; + + $prop = $dom->createElementNS('DAV:','d:prop'); + $dom->appendChild($prop); + + $lockDiscovery = $dom->createElementNS('DAV:','d:lockdiscovery'); + $prop->appendChild($lockDiscovery); + + $lockObj = new DAV\Property\LockDiscovery(array($lockInfo),true); + $lockObj->serialize($this->server,$lockDiscovery); + + return $dom->saveXML(); + + } + + /** + * validateLock should be called when a write operation is about to happen + * It will check if the requested url is locked, and see if the correct lock tokens are passed + * + * @param mixed $urls List of relevant urls. Can be an array, a string or nothing at all for the current request uri + * @param mixed $lastLock This variable will be populated with the last checked lock object (Sabre\DAV\Locks\LockInfo) + * @param bool $checkChildLocks If set to true, this function will also look for any locks set on child resources of the supplied urls. This is needed for for example deletion of entire trees. + * @return bool + */ + protected function validateLock($urls = null,&$lastLock = null, $checkChildLocks = false) { + + if (is_null($urls)) { + $urls = array($this->server->getRequestUri()); + } elseif (is_string($urls)) { + $urls = array($urls); + } elseif (!is_array($urls)) { + throw new DAV\Exception('The urls parameter should either be null, a string or an array'); + } + + $conditions = $this->getIfConditions(); + + // We're going to loop through the urls and make sure all lock conditions are satisfied + foreach($urls as $url) { + + $locks = $this->getLocks($url, $checkChildLocks); + + // If there were no conditions, but there were locks, we fail + if (!$conditions && $locks) { + reset($locks); + $lastLock = current($locks); + return false; + } + + // If there were no locks or conditions, we go to the next url + if (!$locks && !$conditions) continue; + + foreach($conditions as $condition) { + + if (!$condition['uri']) { + $conditionUri = $this->server->getRequestUri(); + } else { + $conditionUri = $this->server->calculateUri($condition['uri']); + } + + // If the condition has a url, and it isn't part of the affected url at all, check the next condition + if ($conditionUri && strpos($url,$conditionUri)!==0) continue; + + // The tokens array contians arrays with 2 elements. 0=true/false for normal/not condition, 1=locktoken + // At least 1 condition has to be satisfied + foreach($condition['tokens'] as $conditionToken) { + + $etagValid = true; + $lockValid = true; + + // key 2 can contain an etag + if ($conditionToken[2]) { + + $uri = $conditionUri?$conditionUri:$this->server->getRequestUri(); + $node = $this->server->tree->getNodeForPath($uri); + $etagValid = $node->getETag()==$conditionToken[2]; + + } + + // key 1 can contain a lock token + if ($conditionToken[1]) { + + $lockValid = false; + // Match all the locks + foreach($locks as $lockIndex=>$lock) { + + $lockToken = 'opaquelocktoken:' . $lock->token; + + // Checking NOT + if (!$conditionToken[0] && $lockToken != $conditionToken[1]) { + + // Condition valid, onto the next + $lockValid = true; + break; + } + if ($conditionToken[0] && $lockToken == $conditionToken[1]) { + + $lastLock = $lock; + // Condition valid and lock matched + unset($locks[$lockIndex]); + $lockValid = true; + break; + + } + + } + + } + + // If, after checking both etags and locks they are stil valid, + // we can continue with the next condition. + if ($etagValid && $lockValid) continue 2; + } + // No conditions matched, so we fail + throw new DAV\Exception\PreconditionFailed('The tokens provided in the if header did not match','If'); + } + + // Conditions were met, we'll also need to check if all the locks are gone + if (count($locks)) { + + reset($locks); + + // There's still locks, we fail + $lastLock = current($locks); + return false; + + } + + + } + + // We got here, this means every condition was satisfied + 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. If this is returned as an + * empty string, this implies it's referring to the request url. + * * tokens - The lock token. another 2 dimensional array containing 2 elements (0 = true/false.. If this is a negative condition its set to false, 1 = the actual token) + * * etag - an etag, if supplied + * + * @return array + */ + public function getIfConditions() { + + $header = $this->server->httpRequest->getHeader('If'); + if (!$header) return array(); + + $matches = array(); + + $regex = '/(?:\<(?P<uri>.*?)\>\s)?\((?P<not>Not\s)?(?:\<(?P<token>[^\>]*)\>)?(?:\s?)(?:\[(?P<etag>[^\]]*)\])?\)/im'; + preg_match_all($regex,$header,$matches,PREG_SET_ORDER); + + $conditions = array(); + + foreach($matches as $match) { + + $condition = array( + 'uri' => $match['uri'], + 'tokens' => array( + array($match['not']?0:1,$match['token'],isset($match['etag'])?$match['etag']:'') + ), + ); + + if (!$condition['uri'] && count($conditions)) $conditions[count($conditions)-1]['tokens'][] = array( + $match['not']?0:1, + $match['token'], + isset($match['etag'])?$match['etag']:'' + ); + else { + $conditions[] = $condition; + } + + } + + return $conditions; + + } + + /** + * Parses a webdav lock xml body, and returns a new Sabre\DAV\Locks\LockInfo object + * + * @param string $body + * @return DAV\Locks\LockInfo + */ + protected function parseLockRequest($body) { + + $xml = simplexml_load_string( + DAV\XMLUtil::convertDAVNamespace($body), + null, + LIBXML_NOWARNING); + $xml->registerXPathNamespace('d','urn:DAV'); + $lockInfo = new LockInfo(); + + $children = $xml->children("urn:DAV"); + $lockInfo->owner = (string)$children->owner; + + $lockInfo->token = DAV\UUIDUtil::getUUID(); + $lockInfo->scope = count($xml->xpath('d:lockscope/d:exclusive'))>0 ? LockInfo::EXCLUSIVE : LockInfo::SHARED; + + return $lockInfo; + + } + + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php new file mode 100644 index 000000000..669acafc0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Mount/Plugin.php @@ -0,0 +1,83 @@ +<?php + +namespace Sabre\DAV\Mount; + +use Sabre\DAV; + +/** + * This plugin provides support for RFC4709: Mounting WebDAV servers + * + * Simply append ?mount to any collection to generate the davmount response. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90); + + } + + /** + * 'beforeMethod' event handles. This event handles intercepts GET requests ending + * with ?mount + * + * @param string $method + * @param string $uri + * @return bool + */ + public function beforeMethod($method, $uri) { + + if ($method!='GET') return; + if ($this->server->httpRequest->getQueryString()!='mount') return; + + $currentUri = $this->server->httpRequest->getAbsoluteUri(); + + // Stripping off everything after the ? + list($currentUri) = explode('?',$currentUri); + + $this->davMount($currentUri); + + // Returning false to break the event chain + return false; + + } + + /** + * Generates the davmount response + * + * @param string $uri absolute uri + * @return void + */ + public function davMount($uri) { + + $this->server->httpResponse->sendStatus(200); + $this->server->httpResponse->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>"; + $this->server->httpResponse->sendBody(ob_get_clean()); + + } + + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Node.php b/vendor/sabre/dav/lib/Sabre/DAV/Node.php new file mode 100644 index 000000000..53c4b831a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Node.php @@ -0,0 +1,55 @@ +<?php + +namespace Sabre\DAV; + +/** + * Node class + * + * This is a helper class, that should aid in getting nodes setup. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class Node implements INode { + + /** + * Returns the last modification time + * + * In this case, it will simply return the current time + * + * @return int + */ + public function getLastModified() { + + return time(); + + } + + /** + * Deletes the current node + * + * @throws Sabre\DAV\Exception\Forbidden + * @return void + */ + public 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 + */ + public function setName($name) { + + throw new Exception\Forbidden('Permission denied to rename file'); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/ObjectTree.php b/vendor/sabre/dav/lib/Sabre/DAV/ObjectTree.php new file mode 100644 index 000000000..fda55e19a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/ObjectTree.php @@ -0,0 +1,159 @@ +<?php + +namespace Sabre\DAV; + +/** + * ObjectTree class + * + * This implementation of the Tree class makes use of the INode, IFile and ICollection API's + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class ObjectTree extends Tree { + + /** + * The root node + * + * @var ICollection + */ + protected $rootNode; + + /** + * This is the node cache. Accessed nodes are stored here + * + * @var array + */ + protected $cache = array(); + + /** + * Creates the object + * + * This method expects the rootObject to be passed as a parameter + * + * @param ICollection $rootNode + */ + public function __construct(ICollection $rootNode) { + + $this->rootNode = $rootNode; + + } + + /** + * Returns the INode object for the requested path + * + * @param string $path + * @return INode + */ + public function getNodeForPath($path) { + + $path = trim($path,'/'); + if (isset($this->cache[$path])) return $this->cache[$path]; + + // Is it the root node? + if (!strlen($path)) { + return $this->rootNode; + } + + // 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. + * + * @param string $path + * @return bool + */ + public 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; + + } + + } + + /** + * Returns a list of childnodes for a given path. + * + * @param string $path + * @return array + */ + public function getChildren($path) { + + $node = $this->getNodeForPath($path); + $children = $node->getChildren(); + foreach($children as $child) { + + $this->cache[trim($path,'/') . '/' . $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 + */ + public 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]); + + } + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php b/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php new file mode 100644 index 000000000..15598a010 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/IFile.php @@ -0,0 +1,40 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface IFile extends DAV\IFile { + + /** + * Updates the data at a given offset + * + * The data argument is a readable stream resource. + * The offset argument is an integer describing the offset. Contrary to + * what's sent in the request, the offset here is a 0-based index. + * + * 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 $data + * @param integer $offset + * @return string|null + */ + function putRange($data, $offset); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/Plugin.php b/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/Plugin.php new file mode 100644 index 000000000..cc06c8369 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/PartialUpdate/Plugin.php @@ -0,0 +1,212 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Plugin extends DAV\ServerPlugin { + + /** + * 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 + */ + public function initialize(DAV\Server $server) { + + $this->server = $server; + $server->subscribeEvent('unknownMethod',array($this,'unknownMethod')); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using DAV\Server::getPlugin + * + * @return string + */ + public function getPluginName() { + + return 'partialupdate'; + + } + + /** + * This method is called by the Server if the user used an HTTP method + * the server didn't recognize. + * + * This plugin intercepts the PATCH methods. + * + * @param string $method + * @param string $uri + * @return bool|null + */ + public function unknownMethod($method, $uri) { + + switch($method) { + + case 'PATCH': + return $this->httpPatch($uri); + + } + + } + + /** + * 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 (partial update) if and only if + * - the node exist + * - the node implements our partial update interface + * + * @param string $uri + * @return array + */ + public function getHTTPMethods($uri) { + + $tree = $this->server->tree; + + if ($tree->nodeExists($uri) && + $tree->getNodeForPath($uri) instanceof IFile) { + return array('PATCH'); + } + + return array(); + + } + + /** + * Returns a list of features for the HTTP OPTIONS Dav: header. + * + * @return array + */ + public function getFeatures() { + + return array('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 string $uri + * @return void + */ + protected function httpPatch($uri) { + + // Get the node. Will throw a 404 if not found + $node = $this->server->tree->getNodeForPath($uri); + if (!($node instanceof IFile)) { + throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.'); + } + + $range = $this->getHTTPUpdateRange(); + + if (!$range) { + throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers'); + } + + $contentType = strtolower( + $this->server->httpRequest->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'); + + // Load the begin and end data + $start = ($range[0])?$range[0]:0; + $end = ($range[1])?$range[1]:$len-1; + + // Check consistency + if($end < $start) + throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); + if($end - $start + 1 != $len) + throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[0] . ') and end (' . $range[1] . ') offsets'); + + // Checking If-None-Match and related headers. + if (!$this->server->checkPreconditions()) return; + + if (!$this->server->broadcastEvent('beforeWriteContent',array($uri, $node, null))) + return; + + $body = $this->server->httpRequest->getBody(); + $etag = $node->putRange($body, $start-1); + + $this->server->broadcastEvent('afterWriteContent',array($uri, $node)); + + $this->server->httpResponse->setHeader('Content-Length','0'); + if ($etag) $this->server->httpResponse->setHeader('ETag',$etag); + $this->server->httpResponse->sendStatus(204); + + return false; + + } + + /** + * Returns the HTTP custom range update 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 + */ + public function getHTTPUpdateRange() { + + $range = $this->server->httpRequest->getHeader('X-Update-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 array( + $matches[1]!==''?$matches[1]:null, + $matches[2]!==''?$matches[2]:null, + ); + + } +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property.php b/vendor/sabre/dav/lib/Sabre/DAV/Property.php new file mode 100644 index 000000000..00f0df3fe --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\DAV; + +/** + * Abstract property class + * + * Extend this class to create custom complex properties + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class Property implements PropertyInterface { + + /** + * Unserializes the property. + * + * This static method should return a an instance of this object. + * + * @param \DOMElement $prop + * @return DAV\IProperty + */ + static function unserialize(\DOMElement $prop) { + + throw new Exception('Unserialize has not been implemented for this class'); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/GetLastModified.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/GetLastModified.php new file mode 100644 index 000000000..9240f55cc --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/GetLastModified.php @@ -0,0 +1,78 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +/** + * This property represents the {DAV:}getlastmodified property. + * + * Although this is normally a simple property, windows requires us to add + * some new attributes. + * + * This class uses unix timestamps internally, and converts them to RFC 1123 times for + * serialization + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class GetLastModified extends DAV\Property { + + /** + * time + * + * @var int + */ + public $time; + + /** + * __construct + * + * @param int|DateTime $time + */ + public function __construct($time) { + + if ($time instanceof \DateTime) { + $this->time = $time; + } elseif (is_int($time) || ctype_digit($time)) { + $this->time = new \DateTime('@' . $time); + } else { + $this->time = new \DateTime($time); + } + + // Setting timezone to UTC + $this->time->setTimezone(new \DateTimeZone('UTC')); + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $prop + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $prop) { + + $doc = $prop->ownerDocument; + //$prop->setAttribute('xmlns:b','urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/'); + //$prop->setAttribute('b:dt','dateTime.rfc1123'); + $prop->nodeValue = HTTP\Util::toHTTPDate($this->time); + + } + + /** + * getTime + * + * @return \DateTime + */ + public function getTime() { + + return $this->time; + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php new file mode 100644 index 000000000..ac198c723 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/Href.php @@ -0,0 +1,99 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * Href property + * + * The href property represents a url within a {DAV:}href element. + * This is used by many WebDAV extensions, but not really within the WebDAV core spec + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Href extends DAV\Property implements IHref { + + /** + * href + * + * @var string + */ + private $href; + + /** + * Automatically prefix the url with the server base directory + * + * @var bool + */ + private $autoPrefix = true; + + /** + * __construct + * + * @param string $href + * @param bool $autoPrefix + */ + public function __construct($href, $autoPrefix = true) { + + $this->href = $href; + $this->autoPrefix = $autoPrefix; + + } + + /** + * Returns the uri + * + * @return string + */ + public function getHref() { + + return $this->href; + + } + + /** + * Serializes this property. + * + * It will additionally prepend the href property with the server's base uri. + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $dom) { + + $prefix = $server->xmlNamespaces['DAV:']; + $elem = $dom->ownerDocument->createElement($prefix . ':href'); + + if ($this->autoPrefix) { + $value = $server->getBaseUri() . DAV\URLUtil::encodePath($this->href); + } else { + $value = $this->href; + } + $elem->appendChild($dom->ownerDocument->createTextNode($value)); + + $dom->appendChild($elem); + + } + + /** + * Unserializes this property from a DOM Element + * + * This method returns an instance of this class. + * It will only decode {DAV:}href values. For non-compatible elements null will be returned. + * + * @param \DOMElement $dom + * @return DAV\Property\Href + */ + static function unserialize(\DOMElement $dom) { + + if ($dom->firstChild && DAV\XMLUtil::toClarkNotation($dom->firstChild)==='{DAV:}href') { + return new self($dom->firstChild->textContent,false); + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php new file mode 100644 index 000000000..3eab755ca --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/HrefList.php @@ -0,0 +1,105 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * HrefList property + * + * This property contains multiple {DAV:}href elements, each containing a url. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class HrefList extends DAV\Property { + + /** + * hrefs + * + * @var array + */ + private $hrefs; + + /** + * Automatically prefix the url with the server base directory + * + * @var bool + */ + private $autoPrefix = true; + + /** + * __construct + * + * @param array $hrefs + * @param bool $autoPrefix + */ + public function __construct(array $hrefs, $autoPrefix = true) { + + $this->hrefs = $hrefs; + $this->autoPrefix = $autoPrefix; + + } + + /** + * Returns the uris + * + * @return array + */ + public function getHrefs() { + + return $this->hrefs; + + } + + /** + * Serializes this property. + * + * It will additionally prepend the href property with the server's base uri. + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $dom) { + + $prefix = $server->xmlNamespaces['DAV:']; + + foreach($this->hrefs as $href) { + + $elem = $dom->ownerDocument->createElement($prefix . ':href'); + if ($this->autoPrefix) { + $value = $server->getBaseUri() . DAV\URLUtil::encodePath($href); + } else { + $value = $href; + } + $elem->appendChild($dom->ownerDocument->createTextNode($value)); + + $dom->appendChild($elem); + } + + } + + /** + * Unserializes this property from a DOM Element + * + * This method returns an instance of this class. + * It will only decode {DAV:}href values. + * + * @param \DOMElement $dom + * @return DAV\Property\HrefList + */ + static function unserialize(\DOMElement $dom) { + + $hrefs = array(); + foreach($dom->childNodes as $child) { + if (DAV\XMLUtil::toClarkNotation($child)==='{DAV:}href') { + $hrefs[] = $child->textContent; + } + } + return new self($hrefs, false); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php new file mode 100644 index 000000000..a4ae09b3f --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/IHref.php @@ -0,0 +1,25 @@ +<?php + +namespace Sabre\DAV\Property; + +/** + * IHref interface + * + * Any property implementing this interface can expose a related url. + * This is used by certain subsystems to aquire more information about for example + * the owner of a file + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface IHref { + + /** + * getHref + * + * @return string + */ + function getHref(); + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/LockDiscovery.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/LockDiscovery.php new file mode 100644 index 000000000..3b36ca6a7 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/LockDiscovery.php @@ -0,0 +1,104 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * Represents {DAV:}lockdiscovery property + * + * This property contains all the open locks on a given resource + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class LockDiscovery extends DAV\Property { + + /** + * locks + * + * @var array + */ + public $locks; + + /** + * Should we show the locktoken as well? + * + * @var bool + */ + public $revealLockToken; + + /** + * Hides the {DAV:}lockroot element from the response. + * + * It was reported that showing the lockroot in the response can break + * Office 2000 compatibility. + */ + static public $hideLockRoot = false; + + /** + * __construct + * + * @param array $locks + * @param bool $revealLockToken + */ + public function __construct($locks, $revealLockToken = false) { + + $this->locks = $locks; + $this->revealLockToken = $revealLockToken; + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $prop + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $prop) { + + $doc = $prop->ownerDocument; + + foreach($this->locks as $lock) { + + $activeLock = $doc->createElementNS('DAV:','d:activelock'); + $prop->appendChild($activeLock); + + $lockScope = $doc->createElementNS('DAV:','d:lockscope'); + $activeLock->appendChild($lockScope); + + $lockScope->appendChild($doc->createElementNS('DAV:','d:' . ($lock->scope==DAV\Locks\LockInfo::EXCLUSIVE?'exclusive':'shared'))); + + $lockType = $doc->createElementNS('DAV:','d:locktype'); + $activeLock->appendChild($lockType); + + $lockType->appendChild($doc->createElementNS('DAV:','d:write')); + + /* {DAV:}lockroot */ + if (!self::$hideLockRoot) { + $lockRoot = $doc->createElementNS('DAV:','d:lockroot'); + $activeLock->appendChild($lockRoot); + $href = $doc->createElementNS('DAV:','d:href'); + $href->appendChild($doc->createTextNode($server->getBaseUri() . $lock->uri)); + $lockRoot->appendChild($href); + } + + $activeLock->appendChild($doc->createElementNS('DAV:','d:depth',($lock->depth == DAV\Server::DEPTH_INFINITY?'infinity':$lock->depth))); + $activeLock->appendChild($doc->createElementNS('DAV:','d:timeout','Second-' . $lock->timeout)); + + if ($this->revealLockToken) { + $lockToken = $doc->createElementNS('DAV:','d:locktoken'); + $activeLock->appendChild($lockToken); + $lockToken->appendChild($doc->createElementNS('DAV:','d:href','opaquelocktoken:' . $lock->token)); + } + + $activeLock->appendChild($doc->createElementNS('DAV:','d:owner',$lock->owner)); + + } + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php new file mode 100644 index 000000000..9582d5598 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResourceType.php @@ -0,0 +1,127 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * This class represents the {DAV:}resourcetype property + * + * Normally for files this is empty, and for collection {DAV:}collection. + * However, other specs define different values for this. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class ResourceType extends DAV\Property { + + /** + * resourceType + * + * @var array + */ + public $resourceType = array(); + + /** + * __construct + * + * @param mixed $resourceType + */ + public function __construct($resourceType = array()) { + + if ($resourceType === DAV\Server::NODE_FILE) + $this->resourceType = array(); + elseif ($resourceType === DAV\Server::NODE_DIRECTORY) + $this->resourceType = array('{DAV:}collection'); + elseif (is_array($resourceType)) + $this->resourceType = $resourceType; + else + $this->resourceType = array($resourceType); + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $prop + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $prop) { + + $propName = null; + $rt = $this->resourceType; + + foreach($rt as $resourceType) { + if (preg_match('/^{([^}]*)}(.*)$/',$resourceType,$propName)) { + + if (isset($server->xmlNamespaces[$propName[1]])) { + $prop->appendChild($prop->ownerDocument->createElement($server->xmlNamespaces[$propName[1]] . ':' . $propName[2])); + } else { + $prop->appendChild($prop->ownerDocument->createElementNS($propName[1],'custom:' . $propName[2])); + } + + } + } + + } + + /** + * Returns the values in clark-notation + * + * For example array('{DAV:}collection') + * + * @return array + */ + public function getValue() { + + return $this->resourceType; + + } + + /** + * Checks if the principal contains a certain value + * + * @param string $type + * @return bool + */ + public function is($type) { + + return in_array($type, $this->resourceType); + + } + + /** + * Adds a resourcetype value to this property + * + * @param string $type + * @return void + */ + public function add($type) { + + $this->resourceType[] = $type; + $this->resourceType = array_unique($this->resourceType); + + } + + /** + * Unserializes a DOM element into a ResourceType property. + * + * @param \DOMElement $dom + * @return DAV\Property\ResourceType + */ + static public function unserialize(\DOMElement $dom) { + + $value = array(); + foreach($dom->childNodes as $child) { + + $value[] = DAV\XMLUtil::toClarkNotation($child); + + } + + return new self($value); + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php new file mode 100644 index 000000000..58e90d89d --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/Response.php @@ -0,0 +1,157 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * Response property + * + * This class represents the {DAV:}response XML element. + * This is used by the Server class to encode individual items within a multistatus + * response. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Response extends DAV\Property implements IHref { + + /** + * Url for the response + * + * @var string + */ + private $href; + + /** + * Propertylist, ordered by HTTP status code + * + * @var array + */ + private $responseProperties; + + /** + * The responseProperties argument is a list of properties + * within an array with keys representing HTTP status codes + * + * @param string $href + * @param array $responseProperties + */ + public function __construct($href, array $responseProperties) { + + $this->href = $href; + $this->responseProperties = $responseProperties; + + } + + /** + * Returns the url + * + * @return string + */ + public function getHref() { + + return $this->href; + + } + + /** + * Returns the property list + * + * @return array + */ + public function getResponseProperties() { + + return $this->responseProperties; + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $dom) { + + $document = $dom->ownerDocument; + $properties = $this->responseProperties; + + $xresponse = $document->createElement('d:response'); + $dom->appendChild($xresponse); + + $uri = DAV\URLUtil::encodePath($this->href); + + // Adding the baseurl to the beginning of the url + $uri = $server->getBaseUri() . $uri; + + $xresponse->appendChild($document->createElement('d:href',$uri)); + + // The properties variable is an array containing properties, grouped by + // HTTP status + foreach($properties as $httpStatus=>$propertyGroup) { + + // The 'href' is also in this array, and it's special cased. + // We will ignore it + if ($httpStatus=='href') continue; + + // If there are no properties in this group, we can also just carry on + if (!count($propertyGroup)) continue; + + $xpropstat = $document->createElement('d:propstat'); + $xresponse->appendChild($xpropstat); + + $xprop = $document->createElement('d:prop'); + $xpropstat->appendChild($xprop); + + $nsList = $server->xmlNamespaces; + + foreach($propertyGroup as $propertyName=>$propertyValue) { + + $propName = null; + preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); + + // special case for empty namespaces + if ($propName[1]=='') { + + $currentProperty = $document->createElement($propName[2]); + $xprop->appendChild($currentProperty); + $currentProperty->setAttribute('xmlns',''); + + } else { + + if (!isset($nsList[$propName[1]])) { + $nsList[$propName[1]] = 'x' . count($nsList); + } + + // If the namespace was defined in the top-level xml namespaces, it means + // there was already a namespace declaration, and we don't have to worry about it. + if (isset($server->xmlNamespaces[$propName[1]])) { + $currentProperty = $document->createElement($nsList[$propName[1]] . ':' . $propName[2]); + } else { + $currentProperty = $document->createElementNS($propName[1],$nsList[$propName[1]].':' . $propName[2]); + } + $xprop->appendChild($currentProperty); + + } + + if (is_scalar($propertyValue)) { + $text = $document->createTextNode($propertyValue); + $currentProperty->appendChild($text); + } elseif ($propertyValue instanceof DAV\PropertyInterface) { + $propertyValue->serialize($server,$currentProperty); + } elseif (!is_null($propertyValue)) { + throw new DAV\Exception('Unknown property value type: ' . gettype($propertyValue) . ' for property: ' . $propertyName); + } + + } + + $xpropstat->appendChild($document->createElement('d:status',$server->httpResponse->getStatusMessage($httpStatus))); + + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php new file mode 100644 index 000000000..b605a4e9a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/ResponseList.php @@ -0,0 +1,59 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * ResponseList property + * + * This class represents multiple {DAV:}response XML elements. + * This is used by the Server class to encode items within a multistatus + * response. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class ResponseList extends DAV\Property { + + /** + * Response objects. + * + * @var array + */ + private $responses; + + /** + * The only valid argument is a list of Sabre\DAV\Property\Response + * objects. + * + * @param array $responses; + */ + public function __construct($responses) { + + foreach($responses as $response) { + if (!($response instanceof Response)) { + throw new \InvalidArgumentException('You must pass an array of Sabre\DAV\Property\Response objects'); + } + } + $this->responses = $responses; + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $dom + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $dom) { + + foreach($this->responses as $response) { + $response->serialize($server, $dom); + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php new file mode 100644 index 000000000..e6f477c38 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedLock.php @@ -0,0 +1,78 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * This class represents the {DAV:}supportedlock property + * + * This property contains information about what kind of locks + * this server supports. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class SupportedLock extends DAV\Property { + + /** + * supportsLocks + * + * @var mixed + */ + public $supportsLocks = false; + + /** + * __construct + * + * @param mixed $supportsLocks + */ + public function __construct($supportsLocks) { + + $this->supportsLocks = $supportsLocks; + + } + + /** + * serialize + * + * @param DAV\Server $server + * @param \DOMElement $prop + * @return void + */ + public function serialize(DAV\Server $server,\DOMElement $prop) { + + $doc = $prop->ownerDocument; + + if (!$this->supportsLocks) return null; + + $lockEntry1 = $doc->createElement('d:lockentry'); + $lockEntry2 = $doc->createElement('d:lockentry'); + + $prop->appendChild($lockEntry1); + $prop->appendChild($lockEntry2); + + $lockScope1 = $doc->createElement('d:lockscope'); + $lockScope2 = $doc->createElement('d:lockscope'); + $lockType1 = $doc->createElement('d:locktype'); + $lockType2 = $doc->createElement('d:locktype'); + + $lockEntry1->appendChild($lockScope1); + $lockEntry1->appendChild($lockType1); + $lockEntry2->appendChild($lockScope2); + $lockEntry2->appendChild($lockType2); + + $lockScope1->appendChild($doc->createElement('d:exclusive')); + $lockScope2->appendChild($doc->createElement('d:shared')); + + $lockType1->appendChild($doc->createElement('d:write')); + $lockType2->appendChild($doc->createElement('d:write')); + + //$frag->appendXML('<d:lockentry><d:lockscope><d:exclusive /></d:lockscope><d:locktype><d:write /></d:locktype></d:lockentry>'); + //$frag->appendXML('<d:lockentry><d:lockscope><d:shared /></d:lockscope><d:locktype><d:write /></d:locktype></d:lockentry>'); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php new file mode 100644 index 000000000..993105b8a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Property/SupportedReportSet.php @@ -0,0 +1,111 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; + +/** + * 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. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class SupportedReportSet extends DAV\Property { + + /** + * List of reports + * + * @var array + */ + protected $reports = array(); + + /** + * 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 mixed $reports + */ + public 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 + */ + public function addReport($report) { + + if (!is_array($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 array + */ + public function getValue() { + + return $this->reports; + + } + + /** + * Serializes the node + * + * @param DAV\Server $server + * @param \DOMElement $prop + * @return void + */ + public function serialize(DAV\Server $server, \DOMElement $prop) { + + foreach($this->reports as $reportName) { + + $supportedReport = $prop->ownerDocument->createElement('d:supported-report'); + $prop->appendChild($supportedReport); + + $report = $prop->ownerDocument->createElement('d:report'); + $supportedReport->appendChild($report); + + preg_match('/^{([^}]*)}(.*)$/',$reportName,$matches); + + list(, $namespace, $element) = $matches; + + $prefix = isset($server->xmlNamespaces[$namespace])?$server->xmlNamespaces[$namespace]:null; + + if ($prefix) { + $report->appendChild($prop->ownerDocument->createElement($prefix . ':' . $element)); + } else { + $report->appendChild($prop->ownerDocument->createElementNS($namespace, 'x:' . $element)); + } + + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php b/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php new file mode 100644 index 000000000..856e643f3 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/PropertyInterface.php @@ -0,0 +1,21 @@ +<?php + +namespace Sabre\DAV; + +/** + * PropertyInterface + * + * Implement this interface to create new complex properties + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +interface PropertyInterface { + + public function serialize(Server $server, \DOMElement $prop); + + static function unserialize(\DOMElement $prop); + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Server.php b/vendor/sabre/dav/lib/Sabre/DAV/Server.php new file mode 100644 index 000000000..11ac642d6 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Server.php @@ -0,0 +1,2175 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +/** + * Main DAV server class + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Server { + + /** + * 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; + + /** + * Nodes that are files, should have this as the type property + */ + const NODE_FILE = 1; + + /** + * Nodes that are directories, should use this value as the type property + */ + const NODE_DIRECTORY = 2; + + /** + * 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; + + /** + * The list of plugins + * + * @var array + */ + protected $plugins = array(); + + /** + * This array contains a list of callbacks we should call when certain events are triggered + * + * @var array + */ + protected $eventSubscriptions = array(); + + /** + * 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 $xmlNamespaces = array( + 'DAV:' => 'd', + 'http://sabredav.org/ns' => 's', + ); + + /** + * The propertymap can be used to map properties from + * requests to property classes. + * + * @var array + */ + public $propertyMap = array( + '{DAV:}resourcetype' => 'Sabre\\DAV\\Property\\ResourceType', + ); + + public $protectedProperties = array( + // 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', + + ); + + /** + * 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 automaticlly added for nodes + * implementing Sabre\DAV\ICollection. + * + * @var array + */ + public $resourceTypeMapping = array( + 'Sabre\\DAV\\ICollection' => '{DAV:}collection', + ); + + /** + * 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 public $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\ObjectTree and use the node as the root. + * + * If nothing is passed, a Sabre\DAV\SimpleCollection is created in + * a Sabre\DAV\ObjectTree. + * + * 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 + */ + public function __construct($treeOrNode = null) { + + if ($treeOrNode instanceof Tree) { + $this->tree = $treeOrNode; + } elseif ($treeOrNode instanceof INode) { + $this->tree = new ObjectTree($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 ObjectTree($root); + + } elseif (is_null($treeOrNode)) { + $root = new SimpleCollection('root'); + $this->tree = new ObjectTree($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->httpResponse = new HTTP\Response(); + $this->httpRequest = new HTTP\Request(); + + } + + /** + * Starts the DAV Server + * + * @return void + */ + public 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->defaultHttpVersion = $this->httpRequest->getHTTPVersion(); + + $this->invokeMethod($this->httpRequest->getMethod(), $this->getRequestUri()); + + } catch (Exception $e) { + + try { + $this->broadcastEvent('exception', array($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'); + + }; + + $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 (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version',$h(Version::VERSION))); + } + + if($e instanceof Exception) { + + $httpCode = $e->getHTTPCode(); + $e->serialize($this,$error); + $headers = $e->getHTTPHeaders($this); + + } else { + + $httpCode = 500; + $headers = array(); + + } + $headers['Content-Type'] = 'application/xml; charset=utf-8'; + + $this->httpResponse->sendStatus($httpCode); + $this->httpResponse->setHeaders($headers); + $this->httpResponse->sendBody($DOM->saveXML()); + + } + + } + + /** + * Sets the base server uri + * + * @param string $uri + * @return void + */ + public 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 + */ + public 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 + */ + public 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 + */ + public 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 + */ + public function getPlugin($name) { + + if (isset($this->plugins[$name])) + return $this->plugins[$name]; + + // This is a fallback and deprecated. + foreach($this->plugins as $plugin) { + if (get_class($plugin)===$name) return $plugin; + } + + return null; + + } + + /** + * Returns all plugins + * + * @return array + */ + public function getPlugins() { + + return $this->plugins; + + } + + + /** + * Subscribe to an event. + * + * When the event is triggered, we'll call all the specified callbacks. + * It is possible to control the order of the callbacks through the + * priority argument. + * + * This is for example used to make sure that the authentication plugin + * is triggered before anything else. If it's not needed to change this + * number, it is recommended to ommit. + * + * @param string $event + * @param callback $callback + * @param int $priority + * @return void + */ + public function subscribeEvent($event, $callback, $priority = 100) { + + if (!isset($this->eventSubscriptions[$event])) { + $this->eventSubscriptions[$event] = array(); + } + while(isset($this->eventSubscriptions[$event][$priority])) $priority++; + $this->eventSubscriptions[$event][$priority] = $callback; + ksort($this->eventSubscriptions[$event]); + + } + + /** + * Broadcasts an event + * + * This method will call all subscribers. If one of the subscribers returns false, the process stops. + * + * The arguments parameter will be sent to all subscribers + * + * @param string $eventName + * @param array $arguments + * @return bool + */ + public function broadcastEvent($eventName,$arguments = array()) { + + if (isset($this->eventSubscriptions[$eventName])) { + + foreach($this->eventSubscriptions[$eventName] as $subscriber) { + + $result = call_user_func_array($subscriber,$arguments); + if ($result===false) return false; + + } + + } + + return true; + + } + + /** + * Handles a http request, and execute a method based on its name + * + * @param string $method + * @param string $uri + * @return void + */ + public function invokeMethod($method, $uri) { + + $method = strtoupper($method); + + if (!$this->broadcastEvent('beforeMethod',array($method, $uri))) return; + + // Make sure this is a HTTP method we support + $internalMethods = array( + 'OPTIONS', + 'GET', + 'HEAD', + 'DELETE', + 'PROPFIND', + 'MKCOL', + 'PUT', + 'PROPPATCH', + 'COPY', + 'MOVE', + 'REPORT' + ); + + if (in_array($method,$internalMethods)) { + + call_user_func(array($this,'http' . $method), $uri); + + } else { + + if ($this->broadcastEvent('unknownMethod',array($method, $uri))) { + // Unsupported method + throw new Exception\NotImplemented('There was no handler found for this "' . $method . '" method'); + } + + } + + } + + // {{{ HTTP Method implementations + + /** + * HTTP OPTIONS + * + * @param string $uri + * @return void + */ + protected function httpOptions($uri) { + + $methods = $this->getAllowedMethods($uri); + + $this->httpResponse->setHeader('Allow',strtoupper(implode(', ',$methods))); + $features = array('1','3', 'extended-mkcol'); + + foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); + + $this->httpResponse->setHeader('DAV',implode(', ',$features)); + $this->httpResponse->setHeader('MS-Author-Via','DAV'); + $this->httpResponse->setHeader('Accept-Ranges','bytes'); + if (self::$exposeVersion) { + $this->httpResponse->setHeader('X-Sabre-Version',Version::VERSION); + } + $this->httpResponse->setHeader('Content-Length',0); + $this->httpResponse->sendStatus(200); + + } + + /** + * HTTP GET + * + * This method simply fetches the contents of a uri, like normal + * + * @param string $uri + * @return bool + */ + protected function httpGet($uri) { + + $node = $this->tree->getNodeForPath($uri,0); + + if (!$this->checkPreconditions(true)) return false; + if (!$node instanceof IFile) throw new Exception\NotImplemented('GET is only implemented on File objects'); + + $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->getHTTPHeaders($uri); + + /* 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; + } + + $this->httpResponse->setHeaders($httpHeaders); + + $range = $this->getHTTPRange(); + $ifRange = $this->httpRequest->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; + + } + + // New read/write stream + $newStream = fopen('php://temp','r+'); + + // stream_copy_to_stream() has a bug/feature: the `whence` argument + // is interpreted as SEEK_SET (count from absolute offset 0), while + // for a stream it should be SEEK_CUR (count from current offset). + // If a stream is nonseekable, the function fails. So we *emulate* + // the correct behaviour with fseek(): + if ($start > 0) { + if (($curOffs = ftell($body)) === false) $curOffs = 0; + fseek($body, $start - $curOffs, SEEK_CUR); + } + stream_copy_to_stream($body, $newStream, $end-$start+1); + rewind($newStream); + + $this->httpResponse->setHeader('Content-Length', $end-$start+1); + $this->httpResponse->setHeader('Content-Range','bytes ' . $start . '-' . $end . '/' . $nodeSize); + $this->httpResponse->sendStatus(206); + $this->httpResponse->sendBody($newStream); + + + } else { + + if ($nodeSize) $this->httpResponse->setHeader('Content-Length',$nodeSize); + $this->httpResponse->sendStatus(200); + $this->httpResponse->sendBody($body); + + } + + } + + /** + * 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 string $uri + * @return void + */ + protected function httpHead($uri) { + + $node = $this->tree->getNodeForPath($uri); + /* This information is only collection for File objects. + * Ideally we want to throw 405 Method Not Allowed for every + * non-file, but MS Office does not like this + */ + if ($node instanceof IFile) { + $headers = $this->getHTTPHeaders($this->getRequestUri()); + if (!isset($headers['Content-Type'])) { + $headers['Content-Type'] = 'application/octet-stream'; + } + $this->httpResponse->setHeaders($headers); + } + $this->httpResponse->sendStatus(200); + + } + + /** + * HTTP Delete + * + * The HTTP delete method, deletes a given uri + * + * @param string $uri + * @return void + */ + protected function httpDelete($uri) { + + if (!$this->broadcastEvent('beforeUnbind',array($uri))) return; + $this->tree->delete($uri); + $this->broadcastEvent('afterUnbind',array($uri)); + + $this->httpResponse->sendStatus(204); + $this->httpResponse->setHeader('Content-Length','0'); + + } + + + /** + * 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 string $uri + * @return void + */ + protected function httpPropfind($uri) { + + $requestedProperties = $this->parsePropFindRequest($this->httpRequest->getBody(true)); + + $depth = $this->getHTTPDepth(1); + // The only two options for the depth of a propfind is 0 or 1 + if ($depth!=0) $depth = 1; + + $newProperties = $this->getPropertiesForPath($uri,$requestedProperties,$depth); + + // This is a multi-status response + $this->httpResponse->sendStatus(207); + $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + $this->httpResponse->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 = array('1','3', 'extended-mkcol'); + foreach($this->plugins as $plugin) $features = array_merge($features,$plugin->getFeatures()); + $this->httpResponse->setHeader('DAV',implode(', ',$features)); + + $prefer = $this->getHTTPPrefer(); + $minimal = $prefer['return-minimal']; + + $data = $this->generateMultiStatus($newProperties, $minimal); + $this->httpResponse->sendBody($data); + + } + + /** + * 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 string $uri + * @return void + */ + protected function httpPropPatch($uri) { + + $newProperties = $this->parsePropPatchRequest($this->httpRequest->getBody(true)); + + $result = $this->updateProperties($uri, $newProperties); + + $prefer = $this->getHTTPPrefer(); + $this->httpResponse->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 $code=>$prop) { + if ((int)$code > 299) { + $ok = false; + } + } + + if ($ok) { + + $this->httpResponse->sendStatus(204); + return; + + } + + } + + $this->httpResponse->sendStatus(207); + $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + + $this->httpResponse->sendBody( + $this->generateMultiStatus(array($result)) + ); + + } + + /** + * 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 string $uri + * @return bool + */ + protected function httpPut($uri) { + + $body = $this->httpRequest->getBody(); + + // Intercepting Content-Range + if ($this->httpRequest->getHeader('Content-Range')) { + /** + Content-Range is dangerous for PUT requests: PUT per definition + stores a full resource. draft-ietf-httpbis-p2-semantics-15 says + in section 7.6: + An origin server SHOULD reject any PUT request that contains a + Content-Range header field, since it might be misinterpreted as + partial content (or might be partial content that is being mistakenly + PUT as a full representation). Partial content updates are possible + by targeting a separately identified resource with state that + overlaps a portion of the larger resource, or by using a different + method that has been specifically defined for partial updates (for + example, the PATCH method defined in [RFC5789]). + This clarifies RFC2616 section 9.6: + The recipient of the entity MUST NOT ignore any Content-* + (e.g. Content-Range) headers that it does not understand or implement + and MUST return a 501 (Not Implemented) response in such cases. + OTOH is a PUT request with a Content-Range currently the only way to + continue an aborted upload request and is supported by curl, mod_dav, + Tomcat and others. Since some clients do use this feature which results + in unexpected behaviour (cf PEAR::HTTP_WebDAV_Client 1.0.1), we reject + all PUT requests with a Content-Range for now. + */ + + throw new Exception\NotImplemented('PUT with Content-Range is not allowed.'); + } + + // Intercepting the Finder problem + if (($expected = $this->httpRequest->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->tree->nodeExists($uri)) { + + $node = $this->tree->getNodeForPath($uri); + + // Checking If-None-Match and related headers. + if (!$this->checkPreconditions()) return; + + // 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->broadcastEvent('beforeWriteContent',array($uri, $node, &$body))) return false; + + $etag = $node->put($body); + + $this->broadcastEvent('afterWriteContent',array($uri, $node)); + + $this->httpResponse->setHeader('Content-Length','0'); + if ($etag) $this->httpResponse->setHeader('ETag',$etag); + $this->httpResponse->sendStatus(204); + + } else { + + $etag = null; + // If we got here, the resource didn't exist yet. + if (!$this->createFile($this->getRequestUri(),$body,$etag)) { + // For one reason or another the file was not created. + return; + } + + $this->httpResponse->setHeader('Content-Length','0'); + if ($etag) $this->httpResponse->setHeader('ETag', $etag); + $this->httpResponse->sendStatus(201); + + } + + } + + + /** + * WebDAV MKCOL + * + * The MKCOL method is used to create a new collection (directory) on the server + * + * @param string $uri + * @return void + */ + protected function httpMkcol($uri) { + + $requestBody = $this->httpRequest->getBody(true); + + if ($requestBody) { + + $contentType = $this->httpRequest->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'); + + } + + $dom = XMLUtil::loadDOMDocument($requestBody); + if (XMLUtil::toClarkNotation($dom->firstChild)!=='{DAV:}mkcol') { + + // We must throw 415 for unsupported mkcol bodies + throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.'); + + } + + $properties = array(); + foreach($dom->firstChild->childNodes as $childNode) { + + if (XMLUtil::toClarkNotation($childNode)!=='{DAV:}set') continue; + $properties = array_merge($properties, XMLUtil::parseProperties($childNode, $this->propertyMap)); + + } + 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 = array(); + $resourceType = array('{DAV:}collection'); + + } + + $result = $this->createCollection($uri, $resourceType, $properties); + + if (is_array($result)) { + $this->httpResponse->sendStatus(207); + $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); + + $this->httpResponse->sendBody( + $this->generateMultiStatus(array($result)) + ); + + } else { + $this->httpResponse->setHeader('Content-Length','0'); + $this->httpResponse->sendStatus(201); + } + + } + + /** + * 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 string $uri + * @return bool + */ + protected function httpMove($uri) { + + $moveInfo = $this->getCopyAndMoveInfo(); + + // If the destination is part of the source tree, we must fail + if ($moveInfo['destination']==$uri) + throw new Exception\Forbidden('Source and destination uri are identical.'); + + if ($moveInfo['destinationExists']) { + + if (!$this->broadcastEvent('beforeUnbind',array($moveInfo['destination']))) return false; + $this->tree->delete($moveInfo['destination']); + $this->broadcastEvent('afterUnbind',array($moveInfo['destination'])); + + } + + if (!$this->broadcastEvent('beforeUnbind',array($uri))) return false; + if (!$this->broadcastEvent('beforeBind',array($moveInfo['destination']))) return false; + $this->tree->move($uri,$moveInfo['destination']); + $this->broadcastEvent('afterUnbind',array($uri)); + $this->broadcastEvent('afterBind',array($moveInfo['destination'])); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $this->httpResponse->setHeader('Content-Length','0'); + $this->httpResponse->sendStatus($moveInfo['destinationExists']?204:201); + + } + + /** + * 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 string $uri + * @return bool + */ + protected function httpCopy($uri) { + + $copyInfo = $this->getCopyAndMoveInfo(); + // If the destination is part of the source tree, we must fail + if ($copyInfo['destination']==$uri) + throw new Exception\Forbidden('Source and destination uri are identical.'); + + if ($copyInfo['destinationExists']) { + if (!$this->broadcastEvent('beforeUnbind',array($copyInfo['destination']))) return false; + $this->tree->delete($copyInfo['destination']); + + } + if (!$this->broadcastEvent('beforeBind',array($copyInfo['destination']))) return false; + $this->tree->copy($uri,$copyInfo['destination']); + $this->broadcastEvent('afterBind',array($copyInfo['destination'])); + + // If a resource was overwritten we should send a 204, otherwise a 201 + $this->httpResponse->setHeader('Content-Length','0'); + $this->httpResponse->sendStatus($copyInfo['destinationExists']?204:201); + + } + + + + /** + * 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 string $uri + * @return void + */ + protected function httpReport($uri) { + + $body = $this->httpRequest->getBody(true); + $dom = XMLUtil::loadDOMDocument($body); + + $reportName = XMLUtil::toClarkNotation($dom->firstChild); + + if ($this->broadcastEvent('report',array($reportName,$dom, $uri))) { + + // If broadcastEvent returned true, it means the report was not supported + throw new Exception\ReportNotSupported(); + + } + + } + + // }}} + // {{{ HTTP/WebDAV protocol helpers + + /** + * Returns an array with all the supported HTTP methods for a specific uri. + * + * @param string $uri + * @return array + */ + public function getAllowedMethods($uri) { + + $methods = array( + 'OPTIONS', + 'GET', + 'HEAD', + 'DELETE', + 'PROPFIND', + 'PUT', + 'PROPPATCH', + 'COPY', + 'MOVE', + 'REPORT' + ); + + // The MKCOL is only allowed on an unmapped uri + try { + $this->tree->getNodeForPath($uri); + } 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($uri)); + array_unique($methods); + + return $methods; + + } + + /** + * Gets the uri for the request, keeping the base uri into consideration + * + * @return string + */ + public function getRequestUri() { + + return $this->calculateUri($this->httpRequest->getUri()); + + } + + /** + * Calculates the uri for a request, making sure that the base uri is stripped out + * + * @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 + */ + public function calculateUri($uri) { + + if ($uri[0]!='/' && strpos($uri,'://')) { + + $uri = parse_url($uri,PHP_URL_PATH); + + } + + $uri = str_replace('//','/',$uri); + + if (strpos($uri,$this->getBaseUri())===0) { + + return trim(URLUtil::decodePath(substr($uri,strlen($this->getBaseUri()))),'/'); + + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri.'/' === $this->getBaseUri()) { + + 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 + */ + public 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 + */ + public 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 array( + $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: + * array( + * '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 + */ + public function getHTTPPrefer() { + + $result = array( + 'return-asynch' => false, + 'return-minimal' => false, + 'return-representation' => false, + 'wait' => null, + 'strict' => false, + 'lenient' => false, + ); + + if ($prefer = $this->httpRequest->getHeader('Prefer')) { + + $parameters = array_map('trim', + explode(',', $prefer) + ); + + foreach($parameters as $parameter) { + + // Right now our regex only supports the tokens actually + // specified in the draft. We may need to expand this if new + // tokens get registered. + if(!preg_match('/^(?P<token>[a-z0-9-]+)(?:=(?P<value>[0-9]+))?$/', $parameter, $matches)) { + continue; + } + + switch($matches['token']) { + + case 'return-asynch' : + case 'return-minimal' : + case 'return-representation' : + case 'strict' : + case 'lenient' : + $result[$matches['token']] = true; + break; + case 'wait' : + $result[$matches['token']] = $matches['value']; + break; + + } + + } + + } + + if ($this->httpRequest->getHeader('Brief')=='t') { + $result['return-minimal'] = true; + } + + 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) + * + * @return array + */ + public function getCopyAndMoveInfo() { + + // Collecting the relevant HTTP headers + if (!$this->httpRequest->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); + $destination = $this->calculateUri($this->httpRequest->getHeader('Destination')); + $overwrite = $this->httpRequest->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; + + + + } + + // These are the three relevant properties we need to return + return array( + 'destination' => $destination, + 'destinationExists' => $destinationNode==true, + '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. + * + * @param string $path + * @param array $propertyNames + */ + public function getProperties($path, $propertyNames) { + + $result = $this->getPropertiesForPath($path,$propertyNames,0); + return $result[0][200]; + + } + + /** + * 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 + */ + public function getPropertiesForChildren($path, $propertyNames) { + + $result = array(); + 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 + */ + public function getHTTPHeaders($path) { + + $propertyMap = array( + '{DAV:}getcontenttype' => 'Content-Type', + '{DAV:}getcontentlength' => 'Content-Length', + '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getetag' => 'ETag', + ); + + $properties = $this->getProperties($path,array_keys($propertyMap)); + + $headers = array(); + 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 Property\GetLastModified) { + $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); + } + + } + + return $headers; + + } + + /** + * 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 + */ + public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) { + + if ($depth!=0) $depth = 1; + + $path = rtrim($path,'/'); + + // This event allows people to intercept these requests early on in the + // process. + // + // We're not doing anything with the result, but this can be helpful to + // pre-fetch certain expensive live properties. + $this->broadCastEvent('beforeGetPropertiesForPath', array($path, $propertyNames, $depth)); + + $returnPropertyList = array(); + + $parentNode = $this->tree->getNodeForPath($path); + $nodes = array( + $path => $parentNode + ); + if ($depth==1 && $parentNode instanceof ICollection) { + foreach($this->tree->getChildren($path) as $childNode) + $nodes[$path . '/' . $childNode->getName()] = $childNode; + } + + // If the propertyNames array is empty, it means all properties are requested. + // We shouldn't actually return everything we know though, and only return a + // sensible list. + $allProperties = count($propertyNames)==0; + + foreach($nodes as $myPath=>$node) { + + $currentPropertyNames = $propertyNames; + + $newProperties = array( + '200' => array(), + '404' => array(), + ); + + if ($allProperties) { + // Default list of propertyNames, when all properties were requested. + $currentPropertyNames = array( + '{DAV:}getlastmodified', + '{DAV:}getcontentlength', + '{DAV:}resourcetype', + '{DAV:}quota-used-bytes', + '{DAV:}quota-available-bytes', + '{DAV:}getetag', + '{DAV:}getcontenttype', + ); + } + + // If the resourceType was not part of the list, we manually add it + // and mark it for removal. We need to know the resourcetype in order + // to make certain decisions about the entry. + // WebDAV dictates we should add a / and the end of href's for collections + $removeRT = false; + if (!in_array('{DAV:}resourcetype',$currentPropertyNames)) { + $currentPropertyNames[] = '{DAV:}resourcetype'; + $removeRT = true; + } + + $result = $this->broadcastEvent('beforeGetProperties',array($myPath, $node, &$currentPropertyNames, &$newProperties)); + // If this method explicitly returned false, we must ignore this + // node as it is inaccessible. + if ($result===false) continue; + + if (count($currentPropertyNames) > 0) { + + if ($node instanceof IProperties) { + $nodeProperties = $node->getProperties($currentPropertyNames); + + // The getProperties method may give us too much, + // properties, in case the implementor was lazy. + // + // So as we loop through this list, we will only take the + // properties that were actually requested and discard the + // rest. + foreach($currentPropertyNames as $k=>$currentPropertyName) { + if (isset($nodeProperties[$currentPropertyName])) { + unset($currentPropertyNames[$k]); + $newProperties[200][$currentPropertyName] = $nodeProperties[$currentPropertyName]; + } + } + + } + + } + + foreach($currentPropertyNames as $prop) { + + if (isset($newProperties[200][$prop])) continue; + + switch($prop) { + case '{DAV:}getlastmodified' : if ($node->getLastModified()) $newProperties[200][$prop] = new Property\GetLastModified($node->getLastModified()); break; + case '{DAV:}getcontentlength' : + if ($node instanceof IFile) { + $size = $node->getSize(); + if (!is_null($size)) { + $newProperties[200][$prop] = (int)$node->getSize(); + } + } + break; + case '{DAV:}quota-used-bytes' : + if ($node instanceof IQuota) { + $quotaInfo = $node->getQuotaInfo(); + $newProperties[200][$prop] = $quotaInfo[0]; + } + break; + case '{DAV:}quota-available-bytes' : + if ($node instanceof IQuota) { + $quotaInfo = $node->getQuotaInfo(); + $newProperties[200][$prop] = $quotaInfo[1]; + } + break; + case '{DAV:}getetag' : if ($node instanceof IFile && $etag = $node->getETag()) $newProperties[200][$prop] = $etag; break; + case '{DAV:}getcontenttype' : if ($node instanceof IFile && $ct = $node->getContentType()) $newProperties[200][$prop] = $ct; break; + case '{DAV:}supported-report-set' : + $reports = array(); + foreach($this->plugins as $plugin) { + $reports = array_merge($reports, $plugin->getSupportedReportSet($myPath)); + } + $newProperties[200][$prop] = new Property\SupportedReportSet($reports); + break; + case '{DAV:}resourcetype' : + $newProperties[200]['{DAV:}resourcetype'] = new Property\ResourceType(); + foreach($this->resourceTypeMapping as $className => $resourceType) { + if ($node instanceof $className) $newProperties[200]['{DAV:}resourcetype']->add($resourceType); + } + break; + + } + + // If we were unable to find the property, we will list it as 404. + if (!$allProperties && !isset($newProperties[200][$prop])) $newProperties[404][$prop] = null; + + } + + $this->broadcastEvent('afterGetProperties',array(trim($myPath,'/'),&$newProperties, $node)); + + $newProperties['href'] = trim($myPath,'/'); + + // Its is a WebDAV recommendation to add a trailing slash to collectionnames. + // Apple's iCal also requires a trailing slash for principals (rfc 3744), though this is non-standard. + if ($myPath!='' && isset($newProperties[200]['{DAV:}resourcetype'])) { + $rt = $newProperties[200]['{DAV:}resourcetype']; + if ($rt->is('{DAV:}collection') || $rt->is('{DAV:}principal')) { + $newProperties['href'] .='/'; + } + } + + // If the resourcetype property was manually added to the requested property list, + // we will remove it again. + if ($removeRT) unset($newProperties[200]['{DAV:}resourcetype']); + + $returnPropertyList[] = $newProperties; + + } + + return $returnPropertyList; + + } + + /** + * 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 + */ + public function createFile($uri,$data, &$etag = null) { + + list($dir,$name) = URLUtil::splitPath($uri); + + if (!$this->broadcastEvent('beforeBind',array($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'); + } + + if (!$this->broadcastEvent('beforeCreateFile',array($uri, &$data, $parent))) return false; + + $etag = $parent->createFile($name,$data); + $this->tree->markDirty($dir . '/' . $name); + + $this->broadcastEvent('afterBind',array($uri)); + $this->broadcastEvent('afterCreateFile',array($uri, $parent)); + + return true; + } + + /** + * This method is invoked by sub-systems creating a new directory. + * + * @param string $uri + * @return void + */ + public function createDirectory($uri) { + + $this->createCollection($uri,array('{DAV:}collection'),array()); + + } + + /** + * Use this method to create a new collection + * + * The {DAV:}resourcetype is specified using the resourceType array. + * At the very least it must contain {DAV:}collection. + * + * The properties array can contain a list of additional properties. + * + * @param string $uri The new uri + * @param array $resourceType The resourceType(s) + * @param array $properties A list of properties + * @return array|null + */ + public function createCollection($uri, array $resourceType, array $properties) { + + list($parentUri,$newName) = URLUtil::splitPath($uri); + + // Making sure {DAV:}collection was specified as resourceType + if (!in_array('{DAV:}collection', $resourceType)) { + throw new Exception\InvalidResourceType('The resourceType for this collection must at least include {DAV:}collection'); + } + + + // 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) { + // This is correct + } + + + if (!$this->broadcastEvent('beforeBind',array($uri))) return; + + // There are 2 modes of operation. The standard collection + // creates the directory, and then updates properties + // the extended collection can create it directly. + if ($parent instanceof IExtendedCollection) { + + $parent->createExtendedCollection($newName, $resourceType, $properties); + + } else { + + // No special resourcetypes are supported + if (count($resourceType)>1) { + throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.'); + } + + $parent->createDirectory($newName); + $rollBack = false; + $exception = null; + $errorResult = null; + + if (count($properties)>0) { + + try { + + $errorResult = $this->updateProperties($uri, $properties); + if (!isset($errorResult[200])) { + $rollBack = true; + } + + } catch (Exception $e) { + + $rollBack = true; + $exception = $e; + + } + + } + + if ($rollBack) { + if (!$this->broadcastEvent('beforeUnbind',array($uri))) return; + $this->tree->delete($uri); + + // Re-throwing exception + if ($exception) throw $exception; + + return $errorResult; + } + + } + $this->tree->markDirty($parentUri); + $this->broadcastEvent('afterBind',array($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 statuscodes for keys, which in turn + * contain arrays with propertynames. This response can be used + * to generate a multistatus body. + * + * @param string $uri + * @param array $properties + * @return array + */ + public function updateProperties($uri, array $properties) { + + // we'll start by grabbing the node, this will throw the appropriate + // exceptions if it doesn't. + $node = $this->tree->getNodeForPath($uri); + + $result = array( + 200 => array(), + 403 => array(), + 424 => array(), + ); + $remainingProperties = $properties; + $hasError = false; + + // Running through all properties to make sure none of them are protected + if (!$hasError) foreach($properties as $propertyName => $value) { + if(in_array($propertyName, $this->protectedProperties)) { + $result[403][$propertyName] = null; + unset($remainingProperties[$propertyName]); + $hasError = true; + } + } + + if (!$hasError) { + // Allowing plugins to take care of property updating + $hasError = !$this->broadcastEvent('updateProperties',array( + &$remainingProperties, + &$result, + $node + )); + } + + // If the node is not an instance of Sabre\DAV\IProperties, every + // property is 403 Forbidden + if (!$hasError && count($remainingProperties) && !($node instanceof IProperties)) { + $hasError = true; + foreach($properties as $propertyName=> $value) { + $result[403][$propertyName] = null; + } + $remainingProperties = array(); + } + + // Only if there were no errors we may attempt to update the resource + if (!$hasError) { + + if (count($remainingProperties)>0) { + + $updateResult = $node->updateProperties($remainingProperties); + + if ($updateResult===true) { + // success + foreach($remainingProperties as $propertyName=>$value) { + $result[200][$propertyName] = null; + } + + } elseif ($updateResult===false) { + // The node failed to update the properties for an + // unknown reason + foreach($remainingProperties as $propertyName=>$value) { + $result[403][$propertyName] = null; + } + + } elseif (is_array($updateResult)) { + + // The node has detailed update information + // We need to merge the results with the earlier results. + foreach($updateResult as $status => $props) { + if (is_array($props)) { + if (!isset($result[$status])) + $result[$status] = array(); + + $result[$status] = array_merge($result[$status], $updateResult[$status]); + } + } + + } else { + throw new Exception('Invalid result from updateProperties'); + } + $remainingProperties = array(); + } + + } + + foreach($remainingProperties as $propertyName=>$value) { + // if there are remaining properties, it must mean + // there's a dependency failure + $result[424][$propertyName] = null; + } + + // Removing empty array values + foreach($result as $status=>$props) { + + if (count($props)===0) unset($result[$status]); + + } + $result['href'] = $uri; + return $result; + + } + + /** + * 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. + * + * If the $handleAsGET argument is set to true, it will also return 304 + * Not Modified for failure of the If-None-Match precondition. This is the + * desired behaviour for HTTP GET and HTTP HEAD requests. + * + * @param bool $handleAsGET + * @return bool + */ + public function checkPreconditions($handleAsGET = false) { + + $uri = $this->getRequestUri(); + $node = null; + $lastMod = null; + $etag = null; + + if ($ifMatch = $this->httpRequest->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($uri); + } 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->getETag(); + 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) { + throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.','If-Match'); + } + } + } + + if ($ifNoneMatch = $this->httpRequest->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($uri); + } 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->getETag(); + + foreach($ifNoneMatch as $ifNoneMatchItem) { + + // Stripping any extra spaces + $ifNoneMatchItem = trim($ifNoneMatchItem,' '); + + if ($etag===$ifNoneMatchItem) $haveMatch = true; + + } + + } + + if ($haveMatch) { + if ($handleAsGET) { + $this->httpResponse->sendStatus(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 = $this->httpRequest->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($uri); + } + $lastMod = $node->getLastModified(); + if ($lastMod) { + $lastMod = new \DateTime('@' . $lastMod); + if ($lastMod <= $date) { + $this->httpResponse->sendStatus(304); + $this->httpResponse->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); + return false; + } + } + } + } + + if ($ifUnmodifiedSince = $this->httpRequest->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($uri); + } + $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'); + } + } + } + + } + return true; + + } + + // }}} + // {{{ 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 + */ + public function generateMultiStatus(array $fileProperties, $strip404s = false) { + + $dom = new \DOMDocument('1.0','utf-8'); + //$dom->formatOutput = true; + $multiStatus = $dom->createElement('d:multistatus'); + $dom->appendChild($multiStatus); + + // Adding in default namespaces + foreach($this->xmlNamespaces as $namespace=>$prefix) { + + $multiStatus->setAttribute('xmlns:' . $prefix,$namespace); + + } + + foreach($fileProperties as $entry) { + + $href = $entry['href']; + unset($entry['href']); + + if ($strip404s && isset($entry[404])) { + unset($entry[404]); + } + + $response = new Property\Response($href,$entry); + $response->serialize($this,$multiStatus); + + } + + return $dom->saveXML(); + + } + + /** + * This method parses a PropPatch request + * + * PropPatch changes the properties for a resource. This method + * returns a list of properties. + * + * The keys in the returned array contain the property name (e.g.: {DAV:}displayname, + * and the value contains the property value. If a property is to be removed the value + * will be null. + * + * @param string $body xml body + * @return array list of properties in need of updating or deletion + */ + public function parsePropPatchRequest($body) { + + //We'll need to change the DAV namespace declaration to something else in order to make it parsable + $dom = XMLUtil::loadDOMDocument($body); + + $newProperties = array(); + + foreach($dom->firstChild->childNodes as $child) { + + if ($child->nodeType !== XML_ELEMENT_NODE) continue; + + $operation = XMLUtil::toClarkNotation($child); + + if ($operation!=='{DAV:}set' && $operation!=='{DAV:}remove') continue; + + $innerProperties = XMLUtil::parseProperties($child, $this->propertyMap); + + foreach($innerProperties as $propertyName=>$propertyValue) { + + if ($operation==='{DAV:}remove') { + $propertyValue = null; + } + + $newProperties[$propertyName] = $propertyValue; + + } + + } + + return $newProperties; + + } + + /** + * This method parses the PROPFIND request and returns its information + * + * This will either be a list of properties, or an empty array; in which case + * an {DAV:}allprop was requested. + * + * @param string $body + * @return array + */ + public function parsePropFindRequest($body) { + + // If the propfind body was empty, it means IE is requesting 'all' properties + if (!$body) return array(); + + $dom = XMLUtil::loadDOMDocument($body); + $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); + return array_keys(XMLUtil::parseProperties($elem)); + + } + + // }}} + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php b/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php new file mode 100644 index 000000000..b8396abaf --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/ServerPlugin.php @@ -0,0 +1,90 @@ +<?php + +namespace Sabre\DAV; + +/** + * The baseclass for all server plugins. + * + * Plugins can modify or extend the servers behaviour. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 public 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 + */ + public function getFeatures() { + + return array(); + + } + + /** + * 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 + */ + public function getHTTPMethods($uri) { + + return array(); + + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + public 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 + */ + public function getSupportedReportSet($uri) { + + return array(); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/SimpleCollection.php b/vendor/sabre/dav/lib/Sabre/DAV/SimpleCollection.php new file mode 100644 index 000000000..c38c39e72 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/SimpleCollection.php @@ -0,0 +1,108 @@ +<?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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class SimpleCollection extends Collection { + + /** + * List of childnodes + * + * @var array + */ + protected $children = array(); + + /** + * 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 array $children + */ + public function __construct($name,array $children = array()) { + + $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 + */ + public function addChild(INode $child) { + + $this->children[$child->getName()] = $child; + + } + + /** + * Returns the name of the collection + * + * @return string + */ + public 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 + */ + public 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 array + */ + public function getChildren() { + + return array_values($this->children); + + } + + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php b/vendor/sabre/dav/lib/Sabre/DAV/SimpleFile.php new file mode 100644 index 000000000..694207eec --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class SimpleFile extends File { + + /** + * File contents + * + * @var string + */ + protected $contents = array(); + + /** + * 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 + */ + public 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 + */ + public function getName() { + + return $this->name; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + public function get() { + + return $this->contents; + + } + + /** + * Returns the size of the file, in bytes. + * + * @return int + */ + public 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 + */ + public function getETag() { + + return '"' . md5($this->contents) . '"'; + + } + + /** + * Returns the mime-type for a file + * + * If null is returned, we'll assume application/octet-stream + * @return string + */ + public function getContentType() { + + return $this->mimeType; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/StringUtil.php new file mode 100644 index 000000000..0f299bf11 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 public 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 public function ensureUTF8($input) { + + $encoding = mb_detect_encoding($input , array('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/Sabre/DAV/TemporaryFileFilterPlugin.php b/vendor/sabre/dav/lib/Sabre/DAV/TemporaryFileFilterPlugin.php new file mode 100644 index 000000000..d1ae18d5b --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/TemporaryFileFilterPlugin.php @@ -0,0 +1,289 @@ +<?php + +namespace Sabre\DAV; + +/** + * 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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 = array( + '/^\._(.*)$/', // 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 + */ + public 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 + */ + public function initialize(Server $server) { + + $this->server = $server; + $server->subscribeEvent('beforeMethod',array($this,'beforeMethod')); + $server->subscribeEvent('beforeCreateFile',array($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 string $method + * @param string $uri + * @return bool + */ + public function beforeMethod($method, $uri) { + + if (!$tempLocation = $this->isTempFile($uri)) + return true; + + switch($method) { + case 'GET' : + return $this->httpGet($tempLocation); + case 'PUT' : + return $this->httpPut($tempLocation); + case 'PROPFIND' : + return $this->httpPropfind($tempLocation, $uri); + case 'DELETE' : + return $this->httpDelete($tempLocation); + } + return true; + + } + + /** + * 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 + * @return bool + */ + public function beforeCreateFile($uri,$data) { + + if ($tempPath = $this->isTempFile($uri)) { + + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp','true'); + file_put_contents($tempPath,$data); + return false; + } + return true; + + } + + /** + * 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 boolean|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 string $tempLocation + * @return bool + */ + public function httpGet($tempLocation) { + + if (!file_exists($tempLocation)) return true; + + $hR = $this->server->httpResponse; + $hR->setHeader('Content-Type','application/octet-stream'); + $hR->setHeader('Content-Length',filesize($tempLocation)); + $hR->setHeader('X-Sabre-Temp','true'); + $hR->sendStatus(200); + $hR->sendBody(fopen($tempLocation,'r')); + return false; + + } + + /** + * This method handles the PUT method. + * + * @param string $tempLocation + * @return bool + */ + public function httpPut($tempLocation) { + + $hR = $this->server->httpResponse; + $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->sendStatus($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 string $tempLocation + * @return bool + */ + public function httpDelete($tempLocation) { + + if (!file_exists($tempLocation)) return true; + + unlink($tempLocation); + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp','true'); + $hR->sendStatus(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 string $tempLocation + * @param string $uri + * @return bool + */ + public function httpPropfind($tempLocation, $uri) { + + if (!file_exists($tempLocation)) return true; + + $hR = $this->server->httpResponse; + $hR->setHeader('X-Sabre-Temp','true'); + $hR->sendStatus(207); + $hR->setHeader('Content-Type','application/xml; charset=utf-8'); + + $this->server->parsePropFindRequest($this->server->httpRequest->getBody(true)); + + $properties = array( + 'href' => $uri, + 200 => array( + '{DAV:}getlastmodified' => new Property\GetLastModified(filemtime($tempLocation)), + '{DAV:}getcontentlength' => filesize($tempLocation), + '{DAV:}resourcetype' => new Property\ResourceType(null), + '{'.Server::NS_SABREDAV.'}tempFile' => true, + + ), + ); + + $data = $this->server->generateMultiStatus(array($properties)); + $hR->sendBody($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/Sabre/DAV/Tree.php b/vendor/sabre/dav/lib/Sabre/DAV/Tree.php new file mode 100644 index 000000000..af08c39f0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Tree.php @@ -0,0 +1,193 @@ +<?php + +namespace Sabre\DAV; + +/** + * Abstract tree object + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +abstract class Tree { + + /** + * This function must return an INode object for a path + * If a Path doesn't exist, thrown a Exception_NotFound + * + * @param string $path + * @throws Exception\NotFound + * @return INode + */ + abstract function getNodeForPath($path); + + /** + * 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 + */ + public function nodeExists($path) { + + try { + + $this->getNodeForPath($path); + return true; + + } 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 + */ + public 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 + */ + public function move($sourcePath, $destinationPath) { + + list($sourceDir, $sourceName) = URLUtil::splitPath($sourcePath); + list($destinationDir, $destinationName) = URLUtil::splitPath($destinationPath); + + if ($sourceDir===$destinationDir) { + $renameable = $this->getNodeForPath($sourcePath); + $renameable->setName($destinationName); + } else { + $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 + */ + public 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 + */ + public function getChildren($path) { + + $node = $this->getNodeForPath($path); + return $node->getChildren(); + + } + + /** + * 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 + */ + public function markDirty($path) { + + + } + + /** + * 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(array()); + $destination->updateProperties($props); + + } + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php b/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php new file mode 100644 index 000000000..9004f678a --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Tree/Filesystem.php @@ -0,0 +1,133 @@ +<?php + +namespace Sabre\DAV\Tree; + +use Sabre\DAV; + +/** + * FileSystem Tree + * + * This class is an alternative to the standard ObjectTree. This tree can only + * use Sabre\DAV\FS\Directory and File classes, but as a result it allows for a few + * optimizations that otherwise wouldn't be possible. + * + * Specifically copying and moving are much, much faster. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Filesystem extends DAV\Tree { + + /** + * Base url on the filesystem. + * + * @var string + */ + protected $basePath; + + /** + * Creates this tree + * + * Supply the path you'd like to share. + * + * @param string $basePath + */ + public function __construct($basePath) { + + $this->basePath = $basePath; + + } + + /** + * Returns a new node for the given path + * + * @param string $path + * @return DAV\FS\Node + */ + public function getNodeForPath($path) { + + $realPath = $this->getRealPath($path); + if (!file_exists($realPath)) { + throw new DAV\Exception\NotFound('File at location ' . $realPath . ' not found'); + } + if (is_dir($realPath)) { + return new DAV\FS\Directory($realPath); + } else { + return new DAV\FS\File($realPath); + } + + } + + /** + * Returns the real filesystem path for a webdav url. + * + * @param string $publicPath + * @return string + */ + protected function getRealPath($publicPath) { + + return rtrim($this->basePath,'/') . '/' . trim($publicPath,'/'); + + } + + /** + * Copies a file or directory. + * + * This method must work recursively and delete the destination + * if it exists + * + * @param string $source + * @param string $destination + * @return void + */ + public function copy($source,$destination) { + + $source = $this->getRealPath($source); + $destination = $this->getRealPath($destination); + $this->realCopy($source,$destination); + + } + + /** + * Used by self::copy + * + * @param string $source + * @param string $destination + * @return void + */ + protected function realCopy($source,$destination) { + + if (is_file($source)) { + copy($source,$destination); + } else { + mkdir($destination); + foreach(scandir($source) as $subnode) { + + if ($subnode=='.' || $subnode=='..') continue; + $this->realCopy($source.'/'.$subnode,$destination.'/'.$subnode); + + } + } + + } + + /** + * Moves a file or directory recursively. + * + * If the destination exists, delete it first. + * + * @param string $source + * @param string $destination + * @return void + */ + public function move($source,$destination) { + + $source = $this->getRealPath($source); + $destination = $this->getRealPath($destination); + rename($source,$destination); + + } + +} + diff --git a/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php new file mode 100644 index 000000000..b71ea003d --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/URLUtil.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAV; + +/** + * URL utility class + * + * This class provides methods to deal with encoding and decoding url (percent encoded) strings. + * + * It was not possible to use PHP's built-in methods for this, because some clients don't like + * encoding of certain characters. + * + * Specifically, it was found that GVFS (gnome's webdav client) does not like encoding of ( and + * ). Since these are reserved, but don't have a reserved meaning in url, these characters are + * kept as-is. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class URLUtil { + + /** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @param string $path + * @return string + */ + static function encodePath($path) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/])/',function($match) { + + return '%'.sprintf('%02x',ord($match[0])); + + }, $path); + + } + + /** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @param string $pathSegment + * @return string + */ + static function encodePathSegment($pathSegment) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)])/',function($match) { + + return '%'.sprintf('%02x',ord($match[0])); + + }, $pathSegment); + } + + /** + * Decodes a url-encoded path + * + * @param string $path + * @return string + */ + static function decodePath($path) { + + return self::decodePathSegment($path); + + } + + /** + * Decodes a url-encoded path segment + * + * @param string $path + * @return string + */ + static function decodePathSegment($path) { + + $path = rawurldecode($path); + $encoding = mb_detect_encoding($path, array('UTF-8','ISO-8859-1')); + + switch($encoding) { + + case 'ISO-8859-1' : + $path = utf8_encode($path); + + } + + return $path; + + } + + /** + * Returns the 'dirname' and 'basename' for a path. + * + * The reason there is a custom function for this purpose, is because + * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale is used) + * and we need a method that just operates on UTF-8 characters. + * + * In addition basename and dirname are platform aware, and will treat backslash (\) as a + * directory separator on windows. + * + * This method returns the 2 components as an array. + * + * If there is no dirname, it will return an empty string. Any / appearing at the end of the + * string is stripped off. + * + * @param string $path + * @return array + */ + static function splitPath($path) { + + $matches = array(); + if(preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u',$path,$matches)) { + return array($matches[1],$matches[2]); + } else { + return array(null,null); + } + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/UUIDUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/UUIDUtil.php new file mode 100644 index 000000000..e6b78fac6 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/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) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/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 + ) == true; + + } + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/Version.php b/vendor/sabre/dav/lib/Sabre/DAV/Version.php new file mode 100644 index 000000000..c82bc17e3 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/Version.php @@ -0,0 +1,24 @@ +<?php + +namespace Sabre\DAV; + +/** + * This class contains the SabreDAV version constants. + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '1.8.7'; + + /** + * Stability : alpha, beta, stable + */ + const STABILITY = 'stable'; + +} diff --git a/vendor/sabre/dav/lib/Sabre/DAV/XMLUtil.php b/vendor/sabre/dav/lib/Sabre/DAV/XMLUtil.php new file mode 100644 index 000000000..298398da0 --- /dev/null +++ b/vendor/sabre/dav/lib/Sabre/DAV/XMLUtil.php @@ -0,0 +1,187 @@ +<?php + +namespace Sabre\DAV; + +/** + * XML utilities for WebDAV + * + * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License + */ +class XMLUtil { + + /** + * Returns the 'clark notation' for an element. + * + * For example, and element encoded as: + * <b:myelem xmlns:b="http://www.example.org/" /> + * will be returned as: + * {http://www.example.org}myelem + * + * This format is used throughout the SabreDAV sourcecode. + * Elements encoded with the urn:DAV namespace will + * be returned as if they were in the DAV: namespace. This is to avoid + * compatibility problems. + * + * This function will return null if a nodetype other than an Element is passed. + * + * @param \DOMNode $dom + * @return string + */ + static function toClarkNotation(\DOMNode $dom) { + + if ($dom->nodeType !== XML_ELEMENT_NODE) return null; + + // Mapping back to the real namespace, in case it was dav + if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI; + + // Mapping to clark notation + return '{' . $ns . '}' . $dom->localName; + + } + + /** + * Parses a clark-notation string, and returns the namespace and element + * name components. + * + * If the string was invalid, it will throw an InvalidArgumentException. + * + * @param string $str + * @throws InvalidArgumentException + * @return array + */ + static function parseClarkNotation($str) { + + if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) { + throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string'); + } + + return array( + $matches[1], + $matches[2] + ); + + } + + /** + * This method takes an XML document (as string) and converts all instances of the + * DAV: namespace to urn:DAV + * + * This is unfortunately needed, because the DAV: namespace violates the xml namespaces + * spec, and causes the DOM to throw errors + * + * @param string $xmlDocument + * @return array|string|null + */ + static function convertDAVNamespace($xmlDocument) { + + // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV: + // namespace is actually a violation of the XML namespaces specification, and will cause errors + return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument); + + } + + /** + * This method provides a generic way to load a DOMDocument for WebDAV use. + * + * This method throws a Sabre\DAV\Exception\BadRequest exception for any xml errors. + * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV. + * + * @param string $xml + * @throws Sabre\DAV\Exception\BadRequest + * @return DOMDocument + */ + static function loadDOMDocument($xml) { + + if (empty($xml)) + throw new Exception\BadRequest('Empty XML document sent'); + + // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower) + // does not support this, so we must intercept this and convert to UTF-8. + if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") { + + // Note: the preceeding byte sequence is "<?xml" encoded as UTF_16, without the BOM. + $xml = iconv('UTF-16LE','UTF-8',$xml); + + // Because the xml header might specify the encoding, we must also change this. + // This regex looks for the string encoding="UTF-16" and replaces it with + // encoding="UTF-8". + $xml = preg_replace('|<\?xml([^>]*)encoding="UTF-16"([^>]*)>|u','<?xml\1encoding="UTF-8"\2>',$xml); + + } + + // Retaining old error setting + $oldErrorSetting = libxml_use_internal_errors(true); + + // Clearing any previous errors + libxml_clear_errors(); + + $dom = new \DOMDocument(); + + // We don't generally care about any whitespace + $dom->preserveWhiteSpace = false; + + $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR); + + if ($error = libxml_get_last_error()) { + libxml_clear_errors(); + throw new Exception\BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')'); + } + + // Restoring old mechanism for error handling + if ($oldErrorSetting===false) libxml_use_internal_errors(false); + + return $dom; + + } + + /** + * Parses all WebDAV properties out of a DOM Element + * + * Generally WebDAV properties are enclosed in {DAV:}prop elements. This + * method helps by going through all these and pulling out the actual + * propertynames, making them array keys and making the property values, + * well.. the array values. + * + * If no value was given (self-closing element) null will be used as the + * value. This is used in for example PROPFIND requests. + * + * Complex values are supported through the propertyMap argument. The + * propertyMap should have the clark-notation properties as it's keys, and + * classnames as values. + * + * When any of these properties are found, the unserialize() method will be + * (statically) called. The result of this method is used as the value. + * + * @param \DOMElement $parentNode + * @param array $propertyMap + * @return array + */ + static function parseProperties(\DOMElement $parentNode, array $propertyMap = array()) { + + $propList = array(); + foreach($parentNode->childNodes as $propNode) { + + if (self::toClarkNotation($propNode)!=='{DAV:}prop') continue; + + foreach($propNode->childNodes as $propNodeData) { + + /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */ + if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue; + + $propertyName = self::toClarkNotation($propNodeData); + if (isset($propertyMap[$propertyName])) { + $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData); + } else { + $propList[$propertyName] = $propNodeData->textContent; + } + } + + + } + return $propList; + + } + +} |