/** * This function handles the calendar-multiget REPORT. * * prefetch events into calendar container class, to avoid single lookup of events * * @param \DOMNode $dom * @return void */ public function calendarMultiGetReport($dom) { $properties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $filters = array('name' => 'VCALENDAR', 'comp-filters' => array(array('name' => 'VEVENT', 'prop-filters' => array()))); foreach ($hrefElems as $elem) { list($dirName, $baseName) = \Sabre\DAV\URLUtil::splitPath($elem->nodeValue); $filters['comp-filters'][0]['prop-filters'][] = array('name' => 'UID', 'text-match' => array('value' => $baseName)); } $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $node->calendarQuery($filters); }
/** * Parses the request. * * @return void */ function parse() { $filter = $this->xpath->query('/cal:calendar-query/cal:filter'); if ($filter->length !== 1) { throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed'); } $compFilters = $this->parseCompFilters($filter->item(0)); if (count($compFilters) !== 1) { throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.'); } $this->filters = $compFilters[0]; $this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild)); $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand'); if ($expand->length > 0) { $this->expand = $this->parseExpand($expand->item(0)); } }
/** * Unserializes the property. * * This static method should return a an instance of this object. * * @param \DOMElement $prop * @param array $propertyMap * @return DAV\IProperty */ static function unserialize(\DOMElement $prop, array $propertyMap) { $xpath = new \DOMXPath($prop->ownerDocument); $xpath->registerNamespace('d', 'urn:DAV'); // Finding the 'response' element $xResponses = $xpath->evaluate('d:response', $prop); $result = []; for ($jj = 0; $jj < $xResponses->length; $jj++) { $xResponse = $xResponses->item($jj); // Parsing 'href' $href = Href::unserialize($xResponse, $propertyMap); $properties = []; // Parsing 'status' in 'd:response' $responseStatus = $xpath->evaluate('string(d:status)', $xResponse); if ($responseStatus) { list(, $responseStatus, ) = explode(' ', $responseStatus, 3); } // Parsing 'propstat' $xPropstat = $xpath->query('d:propstat', $xResponse); for ($ii = 0; $ii < $xPropstat->length; $ii++) { // Parsing 'status' $status = $xpath->evaluate('string(d:status)', $xPropstat->item($ii)); list(, $statusCode, ) = explode(' ', $status, 3); $usedPropertyMap = $statusCode == '200' ? $propertyMap : []; // Parsing 'prop' $properties[$statusCode] = DAV\XMLUtil::parseProperties($xPropstat->item($ii), $usedPropertyMap); } $result[] = new Response($href->getHref(), $properties, $responseStatus ? $responseStatus : null); } return new self($result); }
/** * This function handles the addressbook-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param \DOMNode $dom * @return void */ function addressbookMultiGetReport($dom) { $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $propertyList = []; $uris = []; foreach ($hrefElems as $elem) { $uris[] = $this->server->calculateUri($elem->nodeValue); } $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('card', Plugin::NS_CARDDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $contentType = $xpath->evaluate("string(/card:addressbook-multiget/dav:prop/card:address-data/@content-type)"); $version = $xpath->evaluate("string(/card:addressbook-multiget/dav:prop/card:address-data/@version)"); if ($version) { $contentType .= '; version=' . $version; } $vcardType = $this->negotiateVCard($contentType); $propertyList = []; foreach ($this->server->getPropertiesForMultiplePaths($uris, $properties) as $props) { 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); } $propertyList[] = $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($propertyList, $prefer['return-minimal'])); }
/** * Parses the request. * * @return void */ function parse() { $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)'); if (is_nan($limit)) { $limit = null; } $filter = $this->xpath->query('/card:addressbook-query/card:filter'); // According to the CardDAV spec there needs to be exactly 1 filter // element. However, KDE 4.8.2 contains a bug that will encode 0 filter // elements, so this is a workaround for that. // // See: https://bugs.kde.org/show_bug.cgi?id=300047 if ($filter->length === 0) { $test = null; $filter = null; } elseif ($filter->length === 1) { $filter = $filter->item(0); $test = $this->xpath->evaluate('string(@test)', $filter); } else { throw new DAV\Exception\BadRequest('Only one filter element is allowed'); } if (!$test) { $test = self::TEST_ANYOF; } if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) { throw new DAV\Exception\BadRequest('The test attribute must either hold "anyof" or "allof"'); } $propFilters = []; if (!is_null($filter)) { $propFilterNodes = $this->xpath->query('card:prop-filter', $filter); for ($ii = 0; $ii < $propFilterNodes->length; $ii++) { $propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii)); } } $this->filters = $propFilters; $this->limit = $limit; $this->requestedProperties = array_keys(DAV\XMLUtil::parseProperties($this->dom->firstChild)); $this->test = $test; }
/** * WebDAV MKCOL * * The MKCOL method is used to create a new collection (directory) on the server * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpMkcol(RequestInterface $request, ResponseInterface $response) { $requestBody = $request->getBodyAsString(); $path = $request->getPath(); if ($requestBody) { $contentType = $request->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') !== 0 && strpos($contentType, 'text/xml') !== 0) { // We must throw 415 for unsupported mkcol bodies throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must have an xml Content-Type'); } $dom = XMLUtil::loadDOMDocument($requestBody); if (XMLUtil::toClarkNotation($dom->firstChild) !== '{DAV:}mkcol') { // We must throw 415 for unsupported mkcol bodies throw new Exception\UnsupportedMediaType('The request body for the MKCOL request must be a {DAV:}mkcol request construct.'); } $properties = []; foreach ($dom->firstChild->childNodes as $childNode) { if (XMLUtil::toClarkNotation($childNode) !== '{DAV:}set') { continue; } $properties = array_merge($properties, XMLUtil::parseProperties($childNode, $this->server->propertyMap)); } if (!isset($properties['{DAV:}resourcetype'])) { throw new Exception\BadRequest('The mkcol request must include a {DAV:}resourcetype property'); } $resourceType = $properties['{DAV:}resourcetype']->getValue(); unset($properties['{DAV:}resourcetype']); } else { $properties = []; $resourceType = ['{DAV:}collection']; } $result = $this->server->createCollection($path, $resourceType, $properties); if (is_array($result)) { $response->setStatus(207); $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $response->setBody($this->server->generateMultiStatus([$result])); } else { $response->setHeader('Content-Length', '0'); $response->setStatus(201); } // Sending back false will interupt the event chain and tell the server // we've handled this method. return false; }
/** * This function handles the addressbook-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param \DOMNode $dom * @return void */ public function addressbookMultiGetReport($dom) { $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $propertyList = array(); foreach ($hrefElems as $elem) { $uri = $this->server->calculateUri($elem->nodeValue); list($propertyList[]) = $this->server->getPropertiesForPath($uri, $properties); } $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($propertyList, $prefer['return-minimal'])); }
/** * parsePrincipalPropertySearchReportRequest * * This method parses the request body from a * {DAV:}principal-property-search report. * * This method returns an array with two elements: * 1. an array with properties to search on, and their values * 2. a list of propertyvalues that should be returned for the request. * * @param \DOMDocument $dom * @return array */ protected function parsePrincipalPropertySearchReportRequest($dom) { $httpDepth = $this->server->getHTTPDepth(0); if ($httpDepth !== 0) { throw new DAV\Exception\BadRequest('This report is only defined when Depth: 0'); } $searchProperties = []; $applyToPrincipalCollectionSet = false; $test = $dom->firstChild->getAttribute('test') === 'anyof' ? 'anyof' : 'allof'; // Parsing the search request foreach ($dom->firstChild->childNodes as $searchNode) { if (DAV\XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') { $applyToPrincipalCollectionSet = true; } if (DAV\XMLUtil::toClarkNotation($searchNode) !== '{DAV:}property-search') { continue; } $propertyName = null; $propertyValue = null; foreach ($searchNode->childNodes as $childNode) { switch (DAV\XMLUtil::toClarkNotation($childNode)) { case '{DAV:}prop': $property = 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 DAV\Exception\BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue); } $searchProperties[$propertyName] = $propertyValue; } return [$searchProperties, array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)), $applyToPrincipalCollectionSet, $test]; }
/** * This method parses the PROPFIND request and returns its information * * 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 */ function parsePropFindRequest($body) { // If the propfind body was empty, it means IE is requesting 'all' properties if (!$body) { return []; } $dom = XMLUtil::loadDOMDocument($body); $elem = $dom->getElementsByTagNameNS('urn:DAV', 'propfind')->item(0); if (is_null($elem)) { throw new Exception\UnsupportedMediaType('We could not find a {DAV:}propfind element in the xml request body'); } return array_keys(XMLUtil::parseProperties($elem)); }
/** * This method parses the PROPFIND request and returns its information * * 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 */ public function parsePropFindRequest($body) { // If the propfind body was empty, it means IE is requesting 'all' properties if (!$body) { return array(); } $dom = XMLUtil::loadDOMDocument($body); $elem = $dom->getElementsByTagNameNS('urn:DAV', 'propfind')->item(0); return array_keys(XMLUtil::parseProperties($elem)); }
/** * Parses the {DAV:}sync-collection REPORT request body. * * This method returns an array with 3 values: * 0 - the value of the {DAV:}sync-token element * 1 - the value of the {DAV:}sync-level element * 2 - The value of the {DAV:}limit element * 3 - A list of requested properties * * @param \DOMDocument $dom * @param int $depth * @return void */ protected function parseSyncCollectionRequest(\DOMDocument $dom, $depth) { $xpath = new \DOMXPath($dom); $xpath->registerNamespace('d', 'urn:DAV'); $syncToken = $xpath->query("//d:sync-token"); if ($syncToken->length !== 1) { throw new DAV\Exception\BadRequest('You must specify a {DAV:}sync-token element, and it must appear exactly once'); } $syncToken = $syncToken->item(0)->nodeValue; // Initial sync if (!$syncToken) { $syncToken = null; } $syncLevel = $xpath->query("//d:sync-level"); if ($syncLevel->length === 0) { // In case there was no sync-level, it could mean that we're dealing // with an old client. For these we must use the depth header // instead. $syncLevel = $depth; } else { $syncLevel = $syncLevel->item(0)->nodeValue; if ($syncLevel === 'infinite') { $syncLevel = DAV\Server::DEPTH_INFINITY; } } $limit = $xpath->query("//d:limit/d:nresults"); if ($limit->length === 0) { $limit = null; } else { $limit = $limit->item(0)->nodeValue; } $prop = $xpath->query('d:prop'); if ($prop->length !== 1) { throw new DAV\Exception\BadRequest('The {DAV:}sync-collection must contain extactly 1 {DAV:}prop'); } $properties = array_keys(DAV\XMLUtil::parseProperties($dom->documentElement)); return [$syncToken, $syncLevel, $limit, $properties]; }
/** * This function handles the calendar-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param \DOMNode $dom * @return void */ function calendarMultiGetReport($dom) { $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('cal', Plugin::NS_CALDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); if ($expand->length > 0) { $expandElem = $expand->item(0); $start = $expandElem->getAttribute('start'); $end = $expandElem->getAttribute('end'); if (!$start || !$end) { throw new DAV\Exception\BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); } $start = VObject\DateTimeParser::parseDateTime($start); $end = VObject\DateTimeParser::parseDateTime($end); if ($end <= $start) { throw new DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); } $expand = true; } else { $expand = false; } $needsJson = $xpath->evaluate("boolean(/cal:calendar-multiget/dav:prop/cal:calendar-data[@content-type='application/calendar+json'])"); $uris = []; foreach ($hrefElems as $elem) { $uris[] = $this->server->calculateUri($elem->nodeValue); } $tz = null; $timeZones = []; foreach ($this->server->getPropertiesForMultiplePaths($uris, $properties) as $uri => $objProps) { if (($needsJson || $expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. list($calendarPath) = URLUtil::splitPath($uri); if (!isset($timeZones[$calendarPath])) { // Checking the calendar-timezone property. $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; $tzResult = $this->server->getProperties($calendarPath, [$tzProp]); if (isset($tzResult[$tzProp])) { // This property contains a VCALENDAR with a single // VTIMEZONE. $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); $timeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); } else { // Defaulting to UTC. $timeZone = new DateTimeZone('UTC'); } $timeZones[$calendarPath] = $timeZone; } $vObject->expand($start, $end, $timeZones[$calendarPath]); } if ($needsJson) { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $propertyList[] = $objProps; } $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($propertyList, $prefer['return-minimal'])); }
/** * This function handles the calendar-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param \DOMNode $dom * @return void */ public function calendarMultiGetReport($dom) { $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); $hrefElems = $dom->getElementsByTagNameNS('urn:DAV', 'href'); $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('cal', Plugin::NS_CALDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $expand = $xpath->query('/cal:calendar-multiget/dav:prop/cal:calendar-data/cal:expand'); if ($expand->length > 0) { $expandElem = $expand->item(0); $start = $expandElem->getAttribute('start'); $end = $expandElem->getAttribute('end'); if (!$start || !$end) { throw new DAV\Exception\BadRequest('The "start" and "end" attributes are required for the CALDAV:expand element'); } $start = VObject\DateTimeParser::parseDateTime($start); $end = VObject\DateTimeParser::parseDateTime($end); if ($end <= $start) { throw new DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.'); } $expand = true; } else { $expand = false; } foreach ($hrefElems as $elem) { $uri = $this->server->calculateUri($elem->nodeValue); list($objProps) = $this->server->getPropertiesForPath($uri, $properties); if ($expand && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); $vObject->expand($start, $end); $objProps[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } $propertyList[] = $objProps; } $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($propertyList, $prefer['return-minimal'])); }
protected function _principalSearchReport(\DOMDocument $dom) { $requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($dom->firstChild)); $searchTokens = $dom->firstChild->getElementsByTagName('search-token'); $searchProperties = array(); if ($searchTokens->length > 0) { $searchProperties['{http://calendarserver.org/ns/}search-token'] = $searchTokens->item(0)->nodeValue; } $result = $this->server->getPlugin('acl')->principalSearch($searchProperties, $requestedProperties); $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'])); }