/** * Unserializes this property from a DOM Element * * This method returns an instance of this class. * It will only decode tag values. * * @param \DOMElement $dom * @param array $propertyMap * @return \OCA\DAV\Connector\Sabre\TagList */ static function unserialize(\DOMElement $dom, array $propertyMap) { $tags = array(); foreach ($dom->childNodes as $child) { if (DAV\XMLUtil::toClarkNotation($child) === '{' . self::NS_OWNCLOUD . '}tag') { $tags[] = $child->textContent; } } return new self($tags); }
/** * Unserializes this property from a DOM Element * * This method returns an instance of this class. * It will only decode {DAV:}href values. * * @param \DOMElement $dom * @param array $propertyMap * @return DAV\Property\HrefList */ static function unserialize(\DOMElement $dom, array $propertyMap) { $hrefs = []; foreach ($dom->childNodes as $child) { if (DAV\XMLUtil::toClarkNotation($child) === '{DAV:}href') { $hrefs[] = $child->textContent; } } return new self($hrefs, false); }
/** * Unserializes the DOMElement back into a Property class. * * @param \DOMElement $node * @return Property_SupportedCalendarComponentSet */ static function unserialize(\DOMElement $node) { $components = array(); foreach ($node->childNodes as $childNode) { if (DAV\XMLUtil::toClarkNotation($childNode) === '{' . CalDAV\Plugin::NS_CALDAV . '}comp') { $components[] = $childNode->getAttribute('name'); } } return new self($components); }
/** * Unserializes the DOMElement back into a Property class. * * @param \DOMElement $node * @param array $propertyMap * @return ScheduleCalendarTransp */ static function unserialize(\DOMElement $node, array $propertyMap) { $value = null; foreach ($node->childNodes as $childNode) { switch (DAV\XMLUtil::toClarkNotation($childNode)) { case '{' . CalDAV\Plugin::NS_CALDAV . '}opaque': $value = self::OPAQUE; break; case '{' . CalDAV\Plugin::NS_CALDAV . '}transparent': $value = self::TRANSPARENT; break; } } if (is_null($value)) { return null; } return new self($value); }
/** * Unserializes a DOM element into a ResourceType property. * * @param \DOMElement $dom * @return DAV\Property\ResourceType */ public static function unserialize(\DOMElement $dom) { $value = array(); foreach ($dom->childNodes as $child) { $value[] = DAV\XMLUtil::toClarkNotation($child); } return new self($value); }
/** * HTTP REPORT method implementation * * Although the REPORT method is not part of the standard WebDAV spec (it's from rfc3253) * It's used in a lot of extensions, so it made sense to implement it into the core. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpReport(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $body = $request->getBodyAsString(); $dom = XMLUtil::loadDOMDocument($body); $reportName = XMLUtil::toClarkNotation($dom->firstChild); if ($this->server->emit('report', [$reportName, $dom, $path])) { // If emit returned true, it means the report was not supported throw new Exception\ReportNotSupported(); } // Sending back false will interupt the event chain and tell the server // we've handled this method. return false; }
/** * Deserializes a DOM element into a property object. * * @param \DOMElement $dom * @return Principal */ public static function unserialize(\DOMElement $dom) { $parent = $dom->firstChild; while (!DAV\XMLUtil::toClarkNotation($parent)) { $parent = $parent->nextSibling; } switch (DAV\XMLUtil::toClarkNotation($parent)) { case '{DAV:}unauthenticated': return new self(self::UNAUTHENTICATED); case '{DAV:}authenticated': return new self(self::AUTHENTICATED); case '{DAV:}href': return new self(self::HREF, $parent->textContent); case '{DAV:}all': return new self(self::ALL); default: throw new DAV\Exception\BadRequest('Unexpected element (' . DAV\XMLUtil::toClarkNotation($parent) . '). Could not deserialize'); } }
/** * Unserializes the {DAV:}current-user-privilege-set element. * * @param \DOMElement $node * @param array $propertyMap * @return CurrentUserPrivilegeSet */ static function unserialize(\DOMElement $node, array $propertyMap) { $result = []; $xprivs = $node->getElementsByTagNameNS('urn:DAV', 'privilege'); for ($jj = 0; $jj < $xprivs->length; $jj++) { $xpriv = $xprivs->item($jj); $privilegeName = null; for ($kk = 0; $kk < $xpriv->childNodes->length; $kk++) { $childNode = $xpriv->childNodes->item($kk); if ($t = DAV\XMLUtil::toClarkNotation($childNode)) { $privilegeName = $t; break; } } $result[] = $privilegeName; } return new self($result); }
/** * This method is responsible for parsing the request and generating the * response for the CALDAV:free-busy-query REPORT. * * @param \DOMNode $dom * @return void */ protected function freeBusyQueryReport(\DOMNode $dom) { $start = null; $end = null; foreach ($dom->firstChild->childNodes as $childNode) { $clark = DAV\XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = VObject\DateTimeParser::parseDateTime($start); } if ($end) { $end = VObject\DateTimeParser::parseDateTime($end); } if (!$start && !$end) { throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if (!$acl) { throw new DAV\Exception('The ACL plugin must be loaded for free-busy queries to work'); } $uri = $this->server->getRequestUri(); $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); $calendar = $this->server->tree->getNodeForPath($uri); if (!$calendar instanceof ICalendar) { throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } // Doing a calendar-query first, to make sure we get the most // performance. $urls = $calendar->calendarQuery(array('name' => 'VCALENDAR', 'comp-filters' => array(array('name' => 'VEVENT', 'comp-filters' => array(), 'prop-filters' => array(), 'is-not-defined' => false, 'time-range' => array('start' => $start, 'end' => $end))), 'prop-filters' => array(), 'is-not-defined' => false, 'time-range' => null)); $objects = array_map(function ($url) use($calendar) { $obj = $calendar->getChild($url)->get(); return $obj; }, $urls); $generator = new VObject\FreeBusyGenerator(); $generator->setObjects($objects); $generator->setTimeRange($start, $end); $result = $generator->getResult(); $result = $result->serialize(); $this->server->httpResponse->sendStatus(200); $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->setHeader('Content-Length', strlen($result)); $this->server->httpResponse->sendBody($result); }
/** * This method parses a PropPatch request * * PropPatch changes the properties for a resource. This method * returns a list of properties. * * 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 */ function parsePropPatchRequest($body) { //We'll need to change the DAV namespace declaration to something else in order to make it parsable $dom = XMLUtil::loadDOMDocument($body); $newProperties = []; foreach ($dom->firstChild->childNodes as $child) { if ($child->nodeType !== XML_ELEMENT_NODE) { continue; } $operation = XMLUtil::toClarkNotation($child); if ($operation !== '{DAV:}set' && $operation !== '{DAV:}remove') { continue; } $innerProperties = XMLUtil::parseProperties($child, $this->propertyMap); foreach ($innerProperties as $propertyName => $propertyValue) { if ($operation === '{DAV:}remove') { $propertyValue = null; } $newProperties[$propertyName] = $propertyValue; } } return $newProperties; }
/** * Unserializes the {DAV:}acl xml element. * * @param \DOMElement $dom * @param array $propertyMap * @return Acl */ static function unserialize(\DOMElement $dom, array $propertyMap) { $privileges = []; $xaces = $dom->getElementsByTagNameNS('urn:DAV', 'ace'); for ($ii = 0; $ii < $xaces->length; $ii++) { $xace = $xaces->item($ii); $principal = $xace->getElementsByTagNameNS('urn:DAV', 'principal'); if ($principal->length !== 1) { throw new DAV\Exception\BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); } $principal = Principal::unserialize($principal->item(0), $propertyMap); switch ($principal->getType()) { case Principal::HREF: $principal = $principal->getHref(); break; case Principal::AUTHENTICATED: $principal = '{DAV:}authenticated'; break; case Principal::UNAUTHENTICATED: $principal = '{DAV:}unauthenticated'; break; case Principal::ALL: $principal = '{DAV:}all'; break; } $protected = false; if ($xace->getElementsByTagNameNS('urn:DAV', 'protected')->length > 0) { $protected = true; } $grants = $xace->getElementsByTagNameNS('urn:DAV', 'grant'); if ($grants->length < 1) { throw new DAV\Exception\NotImplemented('Every {DAV:}ace element must have a {DAV:}grant element. {DAV:}deny is not yet supported'); } $grant = $grants->item(0); $xprivs = $grant->getElementsByTagNameNS('urn:DAV', 'privilege'); for ($jj = 0; $jj < $xprivs->length; $jj++) { $xpriv = $xprivs->item($jj); $privilegeName = null; for ($kk = 0; $kk < $xpriv->childNodes->length; $kk++) { $childNode = $xpriv->childNodes->item($kk); if ($t = DAV\XMLUtil::toClarkNotation($childNode)) { $privilegeName = $t; break; } } if (is_null($privilegeName)) { throw new DAV\Exception\BadRequest('{DAV:}privilege elements must have a privilege element contained within them.'); } $privileges[] = ['principal' => $principal, 'protected' => $protected, 'privilege' => $privilegeName]; } } return new self($privileges); }
/** * This event is triggered when the server didn't know how to handle a * certain request. * * We intercept this to handle POST requests on calendars. * * @param string $method * @param string $uri * @return null|bool */ public function unknownMethod($method, $uri) { if ($method !== 'POST') { return; } // Only handling xml $contentType = $this->server->httpRequest->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) { return; } // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($uri); } catch (DAV\Exception\NotFound $e) { return; } $requestBody = $this->server->httpRequest->getBody(true); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $this->server->httpRequest->setBody($requestBody); $dom = DAV\XMLUtil::loadDOMDocument($requestBody); $documentType = DAV\XMLUtil::toClarkNotation($dom->firstChild); switch ($documentType) { // Dealing with the 'share' document, which modified invitees on a // calendar. case '{' . Plugin::NS_CALENDARSERVER . '}share': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $mutations = $this->parseShareRequest($dom); $node->updateShares($mutations[0], $mutations[1]); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; // The invite-reply document is sent when the user replies to an // invitation of a calendar share. // The invite-reply document is sent when the user replies to an // invitation of a calendar share. case '{' . Plugin::NS_CALENDARSERVER . '}invite-reply': // This only works on the calendar-home-root node. if (!$node instanceof UserCalendars) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $message = $this->parseInviteReplyRequest($dom); $url = $node->shareReply($message['href'], $message['status'], $message['calendarUri'], $message['inReplyTo'], $message['summary']); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); if ($url) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElement('cs:shared-as'); foreach ($this->server->xmlNamespaces as $namespace => $prefix) { $root->setAttribute('xmlns:' . $prefix, $namespace); } $dom->appendChild($root); $href = new DAV\Property\Href($url); $href->serialize($this->server, $root); $this->server->httpResponse->setHeader('Content-Type', 'application/xml'); $this->server->httpResponse->sendBody($dom->saveXML()); } // Breaking the event chain return false; case '{' . Plugin::NS_CALENDARSERVER . '}publish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $node->setPublishStatus(true); // iCloud sends back the 202, so we will too. $this->server->httpResponse->sendStatus(202); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; case '{' . Plugin::NS_CALENDARSERVER . '}unpublish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableCalendar) { return; } // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($uri, '{DAV:}write'); } $node->setPublishStatus(false); $this->server->httpResponse->sendStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $this->server->httpResponse->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } }
/** * Unserializes a DOM element into a ResourceType property. * * @param \DOMElement $dom * @param array $propertyMap * @return DAV\Property\ResourceType */ static function unserialize(\DOMElement $dom, array $propertyMap) { $value = []; foreach ($dom->childNodes as $child) { $value[] = DAV\XMLUtil::toClarkNotation($child); } return new self($value); }
/** * This method is responsible for parsing the request and generating the * response for the CALDAV:free-busy-query REPORT. * * @param \DOMNode $dom * @return void */ protected function freeBusyQueryReport(\DOMNode $dom) { $start = null; $end = null; foreach ($dom->firstChild->childNodes as $childNode) { $clark = DAV\XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = VObject\DateTimeParser::parseDateTime($start); } if ($end) { $end = VObject\DateTimeParser::parseDateTime($end); } $uri = $this->server->getRequestUri(); if (!$start && !$end) { throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if ($acl) { $acl->checkPrivileges($uri, '{' . self::NS_CALDAV . '}read-free-busy'); } $calendar = $this->server->tree->getNodeForPath($uri); if (!$calendar instanceof ICalendar) { throw new DAV\Exception\NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; // Figuring out the default timezone for the calendar, for floating // times. $calendarProps = $this->server->getProperties($uri, [$tzProp]); if (isset($calendarProps[$tzProp])) { $vtimezoneObj = VObject\Reader::read($calendarProps[$tzProp]); $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); } else { $calendarTimeZone = new DateTimeZone('UTC'); } // Doing a calendar-query first, to make sure we get the most // performance. $urls = $calendar->calendarQuery(['name' => 'VCALENDAR', 'comp-filters' => [['name' => 'VEVENT', 'comp-filters' => [], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => ['start' => $start, 'end' => $end]]], 'prop-filters' => [], 'is-not-defined' => false, 'time-range' => null]); $objects = array_map(function ($url) use($calendar) { $obj = $calendar->getChild($url)->get(); return $obj; }, $urls); $generator = new VObject\FreeBusyGenerator(); $generator->setObjects($objects); $generator->setTimeRange($start, $end); $generator->setTimeZone($calendarTimeZone); $result = $generator->getResult(); $result = $result->serialize(); $this->server->httpResponse->setStatus(200); $this->server->httpResponse->setHeader('Content-Type', 'text/calendar'); $this->server->httpResponse->setHeader('Content-Length', strlen($result)); $this->server->httpResponse->setBody($result); }
/** * This function handles support for the POST method * * @param string $method * @param string $uri * @return bool */ public function unknownMethod($method, $uri) { switch ($method) { case 'POST': $body = $this->server->httpRequest->getBody(true); try { $dom = \Sabre\DAV\XMLUtil::loadDOMDocument($body); } catch (\Sabre\DAV\Exception\BadRequest $sdavebr) { return; } $reportName = \Sabre\DAV\XMLUtil::toClarkNotation($dom->firstChild); switch ($reportName) { case '{' . self::NS_INVERSE . '}acl-query': $this->aclQueryPost($dom, $uri); return false; } } }
/** * Unserializes this property from a DOM Element * * This method returns an instance of this class. * It will only decode {DAV:}href values. For non-compatible elements null will be returned. * * @param \DOMElement $dom * @param array $propertyMap * @return DAV\Property\Href */ static function unserialize(\DOMElement $dom, array $propertyMap) { if ($dom->firstChild && DAV\XMLUtil::toClarkNotation($dom->firstChild) === '{DAV:}href') { return new self($dom->firstChild->textContent, false); } }
/** * 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]; }
/** * We intercept this to handle POST requests on calendars. * * @param RequestInterface $request * @param ResponseInterface $response * @return null|bool */ function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); // Only handling xml $contentType = $request->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) { return; } // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($path); } catch (NotFound $e) { return; } // CSRF protection $this->protectAgainstCSRF(); $requestBody = $request->getBodyAsString(); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $request->setBody($requestBody); $dom = XMLUtil::loadDOMDocument($requestBody); $documentType = XMLUtil::toClarkNotation($dom->firstChild); switch ($documentType) { // Dealing with the 'share' document, which modified invitees on a // calendar. case '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}share': // We can only deal with IShareableCalendar objects if (!$node instanceof IShareableAddressBook) { return; } $this->server->transactionType = 'post-calendar-share'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($path, '{DAV:}write'); } $mutations = $this->parseShareRequest($dom); $node->updateShares($mutations[0], $mutations[1]); $response->setStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } }