diff options
Diffstat (limited to '3rdparty/Sabre/DAV/Server.php')
-rwxr-xr-x[-rw-r--r--] | 3rdparty/Sabre/DAV/Server.php | 506 |
1 files changed, 282 insertions, 224 deletions
diff --git a/3rdparty/Sabre/DAV/Server.php b/3rdparty/Sabre/DAV/Server.php index 3d76d4f1918..4284c127b6e 100644..100755 --- a/3rdparty/Sabre/DAV/Server.php +++ b/3rdparty/Sabre/DAV/Server.php @@ -2,11 +2,11 @@ /** * Main DAV server class - * + * * @package Sabre * @subpackage DAV - * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved. - * @author Evert Pot (http://www.rooftopsolutions.nl/) + * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved. + * @author Evert Pot (http://www.rooftopsolutions.nl/) * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class Sabre_DAV_Server { @@ -26,9 +26,6 @@ class Sabre_DAV_Server { */ const NODE_DIRECTORY = 2; - const PROP_SET = 1; - const PROP_REMOVE = 2; - /** * XML namespace for all SabreDAV related elements */ @@ -36,42 +33,42 @@ class Sabre_DAV_Server { /** * The tree object - * - * @var Sabre_DAV_Tree + * + * @var Sabre_DAV_Tree */ public $tree; /** - * The base uri - * - * @var string + * The base uri + * + * @var string */ - protected $baseUri = null; + protected $baseUri = null; /** - * httpResponse - * - * @var Sabre_HTTP_Response + * httpResponse + * + * @var Sabre_HTTP_Response */ public $httpResponse; /** * httpRequest - * - * @var Sabre_HTTP_Request + * + * @var Sabre_HTTP_Request */ public $httpRequest; /** - * The list of plugins - * - * @var array + * The list of plugins + * + * @var array */ protected $plugins = array(); /** - * This array contains a list of callbacks we should call when certain events are triggered - * + * This array contains a list of callbacks we should call when certain events are triggered + * * @var array */ protected $eventSubscriptions = array(); @@ -81,7 +78,7 @@ class Sabre_DAV_Server { * * 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( @@ -90,9 +87,9 @@ class Sabre_DAV_Server { ); /** - * The propertymap can be used to map properties from + * The propertymap can be used to map properties from * requests to property classes. - * + * * @var array */ public $propertyMap = array( @@ -125,23 +122,32 @@ class Sabre_DAV_Server { * This is a flag that allow or not showing file, line and code * of the exception in the returned XML * - * @var bool + * @var bool */ public $debugExceptions = false; /** - * This property allows you to automatically add the 'resourcetype' value + * 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 + * 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 @@ -150,14 +156,13 @@ class Sabre_DAV_Server { * 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 + * 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 Sabre_DAV_Tree $tree The tree object - * @return void + * the nodes in the array as top-level children. + * + * @param Sabre_DAV_Tree|Sabre_DAV_INode|null $treeOrNode The tree object */ public function __construct($treeOrNode = null) { @@ -190,7 +195,7 @@ class Sabre_DAV_Server { } /** - * Starts the DAV Server + * Starts the DAV Server * * @return void */ @@ -218,7 +223,9 @@ class Sabre_DAV_Server { $error->appendChild($DOM->createElement('s:stacktrace',$e->getTraceAsString())); } - $error->appendChild($DOM->createElement('s:sabredav-version',Sabre_DAV_Version::VERSION)); + if (self::$exposeVersion) { + $error->appendChild($DOM->createElement('s:sabredav-version',Sabre_DAV_Version::VERSION)); + } if($e instanceof Sabre_DAV_Exception) { @@ -233,7 +240,7 @@ class Sabre_DAV_Server { } $headers['Content-Type'] = 'application/xml; charset=utf-8'; - + $this->httpResponse->sendStatus($httpCode); $this->httpResponse->setHeaders($headers); $this->httpResponse->sendBody($DOM->saveXML()); @@ -244,24 +251,24 @@ class Sabre_DAV_Server { /** * 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]!=='/') + if ($uri[strlen($uri)-1]!=='/') $uri.='/'; - $this->baseUri = $uri; + $this->baseUri = $uri; } /** * Returns the base responding uri - * - * @return string + * + * @return string */ public function getBaseUri() { @@ -272,11 +279,11 @@ class Sabre_DAV_Server { /** * 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. + * Only the PATH_INFO variable is considered. * - * @return void + * If this variable is not set, the root (/) is assumed. + * + * @return string */ public function guessBaseUri() { @@ -303,21 +310,21 @@ class Sabre_DAV_Server { return rtrim($baseUri,'/') . '/'; } - throw new Sabre_DAV_Exception('The REQUEST_URI ('. $uri . ') did not end with the contents of PATH_INFO (' . $pathInfo . '). This server might be misconfigured.'); + throw new Sabre_DAV_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. + // 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 Sabre_DAV_ServerPlugin $plugin + * @param Sabre_DAV_ServerPlugin $plugin * @return void */ public function addPlugin(Sabre_DAV_ServerPlugin $plugin) { @@ -333,11 +340,11 @@ class Sabre_DAV_Server { * This function returns null if the plugin was not found. * * @param string $name - * @return Sabre_DAV_ServerPlugin + * @return Sabre_DAV_ServerPlugin */ public function getPlugin($name) { - if (isset($this->plugins[$name])) + if (isset($this->plugins[$name])) return $this->plugins[$name]; // This is a fallback and deprecated. @@ -350,9 +357,9 @@ class Sabre_DAV_Server { } /** - * Returns all plugins - * - * @return array + * Returns all plugins + * + * @return array */ public function getPlugins() { @@ -361,7 +368,6 @@ class Sabre_DAV_Server { } - /** * Subscribe to an event. * @@ -371,9 +377,9 @@ class Sabre_DAV_Server { * * 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 + * number, it is recommended to ommit. + * + * @param string $event * @param callback $callback * @param int $priority * @return void @@ -398,7 +404,7 @@ class Sabre_DAV_Server { * * @param string $eventName * @param array $arguments - * @return bool + * @return bool */ public function broadcastEvent($eventName,$arguments = array()) { @@ -418,7 +424,7 @@ class Sabre_DAV_Server { } /** - * 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 string $method * @param string $uri @@ -426,7 +432,7 @@ class Sabre_DAV_Server { */ public function invokeMethod($method, $uri) { - $method = strtoupper($method); + $method = strtoupper($method); if (!$this->broadcastEvent('beforeMethod',array($method, $uri))) return; @@ -453,7 +459,7 @@ class Sabre_DAV_Server { if ($this->broadcastEvent('unknownMethod',array($method, $uri))) { // Unsupported method - throw new Sabre_DAV_Exception_NotImplemented(); + throw new Sabre_DAV_Exception_NotImplemented('There was no handler found for this "' . $method . '" method'); } } @@ -461,9 +467,9 @@ class Sabre_DAV_Server { } // {{{ HTTP Method implementations - + /** - * HTTP OPTIONS + * HTTP OPTIONS * * @param string $uri * @return void @@ -476,11 +482,13 @@ class Sabre_DAV_Server { $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'); - $this->httpResponse->setHeader('X-Sabre-Version',Sabre_DAV_Version::VERSION); + if (self::$exposeVersion) { + $this->httpResponse->setHeader('X-Sabre-Version',Sabre_DAV_Version::VERSION); + } $this->httpResponse->setHeader('Content-Length',0); $this->httpResponse->sendStatus(200); @@ -492,13 +500,13 @@ class Sabre_DAV_Server { * This method simply fetches the contents of a uri, like normal * * @param string $uri - * @return void + * @return bool */ protected function httpGet($uri) { $node = $this->tree->getNodeForPath($uri,0); - if (!$this->checkPreconditions(true)) return false; + if (!$this->checkPreconditions(true)) return false; if (!($node instanceof Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_NotImplemented('GET is only implemented on File objects'); $body = $node->get(); @@ -535,7 +543,7 @@ class Sabre_DAV_Server { } else { $nodeSize = null; } - + $this->httpResponse->setHeaders($httpHeaders); $range = $this->getHTTPRange(); @@ -545,12 +553,12 @@ class Sabre_DAV_Server { // 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; @@ -560,8 +568,8 @@ class Sabre_DAV_Server { } } catch (Exception $e) { - - // It's an entity. We can do a simple comparison. + + // It's an entity. We can do a simple comparison. if (!isset($httpHeaders['ETag'])) $ignoreRangeHeader = true; elseif ($httpHeaders['ETag']!==$ifRange) $ignoreRangeHeader = true; } @@ -575,7 +583,7 @@ class Sabre_DAV_Server { $start = $range[0]; $end = $range[1]?$range[1]:$nodeSize-1; - if($start >= $nodeSize) + if($start >= $nodeSize) throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('The start offset (' . $range[0] . ') exceeded the size of the entity (' . $nodeSize . ')'); if($end < $start) throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')'); @@ -625,7 +633,7 @@ class Sabre_DAV_Server { $node = $this->tree->getNodeForPath($uri); /* This information is only collection for File objects. - * Ideally we want to throw 405 Method Not Allowed for every + * Ideally we want to throw 405 Method Not Allowed for every * non-file, but MS Office does not like this */ if ($node instanceof Sabre_DAV_IFile) { @@ -640,7 +648,7 @@ class Sabre_DAV_Server { } /** - * HTTP Delete + * HTTP Delete * * The HTTP delete method, deletes a given uri * @@ -651,6 +659,7 @@ class Sabre_DAV_Server { 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'); @@ -659,13 +668,13 @@ class Sabre_DAV_Server { /** - * WebDAV PROPFIND + * 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 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 @@ -679,7 +688,7 @@ class Sabre_DAV_Server { $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 + // 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); @@ -688,8 +697,8 @@ class Sabre_DAV_Server { $this->httpResponse->sendStatus(207); $this->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - // Normally this header is only needed for OPTIONS responses, however.. - // iCal seems to also depend on these being set for PROPFIND. Since + // 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()); @@ -712,7 +721,7 @@ class Sabre_DAV_Server { protected function httpPropPatch($uri) { $newProperties = $this->parsePropPatchRequest($this->httpRequest->getBody(true)); - + $result = $this->updateProperties($uri, $newProperties); $this->httpResponse->sendStatus(207); @@ -725,14 +734,14 @@ class Sabre_DAV_Server { } /** - * HTTP PUT method - * + * 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 200 Ok + * 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 void + * @return bool */ protected function httpPut($uri) { @@ -768,13 +777,13 @@ class Sabre_DAV_Server { // 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, + 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-lenght files in PHP. + 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, @@ -808,29 +817,36 @@ class Sabre_DAV_Server { } - if ($this->tree->nodeExists($uri)) { + 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 Sabre_DAV_IFile)) throw new Sabre_DAV_Exception_Conflict('PUT is not allowed on non-files.'); - if (!$this->broadcastEvent('beforeWriteContent',array($this->getRequestUri()))) return false; + if (!$this->broadcastEvent('beforeWriteContent',array($uri, $node, &$body))) return false; + + $etag = $node->put($body); + + $this->broadcastEvent('afterWriteContent',array($uri, $node)); - $node->put($body); $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)) { + 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); } @@ -855,7 +871,7 @@ class Sabre_DAV_Server { $contentType = $this->httpRequest->getHeader('Content-Type'); if (strpos($contentType,'application/xml')!==0 && strpos($contentType,'text/xml')!==0) { - // We must throw 415 for unsupport mkcol bodies + // We must throw 415 for unsupported mkcol bodies throw new Sabre_DAV_Exception_UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); } @@ -863,7 +879,7 @@ class Sabre_DAV_Server { $dom = Sabre_DAV_XMLUtil::loadDOMDocument($requestBody); if (Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild)!=='{DAV:}mkcol') { - // We must throw 415 for unsupport mkcol bodies + // We must throw 415 for unsupported mkcol bodies throw new Sabre_DAV_Exception_UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.'); } @@ -875,7 +891,7 @@ class Sabre_DAV_Server { $properties = array_merge($properties, Sabre_DAV_XMLUtil::parseProperties($childNode, $this->propertyMap)); } - if (!isset($properties['{DAV:}resourcetype'])) + if (!isset($properties['{DAV:}resourcetype'])) throw new Sabre_DAV_Exception_BadRequest('The mkcol request must include a {DAV:}resourcetype property'); $resourceType = $properties['{DAV:}resourcetype']->getValue(); @@ -918,19 +934,21 @@ class Sabre_DAV_Server { $moveInfo = $this->getCopyAndMoveInfo(); // If the destination is part of the source tree, we must fail - if ($moveInfo['destination']==$uri) + if ($moveInfo['destination']==$uri) throw new Sabre_DAV_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 @@ -946,13 +964,13 @@ class Sabre_DAV_Server { * A lot of the actual request processing is done in getCopyMoveInfo * * @param string $uri - * @return void + * @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) + if ($copyInfo['destination']==$uri) throw new Sabre_DAV_Exception_Forbidden('Source and destination uri are identical.'); if ($copyInfo['destinationExists']) { @@ -998,13 +1016,13 @@ class Sabre_DAV_Server { } // }}} - // {{{ HTTP/WebDAV protocol helpers + // {{{ HTTP/WebDAV protocol helpers /** - * Returns an array with all the supported HTTP methods for a specific uri. + * Returns an array with all the supported HTTP methods for a specific uri. * - * @param string $uri - * @return array + * @param string $uri + * @return array */ public function getAllowedMethods($uri) { @@ -1023,13 +1041,13 @@ class Sabre_DAV_Server { // The MKCOL is only allowed on an unmapped uri try { - $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + $this->tree->getNodeForPath($uri); + } catch (Sabre_DAV_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)); + foreach($this->plugins as $plugin) $methods = array_merge($methods, $plugin->getHTTPMethods($uri)); array_unique($methods); return $methods; @@ -1037,8 +1055,8 @@ class Sabre_DAV_Server { } /** - * 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 */ public function getRequestUri() { @@ -1048,9 +1066,9 @@ class Sabre_DAV_Server { } /** - * Calculates the uri for a request, making sure that the base uri is stripped out - * - * @param string $uri + * Calculates the uri for a request, making sure that the base uri is stripped out + * + * @param string $uri * @throws Sabre_DAV_Exception_Forbidden A permission denied exception is thrown whenever there was an attempt to supply a uri outside of the base uri * @return string */ @@ -1068,9 +1086,9 @@ class Sabre_DAV_Server { return trim(Sabre_DAV_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()) { + // A special case, if the baseUri was accessed without a trailing + // slash, we'll accept it as well. + } elseif ($uri.'/' === $this->getBaseUri()) { return ''; @@ -1086,10 +1104,10 @@ class Sabre_DAV_Server { * 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-existant - * - * @param mixed $default - * @return int + * 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) { @@ -1100,7 +1118,7 @@ class Sabre_DAV_Server { if ($depth == 'infinity') return self::DEPTH_INFINITY; - + // If its an unknown value. we'll grab the default if (!ctype_digit($depth)) return $default; @@ -1118,14 +1136,14 @@ class Sabre_DAV_Server { * 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 + * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity * - * return $mixed + * @return array|null */ 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 @@ -1143,15 +1161,15 @@ class Sabre_DAV_Server { /** * 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 - * + * + * 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 - Wether or not the destination is an existing url (and should therefore be overwritten) + * * destinationExists - Whether or not the destination is an existing url (and should therefore be overwritten) * - * @return array + * @return array */ public function getCopyAndMoveInfo() { @@ -1170,7 +1188,7 @@ class Sabre_DAV_Server { try { $destinationParent = $this->tree->getNodeForPath($destinationDir); if (!($destinationParent instanceof Sabre_DAV_ICollection)) throw new Sabre_DAV_Exception_UnsupportedMediaType('The destination node is not a collection'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // If the destination parent node is not found, we throw a 409 throw new Sabre_DAV_Exception_Conflict('The destination node is not found'); @@ -1179,12 +1197,12 @@ class Sabre_DAV_Server { 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 Sabre_DAV_Exception_PreconditionFailed('The destination node already exists, and the overwrite header is set to false','Overwrite'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // Destination didn't exist, we're all good $destinationNode = false; @@ -1220,22 +1238,50 @@ class Sabre_DAV_Server { } /** + * 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 + * 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:}getlastmodified' => 'Last-Modified', '{DAV:}getetag' => 'ETag', ); @@ -1245,40 +1291,40 @@ class Sabre_DAV_Server { foreach($propertyMap as $property=>$header) { if (!isset($properties[$property])) continue; - if (is_scalar($properties[$property])) { + if (is_scalar($properties[$property])) { $headers[$header] = $properties[$property]; - // GetLastModified gets special cased + // GetLastModified gets special cased } elseif ($properties[$property] instanceof Sabre_DAV_Property_GetLastModified) { - $headers[$header] = $properties[$property]->getTime()->format(DateTime::RFC1123); + $headers[$header] = Sabre_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 string $path * @param array $propertyNames - * @param int $depth + * @param int $depth * @return array */ - public function getPropertiesForPath($path,$propertyNames = array(),$depth = 0) { + public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) { if ($depth!=0) $depth = 1; $returnPropertyList = array(); - + $parentNode = $this->tree->getNodeForPath($path); $nodes = array( $path => $parentNode @@ -1286,11 +1332,11 @@ class Sabre_DAV_Server { if ($depth==1 && $parentNode instanceof Sabre_DAV_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. + // sensible list. $allProperties = count($propertyNames)==0; foreach($nodes as $myPath=>$node) { @@ -1315,8 +1361,8 @@ class Sabre_DAV_Server { ); } - // 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 + // 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; @@ -1326,32 +1372,39 @@ class Sabre_DAV_Server { } $result = $this->broadcastEvent('beforeGetProperties',array($myPath, $node, &$currentPropertyNames, &$newProperties)); - // If this method explicitly returned false, we must ignore this - // node as it is inacessible. + // 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 Sabre_DAV_IProperties) + if ($node instanceof Sabre_DAV_IProperties) $newProperties['200'] = $newProperties[200] + $node->getProperties($currentPropertyNames); } foreach($currentPropertyNames as $prop) { - + if (isset($newProperties[200][$prop])) continue; switch($prop) { case '{DAV:}getlastmodified' : if ($node->getLastModified()) $newProperties[200][$prop] = new Sabre_DAV_Property_GetLastModified($node->getLastModified()); break; - case '{DAV:}getcontentlength' : if ($node instanceof Sabre_DAV_IFile) $newProperties[200][$prop] = (int)$node->getSize(); break; - case '{DAV:}quota-used-bytes' : + case '{DAV:}getcontentlength' : + if ($node instanceof Sabre_DAV_IFile) { + $size = $node->getSize(); + if (!is_null($size)) { + $newProperties[200][$prop] = (int)$node->getSize(); + } + } + break; + case '{DAV:}quota-used-bytes' : if ($node instanceof Sabre_DAV_IQuota) { $quotaInfo = $node->getQuotaInfo(); $newProperties[200][$prop] = $quotaInfo[0]; } break; - case '{DAV:}quota-available-bytes' : + case '{DAV:}quota-available-bytes' : if ($node instanceof Sabre_DAV_IQuota) { $quotaInfo = $node->getQuotaInfo(); $newProperties[200][$prop] = $quotaInfo[1]; @@ -1364,7 +1417,7 @@ class Sabre_DAV_Server { foreach($this->plugins as $plugin) { $reports = array_merge($reports, $plugin->getSupportedReportSet($myPath)); } - $newProperties[200][$prop] = new Sabre_DAV_Property_SupportedReportSet($reports); + $newProperties[200][$prop] = new Sabre_DAV_Property_SupportedReportSet($reports); break; case '{DAV:}resourcetype' : $newProperties[200]['{DAV:}resourcetype'] = new Sabre_DAV_Property_ResourceType(); @@ -1379,14 +1432,14 @@ class Sabre_DAV_Server { if (!$allProperties && !isset($newProperties[200][$prop])) $newProperties[404][$prop] = null; } - + $this->broadcastEvent('afterGetProperties',array(trim($myPath,'/'),&$newProperties)); - $newProperties['href'] = trim($myPath,'/'); + $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). - // Therefore we add a trailing / for any non-file. This might need adjustments + // Therefore we add a trailing / for any non-file. This might need adjustments // if we find there are other edge cases. if ($myPath!='' && isset($newProperties[200]['{DAV:}resourcetype']) && count($newProperties[200]['{DAV:}resourcetype']->getValue())>0) $newProperties['href'] .='/'; @@ -1397,7 +1450,7 @@ class Sabre_DAV_Server { $returnPropertyList[] = $newProperties; } - + return $returnPropertyList; } @@ -1406,27 +1459,31 @@ class Sabre_DAV_Server { * 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, + * 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 - * @return bool + * + * @param string $uri + * @param resource $data + * @param string $etag + * @return bool */ - public function createFile($uri,$data) { + public function createFile($uri,$data, &$etag = null) { list($dir,$name) = Sabre_DAV_URLUtil::splitPath($uri); if (!$this->broadcastEvent('beforeBind',array($uri))) return false; - if (!$this->broadcastEvent('beforeCreateFile',array($uri,$data))) return false; $parent = $this->tree->getNodeForPath($dir); - $parent->createFile($name,$data); + + if (!$this->broadcastEvent('beforeCreateFile',array($uri, &$data, $parent))) return false; + + $etag = $parent->createFile($name,$data); $this->tree->markDirty($dir); $this->broadcastEvent('afterBind',array($uri)); + $this->broadcastEvent('afterCreateFile',array($uri, $parent)); return true; } @@ -1434,7 +1491,7 @@ class Sabre_DAV_Server { /** * This method is invoked by sub-systems creating a new directory. * - * @param string $uri + * @param string $uri * @return void */ public function createDirectory($uri) { @@ -1447,14 +1504,14 @@ class Sabre_DAV_Server { * 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. + * 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 string $uri The new uri + * @param array $resourceType The resourceType(s) * @param array $properties A list of properties - * @return void + * @return array|null */ public function createCollection($uri, array $resourceType, array $properties) { @@ -1471,7 +1528,7 @@ class Sabre_DAV_Server { $parent = $this->tree->getNodeForPath($parentUri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { throw new Sabre_DAV_Exception_Conflict('Parent node does not exist'); @@ -1491,14 +1548,14 @@ class Sabre_DAV_Server { // If we got here.. it means there's already a node on that url, and we need to throw a 405 throw new Sabre_DAV_Exception_MethodNotAllowed('The resource you tried to create already exists'); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { // This is correct } - + if (!$this->broadcastEvent('beforeBind',array($uri))) return; - // There are 2 modes of operation. The standard collection + // 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 Sabre_DAV_IExtendedCollection) { @@ -1513,7 +1570,7 @@ class Sabre_DAV_Server { } $parent->createDirectory($newName); - $rollBack = false; + $rollBack = false; $exception = null; $errorResult = null; @@ -1544,7 +1601,7 @@ class Sabre_DAV_Server { return $errorResult; } - + } $this->tree->markDirty($parentUri); $this->broadcastEvent('afterBind',array($uri)); @@ -1557,29 +1614,29 @@ class Sabre_DAV_Server { * 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 + * + * 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 + * + * @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. + // exceptions if it doesn't. $node = $this->tree->getNodeForPath($uri); - + $result = array( 200 => array(), 403 => array(), 424 => array(), - ); + ); $remainingProperties = $properties; $hasError = false; @@ -1684,14 +1741,15 @@ class Sabre_DAV_Server { * 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 + * 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 + * 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. * - * @return bool + * @param bool $handleAsGET + * @return bool */ public function checkPreconditions($handleAsGET = false) { @@ -1708,7 +1766,7 @@ class Sabre_DAV_Server { // request succeed if a resource exists at that url. try { $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { throw new Sabre_DAV_Exception_PreconditionFailed('An If-Match header was specified and the resource did not exist','If-Match'); } @@ -1722,7 +1780,7 @@ class Sabre_DAV_Server { // Stripping any extra spaces $ifMatchItem = trim($ifMatchItem,' '); - + $etag = $node->getETag(); if ($etag===$ifMatchItem) { $haveMatch = true; @@ -1744,7 +1802,7 @@ class Sabre_DAV_Server { if (!$node) { try { $node = $this->tree->getNodeForPath($uri); - } catch (Sabre_DAV_Exception_FileNotFound $e) { + } catch (Sabre_DAV_Exception_NotFound $e) { $nodeExists = false; } } @@ -1758,10 +1816,10 @@ class Sabre_DAV_Server { $etag = $node->getETag(); foreach($ifNoneMatch as $ifNoneMatchItem) { - + // Stripping any extra spaces $ifNoneMatchItem = trim($ifNoneMatchItem,' '); - + if ($etag===$ifNoneMatchItem) $haveMatch = true; } @@ -1781,7 +1839,7 @@ class Sabre_DAV_Server { } 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 @@ -1799,23 +1857,24 @@ class Sabre_DAV_Server { $lastMod = new DateTime('@' . $lastMod); if ($lastMod <= $date) { $this->httpResponse->sendStatus(304); + $this->httpResponse->setHeader('Last-Modified', Sabre_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 = Sabre_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); @@ -1830,16 +1889,15 @@ class Sabre_DAV_Server { } - // }}} - // {{{ XML Readers & Writers - - + // }}} + // {{{ XML Readers & Writers + + /** - * Generates a WebDAV propfind response body based on a list of nodes - * + * Generates a WebDAV propfind response body based on a list of nodes + * * @param array $fileProperties The list with nodes - * @param array $requestedProperties The properties that should be returned - * @return string + * @return string */ public function generateMultiStatus(array $fileProperties) { @@ -1859,7 +1917,7 @@ class Sabre_DAV_Server { $href = $entry['href']; unset($entry['href']); - + $response = new Sabre_DAV_Property_Response($href,$entry); $response->serialize($this,$multiStatus); @@ -1878,7 +1936,7 @@ class Sabre_DAV_Server { * 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 */ @@ -1886,17 +1944,17 @@ class Sabre_DAV_Server { //We'll need to change the DAV namespace declaration to something else in order to make it parsable $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); - + $newProperties = array(); foreach($dom->firstChild->childNodes as $child) { - if ($child->nodeType !== XML_ELEMENT_NODE) continue; + if ($child->nodeType !== XML_ELEMENT_NODE) continue; $operation = Sabre_DAV_XMLUtil::toClarkNotation($child); if ($operation!=='{DAV:}set' && $operation!=='{DAV:}remove') continue; - + $innerProperties = Sabre_DAV_XMLUtil::parseProperties($child, $this->propertyMap); foreach($innerProperties as $propertyName=>$propertyValue) { @@ -1920,9 +1978,9 @@ class Sabre_DAV_Server { * * 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 + * + * @param string $body + * @return array */ public function parsePropFindRequest($body) { @@ -1931,7 +1989,7 @@ class Sabre_DAV_Server { $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); - return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); + return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); } |