diff options
author | Max Kostikov <max@kostikov.co> | 2019-11-18 13:00:43 +0100 |
---|---|---|
committer | Max Kostikov <max@kostikov.co> | 2019-11-18 13:00:43 +0100 |
commit | 498c021aec25d845c67150475294066113de6d20 (patch) | |
tree | c8934c83f6519bf58f27f410683e06cc6615a6a0 /vendor/sabre/dav/lib/DAV/Server.php | |
parent | 5ee133843f166a58e0c6236e543204be8dae70d3 (diff) | |
parent | ef75d27afbbee4cfe7d4441269e350b8f7f432f9 (diff) | |
download | volse-hubzilla-498c021aec25d845c67150475294066113de6d20.tar.gz volse-hubzilla-498c021aec25d845c67150475294066113de6d20.tar.bz2 volse-hubzilla-498c021aec25d845c67150475294066113de6d20.zip |
Merge branch 'dev' into 'dev'
Dev sync
See merge request kostikov/core!5
Diffstat (limited to 'vendor/sabre/dav/lib/DAV/Server.php')
-rw-r--r-- | vendor/sabre/dav/lib/DAV/Server.php | 694 |
1 files changed, 337 insertions, 357 deletions
diff --git a/vendor/sabre/dav/lib/DAV/Server.php b/vendor/sabre/dav/lib/DAV/Server.php index f7fcf3057..09760e9d1 100644 --- a/vendor/sabre/dav/lib/DAV/Server.php +++ b/vendor/sabre/dav/lib/DAV/Server.php @@ -1,76 +1,79 @@ <?php +declare(strict_types=1); + namespace Sabre\DAV; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; -use Sabre\Event\EventEmitter; +use Sabre\Event\EmitterInterface; +use Sabre\Event\WildcardEmitterTrait; use Sabre\HTTP; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; -use Sabre\HTTP\URLUtil; use Sabre\Uri; /** - * Main DAV server class + * Main DAV server class. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ -class Server extends EventEmitter implements LoggerAwareInterface { - +class Server implements LoggerAwareInterface, EmitterInterface +{ + use WildcardEmitterTrait; use LoggerAwareTrait; /** - * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree + * Infinity is used for some request supporting the HTTP Depth header and indicates that the operation should traverse the entire tree. */ const DEPTH_INFINITY = -1; /** - * XML namespace for all SabreDAV related elements + * XML namespace for all SabreDAV related elements. */ const NS_SABREDAV = 'http://sabredav.org/ns'; /** - * The tree object + * The tree object. * * @var Tree */ public $tree; /** - * The base uri + * The base uri. * * @var string */ protected $baseUri = null; /** - * httpResponse + * httpResponse. * * @var HTTP\Response */ public $httpResponse; /** - * httpRequest + * httpRequest. * * @var HTTP\Request */ public $httpRequest; /** - * PHP HTTP Sapi + * PHP HTTP Sapi. * * @var HTTP\Sapi */ public $sapi; /** - * The list of plugins + * The list of plugins. * * @var array */ @@ -98,7 +101,6 @@ class Server extends EventEmitter implements LoggerAwareInterface { * @var string[] */ public $protectedProperties = [ - // RFC4918 '{DAV:}getcontentlength', '{DAV:}getetag', @@ -129,12 +131,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { // sabredav extensions '{http://sabredav.org/ns}sync-token', - ]; /** * This is a flag that allow or not showing file, line and code - * of the exception in the returned XML + * of the exception in the returned XML. * * @var bool */ @@ -181,10 +182,10 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @var bool */ - static $exposeVersion = true; + public static $exposeVersion = true; /** - * Sets up the server + * 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 @@ -198,25 +199,15 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @param Tree|INode|array|null $treeOrNode The tree object */ - function __construct($treeOrNode = null) { - + public function __construct($treeOrNode = null) + { if ($treeOrNode instanceof Tree) { $this->tree = $treeOrNode; } elseif ($treeOrNode instanceof INode) { $this->tree = new Tree($treeOrNode); } elseif (is_array($treeOrNode)) { - - // If it's an array, a list of nodes was passed, and we need to - // create the root node. - foreach ($treeOrNode as $node) { - if (!($node instanceof INode)) { - throw new Exception('Invalid argument passed to constructor. If you\'re passing an array, all the values must implement Sabre\\DAV\\INode'); - } - } - $root = new SimpleCollection('root', $treeOrNode); $this->tree = new Tree($root); - } elseif (is_null($treeOrNode)) { $root = new SimpleCollection('root'); $this->tree = new Tree($root); @@ -229,18 +220,14 @@ class Server extends EventEmitter implements LoggerAwareInterface { $this->httpResponse = new HTTP\Response(); $this->httpRequest = $this->sapi->getRequest(); $this->addPlugin(new CorePlugin()); - } /** - * Starts the DAV Server - * - * @return void + * Starts the DAV Server. */ - function exec() { - + public function start() + { 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. @@ -252,9 +239,7 @@ class Server extends EventEmitter implements LoggerAwareInterface { // Setting the base url $this->httpRequest->setBaseUrl($this->getBaseUri()); $this->invokeMethod($this->httpRequest, $this->httpResponse); - - } catch (\Exception $e) { - + } catch (\Throwable $e) { try { $this->emit('exception', [$e]); } catch (\Exception $ignore) { @@ -266,10 +251,8 @@ class Server extends EventEmitter implements LoggerAwareInterface { $error->setAttribute('xmlns:s', self::NS_SABREDAV); $DOM->appendChild($error); - $h = function($v) { - - return htmlspecialchars($v, ENT_NOQUOTES, 'UTF-8'); - + $h = function ($v) { + return htmlspecialchars((string) $v, ENT_NOQUOTES, 'UTF-8'); }; if (self::$exposeVersion) { @@ -299,18 +282,13 @@ class Server extends EventEmitter implements LoggerAwareInterface { } } - if ($e instanceof Exception) { - $httpCode = $e->getHTTPCode(); $e->serialize($this, $error); $headers = $e->getHTTPHeaders($this); - } else { - $httpCode = 500; $headers = []; - } $headers['Content-Type'] = 'application/xml; charset=utf-8'; @@ -318,37 +296,46 @@ class Server extends EventEmitter implements LoggerAwareInterface { $this->httpResponse->setHeaders($headers); $this->httpResponse->setBody($DOM->saveXML()); $this->sapi->sendResponse($this->httpResponse); - } + } + /** + * Alias of start(). + * + * @deprecated + */ + public function exec() + { + $this->start(); } /** - * Sets the base server uri + * Sets the base server uri. * * @param string $uri - * @return void */ - function setBaseUri($uri) { - + public function setBaseUri($uri) + { // If the baseUri does not end with a slash, we must add it - if ($uri[strlen($uri) - 1] !== '/') + if ('/' !== $uri[strlen($uri) - 1]) { $uri .= '/'; + } $this->baseUri = $uri; - } /** - * Returns the base responding uri + * Returns the base responding uri. * * @return string */ - function getBaseUri() { + public function getBaseUri() + { + if (is_null($this->baseUri)) { + $this->baseUri = $this->guessBaseUri(); + } - if (is_null($this->baseUri)) $this->baseUri = $this->guessBaseUri(); return $this->baseUri; - } /** @@ -359,53 +346,50 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @return string */ - function guessBaseUri() { - + 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, '?')) + 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); + $decodedUri = HTTP\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.'); + 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 + * Adds a plugin to the server. * * For more information, console the documentation of Sabre\DAV\ServerPlugin * * @param ServerPlugin $plugin - * @return void */ - function addPlugin(ServerPlugin $plugin) { - + public function addPlugin(ServerPlugin $plugin) + { $this->plugins[$plugin->getPluginName()] = $plugin; $plugin->initialize($this); - } /** @@ -414,26 +398,26 @@ class Server extends EventEmitter implements LoggerAwareInterface { * This function returns null if the plugin was not found. * * @param string $name + * * @return ServerPlugin */ - function getPlugin($name) { - - if (isset($this->plugins[$name])) + public function getPlugin($name) + { + if (isset($this->plugins[$name])) { return $this->plugins[$name]; + } return null; - } /** - * Returns all plugins + * Returns all plugins. * * @return array */ - function getPlugins() { - + public function getPlugins() + { return $this->plugins; - } /** @@ -441,29 +425,29 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @return LoggerInterface */ - function getLogger() { - + public function getLogger() + { if (!$this->logger) { $this->logger = new NullLogger(); } - return $this->logger; + return $this->logger; } /** - * Handles a http request, and execute a method based on its name + * Handles a http request, and execute a method based on its name. * - * @param RequestInterface $request + * @param RequestInterface $request * @param ResponseInterface $response - * @param bool $sendResponse Whether to send the HTTP response to the DAV client. - * @return void + * @param bool $sendResponse whether to send the HTTP response to the DAV client */ - function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) { - + public function invokeMethod(RequestInterface $request, ResponseInterface $response, $sendResponse = true) + { $method = $request->getMethod(); - if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return; - if (!$this->emit('beforeMethod', [$request, $response])) return; + if (!$this->emit('beforeMethod:'.$method, [$request, $response])) { + return; + } if (self::$exposeVersion) { $response->setHeader('X-Sabre-Version', Version::VERSION); @@ -473,32 +457,31 @@ class Server extends EventEmitter implements LoggerAwareInterface { if (!$this->checkPreconditions($request, $response)) { $this->sapi->sendResponse($response); + return; } - if ($this->emit('method:' . $method, [$request, $response])) { - if ($this->emit('method', [$request, $response])) { - $exMessage = "There was no plugin in the system that was willing to handle this " . $method . " method."; - if ($method === "GET") { - $exMessage .= " Enable the Browser plugin to get a better result here."; - } - - // Unsupported method - throw new Exception\NotImplemented($exMessage); + if ($this->emit('method:'.$method, [$request, $response])) { + $exMessage = 'There was no plugin in the system that was willing to handle this '.$method.' method.'; + if ('GET' === $method) { + $exMessage .= ' Enable the Browser plugin to get a better result here.'; } + + // Unsupported method + throw new Exception\NotImplemented($exMessage); } - if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; - if (!$this->emit('afterMethod', [$request, $response])) return; + if (!$this->emit('afterMethod:'.$method, [$request, $response])) { + return; + } - if ($response->getStatus() === null) { + if (null === $response->getStatus()) { throw new Exception('No subsystem set a valid HTTP status code. Something must have interrupted the request without providing further detail.'); } if ($sendResponse) { $this->sapi->sendResponse($response); $this->emit('afterResponse', [$request, $response]); } - } // {{{ HTTP/WebDAV protocol helpers @@ -507,10 +490,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { * Returns an array with all the supported HTTP methods for a specific uri. * * @param string $path + * * @return array */ - function getAllowedMethods($path) { - + public function getAllowedMethods($path) + { $methods = [ 'OPTIONS', 'GET', @@ -521,7 +505,7 @@ class Server extends EventEmitter implements LoggerAwareInterface { 'PROPPATCH', 'COPY', 'MOVE', - 'REPORT' + 'REPORT', ]; // The MKCOL is only allowed on an unmapped uri @@ -532,22 +516,22 @@ class Server extends EventEmitter implements LoggerAwareInterface { } // We're also checking if any of the plugins register any new methods - foreach ($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($path)); + foreach ($this->plugins as $plugin) { + $methods = array_merge($methods, $plugin->getHTTPMethods($path)); + } array_unique($methods); return $methods; - } /** - * Gets the uri for the request, keeping the base uri into consideration + * Gets the uri for the request, keeping the base uri into consideration. * * @return string */ - function getRequestUri() { - + public function getRequestUri() + { return $this->calculateUri($this->httpRequest->getUrl()); - } /** @@ -559,66 +543,65 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * uri-decodes the path * * @param string $uri + * * @throws Exception\Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri + * * @return string */ - function calculateUri($uri) { - - if ($uri[0] != '/' && strpos($uri, '://')) { - + public function calculateUri($uri) + { + if ('' != $uri && '/' != $uri[0] && strpos($uri, '://')) { $uri = parse_url($uri, PHP_URL_PATH); - } - $uri = Uri\normalize(str_replace('//', '/', $uri)); + $uri = Uri\normalize(preg_replace('|/+|', '/', $uri)); $baseUri = Uri\normalize($this->getBaseUri()); - if (strpos($uri, $baseUri) === 0) { - - return trim(URLUtil::decodePath(substr($uri, strlen($baseUri))), '/'); + if (0 === strpos($uri, $baseUri)) { + return trim(HTTP\decodePath(substr($uri, strlen($baseUri))), '/'); // A special case, if the baseUri was accessed without a trailing // slash, we'll accept it as well. - } elseif ($uri . '/' === $baseUri) { - + } elseif ($uri.'/' === $baseUri) { return ''; - } else { - - throw new Exception\Forbidden('Requested uri (' . $uri . ') is out of base uri (' . $this->getBaseUri() . ')'); - + throw new Exception\Forbidden('Requested uri ('.$uri.') is out of base uri ('.$this->getBaseUri().')'); } - } /** - * Returns the HTTP depth header + * Returns the HTTP depth header. * * This method returns the contents of the HTTP depth request header. If the depth header was 'infinity' it will return the Sabre\DAV\Server::DEPTH_INFINITY object * It is possible to supply a default depth value, which is used when the depth header has invalid content, or is completely non-existent * * @param mixed $default + * * @return int */ - function getHTTPDepth($default = self::DEPTH_INFINITY) { - + 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 (is_null($depth)) { + return $default; + } + if ('infinity' == $depth) { + return self::DEPTH_INFINITY; + } // If its an unknown value. we'll grab the default - if (!ctype_digit($depth)) return $default; - - return (int)$depth; + if (!ctype_digit($depth)) { + return $default; + } + return (int) $depth; } /** - * Returns the HTTP range header + * Returns the HTTP range header. * * This method returns null if there is no well-formed HTTP range request * header or array($start, $end). @@ -629,24 +612,29 @@ class Server extends EventEmitter implements LoggerAwareInterface { * 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 + * @return int[]|null */ - function getHTTPRange() { - + public function getHTTPRange() + { $range = $this->httpRequest->getHeader('range'); - if (is_null($range)) return null; + 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 (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i', $range, $matches)) { + return null; + } - if ($matches[1] === '' && $matches[2] === '') return null; + if ('' === $matches[1] && '' === $matches[2]) { + return null; + } return [ - $matches[1] !== '' ? $matches[1] : null, - $matches[2] !== '' ? $matches[2] : null, + '' !== $matches[1] ? (int) $matches[1] : null, + '' !== $matches[2] ? (int) $matches[2] : null, ]; - } /** @@ -675,8 +663,8 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @return array */ - function getHTTPPrefer() { - + public function getHTTPPrefer() + { $result = [ // can be true or false 'respond-async' => false, @@ -689,23 +677,19 @@ class Server extends EventEmitter implements LoggerAwareInterface { ]; if ($prefer = $this->httpRequest->getHeader('Prefer')) { - $result = array_merge( $result, HTTP\parsePrefer($prefer) ); - - } elseif ($this->httpRequest->getHeader('Brief') == 't') { + } elseif ('t' == $this->httpRequest->getHeader('Brief')) { $result['return'] = 'minimal'; } return $result; - } - /** - * Returns information about Copy and Move requests + * 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 @@ -715,74 +699,82 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) * * @param RequestInterface $request - * @throws Exception\BadRequest upon missing or broken request headers + * + * @throws Exception\BadRequest upon missing or broken request headers * @throws Exception\UnsupportedMediaType when trying to copy into a - * non-collection. - * @throws Exception\PreconditionFailed If overwrite is set to false, but - * the destination exists. - * @throws Exception\Forbidden when source and destination paths are - * identical. - * @throws Exception\Conflict When trying to copy a node into its own - * subtree. + * non-collection + * @throws Exception\PreconditionFailed if overwrite is set to false, but + * the destination exists + * @throws Exception\Forbidden when source and destination paths are + * identical + * @throws Exception\Conflict when trying to copy a node into its own + * subtree + * * @return array */ - function getCopyAndMoveInfo(RequestInterface $request) { - + public function getCopyAndMoveInfo(RequestInterface $request) + { // Collecting the relevant HTTP headers - if (!$request->getHeader('Destination')) throw new Exception\BadRequest('The destination header was not supplied'); + if (!$request->getHeader('Destination')) { + throw new Exception\BadRequest('The destination header was not supplied'); + } $destination = $this->calculateUri($request->getHeader('Destination')); $overwrite = $request->getHeader('Overwrite'); - if (!$overwrite) $overwrite = 'T'; - if (strtoupper($overwrite) == 'T') $overwrite = true; - elseif (strtoupper($overwrite) == 'F') $overwrite = false; + if (!$overwrite) { + $overwrite = 'T'; + } + if ('T' == strtoupper($overwrite)) { + $overwrite = true; + } elseif ('F' == strtoupper($overwrite)) { + $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); + else { + throw new Exception\BadRequest('The HTTP Overwrite header should be either T or F'); + } + list($destinationDir) = Uri\split($destination); try { $destinationParent = $this->tree->getNodeForPath($destinationDir); - if (!($destinationParent instanceof ICollection)) throw new Exception\UnsupportedMediaType('The destination node is not a collection'); + 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'); - + if (!$overwrite) { + throw new Exception\PreconditionFailed('The destination node already exists, and the overwrite header is set to false', 'Overwrite'); + } } catch (Exception\NotFound $e) { - // Destination didn't exist, we're all good $destinationNode = false; - } $requestPath = $request->getPath(); if ($destination === $requestPath) { throw new Exception\Forbidden('Source and destination uri are identical.'); } - if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath . '/') { + if (substr($destination, 0, strlen($requestPath) + 1) === $requestPath.'/') { throw new Exception\Conflict('The destination may not be part of the same subtree as the source path.'); } // These are the three relevant properties we need to return return [ - 'destination' => $destination, - 'destinationExists' => !!$destinationNode, - 'destinationNode' => $destinationNode, + 'destination' => $destination, + 'destinationExists' => (bool) $destinationNode, + 'destinationNode' => $destinationNode, ]; - } /** - * Returns a list of properties for a path + * 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 @@ -793,18 +785,18 @@ class Server extends EventEmitter implements LoggerAwareInterface { * returned. * * @param string $path - * @param array $propertyNames + * @param array $propertyNames + * * @return array */ - function getProperties($path, $propertyNames) { - + public function getProperties($path, $propertyNames) + { $result = $this->getPropertiesForPath($path, $propertyNames, 0); if (isset($result[0][200])) { return $result[0][200]; } else { return []; } - } /** @@ -816,26 +808,27 @@ class Server extends EventEmitter implements LoggerAwareInterface { * The parent node will not be returned. * * @param string $path - * @param array $propertyNames + * @param array $propertyNames + * * @return array */ - function getPropertiesForChildren($path, $propertyNames) { - + public function getPropertiesForChildren($path, $propertyNames) + { $result = []; foreach ($this->getPropertiesForPath($path, $propertyNames, 1) as $k => $row) { - // Skipping the parent path - if ($k === 0) continue; + if (0 === $k) { + continue; + } $result[$row['href']] = $row[200]; - } - return $result; + return $result; } /** - * Returns a list of HTTP headers for a particular resource + * 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 @@ -844,61 +837,64 @@ class Server extends EventEmitter implements LoggerAwareInterface { * The headers are intended to be used for HEAD and GET requests. * * @param string $path + * * @return array */ - function getHTTPHeaders($path) { - + public function getHTTPHeaders($path) + { $propertyMap = [ - '{DAV:}getcontenttype' => 'Content-Type', + '{DAV:}getcontenttype' => 'Content-Type', '{DAV:}getcontentlength' => 'Content-Length', - '{DAV:}getlastmodified' => 'Last-Modified', - '{DAV:}getetag' => 'ETag', + '{DAV:}getlastmodified' => 'Last-Modified', + '{DAV:}getetag' => 'ETag', ]; $properties = $this->getProperties($path, array_keys($propertyMap)); $headers = []; foreach ($propertyMap as $property => $header) { - if (!isset($properties[$property])) continue; + if (!isset($properties[$property])) { + continue; + } if (is_scalar($properties[$property])) { $headers[$header] = $properties[$property]; // GetLastModified gets special cased } elseif ($properties[$property] instanceof Xml\Property\GetLastModified) { - $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); + $headers[$header] = HTTP\toDate($properties[$property]->getTime()); } - } return $headers; - } /** * Small helper to support PROPFIND with DEPTH_INFINITY. * * @param PropFind $propFind - * @param array $yieldFirst - * @return \Iterator + * @param array $yieldFirst + * + * @return \Traversable */ - private function generatePathNodes(PropFind $propFind, array $yieldFirst = null) { - if ($yieldFirst !== null) { + private function generatePathNodes(PropFind $propFind, array $yieldFirst = null) + { + if (null !== $yieldFirst) { yield $yieldFirst; } $newDepth = $propFind->getDepth(); $path = $propFind->getPath(); - if ($newDepth !== self::DEPTH_INFINITY) { - $newDepth--; + if (self::DEPTH_INFINITY !== $newDepth) { + --$newDepth; } $propertyNames = $propFind->getRequestedProperties(); $propFindType = !empty($propertyNames) ? PropFind::NORMAL : PropFind::ALLPROPS; foreach ($this->tree->getChildren($path) as $childNode) { - if ($path !== '') { - $subPath = $path . '/' . $childNode->getName(); + if ('' !== $path) { + $subPath = $path.'/'.$childNode->getName(); } else { $subPath = $childNode->getName(); } @@ -906,20 +902,19 @@ class Server extends EventEmitter implements LoggerAwareInterface { yield [ $subPropFind, - $childNode + $childNode, ]; - if (($newDepth === self::DEPTH_INFINITY || $newDepth >= 1) && $childNode instanceof ICollection) { + if ((self::DEPTH_INFINITY === $newDepth || $newDepth >= 1) && $childNode instanceof ICollection) { foreach ($this->generatePathNodes($subPropFind) as $subItem) { yield $subItem; } } - } } /** - * Returns a list of properties for a given path + * 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 @@ -928,20 +923,21 @@ class Server extends EventEmitter implements LoggerAwareInterface { * If a depth of 1 is requested child elements will also be returned. * * @param string $path - * @param array $propertyNames - * @param int $depth + * @param array $propertyNames + * @param int $depth + * * @return array * * @deprecated Use getPropertiesIteratorForPath() instead (as it's more memory efficient) * @see getPropertiesIteratorForPath() */ - function getPropertiesForPath($path, $propertyNames = [], $depth = 0) { - + public function getPropertiesForPath($path, $propertyNames = [], $depth = 0) + { return iterator_to_array($this->getPropertiesIteratorForPath($path, $propertyNames, $depth)); - } + /** - * Returns a list of properties for a given path + * 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 @@ -950,33 +946,35 @@ class Server extends EventEmitter implements LoggerAwareInterface { * If a depth of 1 is requested child elements will also be returned. * * @param string $path - * @param array $propertyNames - * @param int $depth + * @param array $propertyNames + * @param int $depth + * * @return \Iterator */ - function getPropertiesIteratorForPath($path, $propertyNames = [], $depth = 0) { - + public function getPropertiesIteratorForPath($path, $propertyNames = [], $depth = 0) + { // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled - if (!$this->enablePropfindDepthInfinity && $depth != 0) $depth = 1; + if (!$this->enablePropfindDepthInfinity && 0 != $depth) { + $depth = 1; + } $path = trim($path, '/'); $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS; - $propFind = new PropFind($path, (array)$propertyNames, $depth, $propFindType); + $propFind = new PropFind($path, (array) $propertyNames, $depth, $propFindType); $parentNode = $this->tree->getNodeForPath($path); $propFindRequests = [[ $propFind, - $parentNode + $parentNode, ]]; - if (($depth > 0 || $depth === self::DEPTH_INFINITY) && $parentNode instanceof ICollection) { + if (($depth > 0 || self::DEPTH_INFINITY === $depth) && $parentNode instanceof ICollection) { $propFindRequests = $this->generatePathNodes(clone $propFind, current($propFindRequests)); } foreach ($propFindRequests as $propFindRequest) { - list($propFind, $node) = $propFindRequest; $r = $this->getPropertiesByNode($propFind, $node); if ($r) { @@ -993,9 +991,7 @@ class Server extends EventEmitter implements LoggerAwareInterface { } yield $result; } - } - } /** @@ -1010,17 +1006,17 @@ class Server extends EventEmitter implements LoggerAwareInterface { * * @param array $paths * @param array $propertyNames + * * @return array */ - function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) { - + public function getPropertiesForMultiplePaths(array $paths, array $propertyNames = []) + { $result = [ ]; $nodes = $this->tree->getMultipleNodes($paths); foreach ($nodes as $path => $node) { - $propFind = new PropFind($path, $propertyNames); $r = $this->getPropertiesByNode($propFind, $node); if ($r) { @@ -1032,14 +1028,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { $result[$path]['href'] .= '/'; } } - } return $result; - } - /** * Determines all properties for a node. * @@ -1051,13 +1044,13 @@ class Server extends EventEmitter implements LoggerAwareInterface { * list of properties. * * @param PropFind $propFind - * @param INode $node + * @param INode $node + * * @return bool */ - function getPropertiesByNode(PropFind $propFind, INode $node) { - + public function getPropertiesByNode(PropFind $propFind, INode $node) + { return $this->emit('propFind', [$propFind, $node]); - } /** @@ -1072,13 +1065,16 @@ class Server extends EventEmitter implements LoggerAwareInterface { * @param string $uri * @param resource $data * @param string $etag + * * @return bool */ - function createFile($uri, $data, &$etag = null) { + public function createFile($uri, $data, &$etag = null) + { + list($dir, $name) = Uri\split($uri); - list($dir, $name) = URLUtil::splitPath($uri); - - if (!$this->emit('beforeBind', [$uri])) return false; + if (!$this->emit('beforeBind', [$uri])) { + return false; + } $parent = $this->tree->getNodeForPath($dir); if (!$parent instanceof ICollection) { @@ -1091,13 +1087,17 @@ class Server extends EventEmitter implements LoggerAwareInterface { // // If $modified is true, we must not send back an ETag. $modified = false; - if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) return false; + if (!$this->emit('beforeCreateFile', [$uri, &$data, $parent, &$modified])) { + return false; + } $etag = $parent->createFile($name, $data); - if ($modified) $etag = null; + if ($modified) { + $etag = null; + } - $this->tree->markDirty($dir . '/' . $name); + $this->tree->markDirty($dir.'/'.$name); $this->emit('afterBind', [$uri]); $this->emit('afterCreateFile', [$uri, $parent]); @@ -1113,10 +1113,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { * @param string $uri * @param resource $data * @param string $etag + * * @return bool */ - function updateFile($uri, $data, &$etag = null) { - + public function updateFile($uri, $data, &$etag = null) + { $node = $this->tree->getNodeForPath($uri); // It is possible for an event handler to modify the content of the @@ -1125,47 +1126,46 @@ class Server extends EventEmitter implements LoggerAwareInterface { // // If $modified is true, we must not send back an ETag. $modified = false; - if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) return false; + if (!$this->emit('beforeWriteContent', [$uri, $node, &$data, &$modified])) { + return false; + } $etag = $node->put($data); - if ($modified) $etag = null; + if ($modified) { + $etag = null; + } $this->emit('afterWriteContent', [$uri, $node]); return true; } - - /** * This method is invoked by sub-systems creating a new directory. * * @param string $uri - * @return void */ - function createDirectory($uri) { - + public function createDirectory($uri) + { $this->createCollection($uri, new MkCol(['{DAV:}collection'], [])); - } /** - * Use this method to create a new collection + * Use this method to create a new collection. + * + * @param string $uri The new uri + * @param MkCol $mkCol * - * @param string $uri The new uri - * @param MkCol $mkCol * @return array|null */ - function createCollection($uri, MkCol $mkCol) { - - list($parentUri, $newName) = URLUtil::splitPath($uri); + public function createCollection($uri, MkCol $mkCol) + { + list($parentUri, $newName) = Uri\split($uri); // Making sure the parent exists try { $parent = $this->tree->getNodeForPath($parentUri); - } catch (Exception\NotFound $e) { throw new Exception\Conflict('Parent node does not exist'); - } // Making sure the parent is a collection @@ -1179,26 +1179,23 @@ class Server extends EventEmitter implements LoggerAwareInterface { // If we got here.. it means there's already a node on that url, and we need to throw a 405 throw new Exception\MethodNotAllowed('The resource you tried to create already exists'); - } catch (Exception\NotFound $e) { // NotFound is the expected behavior. } - - if (!$this->emit('beforeBind', [$uri])) return; + if (!$this->emit('beforeBind', [$uri])) { + return; + } if ($parent instanceof IExtendedCollection) { - - /** + /* * If the parent is an instance of IExtendedCollection, it means that * we can pass the MkCol object directly as it may be able to store * properties immediately. */ $parent->createExtendedCollection($newName, $mkCol); - } else { - - /** + /* * If the parent is a standard ICollection, it means only * 'standard' collections can be created, so we should fail any * MKCOL operation that carries extra resourcetypes. @@ -1208,7 +1205,6 @@ class Server extends EventEmitter implements LoggerAwareInterface { } $parent->createDirectory($newName); - } // If there are any properties that have not been handled/stored, @@ -1227,23 +1223,21 @@ class Server extends EventEmitter implements LoggerAwareInterface { ]; foreach ($result as $propertyName => $status) { - if (!isset($formattedResult[$status])) { $formattedResult[$status] = []; } $formattedResult[$status][$propertyName] = null; - } + return $formattedResult; } $this->tree->markDirty($parentUri); $this->emit('afterBind', [$uri]); - } /** - * This method updates a resource's properties + * 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. @@ -1256,17 +1250,17 @@ class Server extends EventEmitter implements LoggerAwareInterface { * as their values. * * @param string $path - * @param array $properties + * @param array $properties + * * @return array */ - function updateProperties($path, array $properties) { - + public function updateProperties($path, array $properties) + { $propPatch = new PropPatch($properties); $this->emit('propPatch', [$path, $propPatch]); $propPatch->commit(); return $propPatch->getResult(); - } /** @@ -1287,19 +1281,19 @@ class Server extends EventEmitter implements LoggerAwareInterface { * related to If-None-Match, If-Match and If-Unmodified Since. It will * set the status to 304 Not Modified for If-Modified_since. * - * @param RequestInterface $request + * @param RequestInterface $request * @param ResponseInterface $response + * * @return bool */ - function checkPreconditions(RequestInterface $request, ResponseInterface $response) { - + public function checkPreconditions(RequestInterface $request, ResponseInterface $response) + { $path = $request->getPath(); $node = null; $lastMod = null; $etag = null; if ($ifMatch = $request->getHeader('If-Match')) { - // If-Match contains an entity tag. Only if the entity-tag // matches we are allowed to make the request succeed. // If the entity-tag is '*' we are only allowed to make the @@ -1311,13 +1305,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { } // Only need to check entity tags if they are not * - if ($ifMatch !== '*') { - + if ('*' !== $ifMatch) { // There can be multiple ETags $ifMatch = explode(',', $ifMatch); $haveMatch = false; foreach ($ifMatch as $ifMatchItem) { - // Stripping any extra spaces $ifMatchItem = trim($ifMatchItem, ' '); @@ -1331,17 +1323,17 @@ class Server extends EventEmitter implements LoggerAwareInterface { $haveMatch = true; } } - } if (!$haveMatch) { - if ($etag) $response->setHeader('ETag', $etag); - throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.', 'If-Match'); + if ($etag) { + $response->setHeader('ETag', $etag); + } + throw new Exception\PreconditionFailed('An If-Match header was specified, but none of the specified ETags matched.', 'If-Match'); } } } if ($ifNoneMatch = $request->getHeader('If-None-Match')) { - // The If-None-Match header contains an ETag. // Only if the ETag does not match the current ETag, the request will succeed // The header can also contain *, in which case the request @@ -1356,46 +1348,46 @@ class Server extends EventEmitter implements LoggerAwareInterface { } if ($nodeExists) { $haveMatch = false; - if ($ifNoneMatch === '*') $haveMatch = true; - else { - + if ('*' === $ifNoneMatch) { + $haveMatch = true; + } else { // There might be multiple ETags $ifNoneMatch = explode(',', $ifNoneMatch); $etag = $node instanceof IFile ? $node->getETag() : null; foreach ($ifNoneMatch as $ifNoneMatchItem) { - // Stripping any extra spaces $ifNoneMatchItem = trim($ifNoneMatchItem, ' '); - if ($etag === $ifNoneMatchItem) $haveMatch = true; - + if ($etag === $ifNoneMatchItem) { + $haveMatch = true; + } } - } if ($haveMatch) { - if ($etag) $response->setHeader('ETag', $etag); - if ($request->getMethod() === 'GET') { + if ($etag) { + $response->setHeader('ETag', $etag); + } + if ('GET' === $request->getMethod()) { $response->setStatus(304); + return false; } else { throw new Exception\PreconditionFailed('An If-None-Match header was specified, but the ETag matched (or * was specified).', 'If-None-Match'); } } } - } if (!$ifNoneMatch && ($ifModifiedSince = $request->getHeader('If-Modified-Since'))) { - // The If-Modified-Since header contains a date. We // will only return the entity if it has been changed since // that date. If it hasn't been changed, we return a 304 // header // Note that this header only has to be checked if there was no If-None-Match header // as per the HTTP spec. - $date = HTTP\Util::parseHTTPDate($ifModifiedSince); + $date = HTTP\parseDate($ifModifiedSince); if ($date) { if (is_null($node)) { @@ -1403,10 +1395,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { } $lastMod = $node->getLastModified(); if ($lastMod) { - $lastMod = new \DateTime('@' . $lastMod); + $lastMod = new \DateTime('@'.$lastMod); if ($lastMod <= $date) { $response->setStatus(304); - $response->setHeader('Last-Modified', HTTP\Util::toHTTPDate($lastMod)); + $response->setHeader('Last-Modified', HTTP\toDate($lastMod)); + return false; } } @@ -1414,10 +1407,9 @@ class Server extends EventEmitter implements LoggerAwareInterface { } if ($ifUnmodifiedSince = $request->getHeader('If-Unmodified-Since')) { - // The If-Unmodified-Since will allow allow the request if the // entity has not changed since the specified date. - $date = HTTP\Util::parseHTTPDate($ifUnmodifiedSince); + $date = HTTP\parseDate($ifUnmodifiedSince); // We must only check the date if it's valid if ($date) { @@ -1426,13 +1418,12 @@ class Server extends EventEmitter implements LoggerAwareInterface { } $lastMod = $node->getLastModified(); if ($lastMod) { - $lastMod = new \DateTime('@' . $lastMod); + $lastMod = new \DateTime('@'.$lastMod); if ($lastMod > $date) { throw new Exception\PreconditionFailed('An If-Unmodified-Since header was specified, but the entity has been changed since the specified date.', 'If-Unmodified-Since'); } } } - } // Now the hardest, the If: header. The If: header can contain multiple @@ -1461,13 +1452,11 @@ class Server extends EventEmitter implements LoggerAwareInterface { // Every ifCondition needs to validate to true, so we exit as soon as // we have an invalid condition. foreach ($ifConditions as $ifCondition) { - $uri = $ifCondition['uri']; $tokens = $ifCondition['tokens']; // We only need 1 valid token for the condition to succeed. foreach ($tokens as $token) { - $tokenValid = $token['validToken'] || !$token['token']; $etagValid = false; @@ -1477,35 +1466,28 @@ class Server extends EventEmitter implements LoggerAwareInterface { // Checking the ETag, only if the token was already deemed // valid and there is one. if ($token['etag'] && $tokenValid) { - // The token was valid, and there was an ETag. We must // grab the current ETag and check it. $node = $this->tree->getNodeForPath($uri); $etagValid = $node instanceof IFile && $node->getETag() == $token['etag']; - } - if (($tokenValid && $etagValid) ^ $token['negate']) { // Both were valid, so we can go to the next condition. continue 2; } - - } // If we ended here, it means there was no valid ETag + token // combination found for the current condition. This means we fail! - throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for ' . $uri, 'If'); - + throw new Exception\PreconditionFailed('Failed to find a valid token/etag combination for '.$uri, 'If'); } return true; - } /** - * This method is created to extract information from the WebDAV HTTP 'If:' header + * 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 @@ -1573,12 +1555,15 @@ class Server extends EventEmitter implements LoggerAwareInterface { * ] * * @param RequestInterface $request + * * @return array */ - function getIfConditions(RequestInterface $request) { - + public function getIfConditions(RequestInterface $request) + { $header = $request->getHeader('If'); - if (!$header) return []; + if (!$header) { + return []; + } $matches = []; @@ -1588,18 +1573,16 @@ class Server extends EventEmitter implements LoggerAwareInterface { $conditions = []; foreach ($matches as $match) { - // If there was no uri specified in this match, and there were // already conditions parsed, we add the condition to the list of // conditions for the previous uri. if (!$match['uri'] && count($conditions)) { $conditions[count($conditions) - 1]['tokens'][] = [ 'negate' => $match['not'] ? true : false, - 'token' => $match['token'], - 'etag' => isset($match['etag']) ? $match['etag'] : '' + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '', ]; } else { - if (!$match['uri']) { $realUri = $request->getPath(); } else { @@ -1607,55 +1590,55 @@ class Server extends EventEmitter implements LoggerAwareInterface { } $conditions[] = [ - 'uri' => $realUri, + 'uri' => $realUri, 'tokens' => [ [ 'negate' => $match['not'] ? true : false, - 'token' => $match['token'], - 'etag' => isset($match['etag']) ? $match['etag'] : '' - ] + 'token' => $match['token'], + 'etag' => isset($match['etag']) ? $match['etag'] : '', + ], ], - ]; } - } return $conditions; - } /** * Returns an array with resourcetypes for a node. * * @param INode $node + * * @return array */ - function getResourceTypeForNode(INode $node) { - + public function getResourceTypeForNode(INode $node) + { $result = []; foreach ($this->resourceTypeMapping as $className => $resourceType) { - if ($node instanceof $className) $result[] = $resourceType; + if ($node instanceof $className) { + $result[] = $resourceType; + } } - return $result; + return $result; } // }}} // {{{ XML Readers & Writers - /** * Generates a WebDAV propfind response body based on a list of nodes. * * If 'strip404s' is set to true, all 404 responses will be removed. * * @param array|\Traversable $fileProperties The list with nodes - * @param bool $strip404s + * @param bool $strip404s + * * @return string */ - function generateMultiStatus($fileProperties, $strip404s = false) { - + public function generateMultiStatus($fileProperties, $strip404s = false) + { $w = $this->xml->getWriter(); $w->openMemory(); $w->contextUri = $this->baseUri; @@ -1664,7 +1647,6 @@ class Server extends EventEmitter implements LoggerAwareInterface { $w->startElement('{DAV:}multistatus'); foreach ($fileProperties as $entry) { - $href = $entry['href']; unset($entry['href']); if ($strip404s) { @@ -1675,14 +1657,12 @@ class Server extends EventEmitter implements LoggerAwareInterface { $entry ); $w->write([ - 'name' => '{DAV:}response', - 'value' => $response + 'name' => '{DAV:}response', + 'value' => $response, ]); } $w->endElement(); return $w->outputMemory(); - } - } |