* CardDAV: store name part of URL from client PUT request, to fully comply with CardDAV spec

This commit is contained in:
Ralf Becker 2011-04-05 20:39:13 +00:00
parent ca7dfd3370
commit 87ee0f0088
8 changed files with 106 additions and 57 deletions

View File

@ -7,7 +7,7 @@
* @package addressbook * @package addressbook
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2007-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @version $Id$
*/ */
@ -77,9 +77,11 @@ class addressbook_groupdav extends groupdav_handler
var $charset = 'utf-8'; var $charset = 'utf-8';
/** /**
* What attribute is used to construct the path, default id, can be uid too * Which attribute to use to contruct name part of url/path
*
* @var string
*/ */
const PATH_ATTRIBUTE = 'id'; static $path_attr = 'id';
/** /**
* Constructor * Constructor
@ -94,6 +96,18 @@ class addressbook_groupdav extends groupdav_handler
parent::__construct($app,$debug,$base_uri,$principalURL); parent::__construct($app,$debug,$base_uri,$principalURL);
$this->bo = new addressbook_bo(); $this->bo = new addressbook_bo();
// since 1.9.007 we allow clients to specify the URL when creating a new contact, as specified by CardDAV
if ($this->bo->account_repository != 'ldap' &&
version_compare($GLOBALS['egw_info']['apps']['phpgwapi']['version'], '1.9.007', '>='))
{
self::$path_attr = 'carddav_name';
groupdav_handler::$path_extension = '';
}
else
{
groupdav_handler::$path_extension = '.vcf';
}
} }
/** /**
@ -104,7 +118,7 @@ class addressbook_groupdav extends groupdav_handler
*/ */
static function get_path($contact) static function get_path($contact)
{ {
return $contact[self::PATH_ATTRIBUTE].'.vcf'; return $contact[self::$path_attr].groupdav_handler::$path_extension;
} }
/** /**
@ -169,7 +183,9 @@ class addressbook_groupdav extends groupdav_handler
unset($filter['address_data']); unset($filter['address_data']);
$files = array(); $files = array();
// we query etag and modified, as LDAP does not have the strong sql etag // we query etag and modified, as LDAP does not have the strong sql etag
if (($contacts =& $this->bo->search(array(),array('id','uid','etag','modified'),'egw_addressbook.contact_id','','',False,'AND',$start,$filter))) $cols = array('id','uid','etag','modified');
if (!in_array(self::$path_attr,$cols)) $cols[] = self::$path_attr;
if (($contacts =& $this->bo->search(array(),$cols,'egw_addressbook.contact_id','','',False,'AND',$start,$filter)))
{ {
foreach($contacts as &$contact) foreach($contacts as &$contact)
{ {
@ -258,15 +274,18 @@ class addressbook_groupdav extends groupdav_handler
if ($option['name'] == 'href') if ($option['name'] == 'href')
{ {
$parts = explode('/',$option['data']); $parts = explode('/',$option['data']);
if (($id = array_pop($parts))) $ids[] = basename($id,'.vcf'); if (($id = array_pop($parts)))
{
$ids[] = groupdav_handler::$path_extension ? basename($id,groupdav_handler::$path_extension) : $id;
} }
} }
if ($ids) $filters[self::PATH_ATTRIBUTE] = $ids; }
if ($ids) $filters[self::$path_attr] = $ids;
if ($this->debug) error_log(__METHOD__."($path,,,$user) addressbook-multiget: ids=".implode(',',$ids)); if ($this->debug) error_log(__METHOD__."($path,,,$user) addressbook-multiget: ids=".implode(',',$ids));
} }
elseif ($id) elseif ($id)
{ {
$filters[self::PATH_ATTRIBUTE] = basename($id,'.vcf'); $filters[self::$path_attr] = groupdav_handler::$path_extension ? basename($id,groupdav_handler::$path_extension) : $id;
} }
return true; return true;
} }
@ -337,37 +356,19 @@ class addressbook_groupdav extends groupdav_handler
} }
} }
if (is_array($oldContact)) $contact = $handler->vcardtoegw($vCard, $charset);
if (is_array($oldContact) || ($oldContact = $this->bo->read(array('contact_uid' => $contact['uid']))))
{ {
$contactId = $oldContact['id']; $contactId = $oldContact['id'];
$retval = true; $retval = true;
} }
else else
{
// new entry?
if (($foundContacts = $handler->search($vCard, null, false, $charset)))
{
if (($contactId = array_shift($foundContacts)) &&
($oldContact = $this->bo->read($contactId)))
{
$retval = '301 Moved Permanently';
}
else
{
// to be safe
$contactId = -1;
$retval = '201 Created';
}
}
else
{ {
// new entry // new entry
$contactId = -1; $contactId = -1;
$retval = '201 Created'; $retval = '201 Created';
} }
}
$contact = $handler->vcardtoegw($vCard, $charset);
if (is_array($contact['cat_id'])) if (is_array($contact['cat_id']))
{ {
@ -384,6 +385,11 @@ class addressbook_groupdav extends groupdav_handler
$contact['uid'] = $oldContact['uid']; $contact['uid'] = $oldContact['uid'];
$contact['owner'] = $oldContact['owner']; $contact['owner'] = $oldContact['owner'];
$contact['private'] = $oldContact['private']; $contact['private'] = $oldContact['private'];
$contact['carddav_name'] = $oldContact['carddav_name'];
}
else
{
$contact['carddav_name'] = $id;
} }
// only set owner, if user is explicitly specified in URL (check via prefix, NOT for /addressbook/ !) // only set owner, if user is explicitly specified in URL (check via prefix, NOT for /addressbook/ !)
if ($prefix) if ($prefix)
@ -415,14 +421,15 @@ class addressbook_groupdav extends groupdav_handler
} }
header('ETag: '.$this->get_etag($contact)); header('ETag: '.$this->get_etag($contact));
if ($retval !== true)
// 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']); $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
header($h='Location: '.$this->base_uri.$path.self::get_path($contact)); 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) error_log(__METHOD__."($method,,$id) header('$h'): $retval");
return $retval;
} }
return true; return $retval;
} }
/** /**
@ -547,18 +554,18 @@ class addressbook_groupdav extends groupdav_handler
{ {
return '412 Precondition Failed'; return '412 Precondition Failed';
} }
//return $ok; return true;
} }
/** /**
* Read a contact * Read a contact
* *
* @param string/id $id * @param string|id $id
* @return array/boolean array with entry, false if no read rights, null if $id does not exist * @return array/boolean array with entry, false if no read rights, null if $id does not exist
*/ */
function read($id) function read($id)
{ {
return $this->bo->read(self::PATH_ATTRIBUTE == 'id' ? $id : array(self::PATH_ATTRIBUTE => $id)); return $this->bo->read(array(self::$path_attr => $id));
} }
/** /**

View File

@ -632,12 +632,22 @@ class addressbook_sql extends so_sql_cf
} }
} }
$update = array();
// enforce a minium uid strength // enforce a minium uid strength
if (!$err && (!isset($this->data['uid']) if (!isset($this->data['uid']) || strlen($this->data['uid']) < $minimum_uid_length)
|| strlen($this->data['uid']) < $minimum_uid_length)) { {
parent::update(array('uid' => common::generate_uid('addressbook',$this->data['id']))); $update['uid'] = common::generate_uid('addressbook',$this->data['id']);
//echo "<p>set uid={$this->data['uid']}, etag={$this->data['etag']}</p>"; //echo "<p>set uid={$this->data['uid']}, etag={$this->data['etag']}</p>";
} }
// set carddav_name, if not given by caller
if (empty($this->data['carddav_name']) && version_compare($GLOBALS['egw_info']['apps']['phpgwapi']['version'], '1.9.007', '>='))
{
$update['carddav_name'] = $this->data['id'].'.vcf';
}
if (!$err && $update)
{
parent::update($update);
}
return $err; return $err;
} }

View File

@ -7,7 +7,7 @@
* @package api * @package api
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2007-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @version $Id$
*/ */
@ -122,7 +122,6 @@ class groupdav extends HTTP_WebDAV_Server
*/ */
var $accounts; var $accounts;
function __construct() function __construct()
{ {
if (!$this->debug) $this->debug = (int)$GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level']; if (!$this->debug) $this->debug = (int)$GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level'];
@ -966,10 +965,7 @@ class groupdav extends HTTP_WebDAV_Server
$user = $GLOBALS['egw_info']['user']['account_id']; $user = $GLOBALS['egw_info']['user']['account_id'];
} }
if (($id = array_pop($parts))) $id = array_pop($parts);
{
list($id) = explode('.',$id); // remove evtl. .ics extension
}
$ok = $id && $user && in_array($app,array('addressbook','calendar','infolog','principals')); $ok = $id && $user && in_array($app,array('addressbook','calendar','infolog','principals'));
if ($this->debug) if ($this->debug)

View File

@ -7,7 +7,7 @@
* @package api * @package api
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2007-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @version $Id$
*/ */
@ -78,6 +78,13 @@ abstract class groupdav_handler
*/ */
var $agent; var $agent;
/**
* Extension to append to url/path
*
* @var string
*/
static $path_extension = '.ics';
/** /**
* Constructor * Constructor
* *
@ -105,7 +112,6 @@ abstract class groupdav_handler
$this->agent = self::get_agent(); $this->agent = self::get_agent();
$this->egw_charset = translation::charset(); $this->egw_charset = translation::charset();
$this->accounts = $GLOBALS['egw']->accounts;
} }
/** /**
@ -230,12 +236,14 @@ abstract class groupdav_handler
* *
* @param string $method GET, PUT, DELETE * @param string $method GET, PUT, DELETE
* @param array &$options * @param array &$options
* @param int $id * @param int|string &$id on return self::$path_extension got removed
* @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false * @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false
* @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id * @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id
*/ */
function _common_get_put_delete($method,&$options,$id,&$return_no_access=false) function _common_get_put_delete($method,&$options,&$id,&$return_no_access=false)
{ {
if (self::$path_extension) $id = basename($id,self::$path_extension);
if ($this->app != 'principals' && !$GLOBALS['egw_info']['user']['apps'][$this->app]) if ($this->app != 'principals' && !$GLOBALS['egw_info']['user']['apps'][$this->app])
{ {
if ($this->debug) error_log(__METHOD__."($method,,$id) 403 Forbidden: no app rights for '$this->app'"); if ($this->debug) error_log(__METHOD__."($method,,$id) 403 Forbidden: no app rights for '$this->app'");
@ -535,6 +543,7 @@ class groupdav_propfind_iterator implements Iterator
$this->start = 0; $this->start = 0;
$this->files = $this->common_files; $this->files = $this->common_files;
if (!$this->files) $this->next(); // otherwise valid will return false and nothing get returned
reset($this->files); reset($this->files);
} }

View File

@ -7,7 +7,7 @@
* @package api * @package api
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2008-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2008-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @version $Id$
*/ */
@ -18,6 +18,12 @@
*/ */
class groupdav_principals extends groupdav_handler class groupdav_principals extends groupdav_handler
{ {
/**
* Reference to the accounts class
*
* @var accounts
*/
var $accounts;
/** /**
* Constructor * Constructor
@ -30,6 +36,8 @@ class groupdav_principals extends groupdav_handler
function __construct($app,$debug=null,$base_uri=null,$principalURL=null) function __construct($app,$debug=null,$base_uri=null,$principalURL=null)
{ {
parent::__construct($app,$debug,$base_uri,$principalURL); parent::__construct($app,$debug,$base_uri,$principalURL);
$this->accounts = $GLOBALS['egw']->accounts;
} }
/** /**

View File

@ -12,7 +12,7 @@
/* Basic information about this app */ /* Basic information about this app */
$setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['name'] = 'phpgwapi';
$setup_info['phpgwapi']['title'] = 'eGroupWare API'; $setup_info['phpgwapi']['title'] = 'eGroupWare API';
$setup_info['phpgwapi']['version'] = '1.9.006'; $setup_info['phpgwapi']['version'] = '1.9.007';
$setup_info['phpgwapi']['versions']['current_header'] = '1.29'; $setup_info['phpgwapi']['versions']['current_header'] = '1.29';
$setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['enable'] = 3;
$setup_info['phpgwapi']['app_order'] = 1; $setup_info['phpgwapi']['app_order'] = 1;
@ -74,4 +74,3 @@ $setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = arra
$setup_info['groupdav']['license'] = 'GPL'; $setup_info['groupdav']['license'] = 'GPL';
$setup_info['groupdav']['hooks']['preferences'] = 'groupdav_hooks::menus'; $setup_info['groupdav']['hooks']['preferences'] = 'groupdav_hooks::menus';
$setup_info['groupdav']['hooks']['settings'] = 'groupdav_hooks::settings'; $setup_info['groupdav']['hooks']['settings'] = 'groupdav_hooks::settings';

View File

@ -336,11 +336,12 @@ $phpgw_baseline = array(
'contact_etag' => array('type' => 'int','precision' => '4','default' => '0'), 'contact_etag' => array('type' => 'int','precision' => '4','default' => '0'),
'contact_uid' => array('type' => 'varchar','precision' => '255'), 'contact_uid' => array('type' => 'varchar','precision' => '255'),
'adr_one_countrycode' => array('type' => 'varchar','precision' => '2'), 'adr_one_countrycode' => array('type' => 'varchar','precision' => '2'),
'adr_two_countrycode' => array('type' => 'varchar','precision' => '2') 'adr_two_countrycode' => array('type' => 'varchar','precision' => '2'),
'carddav_name' => array('type' => 'varchar','precision' => '64','comment' => 'name part of CardDAV URL, if specified by client')
), ),
'pk' => array('contact_id'), 'pk' => array('contact_id'),
'fk' => array(), 'fk' => array(),
'ix' => array('contact_owner','cat_id','n_fileas','contact_uid',array('n_family','n_given'),array('n_given','n_family'),array('org_name','n_family','n_given')), 'ix' => array('contact_owner','cat_id','n_fileas','contact_uid','caldav_name',array('n_family','n_given'),array('n_given','n_family'),array('org_name','n_family','n_given')),
'uc' => array('account_id') 'uc' => array('account_id')
), ),
'egw_addressbook_extra' => array( 'egw_addressbook_extra' => array(

View File

@ -154,3 +154,22 @@ function phpgwapi_upgrade1_9_005()
return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.9.006'; return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.9.006';
} }
/**
* Add column to store CalDAV name given by client
*/
function phpgwapi_upgrade1_9_006()
{
$GLOBALS['egw_setup']->oProc->AddColumn('egw_addressbook','carddav_name',array(
'type' => 'varchar',
'precision' => '64',
'comment' => 'name part of CardDAV URL, if specified by client'
));
$GLOBALS['egw_setup']->db->query($sql='UPDATE egw_addressbook SET carddav_name='.
$GLOBALS['egw_setup']->db->concat(
$GLOBALS['egw_setup']->db->to_varchar('contact_id'),"'.vcf'"),__LINE__,__FILE__);
$GLOBALS['egw_setup']->oProc->CreateIndex('egw_addressbook','carddav_name');
return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.9.007';
}