From 21dfe8ac02aeaf88d6e498ae960430fb499b4e63 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 10 Feb 2012 11:28:56 +0000 Subject: [PATCH] implement RFC 5995 add member to collection via POST, but not yet set DAV:add-member due to problems with OS X clients --- .../inc/class.addressbook_groupdav.inc.php | 11 ++------ calendar/inc/class.calendar_groupdav.inc.php | 11 +++----- infolog/inc/class.infolog_groupdav.inc.php | 10 ++----- phpgwapi/inc/class.groupdav.inc.php | 28 +++++++++++++++++-- phpgwapi/inc/class.groupdav_handler.inc.php | 23 +++++++++++++++ 5 files changed, 57 insertions(+), 26 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index e21adb37f3..32714f93db 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -506,16 +506,9 @@ class addressbook_groupdav extends groupdav_handler $contact = $this->read($save_ok); } - // we should not return an etag here, as we never store the PUT vcard byte-by-byte - //header('ETag: "'.$this->get_etag($contact).'"'); + // send evtl. necessary respose headers: Location, etag, ... + $this->put_response_headers($contact, $options['path'], $retval, self::$path_attr != 'id'); - // send GroupDAV Location header only if we dont use carddav_name as path-attribute - if ($retval !== true && self::$path_attr == 'id') - { - $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); - header($h='Location: '.$this->base_uri.$path.self::get_path($contact)); - if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): $retval"); - } if ($this->debug > 1) error_log(__METHOD__."(,'$id', $user, '$prefix') returning ".array2string($retval)); return $retval; } diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index da77c8ab2a..3e0492eaf1 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -703,13 +703,10 @@ class calendar_groupdav extends groupdav_handler //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') - { - $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); - if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval"); - header('Location: '.$this->base_uri.$path.$this->get_path($cal_id)); - } + + // send evtl. necessary respose headers: Location, etag, ... + $this->put_response_headers($cal_id, $options['path'], $retval, self::$path_attr == 'caldav_name'); + return $retval; } diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index 5f95046add..79b49788f8 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -470,15 +470,9 @@ class infolog_groupdav extends groupdav_handler $retval = '201 Created'; } - // we should not return an etag here, as we never store the PUT ical byte-by-byte - //header('ETag: "'.$this->get_etag($infoId).'"'); + // send evtl. necessary respose headers: Location, etag, ... + $this->put_response_headers($infoId, $options['path'], $retval, self::$path_attr == 'caldav_name'); - // send GroupDAV Location header only if we dont use caldav_name as path-attribute - if ($retval !== true && self::$path_attr != 'caldav_name') - { - $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); - header('Location: '.$this->base_uri.$path.self::get_path($infoId)); - } return $retval; } diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index 417ee0e98c..979be2adba 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -238,6 +238,14 @@ class groupdav extends HTTP_WebDAV_Server $this->dav_powered_by); parent::HTTP_WebDAV_Server(); + // hack to allow to use query parameters in WebDAV, which HTTP_WebDAV_Server interprets as part of the path + list($this->_SERVER['REQUEST_URI']) = explode('?',$this->_SERVER['REQUEST_URI']); + /*if (substr($this->_SERVER['REQUEST_URI'],-13) == '/;add-member/') + { + $_GET['add-member'] = ''; + $this->_SERVER['REQUEST_URI'] = substr($this->_SERVER['REQUEST_URI'],0,-12); + }*/ + //error_log($_SERVER['REQUEST_URI']." --> ".$this->_SERVER['REQUEST_URI']); $this->egw_charset = translation::charset(); if (strpos($this->base_uri, 'http') === 0) @@ -763,6 +771,13 @@ class groupdav extends HTTP_WebDAV_Server $props['displayname'] = translation::convert(lang($app).' '.$this->account_name($user),$this->egw_charset,'utf-8'); } + // rfc 5995 (Use POST to add members to WebDAV collections): we use collection path with add-member query param + /* leaving it switched off, until further testing, because OS X iCal seem to ignore it and OS X Addressbook uses POST to full URL without ?add-member + if ($app && !in_array($app,array('inbox','outbox','principals'))) // not on inbox, outbox or principals + { + $props['add-member'][] = self::mkprop('href',$this->base_uri.$path.'?add-member'); + }*/ + // add props modifyable via proppatch from client, eg. calendar-color, see self::$proppatch_props foreach((array)$GLOBALS['egw_info']['user']['preferences'][$app] as $name => $value) { @@ -1093,6 +1108,13 @@ class groupdav extends HTTP_WebDAV_Server */ function POST(&$options) { + // for some reason OS X Addressbook (CFNetwork user-agent) uses now (DAV:add-member given with collection URL+"?add-member") + // POST to the collection URL plus a UID like name component (like for regular PUT) to create new entrys + if (isset($_GET['add-member']) || groupdav_handler::get_agent() == 'cfnetwork') + { + $_GET['add-member'] = ''; // otherwise we give no Location header + return $this->PUT($options); + } // read the content in a string, if a stream is given if (isset($options['stream'])) { @@ -1446,7 +1468,9 @@ class groupdav extends HTTP_WebDAV_Server $id = array_pop($parts); - $ok = $id && ($user || $user === 0) && in_array($app,array('addressbook','calendar','infolog','principals')); + $ok = ($id || isset($_GET['add-member']) && $_SERVER['REQUEST_METHOD'] == 'POST') && + ($user || $user === 0) && in_array($app,array('addressbook','calendar','infolog','principals')); + if ($this->debug) { error_log(__METHOD__."('$path') returning " . ($ok ? 'true' : 'false') . ": id='$id', app='$app', user='$user', user_prefix='$user_prefix'"); @@ -1525,9 +1549,9 @@ class groupdav extends HTTP_WebDAV_Server ': '.($name=='AUTHORIZATION'?'Basic ***************':$value).$msg_nl,$msg_type,$msg_file); } } + error_log(''.$msg_nl,$msg_type,$msg_file); if ($this->request) { - error_log(''.$msg_nl,$msg_type,$msg_file); foreach(explode("\n",$this->request) as $line) error_log($line.$msg_nl,$msg_type,$msg_file); } error_log('HTTP/1.1 '.$this->_http_status.$msg_nl,$msg_type,$msg_file); diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index 77ffb072ab..db8e91a516 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -457,6 +457,29 @@ abstract class groupdav_handler return $entry[self::$path_attr].self::$path_extension; } + /** + * Send response-headers for a PUT (or POST with add-member query parameter) + * + * @param int|array $entry id or array of new created entry + * @param string $path + * @param int|string $retval + * @param boolean $path_attr_is_name=true true: path_attr is ca(l|rd)dav_name, false: id (GroupDAV needs Location header) + */ + function put_response_headers($entry, $path, $retval, $path_attr_is_name=true) + { + // we should not return an etag here, as EGroupware never stores ical/vcard byte-by-byte + //header('ETag: "'.$this->get_etag($entry).'"'); + + // send Location header only if we dont use caldav_name as path-attribute or + if ($retval !== true && (!$path_attr_is_name || + // POST with add-member query parameter + $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_GET['add-member']))) + { + $path = preg_replace('|(.*)/[^/]*|', '\1/', $path); + header('Location: '.$this->base_uri.$path.$this->get_path($entry)); + } + } + /** * Return calendars/addressbooks shared from other users with the current one *