function testSerialize() { $dt = new DateTime('2010-03-14 16:35', new DateTimeZone('UTC')); $lastMod = new Sabre_DAV_Property_GetLastModified($dt); $doc = new DOMDocument(); $root = $doc->createElement('d:getlastmodified'); $root->setAttribute('xmlns:d', 'DAV:'); $doc->appendChild($root); $objectTree = new Sabre_DAV_ObjectTree(new Sabre_DAV_SimpleDirectory('rootdir')); $server = new Sabre_DAV_Server($objectTree); $lastMod->serialize($server, $root); $xml = $doc->saveXML(); $this->assertEquals('<?xml version="1.0"?> <d:getlastmodified xmlns:d="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">' . Sabre_HTTP_Util::toHTTPDate($dt) . '</d:getlastmodified> ', $xml); $ok = false; try { Sabre_DAV_Property_GetLastModified::unserialize(Sabre_DAV_XMLUtil::loadDOMDocument($xml)->firstChild); } catch (Sabre_DAV_Exception $e) { $ok = true; } if (!$ok) { $this->markTestFailed('Unserialize should not be supported'); } }
function parse($xml) { $xml = implode("\n", $xml); $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $q = new Sabre_CardDAV_AddressBookQueryParser($dom); $q->parse(); return $q; }
/** * @depends testSimple */ function testUnserializer() { $xml = '<?xml version="1.0"?> <d:root xmlns:d="DAV:" xmlns:cal="' . Sabre_CalDAV_Plugin::NS_CALDAV . '">' . '<cal:comp name="VEVENT"/>' . '<cal:comp name="VJOURNAL"/>' . '</d:root>'; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $property = Sabre_CalDAV_Property_SupportedCalendarComponentSet::unserialize($dom->firstChild); $this->assertTrue($property instanceof Sabre_CalDAV_Property_SupportedCalendarComponentSet); $this->assertEquals(array('VEVENT', 'VJOURNAL'), $property->getValue()); }
/** * @depends testConstruct */ function testUnserialize() { $xml = '<?xml version="1.0"?> <d:anything xmlns:d="DAV:"><d:collection/><d:principal/></d:anything> '; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $resourceType = Sabre_DAV_Property_ResourceType::unserialize($dom->firstChild); $this->assertEquals(array('{DAV:}collection', '{DAV:}principal'), $resourceType->getValue()); }
/** * 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 * @return Sabre_DAV_Property_Href */ static function unserialize(DOMElement $dom) { $hrefs = array(); foreach ($dom->childNodes as $child) { if (Sabre_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 void */ static function unserialize(DOMElement $node) { $components = array(); foreach ($node->childNodes as $childNode) { if (Sabre_DAV_XMLUtil::toClarkNotation($childNode) === '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}comp') { $components[] = $childNode->getAttribute('name'); } } return new self($components); }
/** * This function parses the calendar-query report request body * * The body is quite complicated, so we're turning it into a PHP * array. * * The resulting associative array has xpath expressions as keys. * By default the xpath expressions should simply be checked for existance * The xpath expressions can point to elements or attributes. * * The array values can contain a number of items, which alters the query * filter. * * * time-range. Must also check if the todo or event falls within the * specified timerange. How this is interpreted depends on * the type of object (VTODO, VEVENT, VJOURNAL, etc) * * is-not-defined * Instead of checking if the attribute or element exist, * we must check if it doesn't. * * text-match * Checks if the value of the attribute or element matches * the specified value. This is actually another array with * the 'collation', 'value' and 'negate-condition' items. * * Refer to the CalDAV spec for more information. * * @param DOMNode $domNode * @param string $basePath used for recursive calls. * @param array $filters used for recursive calls. * @return array */ public static function parseCalendarQueryFilters($domNode, $basePath = '/c:iCalendar', &$filters = array()) { foreach ($domNode->childNodes as $child) { switch (Sabre_DAV_XMLUtil::toClarkNotation($child)) { case '{urn:ietf:params:xml:ns:caldav}comp-filter': case '{urn:ietf:params:xml:ns:caldav}prop-filter': $filterName = $basePath . '/' . 'c:' . strtolower($child->getAttribute('name')); $filters[$filterName] = array(); self::parseCalendarQueryFilters($child, $filterName, $filters); break; case '{urn:ietf:params:xml:ns:caldav}time-range': if ($start = $child->getAttribute('start')) { $start = self::parseICalendarDateTime($start); } else { $start = null; } if ($end = $child->getAttribute('end')) { $end = self::parseICalendarDateTime($end); } else { $end = null; } if (!is_null($start) && !is_null($end) && $end <= $start) { throw new Sabre_DAV_Exception_BadRequest('The end-date must be larger than the start-date in the time-range filter'); } $filters[$basePath]['time-range'] = array('start' => $start, 'end' => $end); break; case '{urn:ietf:params:xml:ns:caldav}is-not-defined': $filters[$basePath]['is-not-defined'] = true; break; case '{urn:ietf:params:xml:ns:caldav}param-filter': $filterName = $basePath . '/@' . strtolower($child->getAttribute('name')); $filters[$filterName] = array(); self::parseCalendarQueryFilters($child, $filterName, $filters); break; case '{urn:ietf:params:xml:ns:caldav}text-match': $collation = $child->getAttribute('collation'); if (!$collation) { $collation = 'i;ascii-casemap'; } $filters[$basePath]['text-match'] = array('collation' => $collation == 'default' ? 'i;ascii-casemap' : $collation, 'negate-condition' => $child->getAttribute('negate-condition') === 'yes', 'value' => $child->nodeValue); break; } } return $filters; }
/** * Parses the request. * * @return void */ public function parse() { $filterNode = null; $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 DOMElement back into a Property class. * * @param DOMElement $node * @return Sabre_CalDAV_Property_ScheduleCalendarTransp */ static function unserialize(DOMElement $node) { $value = null; foreach ($node->childNodes as $childNode) { switch (Sabre_DAV_XMLUtil::toClarkNotation($childNode)) { case '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}opaque': $value = self::OPAQUE; break; case '{' . Sabre_CalDAV_Plugin::NS_CALDAV . '}transparent': $value = self::TRANSPARENT; break; } } if (is_null($value)) { return null; } return new self($value); }
/** * 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 * @return Sabre_DAV_Property_Href */ static function unserialize(DOMElement $dom) { if (Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild) === '{DAV:}href') { return new self($dom->firstChild->textContent, false); } }
/** * Unserializes a DOM element into a ResourceType property. * * @param DOMElement $dom * @return Sabre_DAV_Property_ResourceType */ public static function unserialize(DOMElement $dom) { $value = array(); foreach ($dom->childNodes as $child) { $value[] = Sabre_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 = Sabre_DAV_XMLUtil::toClarkNotation($childNode); if ($clark == '{' . self::NS_CALDAV . '}time-range') { $start = $childNode->getAttribute('start'); $end = $childNode->getAttribute('end'); break; } } if ($start) { $start = Sabre_VObject_DateTimeParser::parseDateTime($start); } if ($end) { $end = Sabre_VObject_DateTimeParser::parseDateTime($end); } if (!$start && !$end) { throw new Sabre_DAV_Exception_BadRequest('The freebusy report must have a time-range filter'); } $acl = $this->server->getPlugin('acl'); if (!$acl) { throw new Sabre_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 Sabre_CalDAV_ICalendar) { throw new Sabre_DAV_Exception_NotImplemented('The free-busy-query REPORT is only implemented on calendars'); } $objects = array_map(function ($child) { $obj = $child->get(); if (is_resource($obj)) { $obj = stream_get_contents($obj); } return $obj; }, $calendar->getChildren()); $generator = new Sabre_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); }
/** * @expectedException Sabre_DAV_Exception_BadRequest */ function testUnserializeUnknown() { $xml = '<?xml version="1.0"?> <d:principal xmlns:d="DAV:">' . ' <d:foo />' . '</d:principal>'; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); Sabre_DAVACL_Property_Principal::unserialize($dom->firstChild); }
/** * @depends testParamFilter */ function testUndefinedNegation() { $xml = <<<XML <?xml version="1.0"?> <C:filter xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav"> <C:comp-filter name="VCALENDAR"> <C:comp-filter name="VTODO"> <C:prop-filter name="COMPLETED"> <C:is-not-defined /> </C:prop-filter> <C:prop-filter name="STATUS"> <C:text-match negate-condition="yes">CANCELLED</C:text-match> </C:prop-filter> </C:comp-filter> </C:comp-filter> </C:filter> XML; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $expected = array('/c:iCalendar/c:vcalendar' => array(), '/c:iCalendar/c:vcalendar/c:vtodo' => array(), '/c:iCalendar/c:vcalendar/c:vtodo/c:completed' => array('is-not-defined' => true), '/c:iCalendar/c:vcalendar/c:vtodo/c:status' => array('text-match' => array('collation' => 'i;ascii-casemap', 'negate-condition' => true, 'value' => 'CANCELLED'))); $result = Sabre_CalDAV_XMLUtil::parseCalendarQueryFilters($dom->firstChild); $this->assertEquals($expected, $result); }
/** * Parses a webdav lock xml body, and returns a new Sabre_DAV_Locks_LockInfo object * * @param string $body * @return Sabre_DAV_Locks_LockInfo */ protected function parseLockRequest($body) { $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($body), null, LIBXML_NOWARNING); $xml->registerXPathNamespace('d', 'urn:DAV'); $lockInfo = new Sabre_DAV_Locks_LockInfo(); $children = $xml->children("urn:DAV"); $lockInfo->owner = (string) $children->owner; $lockInfo->token = Sabre_DAV_UUIDUtil::getUUID(); $lockInfo->scope = count($xml->xpath('d:lockscope/d:exclusive')) > 0 ? Sabre_DAV_Locks_LockInfo::EXCLUSIVE : Sabre_DAV_Locks_LockInfo::SHARED; return $lockInfo; }
/** * @depends testSupportedReportSetProperty * @depends testCalendarMultiGetReport */ function testCalendarQueryReport1ObjectNoCalData() { $body = '<?xml version="1.0"?>' . '<c:calendar-query xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:">' . '<d:prop>' . ' <d:getetag />' . '</d:prop>' . '<c:filter>' . ' <c:comp-filter name="VCALENDAR">' . ' <c:comp-filter name="VEVENT" />' . ' </c:comp-filter>' . '</c:filter>' . '</c:calendar-query>'; $request = new Sabre_HTTP_Request(array('REQUEST_METHOD' => 'REPORT', 'REQUEST_URI' => '/calendars/user1/UUID-123467/UUID-2345', 'HTTP_DEPTH' => '0')); $request->setBody($body); $this->server->httpRequest = $request; $this->server->exec(); $this->assertEquals('HTTP/1.1 207 Multi-Status', $this->response->status, 'Received an unexpected status. Full response body: ' . $this->response->body); $xml = simplexml_load_string(Sabre_DAV_XMLUtil::convertDAVNamespace($this->response->body)); $xml->registerXPathNamespace('d', 'urn:DAV'); $xml->registerXPathNamespace('c', 'urn:ietf:params:xml:ns:caldav'); $check = array('/d:multistatus', '/d:multistatus/d:response', '/d:multistatus/d:response/d:href', '/d:multistatus/d:response/d:propstat', '/d:multistatus/d:response/d:propstat/d:prop', '/d:multistatus/d:response/d:propstat/d:prop/d:getetag', '/d:multistatus/d:response/d:propstat/d:status' => 'HTTP/1.1 200 OK'); foreach ($check as $v1 => $v2) { $xpath = is_int($v1) ? $v2 : $v1; $result = $xml->xpath($xpath); $this->assertEquals(1, count($result), 'We expected 1 ' . $xpath . ' elements. We\'ve found ' . count($result) . '. Full result: ' . $this->response->body); if (!is_int($v1)) { $this->assertEquals($v2, (string) $result[0]); } } }
/** * 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 Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0'); } $searchProperties = array(); $applyToPrincipalCollectionSet = false; // Parsing the search request foreach ($dom->firstChild->childNodes as $searchNode) { if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode) == '{DAV:}apply-to-principal-collection-set') { $applyToPrincipalCollectionSet = true; } 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)), $applyToPrincipalCollectionSet); }
/** * 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(Sabre_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); } $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList)); }
/** * Parses the request. * * @return void */ public function parse() { $filterNode = null; $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 Sabre_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 Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"'); } $propFilters = array(); $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(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild)); $this->test = $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 */ public function parsePropFindRequest($body) { // If the propfind body was empty, it means IE is requesting 'all' properties if (!$body) { return array(); } $dom = Sabre_DAV_XMLUtil::loadDOMDocument($body); $elem = $dom->getElementsByTagNameNS('urn:DAV', 'propfind')->item(0); return array_keys(Sabre_DAV_XMLUtil::parseProperties($elem)); }
/** * @expectedException InvalidArgumentException */ function testParseClarkNotationFail() { Sabre_DAV_XMLUtil::parseClarkNotation('}foo'); }
function testParsePropertiesMapHref() { $xml = '<?xml version="1.0"?> <root xmlns="DAV:"> <prop> <displayname>Calendars</displayname> </prop> <prop> <someprop><href>http://sabredav.org/</href></someprop> </prop> </root>'; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $properties = Sabre_DAV_XMLUtil::parseProperties($dom->firstChild, array('{DAV:}someprop' => 'Sabre_DAV_Property_Href')); $this->assertEquals(array('{DAV:}displayname' => 'Calendars', '{DAV:}someprop' => new Sabre_DAV_Property_Href('http://sabredav.org/', false)), $properties); }
/** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param DOMNode $dom * @return void */ public function calendarQueryReport($dom) { $requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)); $filterNode = $dom->getElementsByTagNameNS('urn:ietf:params:xml:ns:caldav', 'filter'); if ($filterNode->length !== 1) { throw new Sabre_DAV_Exception_BadRequest('The calendar-query report must have a filter element'); } $filters = Sabre_CalDAV_XMLUtil::parseCalendarQueryFilters($filterNode->item(0)); $requestedCalendarData = true; if (!in_array('{urn:ietf:params:xml:ns:caldav}calendar-data', $requestedProperties)) { // We always retrieve calendar-data, as we need it for filtering. $requestedProperties[] = '{urn:ietf:params:xml:ns:caldav}calendar-data'; // If calendar-data wasn't explicitly requested, we need to remove // it after processing. $requestedCalendarData = false; } // These are the list of nodes that potentially match the requirement $candidateNodes = $this->server->getPropertiesForPath($this->server->getRequestUri(), $requestedProperties, $this->server->getHTTPDepth(0)); $verifiedNodes = array(); foreach ($candidateNodes as $node) { // If the node didn't have a calendar-data property, it must not be a calendar object if (!isset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { continue; } if ($this->validateFilters($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'], $filters)) { if (!$requestedCalendarData) { unset($node[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } $verifiedNodes[] = $node; } } $this->server->httpResponse->sendStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->sendBody($this->server->generateMultiStatus($verifiedNodes)); }
/** * Unserializes the {DAV:}acl xml element. * * @param DOMElement $dom * @return Sabre_DAVACL_Property_Acl */ public static function unserialize(DOMElement $dom) { $privileges = array(); $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 Sabre_DAV_Exception_BadRequest('Each {DAV:}ace element must have one {DAV:}principal element'); } $principal = Sabre_DAVACL_Property_Principal::unserialize($principal->item(0)); if ($principal->getType() !== Sabre_DAVACL_Property_Principal::HREF) { throw new Sabre_DAV_Exception_NotImplemented('Currently only uri based principals are support, {DAV:}all, {DAV:}unauthenticated and {DAV:}authenticated are not implemented yet'); } $principal = $principal->getHref(); $protected = false; if ($xace->getElementsByTagNameNS('urn:DAV', 'protected')->length > 0) { $protected = true; } $grants = $xace->getElementsByTagNameNS('urn:DAV', 'grant'); if ($grants->length < 1) { throw new Sabre_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 = Sabre_DAV_XMLUtil::toClarkNotation($childNode)) { $privilegeName = $t; break; } } if (is_null($privilegeName)) { throw new Sabre_DAV_Exception_BadRequest('{DAV:}privilege elements must have a privilege element contained within them.'); } $privileges[] = array('principal' => $principal, 'protected' => $protected, 'privilege' => $privilegeName); } } return new self($privileges); }
/** * Parses all WebDAV properties out of a DOM Element * * Generally WebDAV properties are enclosed in {DAV:}prop elements. This * method helps by going through all these and pulling out the actual * propertynames, making them array keys and making the property values, * well.. the array values. * * If no value was given (self-closing element) null will be used as the * value. This is used in for example PROPFIND requests. * * Complex values are supported through the propertyMap argument. The * propertyMap should have the clark-notation properties as it's keys, and * classnames as values. * * When any of these properties are found, the unserialize() method will be * (statically) called. The result of this method is used as the value. * * @param DOMElement $parentNode * @param array $propertyMap * @return array */ static function parseProperties(DOMElement $parentNode, array $propertyMap = array()) { $propList = array(); foreach ($parentNode->childNodes as $propNode) { if (Sabre_DAV_XMLUtil::toClarkNotation($propNode) !== '{DAV:}prop') { continue; } foreach ($propNode->childNodes as $propNodeData) { /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */ if ($propNodeData->nodeType != XML_ELEMENT_NODE) { continue; } $propertyName = Sabre_DAV_XMLUtil::toClarkNotation($propNodeData); if (isset($propertyMap[$propertyName])) { $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName], 'unserialize'), $propNodeData); } else { $propList[$propertyName] = $propNodeData->textContent; } } } return $propList; }
/** * @expectedException Sabre_DAV_Exception_BadRequest */ function testUnserializeMissingPriv() { $source = '<?xml version="1.0"?> <d:root xmlns:d="DAV:"> <d:ace> <d:grant> <d:privilege /> </d:grant> <d:principal><d:href>/principals/evert</d:href></d:principal> </d:ace> </d:root> '; $dom = Sabre_DAV_XMLUtil::loadDOMDocument($source); Sabre_DAVACL_Property_Acl::unserialize($dom->firstChild); }
/** * 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 (Sabre_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 = Sabre_DAV_XMLUtil::loadDOMDocument($requestBody); $documentType = Sabre_DAV_XMLUtil::toClarkNotation($dom->firstChild); switch ($documentType) { // Dealing with the 'share' document, which modified invitees on a // calendar. case '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}share': // We can only deal with IShareableCalendar objects if (!$node instanceof Sabre_CalDAV_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 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}invite-reply': // This only works on the calendar-home-root node. if (!$node instanceof Sabre_CalDAV_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 Sabre_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 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}publish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof Sabre_CalDAV_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 '{' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '}unpublish-calendar': // We can only deal with IShareableCalendar objects if (!$node instanceof Sabre_CalDAV_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; } }
/** * Parses a WebDAV multistatus response body * * This method returns an array with the following structure * * array( * 'url/to/resource' => array( * '200' => array( * '{DAV:}property1' => 'value1', * '{DAV:}property2' => 'value2', * ), * '404' => array( * '{DAV:}property1' => null, * '{DAV:}property2' => null, * ), * ) * 'url/to/resource2' => array( * .. etc .. * ) * ) * * * @param string $body xml body * @return array */ public function parseMultiStatus($body) { $responseXML = simplexml_load_string($body, null, LIBXML_NOBLANKS | LIBXML_NOCDATA); if ($responseXML === false) { throw new InvalidArgumentException('The passed data is not valid XML'); } $responseXML->registerXPathNamespace('d', 'DAV:'); $propResult = array(); foreach ($responseXML->xpath('d:response') as $response) { $response->registerXPathNamespace('d', 'DAV:'); $href = $response->xpath('d:href'); $href = (string) $href[0]; $properties = array(); foreach ($response->xpath('d:propstat') as $propStat) { $propStat->registerXPathNamespace('d', 'DAV:'); $status = $propStat->xpath('d:status'); list($httpVersion, $statusCode, $message) = explode(' ', (string) $status[0], 3); $properties[$statusCode] = Sabre_DAV_XMLUtil::parseProperties(dom_import_simplexml($propStat), $this->propertyMap); } $propResult[$href] = $properties; } return $propResult; }
/** * @expectedException Sabre_DAV_Exception */ function testUnserializeNoStatus() { $xml = '<?xml version="1.0"?> <d:root xmlns:d="DAV:" xmlns:cal="' . Sabre_CalDAV_Plugin::NS_CALDAV . '" xmlns:cs="' . Sabre_CalDAV_Plugin::NS_CALENDARSERVER . '"> <cs:user> <d:href>mailto:user1@example.org</d:href> <!-- <cs:invite-accepted/> --> <cs:access> <cs:read-write/> </cs:access> </cs:user> </d:root>'; $doc2 = Sabre_DAV_XMLUtil::loadDOMDocument($xml); $outputProperty = Sabre_CalDAV_Property_Invite::unserialize($doc2->firstChild); }
/** * Parses the request. * * @return void */ public function parse() { $filterNode = null; $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'); if ($filter->length !== 1) { throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed'); } $filter = $filter->item(0); $test = $this->xpath->evaluate('string(@test)', $filter); if (!$test) { $test = self::TEST_ANYOF; } if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) { throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"'); } $propFilters = array(); $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(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild)); $this->test = $test; }