/** * Return XML for a single component from the DB * * @param array $properties The properties for this component * @param string $item The DB row data for this component * * @return string An XML document which is the response for the component */ function component_to_xml($properties, $item) { global $session, $c, $request, $reply; dbg_error_log("REPORT", "Building XML Response for item '%s'", $item->dav_name); $denied = array(); $unsupported = array(); $caldav_data = $item->caldav_data; $displayname = preg_replace('{^.*/}', '', $item->dav_name); $type = 'unknown'; $contenttype = 'text/plain'; switch ($item->caldav_type) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } if (isset($properties['calendar-data']) || isset($properties['displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal /** @todo We should examine the ORGANIZER and ATTENDEE fields in the event. If this person is there then they should see this */ if ($type == 'calendar' && $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { $ical = new iCalComponent($caldav_data); $resources = $ical->GetComponents('VTIMEZONE', false); $first = $resources[0]; // if the event is confidential we fake one that just says "Busy" $confidential = new iCalComponent(); $confidential->SetType($first->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($first->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($first->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($first->GetProperties('DURATION'), 'DURATION'); $confidential->SetProperties($first->GetProperties('DTEND'), 'DTEND'); $confidential->SetProperties($first->GetProperties('UID'), 'UID'); $ical->SetComponents(array($confidential), $confidential->GetType()); $caldav_data = $ical->Render(); $displayname = translate('Busy'); } } } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); foreach ($properties as $k => $v) { switch ($k) { case 'getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($k, $contentlength); break; case 'getlastmodified': $prop->NewElement($k, ISODateToHTTPDate($item->modified)); break; case 'calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'getcontenttype': $prop->NewElement($k, $contenttype); break; case 'current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'displayname': $prop->NewElement($k, $displayname); break; case 'resourcetype': $prop->NewElement($k); // Just an empty resourcetype for a non-collection. break; case 'getetag': $prop->NewElement($k, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($k, privileges($request->permissions)); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $k; break; default: dbg_error_log('REPORT', "Request for unsupported property '%s' of calendar item.", $v); $unsupported[] = $k; } } $status = new XMLElement("status", "HTTP/1.1 200 OK"); $propstat = new XMLElement("propstat", array($prop, $status)); $href = new XMLElement("href", $url); $elements = array($href, $propstat); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } $response = new XMLElement("response", $elements); return $response; }
require_once 'vendor/autoload.php'; $body = file_get_contents('testing/samples/meeting_request.txt'); $ical = new iCalComponent(); $ical->ParseFrom($body); $props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); if (count($props) == 1) { if (isset($props[0]->Parameters()["PARTSTAT"])) { printf("DOES THIS CAUSE ERROR? %s\n", $props[0]->Parameters()["PARTSTAT"]); } } // MODIFICATIONS // METHOD $ical->SetPValue("METHOD", "REPLY"); //ATTENDEE $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", "ACCEPTED"); printf("%s\n", $ical->Render()); $mail = new Mail_mimepart(); $headers = array("MIME-version" => "1.0", "From" => $mail->encodeHeader("from", "Pedro Picapiedra <*****@*****.**>", "UTF-8"), "To" => $mail->encodeHeader("to", "Pablo Marmol <*****@*****.**>", "UTF-8"), "Date" => gmdate("D, d M Y H:i:s", time()) . " GMT", "Subject" => $mail->encodeHeader("subject", "This is a subject", "UTF-8"), "Content-class" => "urn:content-classes:calendarmessage", "Content-transfer-encoding" => "8BIT"); $mail = new Mail_mimepart($ical->Render(), array("content_type" => "text/calendar; method=REPLY; charset=UTF-8", "headers" => $headers)); $message = ""; $encoded_mail = $mail->encode(); foreach ($encoded_mail["headers"] as $k => $v) { $message .= $k . ": " . $v . "\r\n"; } $message .= "\r\n" . $encoded_mail["body"] . "\r\n"; printf("%s\n", $message); define('LOGLEVEL', LOGLEVEL_DEBUG); define('LOGUSERLEVEL', LOGLEVEL_DEVICEID); $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { $tzid = $props[0]->Value();
/** * Modify a text/calendar part to transform it in a reply * * @access private * @param $part MIME part * @param $response Response numeric value * @param $condition_value string * @return string MIME text/calendar */ function reply_meeting_calendar($part, $response, $username) { $status_attendee = "ACCEPTED"; // 1 or default is ACCEPTED $status_event = "CONFIRMED"; switch ($response) { case 1: $status_attendee = "ACCEPTED"; $status_event = "CONFIRMED"; break; case 2: $status_attendee = $status_event = "TENTATIVE"; break; case 3: // We won't hit this case ever, because we won't create an event if we are rejecting it $status_attendee = "DECLINED"; $status_event = "CANCELLED"; break; } $ical = new iCalComponent(); $ical->ParseFrom($part->body); $ical->SetPValue("METHOD", "REPLY"); $ical->SetCPParameterValue("VEVENT", "STATUS", $status_event, null); // Update my information as attendee, but only mine $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", $status_attendee, sprintf("MAILTO:%s", $username)); $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "RSVP", null, sprintf("MAILTO:%s", $username)); return $ical->Render(); }
/** * Generate a iCAL VCalendar from ActiveSync object. * @param string $data * @param string $folderid * @param string $id */ private function _ParseASToVCalendar($data, $folderid, $id) { $ical = new iCalComponent(); $ical->SetType("VCALENDAR"); $ical->AddProperty("VERSION", "2.0"); $ical->AddProperty("PRODID", "-//php-push//NONSGML PHP-Push Calendar//EN"); $ical->AddProperty("CALSCALE", "GREGORIAN"); if ($folderid[0] == "C") { $vevent = $this->_ParseASEventToVEvent($data, $id); $vevent->AddProperty("UID", $id); $ical->AddComponent($vevent); if (isset($data->exceptions) && is_array($data->exceptions)) { foreach ($data->exceptions as $ex) { $exception = $this->_ParseASEventToVEvent($ex, $id); if ($data->alldayevent == 1) { $exception->AddProperty("RECURRENCE-ID", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE")); } else { $exception->AddProperty("RECURRENCE-ID", gmdate("Ymd\\THis\\Z", $ex->exceptionstarttime)); } $exception->AddProperty("UID", $id); $ical->AddComponent($exception); } } } if ($folderid[0] == "T") { $vtodo = $this->_ParseASTaskToVTodo($data, $id); $vtodo->AddProperty("UID", $id); $vtodo->AddProperty("DTSTAMP", gmdate("Ymd\\THis\\Z")); $ical->AddComponent($vtodo); } return $ical->Render(); }
/** * Modify a text/calendar part to transform it in a reply * * @access private * @param $part MIME part * @param $response Response numeric value * @return string MIME text/calendar */ private function replyMeetingCalendar($part, $response) { $response_text = "ACCEPTED"; // 1 or default is ACCEPTED switch ($response) { case 1: $response_text = "ACCEPTED"; break; case 2: $response_text = "TENTATIVE"; break; case 3: $response_text = "DECLINED"; break; } $ical = new iCalComponent(); $ical->ParseFrom($part->body); $ical->SetPValue("METHOD", "REPLY"); $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", $response_text); return $ical->Render(); }
if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $resource->user_no || $session->user_no == $resource->logged_user || $c->allow_get_email_visibility && $ic->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $allowed = true; } else { if ($resource->class != 'PRIVATE') { $allowed = true; // but we may well obfuscate it below if (!$dav_resource->HavePrivilegeTo('DAV::read') || $resource->class == 'CONFIDENTIAL' && !$request->HavePrivilegeTo('DAV::write-content')) { $ical = new iCalComponent($resource->caldav_data); $comps = $ical->GetComponents('VTIMEZONE', false); $confidential = obfuscated_event($comps[0]); $ical->SetComponents(array($confidential), $resource->caldav_type); $resource->caldav_data = $ical->Render(); } } } // else $resource->class == 'PRIVATE' and this person may not see it. if (!$allowed) { $request->DoResponse(403, translate("Forbidden")); } header('Etag: "' . $resource->dav_etag . '"'); header('Content-Length: ' . strlen($resource->caldav_data)); $contenttype = 'text/plain'; switch ($resource->caldav_type) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $contenttype = 'text/calendar';
function export_iCalendar(DAVResource $dav_resource) { global $session, $c, $request; if (!$dav_resource->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections)) { /** RFC2616 says we must send an Allow header if we send a 405 */ header("Allow: PROPFIND,PROPPATCH,OPTIONS,MKCOL,REPORT,DELETE"); $request->DoResponse(405, translate("GET requests on collections are only supported for calendars.")); } /** * The CalDAV specification does not define GET on a collection, but typically this is * used as a .ics download for the whole collection, which is what we do also. */ if (isset($c->get_includes_subcollections) && $c->get_includes_subcollections) { $where = 'caldav_data.collection_id IN '; $where .= '(SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match '; $where .= 'UNION '; $where .= 'SELECT collection_id FROM collection WHERE collection.dav_name ~ :path_match) '; $params = array(':path_match' => '^' . $dav_resource->dav_name()); $distinct = 'DISTINCT ON (calendar_item.uid) '; } else { $where = 'caldav_data.collection_id = :collection_id '; $params = array(':collection_id' => $dav_resource->resource_id()); $distinct = ''; } $sql = 'SELECT ' . $distinct . ' caldav_data, class, caldav_type, calendar_item.user_no, logged_user '; $sql .= 'FROM collection INNER JOIN caldav_data USING(collection_id) '; $sql .= 'INNER JOIN calendar_item USING ( dav_id ) WHERE ' . $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= ' ORDER BY calendar_item.uid, calendar_item.dav_id'; } $qry = new AwlQuery($sql, $params); if (!$qry->Exec("GET", __LINE__, __FILE__)) { $request->DoResponse(500, translate("Database Error")); } /** * Here we are constructing a whole calendar response for this collection, including * the timezones that are referred to by the events we have selected. */ $vcal = new iCalComponent(); $vcal->VCalendar(); $displayname = $dav_resource->GetProperty('displayname'); if (isset($displayname)) { $vcal->AddProperty("X-WR-CALNAME", $displayname); } if (!empty($c->auto_refresh_duration)) { $vcal->AddProperty("X-APPLE-AUTO-REFRESH-INTERVAL", $c->auto_refresh_duration); $vcal->AddProperty("AUTO-REFRESH", $c->auto_refresh_duration); $vcal->AddProperty("X-PUBLISHED-TTL", $c->auto_refresh_duration); } $need_zones = array(); $timezones = array(); while ($event = $qry->Fetch()) { $ical = new iCalComponent($event->caldav_data); /** Save the timezone component(s) into a minimal set for inclusion later */ $event_zones = $ical->GetComponents('VTIMEZONE', true); foreach ($event_zones as $k => $tz) { $tzid = $tz->GetPValue('TZID'); if (!isset($tzid)) { continue; } if ($tzid != '' && !isset($timezones[$tzid])) { $timezones[$tzid] = $tz; } } /** Work out which ones are actually used here */ $comps = $ical->GetComponents('VTIMEZONE', false); foreach ($comps as $k => $comp) { $tzid = $comp->GetPParamValue('DTSTART', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DUE', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DTEND', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $event->user_no || $session->user_no == $event->logged_user || isset($session->email) && $c->allow_get_email_visibility && $comp->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $vcal->AddComponent($comp); continue; } /** No visibility even of the existence of these events if they aren't admin/owner/attendee */ if ($event->class == 'PRIVATE') { continue; } if (!$dav_resource->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL') { $vcal->AddComponent(obfuscated_event($comp)); } elseif (isset($c->hide_alarm) && $c->hide_alarm) { // Otherwise we hide the alarms (if configured to) $comp->ClearComponents('VALARM'); $vcal->AddComponent($comp); } else { $vcal->AddComponent($comp); } } } /** Put the timezones on there that we need */ foreach ($need_zones as $tzid => $v) { if (isset($timezones[$tzid])) { $vcal->AddComponent($timezones[$tzid]); } } return $vcal->Render(); }
<?php /** * Handle the FREE-BUSY-QUERY variant of REPORT */ include_once "freebusy-functions.php"; $fbq_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:free-busy-query'); $fbq_start = $fbq_content[0]->GetAttribute('start'); $fbq_end = $fbq_content[0]->GetAttribute('end'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a time-range filter'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); /** We use the same code for the REPORT, the POST and the freebusy GET... */ $freebusy = get_freebusy('^' . $request->path . $request->DepthRegexTail(), $range_start, $range_end); $result = new iCalComponent(); $result->VCalendar(); $result->AddComponent($freebusy); $request->DoResponse(200, $result->Render(), 'text/calendar'); // Won't return from that
function handle_freebusy_request($ic) { global $c, $session, $request; $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C")); $responses = array(); $fbq_start = $ic->GetPValue('DTSTART'); $fbq_end = $ic->GetPValue('DTEND'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); $attendees = $ic->GetProperties('ATTENDEE'); if (preg_match('# iCal/\\d#', $_SERVER['HTTP_USER_AGENT'])) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Responding with free/busy for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Calculating free/busy for %s", $attendee_email); /** @TODO: Refactor this so we only do one query here and loop through the results */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email); $qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); if (!$qry->Exec('POST', __LINE__, __FILE__)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() > 1) { // Unlikely, but if we get more than one result we'll do an exact match instead. if (!$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() == 0) { /** Sigh... Go back to the original case-insensitive match */ $qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); } } $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); if ($qry->rows() == 0) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } if (!($attendee_usr = $qry->Fetch())) { $request->DoResponse(501, 'Database error'); } if ((privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0) { $reply->CalDAVElement($response, "request-status", "3.8;No authority"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } $attendee_path_match = '^/' . $attendee_usr->username . '/'; $fb = get_freebusy($attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p)); $fb->AddProperty('UID', $ic->GetPValue('UID')); $fb->SetProperties($ic->GetProperties('ORGANIZER'), 'ORGANIZER'); $fb->AddProperty($attendee); $vcal = new iCalComponent(); $vcal->VCalendar(array('METHOD' => 'REPLY')); $vcal->AddComponent($fb); $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); $reply->CalDAVElement($response, "request-status", "2.0;Success"); // Cargo-cult setting $reply->CalDAVElement($response, "calendar-data", $vcal->Render()); $responses[] = $response; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav'); $request->XMLResponse(200, $response); }
/** * Return general server-related properties for this URL */ function ResourceProperty($tag, $prop, &$reply, &$denied) { global $c, $session, $request; // dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name ); if ($reply === null) { $reply = $GLOBALS['reply']; } switch ($tag) { case 'DAV::allprop': $property_list = $this->DAV_AllProperties(); $discarded = array(); foreach ($property_list as $k => $v) { $this->ResourceProperty($v, $prop, $reply, $discarded); } break; case 'DAV::href': $prop->NewElement('href', ConstructURL($this->dav_name)); break; case 'DAV::resource-id': if ($this->resource_id > 0) { $reply->DAVElement($prop, 'resource-id', $reply->href(ConstructURL('/.resources/' . $this->resource_id))); } else { return false; } break; case 'DAV::parent-set': $sql = <<<EOQRY SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id) WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from EOQRY; $qry = new AwlQuery($sql, array(':bound_from' => $this->bound_from())); $parents = array(); if ($qry->Exec('DAVResource', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { $parents[$row->parent_container] = true; } } $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->bound_from())] = true; $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->dav_name())] = true; $parent_set = $reply->DAVElement($prop, 'parent-set'); foreach ($parents as $parent => $v) { if (preg_match('{^(.*)?/([^/]+)/?$}', $parent, $matches)) { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', ConstructURL($matches[1])), new XMLElement('segment', $matches[2]))); } else { if ($parent == '/') { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', '/'), new XMLElement('segment', ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))); } } } break; case 'DAV::getcontenttype': if (!isset($this->contenttype) && !$this->_is_collection && !isset($this->resource)) { $this->FetchResource(); } $prop->NewElement('getcontenttype', $this->contenttype); break; case 'DAV::resourcetype': $resourcetypes = $prop->NewElement('resourcetype'); if ($this->_is_collection) { $type_list = $this->GetProperty('resourcetype'); if (!is_array($type_list)) { return true; } // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) ); foreach ($type_list as $k => $v) { if ($v == '') { continue; } $reply->NSElement($resourcetypes, $v); } if ($this->_is_binding) { $reply->NSElement($resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding'); } } break; case 'DAV::getlastmodified': /** getlastmodified is HTTP Date format: i.e. the Last-Modified header in response to a GET */ $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified'))); break; case 'DAV::creationdate': /** creationdate is ISO8601 format */ $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true)); break; case 'DAV::getcontentlength': if ($this->_is_collection) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } if (isset($this->resource)) { $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data)); } break; case 'DAV::getcontentlanguage': $locale = isset($c->current_locale) ? $c->current_locale : ''; if (isset($this->locale) && $this->locale != '') { $locale = $this->locale; } $reply->NSElement($prop, $tag, $locale); break; case 'DAV::acl-restrictions': $reply->NSElement($prop, $tag, array(new XMLElement('grant-only'), new XMLElement('no-invert'))); break; case 'DAV::inherited-acl-set': $inherited_acls = array(); if (!$this->_is_collection) { $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name)); } $reply->NSElement($prop, $tag, $inherited_acls); break; case 'DAV::owner': // The principal-URL of the owner if ($this->IsExternal()) { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL($this->collection->bound_from))); } else { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL(DeconstructURL($this->principal_url())))); } break; case 'DAV::add-member': if (!$this->_is_collection) { return false; } if (isset($c->post_add_member) && $c->post_add_member === false) { return false; } $reply->DAVElement($prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())) . '?add-member')); break; // Empty tag responses. // Empty tag responses. case 'DAV::group': case 'DAV::alternate-URI-set': $reply->NSElement($prop, $tag); break; case 'DAV::getetag': if ($this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'http://calendarserver.org/ns/:getctag': if (!$this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'DAV::sync-token': if (!$this->_is_collection) { return false; } $sync_token = $this->sync_token(); if (empty($sync_token)) { return false; } $reply->NSElement($prop, $tag, $sync_token); break; case 'http://calendarserver.org/ns/:calendar-proxy-read-for': $proxy_type = 'read'; case 'http://calendarserver.org/ns/:calendar-proxy-write-for': if (isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy) { return false; } if (!isset($proxy_type)) { $proxy_type = 'write'; } // ProxyFor is an already constructed URL $reply->CalendarserverElement($prop, 'calendar-proxy-' . $proxy_type . '-for', $reply->href($this->principal->ProxyFor($proxy_type))); break; case 'DAV::current-user-privilege-set': if ($this->HavePrivilegeTo('DAV::read-current-user-privilege-set')) { $reply->NSElement($prop, $tag, $this->BuildPrivileges()); } else { $denied[] = $tag; } break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': if (!$this->IsCalendar() && !$this->IsSchedulingCollection()) { return false; } $reply->NSElement($prop, $tag, 'text/calendar'); break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set': if (!$this->_is_collection) { return false; } if ($this->IsCalendar()) { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $set_of_components = $this->dead_properties[$tag]; foreach ($set_of_components as $k => $v) { if (preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches)) { $set_of_components[$k] = $matches[1]; } else { unset($set_of_components[$k]); } } } else { if (isset($c->default_calendar_components) && is_array($c->default_calendar_components)) { $set_of_components = $c->default_calendar_components; } else { $set_of_components = array('VEVENT', 'VTODO', 'VJOURNAL'); } } } else { if ($this->IsSchedulingCollection()) { $set_of_components = array('VEVENT', 'VTODO', 'VFREEBUSY'); } else { return false; } } $components = array(); foreach ($set_of_components as $v) { $components[] = $reply->NewXMLElement('comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav'); } $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components); break; case 'DAV::supported-method-set': $prop->NewElement('supported-method-set', $this->BuildSupportedMethods()); break; case 'DAV::supported-report-set': $prop->NewElement('supported-report-set', $this->BuildSupportedReports($reply)); break; case 'DAV::supportedlock': $prop->NewElement('supportedlock', new XMLElement('lockentry', array(new XMLElement('lockscope', new XMLElement('exclusive')), new XMLElement('locktype', new XMLElement('write'))))); break; case 'DAV::supported-privilege-set': $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply)); break; case 'DAV::principal-collection-set': $prop->NewElement('principal-collection-set', $reply->href(ConstructURL('/'))); break; case 'DAV::current-user-principal': $prop->NewElement('current-user-principal', $reply->href(ConstructURL(DeconstructURL($request->principal->url())))); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $reply->Tag($tag); break; case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': if (!$this->_is_collection) { return false; } if (!isset($this->collection->vtimezone) || $this->collection->vtimezone == '') { return false; } $cal = new iCalComponent(); $cal->VCalendar(); $cal->AddComponent(new iCalComponent($this->collection->vtimezone)); $reply->NSElement($prop, $tag, $cal->Render()); break; case 'urn:ietf:params:xml:ns:carddav:address-data': case 'urn:ietf:params:xml:ns:caldav:calendar-data': if ($this->_is_collection) { return false; } if (!isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } $reply->NSElement($prop, $tag, $this->resource->caldav_data); break; case 'urn:ietf:params:xml:ns:carddav:max-resource-size': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $reply->NSElement($prop, $tag, 65500); break; case 'urn:ietf:params:xml:ns:carddav:supported-address-data': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $address_data = $reply->NewXMLElement('address-data', false, array('content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav'); $reply->NSElement($prop, $tag, $address_data); break; case 'DAV::acl': if ($this->HavePrivilegeTo('DAV::read-acl')) { $reply->NSElement($prop, $tag, $this->GetACL($reply)); } else { $denied[] = $tag; } break; case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery': case 'DAV::ticketdiscovery': $reply->NSElement($prop, 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply)); break; default: $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag)); if (isset($property_value)) { $reply->NSElement($prop, $tag, $property_value); } else { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $reply->NSElement($prop, $tag, $this->dead_properties[$tag]); } else { // dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name ); return false; } } } return true; }
/** * Deliver scheduling replies to organizer and other attendees * @param iCalComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply($ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ic->GetProperties('ORGANIZER'); // for now we treat events with out organizers as an error if (count($organizer) < 1) { return false; } $attendees = array_merge($organizer, $ic->GetProperties('ATTENDEE')); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); if (count($wr_attendees) > 0) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Delivering to %s", $attendee_email); $attendee_principal = new CalDAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = preg_replace('/^.*caldav.php/', '', $attendee_principal->schedule_inbox_url); $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("POST", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new iCalComponent(); $ncal->VCalendar(); $ncal->AddProperty('METHOD', 'REPLY'); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource($attendee_principal->user_no, $deliver_path . $etag . '.ics', $content, $ar->GetProperty('collection_id'), $request->user_no, md5($content), $ncal, $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }