From 460f6e57bd9bca068f096c78f351cee297786590 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 8 Nov 2011 21:11:36 +0000 Subject: [PATCH] - fixed calendar_ical::importVCal() to (again) return 0 on etag mis-match, as documented and return 412 Precondition Failed in that case - Disabling Schedule-Tag handling completly, as current implementation seems to create too much trouble with iCal under OS X or iOS, which send If-Schedule-Tag-Match condition event for major updates, not just changing own status or alarms --- calendar/inc/class.calendar_groupdav.inc.php | 85 +++++++++++++++----- calendar/inc/class.calendar_ical.inc.php | 8 +- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 91dc885fa6..07264f4012 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -56,6 +56,20 @@ class calendar_groupdav extends groupdav_handler */ var $client_shared_uid_exceptions = true; + /** + * Enable or disable Schedule-Tag handling: + * - return Schedule-Tag header in PUT response + * - update only status and alarms of calendar owner, if If-Schedule-Tag-Match header in PUT + * + * Disabling Schedule-Tag for iCal, as current implementation seems to create too much trouble :-( + * - iCal on OS X always uses If-Schedule-Tag-Match, even if other stuff in event is changed (eg. title) + * - iCal on iOS allways uses both If-Schedule-Tag-Match and If-Match (ETag) + * - Lighting 1.0 is NOT using it + * + * @var boolean + */ + var $use_schedule_tag = false; + /** * Are we using id, uid or caldav_name for the path/url * @@ -243,9 +257,12 @@ class calendar_groupdav extends groupdav_handler $props = array( 'getcontenttype' => $this->agent != 'kde' ? 'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar', 'getetag' => '"'.$etag.'"', - 'schedule-tag' => HTTP_WebDAV_Server::mkprop(groupdav::CALDAV, 'schedule-tag', '"'.$schedule_tag.'"'), 'getlastmodified' => max($event['modified'], $event['max_user_modified']), ); + if ($this->use_schedule_tag) + { + $props['schedule-tag'] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV, 'schedule-tag', '"'.$schedule_tag.'"'); + } //error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data"); if ($calendar_data) { @@ -408,8 +425,10 @@ class calendar_groupdav extends groupdav_handler $options['mimetype'] = 'text/calendar; charset=utf-8'; header('Content-Encoding: identity'); header('ETag: "'.$this->get_etag($event, $schedule_tag).'"'); - header('Schedule-Tag: "'.$schedule_tag.'"'); - + if ($this->use_schedule_tag) + { + header('Schedule-Tag: "'.$schedule_tag.'"'); + } return true; } @@ -579,8 +598,8 @@ class calendar_groupdav extends groupdav_handler { $eventId = $oldEvent['id']; - // client specified a CalDAV Scheduling schedule-tag precondition - if (isset($_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH'])) + //client specified a CalDAV Scheduling schedule-tag precondition + if ($this->use_schedule_tag && isset($_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH'])) { $schedule_tag_match = $_SERVER['HTTP_IF_SCHEDULE_TAG_MATCH']; if ($schedule_tag_match[0] == '"') $schedule_tag_match = substr($schedule_tag_match, 1, -1); @@ -588,28 +607,48 @@ class calendar_groupdav extends groupdav_handler if ($schedule_tag_match !== $schedule_tag) { + if ($this->debug) error_log(__METHOD__."(,,$user) schedule_tag missmatch: given '$schedule_tag_match' != '$schedule_tag'"); return '412 Precondition Failed'; } // update only participant status and alarms of current user - if (($events = $handler->icaltoegw($vCalendar))) + // fix for iCal on OS X, which uses only a schedule-tag (no etag), if event has no participants (only calendar owner) + // --> do regular calendar update as with matching etag (otherwise no updates possible) + if (!(count($oldEvent['participants']) == 1 && isset($oldEvent['participants'][$user])) && + ($events = $handler->icaltoegw($vCalendar))) { // todo check behavior for recuring events foreach($events as $event) { - if ($this->debug) error_log(__METHOD__."(, $id, $user, '$prefix') eventId=$eventId, user=$user, event=".array2string($event)); - if ($event['participants'][$user] != $oldEvent['participants'][$user] && - !$this->bo->set_status($eventId, $user, $event['participants'][$user], $event['recurrence'])) + if ($this->debug) error_log(__METHOD__."(, $id, $user, '$prefix') eventId=$eventId, user=$user, old-status='{$oldEvent['participants'][$user]}', new-status='{$event['participants'][$user]}', event=".array2string($event)); + if (isset($event['participants']) && $event['participants'][$user] !== $oldEvent['participants'][$user]) { - if ($this->debug) error_log(__METHOD__."(,,$user) failed to set_status($eventId, $user, '{$event['participants'][$user]}')"); - return '403 Forbidden'; + if (!$this->bo->set_status($eventId, $user, $event['participants'][$user], $event['recurrence'])) + { + if ($this->debug) error_log(__METHOD__."(,,$user) failed to set_status($eventId, $user, '{$event['participants'][$user]}')"); + return '403 Forbidden'; + } + else + { + if ($this->debug) error_log(__METHOD__."() set_status($eventId, $user, ".array2string($event['participants'][$user])." , $event[recurrence])"); + } + } + // import alarms, if given and changed + if ((array)$event['alarm'] !== (array)$oldEvent['alarm']) + { + $this->sync_alarms($eventId, (array)$event['alarm'], (array)$oldEvent['alarm'], $user, $event['start']); + } + elseif (!isset($event['participants']) || $event['participants'][$user] === $oldEvent['participants'][$user]) + { + error_log(__METHOD__."(,,$user) schedule-tag given, but NO change for current user event=".array2string($event).', old-event='.array2string($oldEvent)); + return '412 Precondition Failed'; } - if ($this->debug) error_log(__METHOD__."() set_status($eventId, $user, ".array2string($event['participants'][$user])." , $event[recurrence])"); - // import alarms - $this->sync_alarms($eventId, (array)$event['alarm'], (array)$oldEvent['alarm'], $user, $event['start']); } + // we should not return an etag here, as we never store the PUT ical byte-by-byte + //header('ETag: "'.$etag.'"'); header('Schedule-Tag: "'.$schedule_tag.'"'); return '204 No Content'; } + if ($this->debug && !isset($events)) error_log(__METHOD__."(,,$user) only schedule-tag given for event without participants (only calendar owner) --> handle as regular PUT"); } if ($return_no_access) { @@ -641,24 +680,30 @@ class calendar_groupdav extends groupdav_handler if (!($cal_id = $handler->importVCal($vCalendar, $eventId, self::etag2value($this->http_if_match), false, 0, $this->groupdav->current_user_principal, $user, $charset, $id))) { - if ($this->debug) error_log(__METHOD__."(,$id) eventId=$eventId: importVCal('$options[content]') returned false"); + if ($this->debug) error_log(__METHOD__."(,$id) eventId=$eventId: importVCal('$options[content]') returned ".array2string($cal_id)); if ($eventId && $cal_id === false) { // ignore import failures $cal_id = $eventId; $retval = true; } + elseif ($cal_id === 0) // etag failure + { + return '412 Precondition Failed'; + } else { return '403 Forbidden'; } } - $etag = $this->get_etag($cal_id, $schedule_tag); - // we should not return an etag here, as we never store the PUT ical byte-by-byte - //header('ETag: "'.$etag.'"'); - header('Schedule-Tag: "'.$schedule_tag.'"'); - + if ($this->use_schedule_tag) + { + $etag = $this->get_etag($cal_id, $schedule_tag); + // we should not return an etag here, as we never store the PUT ical byte-by-byte + //header('ETag: "'.$etag.'"'); + header('Schedule-Tag: "'.$schedule_tag.'"'); + } // send GroupDAV Location header only if we dont use caldav_name as path-attribute if ($retval !== true && self::$path_attr != 'caldav_name') { diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index f450ef2e2c..c2e24490a8 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -484,6 +484,9 @@ class calendar_ical extends calendar_boupdate case 'r': $participantURL = 'urn:uuid:'.common::generate_uid('resources', substr($uid, 1)); $cutype = groupdav_principals::resource_is_location(substr($uid, 1)) ? 'ROOM' : 'RESOURCE'; + // unset resource email (email of responsible user) as iCal at least has problems, + // if resonpsible is also pariticipant or organizer + unset($info['email']); break; case 'u': // account case 'c': // contact @@ -1325,7 +1328,7 @@ class calendar_ical extends calendar_boupdate // as we no longer export event owner/ORGANIZER as only participant, we have to re-add owner as participant // to not loose him, as EGroupware knows events without owner/ORGANIZER as participant - if (isset($event_info['stored_event']['participants'][$event['owner']]) && !isset($event['participant'][$event['owner']])) + if (isset($event_info['stored_event']['participants'][$event['owner']]) && !isset($event['participants'][$event['owner']])) { $event['participant'][$event['owner']] = $event_info['stored_event']['participants'][$event['owner']]; } @@ -1726,7 +1729,8 @@ class calendar_ical extends calendar_boupdate } } date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']); - return $return_id; + + return $updated_id === 0 ? 0 : $return_id; } /**