/** * WebDAV PROPFIND * * This WebDAV method requests information about an uri resource, or a list of resources * If a client wants to receive the properties for a single resource it will add an HTTP Depth: header with a 0 value * If the value is 1, it means that it also expects a list of sub-resources (e.g.: files in a directory) * * The request body contains an XML data structure that has a list of properties the client understands * The response body is also an xml document, containing information about every uri resource and the requested properties * * It has to return a HTTP 207 Multi-status status code * * @param RequestInterface $request * @param ResponseInterface $response * @return void */ function httpPropfind(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); $requestedProperties = $this->server->parsePropFindRequest($request->getBodyAsString()); $depth = $this->server->getHTTPDepth(1); // The only two options for the depth of a propfind is 0 or 1 - as long as depth infinity is not enabled if (!$this->server->enablePropfindDepthInfinity && $depth != 0) { $depth = 1; } $newProperties = $this->server->getPropertiesForPath($path, $requestedProperties, $depth); // This is a multi-status response $response->setStatus(207); $response->setHeader('Content-Type', 'application/xml; charset=utf-8'); $response->setHeader('Vary', 'Brief,Prefer'); // Normally this header is only needed for OPTIONS responses, however.. // iCal seems to also depend on these being set for PROPFIND. Since // this is not harmful, we'll add it. $features = ['1', '3', 'extended-mkcol']; foreach ($this->server->getPlugins() as $plugin) { $features = array_merge($features, $plugin->getFeatures()); } $response->setHeader('DAV', implode(', ', $features)); $prefer = $this->server->getHTTPPrefer(); $minimal = $prefer['return-minimal']; $data = $this->server->generateMultiStatus($newProperties, $minimal); $response->setBody($data); // Sending back false will interupt the event chain and tell the server // we've handled this method. return false; }
/** * This method handles the {DAV:}sync-collection HTTP REPORT. * * @param string $uri * @param \DOMDocument $dom * @return void */ function syncCollection($uri, \DOMDocument $dom) { // rfc3253 specifies 0 is the default value for Depth: $depth = $this->server->getHTTPDepth(0); list($syncToken, $syncLevel, $limit, $properties) = $this->parseSyncCollectionRequest($dom, $depth); // Getting the data $node = $this->server->tree->getNodeForPath($uri); if (!$node instanceof ISyncCollection) { throw new DAV\Exception\ReportNotSupported('The {DAV:}sync-collection REPORT is not supported on this url.'); } $token = $node->getSyncToken(); if (!$token) { throw new DAV\Exception\ReportNotSupported('No sync information is available at this node'); } if (!is_null($syncToken)) { // Sync-token must start with our prefix if (substr($syncToken, 0, strlen(self::SYNCTOKEN_PREFIX)) !== self::SYNCTOKEN_PREFIX) { throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); } $syncToken = substr($syncToken, strlen(self::SYNCTOKEN_PREFIX)); } $changeInfo = $node->getChanges($syncToken, $syncLevel, $limit); if (is_null($changeInfo)) { throw new DAV\Exception\InvalidSyncToken('Invalid or unknown sync token'); } // Encoding the response $this->sendSyncCollectionResponse($changeInfo['syncToken'], $uri, $changeInfo['added'], $changeInfo['modified'], $changeInfo['deleted'], $properties); }
/** * Windows Phone liefert kein "Depth" mit, daher funktioniert der Calendar-Query nicht, * ohne den aber keine Daten abgerufen werden können. * * @param int $default * @return int */ function getHTTPDepth($default = self::DEPTH_INFINITY) { $depth = $this->httpRequest->getHeader('Depth'); if (is_null($depth) && $default == 0) { return 1; } if ($depth == 0 && $default == 0) { return 1; } return parent::getHTTPDepth($default); }
/** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param Xml\Request\CalendarQueryReport $report * @return void */ function calendarQueryReport($report) { $path = $this->server->getRequestUri(); $needsJson = $report->contentType === 'application/calendar+json'; $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = []; $calendarTimeZone = null; if ($report->expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; $tzResult = $this->server->getProperties($path, [$tzProp]); if (isset($tzResult[$tzProp])) { // This property contains a VCALENDAR with a single // VTIMEZONE. $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); unset($vtimezoneObj); } else { // Defaulting to UTC. $calendarTimeZone = new DateTimeZone('UTC'); } } // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $report->properties; 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; } $properties = $this->server->getPropertiesForPath($path, $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $report->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($report->expand) { $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } elseif ($report->expand) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = [$properties]; } } } if ($node instanceof ICalendarObjectContainer && $depth === 0) { if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-WP/') === 0) { // Windows phone incorrectly supplied depth as 0, when it actually // should have set depth to 1. We're implementing a workaround here // to deal with this. $depth = 1; } else { throw new BadRequest('A calendar-query REPORT on a calendar with a Depth: 0 is undefined. Set Depth to 1'); } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendarObjectContainer && $depth == 1) { $nodePaths = $node->calendarQuery($report->filters); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $report->properties); if ($needsJson || $report->expand) { $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($report->expand) { $vObject->expand($report->expand['start'], $report->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result[] = $properties; } } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); }
/** * 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 */ function calendarQueryReport($dom) { $parser = new CalendarQueryParser($dom); $parser->parse(); $path = $this->server->getRequestUri(); // TODO: move this into CalendarQueryParser $xpath = new \DOMXPath($dom); $xpath->registerNameSpace('cal', Plugin::NS_CALDAV); $xpath->registerNameSpace('dav', 'urn:DAV'); $needsJson = $xpath->evaluate("boolean(/cal:calendar-query/dav:prop/cal:calendar-data[@content-type='application/calendar+json'])"); $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = []; $calendarTimeZone = null; if ($parser->expand) { // We're expanding, and for that we need to figure out the // calendar's timezone. $tzProp = '{' . self::NS_CALDAV . '}calendar-timezone'; $tzResult = $this->server->getProperties($path, [$tzProp]); if (isset($tzResult[$tzProp])) { // This property contains a VCALENDAR with a single // VTIMEZONE. $vtimezoneObj = VObject\Reader::read($tzResult[$tzProp]); $calendarTimeZone = $vtimezoneObj->VTIMEZONE->getTimeZone(); unset($vtimezoneObj); } else { // Defaulting to UTC. $calendarTimeZone = new DateTimeZone('UTC'); } } // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $parser->requestedProperties; 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; } $properties = $this->server->getPropertiesForPath($path, $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $parser->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } elseif ($parser->expand) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = [$properties]; } } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendarObjectContainer && $depth == 1) { $nodePaths = $node->calendarQuery($parser->filters); $timeZones = []; foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); if ($needsJson || $parser->expand) { $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end'], $calendarTimeZone); } if ($needsJson) { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); } else { $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result[] = $properties; } } $prefer = $this->server->getHTTPPRefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return-minimal'])); }
/** * 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) { $parser = new CalendarQueryParser($dom); $parser->parse(); $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(0); // The default result is an empty array $result = array(); // The calendarobject was requested directly. In this case we handle // this locally. if ($depth == 0 && $node instanceof ICalendarObject) { $requestedCalendarData = true; $requestedProperties = $parser->requestedProperties; 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; } $properties = $this->server->getPropertiesForPath($this->server->getRequestUri(), $requestedProperties, 0); // This array should have only 1 element, the first calendar // object. $properties = current($properties); // If there wasn't any calendar-data returned somehow, we ignore // this. if (isset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data'])) { $validator = new CalendarQueryValidator(); $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); if ($validator->validate($vObject, $parser->filters)) { // If the client didn't require the calendar-data property, // we won't give it back. if (!$requestedCalendarData) { unset($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); } else { if ($parser->expand) { $vObject->expand($parser->expand['start'], $parser->expand['end']); $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } } $result = array($properties); } } } // If we're dealing with a calendar, the calendar itself is responsible // for the calendar-query. if ($node instanceof ICalendar && ($depth = 1)) { $nodePaths = $node->calendarQuery($parser->filters); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); if ($parser->expand) { // We need to do some post-processing $vObject = VObject\Reader::read($properties[200]['{urn:ietf:params:xml:ns:caldav}calendar-data']); $vObject->expand($parser->expand['start'], $parser->expand['end']); $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); } $result[] = $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($result, $prefer['return-minimal'])); }