/** * 'beforeMethod' event handles. This event handles intercepts GET requests ending * with ?export * * @param string $method * @param string $uri * @return bool */ public function beforeMethod($method, $uri) { if ($method != 'GET') { return; } if ($this->server->httpRequest->getQueryString() != 'export') { return; } // splitting uri list($uri) = explode('?', $uri, 2); $node = $this->server->tree->getNodeForPath($uri); if (!$node instanceof IAddressBook) { return; } // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { $aclPlugin->checkPrivileges($uri, '{DAV:}read'); } $this->server->httpResponse->setHeader('Content-Type', 'text/directory'); $this->server->httpResponse->sendStatus(200); $nodes = $this->server->getPropertiesForPath($uri, array('{' . Plugin::NS_CARDDAV . '}address-data'), 1); $this->server->httpResponse->sendBody($this->generateVCF($nodes)); // Returning false to break the event chain return false; }
/** * Intercepts GET requests on addressbook urls ending with ?export. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpGet(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); if (!array_key_exists('export', $queryParams)) { return; } $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path); if (!$node instanceof IAddressBook) { return; } $this->server->transactionType = 'get-addressbook-export'; // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { $aclPlugin->checkPrivileges($path, '{DAV:}read'); } $nodes = $this->server->getPropertiesForPath($path, ['{' . Plugin::NS_CARDDAV . '}address-data'], 1); $format = 'text/directory'; $output = null; $filenameExtension = null; switch ($format) { case 'text/directory': $output = $this->generateVCF($nodes); $filenameExtension = '.vcf'; break; } $filename = preg_replace('/[^a-zA-Z0-9-_ ]/um', '', $node->getName()); $filename .= '-' . date('Y-m-d') . $filenameExtension; $response->setHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); $response->setHeader('Content-Type', $format); $response->setStatus(200); $response->setBody($output); // Returning false to break the event chain return false; }
function testPrincipalProperties() { $httpRequest = HTTP\Sapi::createFromServerArray(array('HTTP_HOST' => 'sabredav.org')); $this->server->httpRequest = $httpRequest; $props = $this->server->getPropertiesForPath('/principals/user1', array('{' . Plugin::NS_CALENDARSERVER . '}notification-URL')); $this->assertArrayHasKey(0, $props); $this->assertArrayHasKey(200, $props[0]); $this->assertArrayHasKey('{' . Plugin::NS_CALENDARSERVER . '}notification-URL', $props[0][200]); $prop = $props[0][200]['{' . Plugin::NS_CALENDARSERVER . '}notification-URL']; $this->assertTrue($prop instanceof DAV\Property\Href); $this->assertEquals('calendars/user1/notifications/', $prop->getHref()); }
function testPrincipalProperties() { $httpRequest = new Request('GET', '/', ['Host' => 'sabredav.org']); $this->server->httpRequest = $httpRequest; $props = $this->server->getPropertiesForPath('principals/admin', ['{' . Plugin::NS_CALENDARSERVER . '}notification-URL']); $this->assertArrayHasKey(0, $props); $this->assertArrayHasKey(200, $props[0]); $this->assertArrayHasKey('{' . Plugin::NS_CALENDARSERVER . '}notification-URL', $props[0][200]); $prop = $props[0][200]['{' . Plugin::NS_CALENDARSERVER . '}notification-URL']; $this->assertTrue($prop instanceof DAV\Xml\Property\Href); $this->assertEquals('calendars/admin/notifications/', $prop->getHref()); }
/** * 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 int $depth * @return array */ protected function expandProperties($path, array $requestedProperties, $depth) { $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); $result = []; 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 DAV\Property\IHref) { $hrefs = [$node[200][$propertyName]->getHref()]; } elseif ($node[200][$propertyName] instanceof DAV\Property\HrefList) { $hrefs = $node[200][$propertyName]->getHrefs(); } $childProps = []; foreach ($hrefs as $href) { $childProps = array_merge($childProps, $this->expandProperties($href, $childRequestedProperties, 0)); } $node[200][$propertyName] = new DAV\Property\ResponseList($childProps); } $result[] = new DAV\Property\Response($node['href'], $node); } return $result; }
/** * This event is triggered when properties are requested for a certain * node. * * This allows us to inject any properties early. * * @param string $path * @param DAV\INode $node * @param array $requestedProperties * @param array $returnedProperties * @return void */ public function beforeGetProperties($path, DAV\INode $node, &$requestedProperties, &$returnedProperties) { if ($node instanceof IShareableCalendar) { if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties)) !== false) { unset($requestedProperties[$index]); $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = new Property\Invite($node->getShares()); } } if ($node instanceof ISharedCalendar) { if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}shared-url', $requestedProperties)) !== false) { unset($requestedProperties[$index]); $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}shared-url'] = new DAV\Property\Href($node->getSharedUrl()); } // The 'invite' property is slightly different for the 'shared' // instance of the calendar, as it also contains the owner // information. if (($index = array_search('{' . Plugin::NS_CALENDARSERVER . '}invite', $requestedProperties)) !== false) { unset($requestedProperties[$index]); // Fetching owner information $props = $this->server->getPropertiesForPath($node->getOwner(), array('{http://sabredav.org/ns}email-address', '{DAV:}displayname'), 1); $ownerInfo = array('href' => $node->getOwner()); if (isset($props[0][200])) { // We're mapping the internal webdav properties to the // elements caldav-sharing expects. if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) { $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address']; } if (isset($props[0][200]['{DAV:}displayname'])) { $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname']; } } $returnedProperties[200]['{' . Plugin::NS_CALENDARSERVER . '}invite'] = new Property\Invite($node->getShares(), $ownerInfo); } } }
/** * This event is triggered when properties are requested for a certain * node. * * This allows us to inject any properties early. * * @param DAV\PropFind $propFind * @param DAV\INode $node * @return void */ function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { if ($node instanceof IShareableCalendar) { $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function () use($node) { return new Xml\Property\Invite($node->getShares()); }); } if ($node instanceof ISharedCalendar) { $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function () use($node) { return new Href($node->getSharedUrl()); }); $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}invite', function () use($node) { // Fetching owner information $props = $this->server->getPropertiesForPath($node->getOwner(), ['{http://sabredav.org/ns}email-address', '{DAV:}displayname'], 0); $ownerInfo = ['href' => $node->getOwner()]; if (isset($props[0][200])) { // We're mapping the internal webdav properties to the // elements caldav-sharing expects. if (isset($props[0][200]['{http://sabredav.org/ns}email-address'])) { $ownerInfo['href'] = 'mailto:' . $props[0][200]['{http://sabredav.org/ns}email-address']; } if (isset($props[0][200]['{DAV:}displayname'])) { $ownerInfo['commonName'] = $props[0][200]['{DAV:}displayname']; } } return new Xml\Property\Invite($node->getShares(), $ownerInfo); }); } }
/** * 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 int $depth * @return array */ protected function expandProperties($path, array $requestedProperties, $depth) { $foundProperties = $this->server->getPropertiesForPath($path, array_keys($requestedProperties), $depth); $result = []; 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 DAV\Xml\Property\Href) { continue; } $childHrefs = $node[200][$propertyName]->getHrefs(); $childProps = []; foreach ($childHrefs as $href) { // Gathering the result of the children $childProps[] = ['name' => '{DAV:}response', 'value' => $this->expandProperties($href, $childRequestedProperties, 0)[0]]; } // Replacing the property with its expannded form. $node[200][$propertyName] = $childProps; } $result[] = new DAV\Xml\Element\Response($node['href'], $node); } return $result; }
/** * This function handles the addressbook-query REPORT * * This report is used by the client to filter an addressbook based on a * complex query. * * @param \DOMNode $dom * @return void */ protected function addressbookQueryReport($dom) { $query = new AddressBookQueryParser($dom); $query->parse(); $depth = $this->server->getHTTPDepth(0); if ($depth == 0) { $candidateNodes = [$this->server->tree->getNodeForPath($this->server->getRequestUri())]; if (!$candidateNodes[0] instanceof ICard) { throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0'); } } else { $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); } $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('card', Plugin::NS_CARDDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $contentType = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@content-type)"); $version = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@version)"); if ($version) { $contentType .= '; version=' . $version; } $vcardType = $this->negotiateVCard($contentType); $validNodes = []; foreach ($candidateNodes as $node) { if (!$node instanceof ICard) { continue; } $blob = $node->get(); if (is_resource($blob)) { $blob = stream_get_contents($blob); } if (!$this->validateFilters($blob, $query->filters, $query->test)) { continue; } $validNodes[] = $node; if ($query->limit && $query->limit <= count($validNodes)) { // We hit the maximum number of items, we can stop now. break; } } $result = []; foreach ($validNodes as $validNode) { if ($depth == 0) { $href = $this->server->getRequestUri(); } else { $href = $this->server->getRequestUri() . '/' . $validNode->getName(); } list($props) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0); if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) { $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard($props[200]['{' . self::NS_CARDDAV . '}address-data'], $vcardType); } $result[] = $props; } $prefer = $this->server->getHTTPPRefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); }
/** * @depends testSupportedReportSetPropertyNonCalendar */ function testSupportedReportSetProperty() { $props = $this->server->getPropertiesForPath('/calendars/user1/UUID-123467', array('{DAV:}supported-report-set')); $this->assertArrayHasKey(0, $props); $this->assertArrayHasKey(200, $props[0]); $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); $prop = $props[0][200]['{DAV:}supported-report-set']; $this->assertTrue($prop instanceof \Sabre\DAV\Property\SupportedReportSet); $value = array('{urn:ietf:params:xml:ns:caldav}calendar-multiget', '{urn:ietf:params:xml:ns:caldav}calendar-query', '{urn:ietf:params:xml:ns:caldav}free-busy-query', '{DAV:}expand-property', '{DAV:}principal-property-search', '{DAV:}principal-search-property-set'); $this->assertEquals($value, $prop->getValue()); }
function testSupportedReportSetUserCalendars() { $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); $props = $this->server->getPropertiesForPath('/calendars/user1', array('{DAV:}supported-report-set')); $this->assertArrayHasKey(0, $props); $this->assertArrayHasKey(200, $props[0]); $this->assertArrayHasKey('{DAV:}supported-report-set', $props[0][200]); $prop = $props[0][200]['{DAV:}supported-report-set']; $this->assertTrue($prop instanceof \Sabre\DAV\Property\SupportedReportSet); $value = array('{DAV:}sync-collection', '{DAV:}expand-property', '{DAV:}principal-property-search', '{DAV:}principal-search-property-set'); $this->assertEquals($value, $prop->getValue()); }
/** * Intercepts GET requests on addressbook urls ending with ?export. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpGet(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); if (!array_key_exists('export', $queryParams)) { return; } $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path); if (!$node instanceof IAddressBook) { return; } $this->server->transactionType = 'get-addressbook-export'; // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { $aclPlugin->checkPrivileges($path, '{DAV:}read'); } $response->setHeader('Content-Type', 'text/directory'); $response->setStatus(200); $nodes = $this->server->getPropertiesForPath($path, ['{' . Plugin::NS_CARDDAV . '}address-data'], 1); $response->setBody($this->generateVCF($nodes)); // Returning false to break the event chain return false; }
/** * This function handles the addressbook-query REPORT * * This report is used by the client to filter an addressbook based on a * complex query. * * @param \DOMNode $dom * @return void */ protected function addressbookQueryReport($dom) { $query = new AddressBookQueryParser($dom); $query->parse(); $depth = $this->server->getHTTPDepth(0); if ($depth == 0) { $candidateNodes = array($this->server->tree->getNodeForPath($this->server->getRequestUri())); } else { $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); } $validNodes = array(); foreach ($candidateNodes as $node) { if (!$node instanceof ICard) { continue; } $blob = $node->get(); if (is_resource($blob)) { $blob = stream_get_contents($blob); } if (!$this->validateFilters($blob, $query->filters, $query->test)) { continue; } $validNodes[] = $node; if ($query->limit && $query->limit <= count($validNodes)) { // We hit the maximum number of items, we can stop now. break; } } $result = array(); foreach ($validNodes as $validNode) { if ($depth == 0) { $href = $this->server->getRequestUri(); } else { $href = $this->server->getRequestUri() . '/' . $validNode->getName(); } list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0); } $prefer = $this->server->getHTTPPRefer(); $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); }
/** * Generates the html directory index for a given url * * @param string $path * @return string */ public function generateDirectoryIndex($path) { $version = ''; if (DAV\Server::$exposeVersion) { $version = DAV\Version::VERSION . "-" . DAV\Version::STABILITY; } $html = "<html>\n<head>\n <title>Index for " . $this->escapeHTML($path) . "/ - SabreDAV " . $version . "</title>\n <style type=\"text/css\">\n body { Font-family: arial}\n h1 { font-size: 150% }\n </style>\n "; if ($this->enableAssets) { $html .= '<link rel="shortcut icon" href="' . $this->getAssetUrl('favicon.ico') . '" type="image/vnd.microsoft.icon" />'; } $html .= "</head>\n<body>\n <h1>Index for " . $this->escapeHTML($path) . "/</h1>\n <table>\n <tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr>\n <tr><td colspan=\"5\"><hr /></td></tr>"; $files = $this->server->getPropertiesForPath($path, array('{DAV:}displayname', '{DAV:}resourcetype', '{DAV:}getcontenttype', '{DAV:}getcontentlength', '{DAV:}getlastmodified'), 1); $parent = $this->server->tree->getNodeForPath($path); if ($path) { list($parentUri) = DAV\URLUtil::splitPath($path); $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); $icon = $this->enableAssets ? '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>' : ''; $html .= "<tr>\n <td>{$icon}</td>\n <td><a href=\"{$fullPath}\">..</a></td>\n <td>[parent]</td>\n <td></td>\n <td></td>\n </tr>"; } foreach ($files as $file) { // This is the current directory, we can skip it if (rtrim($file['href'], '/') == $path) { continue; } list(, $name) = DAV\URLUtil::splitPath($file['href']); $type = null; if (isset($file[200]['{DAV:}resourcetype'])) { $type = $file[200]['{DAV:}resourcetype']->getValue(); // resourcetype can have multiple values if (!is_array($type)) { $type = array($type); } foreach ($type as $k => $v) { // Some name mapping is preferred switch ($v) { case '{DAV:}collection': $type[$k] = 'Collection'; break; case '{DAV:}principal': $type[$k] = 'Principal'; break; case '{urn:ietf:params:xml:ns:carddav}addressbook': $type[$k] = 'Addressbook'; break; case '{urn:ietf:params:xml:ns:caldav}calendar': $type[$k] = 'Calendar'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-inbox': $type[$k] = 'Schedule Inbox'; break; case '{urn:ietf:params:xml:ns:caldav}schedule-outbox': $type[$k] = 'Schedule Outbox'; break; case '{http://calendarserver.org/ns/}calendar-proxy-read': $type[$k] = 'Proxy-Read'; break; case '{http://calendarserver.org/ns/}calendar-proxy-write': $type[$k] = 'Proxy-Write'; break; } } $type = implode(', ', $type); } // If no resourcetype was found, we attempt to use // the contenttype property if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { $type = $file[200]['{DAV:}getcontenttype']; } if (!$type) { $type = 'Unknown'; } $size = isset($file[200]['{DAV:}getcontentlength']) ? (int) $file[200]['{DAV:}getcontentlength'] : ''; $lastmodified = isset($file[200]['{DAV:}getlastmodified']) ? $file[200]['{DAV:}getlastmodified']->getTime()->format(\DateTime::ATOM) : ''; $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path ? $path . '/' : '') . $name, '/')); $displayName = isset($file[200]['{DAV:}displayname']) ? $file[200]['{DAV:}displayname'] : $name; $displayName = $this->escapeHTML($displayName); $type = $this->escapeHTML($type); $icon = ''; if ($this->enableAssets) { $node = $this->server->tree->getNodeForPath(($path ? $path . '/' : '') . $name); foreach (array_reverse($this->iconMap) as $class => $iconName) { if ($node instanceof $class) { $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>'; break; } } } $html .= "<tr>\n <td>{$icon}</td>\n <td><a href=\"{$fullPath}\">{$displayName}</a></td>\n <td>{$type}</td>\n <td>{$size}</td>\n <td>{$lastmodified}</td>\n </tr>"; } $html .= "<tr><td colspan=\"5\"><hr /></td></tr>"; $output = ''; if ($this->enablePost) { $this->server->broadcastEvent('onHTMLActionsPanel', array($parent, &$output)); } $html .= $output; $html .= "</table>\n <address>Generated by SabreDAV " . $version . " (c)2007-2014 <a href=\"http://code.google.com/p/sabredav/\">http://code.google.com/p/sabredav/</a></address>\n </body>\n </html>"; return $html; }