nextcloud/lib/private/connector/sabre/server.php

250 lines
8.2 KiB
PHP

<?php
/**
* ownCloud / SabreDAV
*
* @author Markus Goetz
*
* @copyright Copyright (C) 2007-2013 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 OC_Connector_Sabre_Server
*
* This class reimplements some methods from @see Sabre_DAV_Server.
*
* Basically we add handling of depth: infinity.
*
* The right way to handle this would have been to submit a patch to the upstream project
* and grab the corresponding version one merged.
*
* Due to time constrains and the limitations where we don't want to upgrade 3rdparty code in
* this stage of the release cycle we did choose this approach.
*
* For ownCloud 7 we will upgrade SabreDAV and submit the patch - if needed.
*
* @see Sabre_DAV_Server
*/
class OC_Connector_Sabre_Server extends Sabre_DAV_Server {
public function setObjectTree($tree) {
$this->tree = $tree;
}
/**
* @see Sabre_DAV_Server
*/
protected function httpPropfind($uri) {
// $xml = new Sabre_DAV_XMLReader(file_get_contents('php://input'));
$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);
}
/**
* Small helper to support PROPFIND with DEPTH_INFINITY.
*
* @param string $path
*/
private function addPathNodesRecursively(&$nodes, $path) {
foreach ($this->tree->getChildren($path) as $childNode) {
$nodes[$path . '/' . $childNode->getName()] = $childNode;
if ($childNode instanceof Sabre_DAV_ICollection)
$this->addPathNodesRecursively($nodes, $path . '/' . $childNode->getName());
}
}
public function getPropertiesForPath($path, $propertyNames = array(), $depth = 0) {
// if ($depth!=0) $depth = 1;
$path = rtrim($path, '/');
$returnPropertyList = array();
$parentNode = $this->tree->getNodeForPath($path);
$nodes = array(
$path => $parentNode
);
if ($depth == 1 && $parentNode instanceof Sabre_DAV_ICollection) {
foreach ($this->tree->getChildren($path) as $childNode) {
$nodes[$path . '/' . $childNode->getName()] = $childNode;
}
} else if ($depth == self::DEPTH_INFINITY && $parentNode instanceof Sabre_DAV_ICollection) {
$this->addPathNodesRecursively($nodes, $path);
}
// 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 Sabre_DAV_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 Sabre_DAV_Property_GetLastModified($node->getLastModified());
break;
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' :
if ($node instanceof Sabre_DAV_IQuota) {
$quotaInfo = $node->getQuotaInfo();
$newProperties[200][$prop] = $quotaInfo[1];
}
break;
case '{DAV:}getetag' :
if ($node instanceof Sabre_DAV_IFile && $etag = $node->getETag()) $newProperties[200][$prop] = $etag;
break;
case '{DAV:}getcontenttype' :
if ($node instanceof Sabre_DAV_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 Sabre_DAV_Property_SupportedReportSet($reports);
break;
case '{DAV:}resourcetype' :
$newProperties[200]['{DAV:}resourcetype'] = new Sabre_DAV_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;
}
}