authBackend = $authBackend; $this->realm = $realm; } /** * Initializes the plugin. This function is automatically called by the server * * @param Sabre_DAV_Server $server * @return void */ public function initialize(Sabre_DAV_Server $server) { $this->server = $server; $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'),10); $this->server->subscribeEvent('afterGetProperties',array($this,'afterGetProperties')); $this->server->subscribeEvent('report',array($this,'report')); } /** * This method intercepts calls to PROPFIND and similar lookups * * This is done to inject the current-user-principal if this is requested. * * @todo support for 'unauthenticated' * @return void */ public function afterGetProperties($href, &$properties) { if (array_key_exists('{DAV:}current-user-principal', $properties[404])) { if ($ui = $this->authBackend->getCurrentUser()) { $properties[200]['{DAV:}current-user-principal'] = new Sabre_DAV_Property_Principal(Sabre_DAV_Property_Principal::HREF, $ui['uri']); } else { $properties[200]['{DAV:}current-user-principal'] = new Sabre_DAV_Property_Principal(Sabre_DAV_Property_Principal::UNAUTHENTICATED); } unset($properties[404]['{DAV:}current-user-principal']); } if (array_key_exists('{DAV:}principal-collection-set', $properties[404])) { $properties[200]['{DAV:}principal-collection-set'] = new Sabre_DAV_Property_Href('principals'); unset($properties[404]['{DAV:}principal-collection-set']); } if (array_key_exists('{DAV:}supported-report-set', $properties[200])) { $properties[200]['{DAV:}supported-report-set']->addReport(array( '{DAV:}expand-property', )); } } /** * This method is called before any HTTP method and forces users to be authenticated * * @param string $method * @throws Sabre_DAV_Exception_NotAuthenticated * @return bool */ public function beforeMethod($method, $uri) { $this->authBackend->authenticate($this->server,$this->realm); } /** * This functions handles REPORT requests * * @param string $reportName * @param DOMNode $dom * @return bool|null */ public function report($reportName,$dom) { switch($reportName) { case '{DAV:}expand-property' : $this->expandPropertyReport($dom); return false; case '{DAV:}principal-property-search' : if ($this->server->getRequestUri()==='principals') { $this->principalPropertySearchReport($dom); return false; } break; case '{DAV:}principal-search-property-set' : if ($this->server->getRequestUri()==='principals') { $this->principalSearchPropertySetReport($dom); return false; } break; } } /** * The expand-property report is defined in RFC3253 section 3-8. * * This report is very similar to a standard PROPFIND. The difference is * that it has the additional ability to look at properties containing a * {DAV:}href element, follow that property and grab additional elements * there. * * Other rfc's, such as ACL rely on this report, so it made sense to put * it in this plugin. * * @param DOMElement $dom * @return void */ protected function expandPropertyReport($dom) { $requestedProperties = $this->parseExpandPropertyReportRequest($dom->firstChild->firstChild); $depth = $this->server->getHTTPDepth(0); $requestUri = $this->server->getRequestUri(); $result = $this->expandProperties($requestUri,$requestedProperties,$depth); $dom = new DOMDocument('1.0','utf-8'); $dom->formatOutput = true; $multiStatus = $dom->createElement('d:multistatus'); $dom->appendChild($multiStatus); // Adding in default namespaces foreach($this->server->xmlNamespaces as $namespace=>$prefix) { $multiStatus->setAttribute('xmlns:' . $prefix,$namespace); } foreach($result as $entry) { $entry->serialize($this->server,$multiStatus); } $xml = $dom->saveXML(); $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->sendBody($xml); // Make sure the event chain is broken return false; } /** * This method is used by expandPropertyReport to parse * out the entire HTTP request. * * @param DOMElement $node * @return array */ protected function parseExpandPropertyReportRequest($node) { $requestedProperties = array(); do { if (Sabre_DAV_XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue; if ($node->firstChild) { $children = $this->parseExpandPropertyReportRequest($node->firstChild); } else { $children = array(); } $namespace = $node->getAttribute('namespace'); if (!$namespace) $namespace = 'DAV:'; $propName = '{'.$namespace.'}' . $node->getAttribute('name'); $requestedProperties[$propName] = $children; } while ($node = $node->nextSibling); return $requestedProperties; } /** * This method expands all the properties and returns * a list with property values * * @param array $path * @param array $requestedProperties the list of required properties * @param array $depth */ protected function expandProperties($path,array $requestedProperties,$depth) { $foundProperties = $this->server->getPropertiesForPath($path,array_keys($requestedProperties),$depth); $result = array(); foreach($foundProperties as $node) { foreach($requestedProperties as $propertyName=>$childRequestedProperties) { // We're only traversing if sub-properties were requested if(count($childRequestedProperties)===0) continue; // We only have to do the expansion if the property was found // and it contains an href element. if (!array_key_exists($propertyName,$node[200])) continue; if (!($node[200][$propertyName] instanceof Sabre_DAV_Property_IHref)) continue; $href = $node[200][$propertyName]->getHref(); list($node[200][$propertyName]) = $this->expandProperties($href,$childRequestedProperties,0); } $result[] = new Sabre_DAV_Property_Response($path, $node); } return $result; } protected function principalSearchPropertySetReport(DOMDocument $dom) { $searchProperties = array( '{DAV:}displayname' => 'display name' ); $httpDepth = $this->server->getHTTPDepth(0); if ($httpDepth!==0) { throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0'); } if ($dom->firstChild->hasChildNodes()) throw new Sabre_DAV_Exception_BadRequest('The principal-search-property-set report element is not allowed to have child elements'); $dom = new DOMDocument('1.0','utf-8'); $dom->formatOutput = true; $root = $dom->createElement('d:principal-search-property-set'); $dom->appendChild($root); // Adding in default namespaces foreach($this->server->xmlNamespaces as $namespace=>$prefix) { $root->setAttribute('xmlns:' . $prefix,$namespace); } $nsList = $this->server->xmlNamespaces; foreach($searchProperties as $propertyName=>$description) { $psp = $dom->createElement('d:principal-search-property'); $root->appendChild($psp); $prop = $dom->createElement('d:prop'); $psp->appendChild($prop); $propName = null; preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName); //if (!isset($nsList[$propName[1]])) { // $nsList[$propName[1]] = 'x' . count($nsList); //} // If the namespace was defined in the top-level xml namespaces, it means // there was already a namespace declaration, and we don't have to worry about it. //if (isset($server->xmlNamespaces[$propName[1]])) { $currentProperty = $dom->createElement($nsList[$propName[1]] . ':' . $propName[2]); //} else { // $currentProperty = $dom->createElementNS($propName[1],$nsList[$propName[1]].':' . $propName[2]); //} $prop->appendChild($currentProperty); $descriptionElem = $dom->createElement('d:description'); $descriptionElem->setAttribute('xml:lang','en'); $descriptionElem->appendChild($dom->createTextNode($description)); $psp->appendChild($descriptionElem); } $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->sendBody($dom->saveXML()); } protected function principalPropertySearchReport($dom) { $searchableProperties = array( '{DAV:}displayname' => 'display name' ); list($searchProperties, $requestedProperties) = $this->parsePrincipalPropertySearchReportRequest($dom); $uri = $this->server->getRequestUri(); $result = array(); $lookupResults = $this->server->getPropertiesForPath($uri, array_keys($searchProperties), 1); // The first item in the results is the parent, so we get rid of it. array_shift($lookupResults); $matches = array(); foreach($lookupResults as $lookupResult) { foreach($searchProperties as $searchProperty=>$searchValue) { if (!isset($searchableProperties[$searchProperty])) { throw new Sabre_DAV_Exception_BadRequest('Searching for ' . $searchProperty . ' is not supported'); } if (isset($lookupResult[200][$searchProperty]) && mb_stripos($lookupResult[200][$searchProperty], $searchValue, 0, 'UTF-8')!==false) { $matches[] = $lookupResult['href']; } } } $matchProperties = array(); foreach($matches as $match) { list($result) = $this->server->getPropertiesForPath($match, $requestedProperties, 0); $matchProperties[] = $result; } $xml = $this->server->generateMultiStatus($matchProperties); $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->sendBody($xml); } protected function parsePrincipalPropertySearchReportRequest($dom) { $httpDepth = $this->server->getHTTPDepth(0); if ($httpDepth!==0) { throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0'); } $searchProperties = array(); // Parsing the search request foreach($dom->firstChild->childNodes as $searchNode) { if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode)!=='{DAV:}property-search') continue; $propertyName = null; $propertyValue = null; foreach($searchNode->childNodes as $childNode) { switch(Sabre_DAV_XMLUtil::toClarkNotation($childNode)) { case '{DAV:}prop' : $property = Sabre_DAV_XMLUtil::parseProperties($searchNode); reset($property); $propertyName = key($property); break; case '{DAV:}match' : $propertyValue = $childNode->textContent; break; } } if (is_null($propertyName) || is_null($propertyValue)) throw new Sabre_DAV_Exception_BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue); $searchProperties[$propertyName] = $propertyValue; } return array($searchProperties, array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild))); } }