/** * @depends testSimple */ function testUnserializerBadData() { $xml = '<?xml version="1.0"?> <d:root xmlns:d="DAV:" xmlns:cal="' . CalDAV\Plugin::NS_CALDAV . '">' . '<cal:foo/>' . '</d:root>'; $dom = DAV\XMLUtil::loadDOMDocument($xml); $this->assertNull(ScheduleCalendarTransp::unserialize($dom->firstChild)); }
function testSerialize() { $dt = new \DateTime('2010-03-14 16:35', new \DateTimeZone('UTC')); $lastMod = new GetLastModified($dt); $doc = new \DOMDocument(); $root = $doc->createElement('d:getlastmodified'); $root->setAttribute('xmlns:d', 'DAV:'); $doc->appendChild($root); $server = new DAV\Server(); $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">' . HTTP\Util::toHTTPDate($dt) . '</d:getlastmodified> ', $xml); */ $this->assertEquals('<?xml version="1.0"?> <d:getlastmodified xmlns:d="DAV:">' . HTTP\Util::toHTTPDate($dt) . '</d:getlastmodified> ', $xml); $ok = false; try { GetLastModified::unserialize(DAV\XMLUtil::loadDOMDocument($xml)->firstChild, array()); } catch (DAV\Exception $e) { $ok = true; } if (!$ok) { $this->markTestFailed('Unserialize should not be supported'); } }
function parse($xml) { $xml = implode("\n", $xml); $dom = DAV\XMLUtil::loadDOMDocument($xml); $q = new 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 = SupportedCalendarComponentSet::unserialize($dom->firstChild); $this->assertTrue($property instanceof 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 = DAV\XMLUtil::loadDOMDocument($xml); $resourceType = ResourceType::unserialize($dom->firstChild, array()); $this->assertEquals(array('{DAV:}collection', '{DAV:}principal'), $resourceType->getValue()); }
function testUnserialize() { $source = '<?xml version="1.0"?> <d:root xmlns:d="DAV:"> <d:privilege> <d:write-properties /> </d:privilege> <d:privilege> <d:read /> </d:privilege> </d:root> '; $dom = DAV\XMLUtil::loadDOMDocument($source); $result = CurrentUserPrivilegeSet::unserialize($dom->firstChild, array()); $this->assertTrue($result->has('{DAV:}read')); $this->assertTrue($result->has('{DAV:}write-properties')); $this->assertFalse($result->has('{DAV:}bind')); }
/** * 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; } }
public function testSubsequentSyncSyncCollectionDepthFallBack() { // Making a change $this->collection->addChange(['file1.txt'], [], []); // Making another change $this->collection->addChange([], ['file2.txt'], ['file3.txt']); $request = HTTP\Sapi::createFromServerArray(['REQUEST_METHOD' => 'REPORT', 'REQUEST_URI' => '/coll/', 'CONTENT_TYPE' => 'application/xml', 'HTTP_DEPTH' => "1"]); $body = <<<BLA <?xml version="1.0" encoding="utf-8" ?> <D:sync-collection xmlns:D="DAV:"> <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> <D:prop> <D:getcontentlength/> </D:prop> </D:sync-collection> BLA; $request->setBody($body); $response = $this->request($request); $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); $dom = DAV\XMLUtil::loadDOMDocument($response->body); // Checking the sync-token $this->assertEquals('http://sabre.io/ns/sync/2', $dom->getElementsByTagNameNS('urn:DAV', 'sync-token')->item(0)->nodeValue); $responses = DAV\Property\ResponseList::unserialize($dom->documentElement, []); $responses = $responses->getResponses(); $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); $response = $responses[0]; $this->assertNull($response->getHttpStatus()); $this->assertEquals('/coll/file2.txt', $response->getHref()); $this->assertEquals([200 => ['{DAV:}getcontentlength' => 3]], $response->getResponseProperties()); $response = $responses[1]; $this->assertEquals('404', $response->getHttpStatus()); $this->assertEquals('/coll/file3.txt', $response->getHref()); $this->assertEquals([], $response->getResponseProperties()); }
/** * @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 = DAV\XMLUtil::loadDOMDocument($source); Acl::unserialize($dom->firstChild, array()); }
/** * @expectedException Sabre\DAV\Exception\BadRequest */ function testExpandBadTimes() { $xml = array('<d:prop>', ' <c:calendar-data>', ' <c:expand start="20120101T000000Z" end="19980101T000000Z"/>', ' </c:calendar-data>', '</d:prop>', '<c:filter>', ' <c:comp-filter name="VCALENDAR" />', '</c:filter>'); $xml = '<?xml version="1.0"?> <c:calendar-query xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:"> ' . implode("\n", $xml) . ' </c:calendar-query>'; $dom = DAV\XMLUtil::loadDOMDocument($xml); $q = new CalendarQueryParser($dom); $q->parse(); }
/** * 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; }
/** * This method is responsible for handling the 'ACL' event. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpAcl(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $body = $request->getBodyAsString(); $dom = DAV\XMLUtil::loadDOMDocument($body); $newAcl = Property\Acl::unserialize($dom->firstChild, $this->server->propertyMap)->getPrivileges(); // Normalizing urls foreach ($newAcl as $k => $newAce) { $newAcl[$k]['principal'] = $this->server->calculateUri($newAce['principal']); } $node = $this->server->tree->getNodeForPath($path); if (!$node instanceof IACL) { throw new DAV\Exception\MethodNotAllowed('This node does not support the ACL method'); } $oldAcl = $this->getACL($node); $supportedPrivileges = $this->getFlatPrivilegeSet($node); /* Checking if protected principals from the existing principal set are not overwritten. */ foreach ($oldAcl as $oldAce) { if (!isset($oldAce['protected']) || !$oldAce['protected']) { continue; } $found = false; foreach ($newAcl as $newAce) { if ($newAce['privilege'] === $oldAce['privilege'] && $newAce['principal'] === $oldAce['principal'] && $newAce['protected']) { $found = true; } } if (!$found) { throw new Exception\AceConflict('This resource contained a protected {DAV:}ace, but this privilege did not occur in the ACL request'); } } foreach ($newAcl as $newAce) { // Do we recognize the privilege if (!isset($supportedPrivileges[$newAce['privilege']])) { throw new Exception\NotSupportedPrivilege('The privilege you specified (' . $newAce['privilege'] . ') is not recognized by this server'); } if ($supportedPrivileges[$newAce['privilege']]['abstract']) { throw new Exception\NoAbstract('The privilege you specified (' . $newAce['privilege'] . ') is an abstract privilege'); } // Looking up the principal try { $principal = $this->server->tree->getNodeForPath($newAce['principal']); } catch (DAV\Exception\NotFound $e) { throw new Exception\NotRecognizedPrincipal('The specified principal (' . $newAce['principal'] . ') does not exist'); } if (!$principal instanceof IPrincipal) { throw new Exception\NotRecognizedPrincipal('The specified uri (' . $newAce['principal'] . ') is not a principal'); } } $node->setACL($newAcl); $response->setStatus(200); // Breaking the event chain, because we handled this method. return false; }
/** * @expectedException Sabre\DAV\Exception\BadRequest */ function testUnserializeUnknown() { $xml = '<?xml version="1.0"?> <d:principal xmlns:d="DAV:">' . ' <d:foo />' . '</d:principal>'; $dom = DAV\XMLUtil::loadDOMDocument($xml); Principal::unserialize($dom->firstChild, array()); }
/** * @expectedException Sabre\DAV\Exception */ function testFreeBusyReportNoACLPlugin() { $this->server = new DAV\Server(); $this->plugin = new Plugin(); $this->server->addPlugin($this->plugin); $reportXML = <<<XML <?xml version="1.0"?> <c:free-busy-query xmlns:c="urn:ietf:params:xml:ns:caldav"> <c:time-range start="20111001T000000Z" end="20111101T000000Z" /> </c:free-busy-query> XML; $dom = DAV\XMLUtil::loadDOMDocument($reportXML); $this->plugin->report('{urn:ietf:params:xml:ns:caldav}free-busy-query', $dom); }
/** * 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)); }
/** * @expectedException Sabre\DAV\Exception */ function testUnserializeNoStatus() { $xml = '<?xml version="1.0"?> <d:root xmlns:d="DAV:" xmlns:cal="' . CalDAV\Plugin::NS_CALDAV . '" xmlns:cs="' . 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 = DAV\XMLUtil::loadDOMDocument($xml); $outputProperty = Invite::unserialize($doc2->firstChild); }
/** * This function handles the MKCALENDAR HTTP method, which creates * a new calendar. * * @param string $uri * @return void */ public function httpMkCalendar($uri) { // Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support // for clients matching iCal in the user agent //$ua = $this->server->httpRequest->getHeader('User-Agent'); //if (strpos($ua,'iCal/')!==false) { // throw new \Sabre\DAV\Exception\Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.'); //} $body = $this->server->httpRequest->getBody(true); $properties = array(); if ($body) { $dom = DAV\XMLUtil::loadDOMDocument($body); foreach ($dom->firstChild->childNodes as $child) { if (DAV\XMLUtil::toClarkNotation($child) !== '{DAV:}set') { continue; } foreach (DAV\XMLUtil::parseProperties($child, $this->server->propertyMap) as $k => $prop) { $properties[$k] = $prop; } } } $resourceType = array('{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'); $this->server->createCollection($uri, $resourceType, $properties); $this->server->httpResponse->sendStatus(201); $this->server->httpResponse->setHeader('Content-Length', 0); }
/** * 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)); }
function testCalendarQueryDepth0() { $xml = <<<XML <?xml version="1.0"?> <c:calendar-query xmlns:c="urn:ietf:params:xml:ns:caldav" xmlns:d="DAV:"> <d:prop> <c:calendar-data content-type="application/calendar+json" /> </d:prop> <c:filter> <c:comp-filter name="VCALENDAR" /> </c:filter> </c:calendar-query> XML; $headers = ['Depth' => '0']; $request = new Request('REPORT', '/calendars/user1/foo/bar.ics', $headers, $xml); $response = $this->request($request); $this->assertEquals(207, $response->getStatus(), "Invalid response code. Full body: " . $response->getBodyAsString()); $body = $response->getBodyAsString(); // Getting from the xml body to the actual returned data is // unfortunately very convoluted. $responses = \Sabre\DAV\Property\ResponseList::unserialize(\Sabre\DAV\XMLUtil::loadDOMDocument($body)->firstChild, $this->server->propertyMap); $responses = $responses->getResponses(); $this->assertEquals(1, count($responses)); $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; $response = json_decode($response, true); if (json_last_error()) { $this->fail('Json decoding error: ' . json_last_error_msg()); } $this->assertEquals(['vcalendar', [], [['vevent', [], []]]], $response); }
/** * Parses a WebDAV multistatus response body * * This method returns an array with the following structure * * [ * 'url/to/resource' => [ * '200' => [ * '{DAV:}property1' => 'value1', * '{DAV:}property2' => 'value2', * ], * '404' => [ * '{DAV:}property1' => null, * '{DAV:}property2' => null, * ], * ], * 'url/to/resource2' => [ * .. etc .. * ] * ] * * * @param string $body xml body * @return array */ function parseMultiStatus($body) { try { $dom = XMLUtil::loadDOMDocument($body); } catch (Exception\BadRequest $e) { throw new \InvalidArgumentException('The body passed to parseMultiStatus could not be parsed. Is it really xml?'); } $responses = Property\ResponseList::unserialize($dom->documentElement, $this->propertyMap); $result = []; foreach ($responses->getResponses() as $response) { $result[$response->getHref()] = $response->getResponseProperties(); } return $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; } } }
/** * This function handles the MKCALENDAR HTTP method, which creates * a new calendar. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ function httpMkCalendar(RequestInterface $request, ResponseInterface $response) { // Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support // for clients matching iCal in the user agent //$ua = $this->server->httpRequest->getHeader('User-Agent'); //if (strpos($ua,'iCal/')!==false) { // throw new \Sabre\DAV\Exception\Forbidden('iCal has major bugs in it\'s RFC3744 support. Therefore we are left with no other choice but disabling this feature.'); //} $body = $request->getBodyAsString(); $path = $request->getPath(); $properties = []; if ($body) { $dom = DAV\XMLUtil::loadDOMDocument($body); foreach ($dom->firstChild->childNodes as $child) { if (DAV\XMLUtil::toClarkNotation($child) !== '{DAV:}set') { continue; } foreach (DAV\XMLUtil::parseProperties($child, $this->server->propertyMap) as $k => $prop) { $properties[$k] = $prop; } } } // iCal abuses MKCALENDAR since iCal 10.9.2 to create server-stored // subscriptions. Before that it used MKCOL which was the correct way // to do this. // // If the body had a {DAV:}resourcetype, it means we stumbled upon this // request, and we simply use it instead of the pre-defined list. if (isset($properties['{DAV:}resourcetype'])) { $resourceType = $properties['{DAV:}resourcetype']->getValue(); } else { $resourceType = ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar']; } $this->server->createCollection($path, $resourceType, $properties); $this->server->httpResponse->setStatus(201); $this->server->httpResponse->setHeader('Content-Length', 0); // This breaks the method chain. return false; }
/** * 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; } }