Initial support for draft-daboo-webdav-sync-01

This commit is contained in:
Andrew McMillan 2009-10-13 21:29:40 +13:00
parent 64571a284e
commit 24a8971080
13 changed files with 917 additions and 83 deletions

View File

@ -44,6 +44,7 @@ GRANT SELECT,UPDATE
ON session_session_id_seq
ON principal_principal_id_seq
ON principal_type_principal_type_id_seq
ON sync_tokens_sync_token_seq
GRANT SELECT,INSERT
ON time_zone

View File

@ -3,10 +3,391 @@
-- as an integer to make calculation of merged permissions simpler
-- through simple binary 'AND'
CREATE or REPLACE FUNCTION legacy_privilege_to_bits( TEXT ) RETURNS BIT(24) AS $$
DECLARE
in_priv ALIAS FOR $1;
out_bits BIT(24);
BEGIN
out_bits := 0::BIT(24);
IF in_priv ~* 'A' THEN
out_bits = ~ out_bits;
RETURN out_bits;
END IF;
-- The CALDAV:read-free-busy privilege MUST be aggregated in the DAV:read privilege.
-- 1 DAV:read
-- 512 CalDAV:read-free-busy
-- 4096 CALDAV:schedule-query-freebusy
IF in_priv ~* 'R' THEN
out_bits := out_bits | 4609::BIT(24);
END IF;
-- DAV:write => DAV:write MUST contain DAV:bind, DAV:unbind, DAV:write-properties and DAV:write-content
-- 2 DAV:write-properties
-- 4 DAV:write-content
-- 64 DAV:bind
-- 128 DAV:unbind
IF in_priv ~* 'W' THEN
out_bits := out_bits | 198::BIT(24);
END IF;
-- 64 DAV:bind
IF in_priv ~* 'B' THEN
out_bits := out_bits | 64::BIT(24);
END IF;
-- 128 DAV:unbind
IF in_priv ~* 'U' THEN
out_bits := out_bits | 128::BIT(24);
END IF;
-- 512 CalDAV:read-free-busy
-- 4096 CALDAV:schedule-query-freebusy
IF in_priv ~* 'F' THEN
out_bits := out_bits | 4608::BIT(24);
END IF;
RETURN out_bits;
END
$$
LANGUAGE 'PlPgSQL' IMMUTABLE STRICT;
-- This legacy conversion function will eventually be removed, once all logic
-- has been converted to use bitmaps, or to use the bits_to_priv() output.
--
-- NOTE: Round-trip through this and then back through legacy_privilege_to_bits
-- function is lossy! Through legacy_privilege_to_bits() and back through
-- this one is not.
--
CREATE or REPLACE FUNCTION bits_to_legacy_privilege( BIT(24) ) RETURNS TEXT AS $$
DECLARE
in_bits ALIAS FOR $1;
out_priv TEXT;
BEGIN
out_priv := '';
IF in_bits = (~ 0::BIT(24)) THEN
out_priv = 'A';
RETURN out_priv;
END IF;
-- The CALDAV:read-free-busy privilege MUST be aggregated in the DAV:read privilege.
-- 1 DAV:read
-- 512 CalDAV:read-free-busy
-- 4096 CALDAV:schedule-query-freebusy
IF (in_bits & 4609::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 1::BIT(24)) != 0::BIT(24) THEN
out_priv := 'R';
ELSE
out_priv := 'F';
END IF;
END IF;
-- DAV:write => DAV:write MUST contain DAV:bind, DAV:unbind, DAV:write-properties and DAV:write-content
-- 2 DAV:write-properties
-- 4 DAV:write-content
-- 64 DAV:bind
-- 128 DAV:unbind
IF (in_bits & 198::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 6::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || 'W';
ELSE
IF (in_bits & 64::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || 'B';
END IF;
IF (in_bits & 128::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || 'U';
END IF;
END IF;
END IF;
RETURN out_priv;
END
$$
LANGUAGE 'PlPgSQL' IMMUTABLE STRICT;
CREATE or REPLACE FUNCTION get_permissions( INT, INT ) RETURNS TEXT AS $$
DECLARE
in_from ALIAS FOR $1;
in_to ALIAS FOR $2;
out_confers TEXT;
bit_confers BIT(24);
group_role_no INT;
tmp_txt TEXT;
dbg TEXT DEFAULT '';
r RECORD;
counter INT;
BEGIN
-- Self can always have full access
IF in_from = in_to THEN
RETURN 'A';
END IF;
-- dbg := 'S-';
SELECT bits_to_legacy_privilege(r1.confers) INTO out_confers FROM relationship r1
WHERE r1.from_user = in_from AND r1.to_user = in_to AND NOT usr_is_role(r1.to_user,'Group');
IF FOUND THEN
RETURN dbg || out_confers;
END IF;
-- RAISE NOTICE 'No simple relationships between % and %', in_from, in_to;
SELECT bit_or(r1.confers & r2.confers) INTO bit_confers
FROM relationship r1
JOIN relationship r2 ON r1.to_user=r2.from_user
WHERE r1.from_user=in_from AND r2.to_user=in_to
AND r2.from_user IN (SELECT user_no FROM roles LEFT JOIN role_member USING(role_no) WHERE role_name='Group');
IF bit_confers != 0::BIT(24) THEN
RETURN dbg || bits_to_legacy_privilege(bit_confers);
END IF;
RETURN '';
-- RAISE NOTICE 'No complex relationships between % and %', in_from, in_to;
SELECT bits_to_legacy_privilege(r1.confers) INTO out_confers FROM relationship r1 LEFT OUTER JOIN relationship r2 ON(r1.to_user = r2.to_user)
WHERE r1.from_user = in_from AND r2.from_user = in_to AND r1.from_user != r2.from_user
AND NOT EXISTS( SELECT 1 FROM relationship r3 WHERE r3.from_user = r1.to_user ) ;
IF FOUND THEN
-- dbg := 'H-';
-- RAISE NOTICE 'Permissions to shared group % ', out_confers;
RETURN dbg || out_confers;
END IF;
-- RAISE NOTICE 'No common group relationships between % and %', in_from, in_to;
RETURN '';
END;
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
CREATE or REPLACE FUNCTION get_group_role_no() RETURNS INT AS $$
SELECT role_no FROM roles WHERE role_name = 'Group'
$$ LANGUAGE 'SQL' IMMUTABLE;
CREATE or REPLACE FUNCTION has_legacy_privilege( INT, TEXT, INT ) RETURNS BOOLEAN AS $$
DECLARE
in_from ALIAS FOR $1;
in_legacy_privilege ALIAS FOR $2;
in_to ALIAS FOR $3;
in_confers BIT(24);
group_role_no INT;
BEGIN
-- Self can always have full access
IF in_from = in_to THEN
RETURN TRUE;
END IF;
SELECT get_group_role_no() INTO group_role_no;
SELECT legacy_privilege_to_bits(in_legacy_privilege) INTO in_confers;
IF EXISTS(SELECT 1 FROM relationship WHERE from_user = in_from AND to_user = in_to
AND (in_confers & confers) = in_confers
AND NOT EXISTS(SELECT 1 FROM role_member WHERE to_user = user_no AND role_no = group_role_no) ) THEN
-- A direct relationship from A to B that grants sufficient
-- RAISE NOTICE 'Permissions directly granted';
RETURN TRUE;
END IF;
IF EXISTS( SELECT 1 FROM relationship r1 JOIN relationship r2 ON r1.to_user=r2.from_user
WHERE (in_confers & r1.confers & r2.confers) = in_confers
AND r1.from_user=in_from AND r2.to_user=in_to
AND r2.from_user IN (SELECT user_no FROM role_member WHERE role_no=group_role_no) ) THEN
-- An indirect relationship from A to B via group G that grants sufficient
-- RAISE NOTICE 'Permissions mediated via group';
RETURN TRUE;
END IF;
IF EXISTS( SELECT 1 FROM relationship r1 JOIN relationship r2 ON r1.to_user=r2.to_user
WHERE (in_confers & r1.confers & r2.confers) = in_confers
AND r1.from_user=in_from AND r2.from_user=in_to
AND r2.to_user IN (SELECT user_no FROM role_member WHERE role_no=group_role_no)
AND NOT EXISTS(SELECT 1 FROM relationship WHERE from_user=r2.to_user) ) THEN
-- An indirect reflexive relationship from both A & B to group G which grants sufficient
-- RAISE NOTICE 'Permissions to shared group';
RETURN TRUE;
END IF;
-- RAISE NOTICE 'No common group relationships between % and %', in_from, in_to;
RETURN FALSE;
END;
$$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
-- Given a verbose DAV: or CalDAV: privilege name return the bitmask
CREATE or REPLACE FUNCTION privilege_to_bits( TEXT ) RETURNS BIT(24) AS $$
DECLARE
raw_priv ALIAS FOR $1;
in_priv TEXT;
BEGIN
in_priv := trim(lower(regexp_replace(raw_priv, '^.*:', '')));
IF in_priv = 'all' THEN
RETURN ~ 0::BIT(24);
END IF;
RETURN (CASE
WHEN in_priv = 'read' THEN 4609 -- 1 + 512 + 4096
WHEN in_priv = 'write' THEN 198 -- 2 + 4 + 64 + 128
WHEN in_priv = 'write-properties' THEN 2
WHEN in_priv = 'write-content' THEN 4
WHEN in_priv = 'unlock' THEN 8
WHEN in_priv = 'read-acl' THEN 16
WHEN in_priv = 'read-current-user-privilege-set' THEN 32
WHEN in_priv = 'bind' THEN 64
WHEN in_priv = 'unbind' THEN 128
WHEN in_priv = 'write-acl' THEN 256
WHEN in_priv = 'read-free-busy' THEN 4608 -- 512 + 4096
WHEN in_priv = 'schedule-deliver' THEN 7168 -- 1024 + 2048 + 4096
WHEN in_priv = 'schedule-deliver-invite' THEN 1024
WHEN in_priv = 'schedule-deliver-reply' THEN 2048
WHEN in_priv = 'schedule-query-freebusy' THEN 4096
WHEN in_priv = 'schedule-send' THEN 57344 -- 8192 + 16384 + 32768
WHEN in_priv = 'schedule-send-invite' THEN 8192
WHEN in_priv = 'schedule-send-reply' THEN 16384
WHEN in_priv = 'schedule-send-freebusy' THEN 32768
ELSE 0 END)::BIT(24);
END
$$
LANGUAGE 'PlPgSQL' IMMUTABLE STRICT;
-- Given an array of verbose DAV: or CalDAV: privilege names return the bitmask
CREATE or REPLACE FUNCTION privilege_to_bits( TEXT[] ) RETURNS BIT(24) AS $$
DECLARE
raw_privs ALIAS FOR $1;
in_priv TEXT;
out_bits BIT(24);
i INT;
all BIT(24);
start INT;
finish INT;
BEGIN
out_bits := 0::BIT(24);
all := ~ out_bits;
SELECT array_lower(raw_privs,1) INTO start;
SELECT array_upper(raw_privs,1) INTO finish;
FOR i IN start .. finish LOOP
SELECT out_bits | privilege_to_bits(raw_privs[i]) INTO out_bits;
IF out_bits = all THEN
RETURN all;
END IF;
END LOOP;
RETURN out_bits;
END
$$
LANGUAGE 'PlPgSQL' IMMUTABLE STRICT;
-- This legacy conversion function will eventually be removed, once all logic
-- has been converted to use bitmaps, or to use the bits_to_priv() output.
--
-- NOTE: Round-trip through this and then back through privilege_to_bits
-- function is lossy! Through privilege_to_bits() and back through
-- this one is not.
--
CREATE or REPLACE FUNCTION bits_to_privilege( BIT(24) ) RETURNS TEXT[] AS $$
DECLARE
in_bits ALIAS FOR $1;
out_priv TEXT[];
BEGIN
out_priv := ARRAY[]::text[];
IF in_bits = (~ 0::BIT(24)) THEN
out_priv := out_priv || ARRAY['DAV:all'];
END IF;
IF (in_bits & 513::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 1::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:read'];
END IF;
IF (in_bits & 512::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:read-free-busy'];
END IF;
END IF;
IF (in_bits & 198::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 198::BIT(24)) = 198::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:write'];
ELSE
IF (in_bits & 2::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:write-properties'];
END IF;
IF (in_bits & 4::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:write-content'];
END IF;
IF (in_bits & 64::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:bind'];
END IF;
IF (in_bits & 128::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:unbind'];
END IF;
END IF;
END IF;
IF (in_bits & 8::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:unlock'];
END IF;
IF (in_bits & 16::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:read-acl'];
END IF;
IF (in_bits & 32::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:read-current-user-privilege-set'];
END IF;
IF (in_bits & 256::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['DAV:write-acl'];
END IF;
IF (in_bits & 7168::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 7168::BIT(24)) = 7168::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-deliver'];
ELSE
IF (in_bits & 1024::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-deliver-invite'];
END IF;
IF (in_bits & 2048::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-deliver-reply'];
END IF;
IF (in_bits & 4096::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-query-freebusy'];
END IF;
END IF;
END IF;
IF (in_bits & 57344::BIT(24)) != 0::BIT(24) THEN
IF (in_bits & 57344::BIT(24)) = 57344::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-send'];
ELSE
IF (in_bits & 8192::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-send-invite'];
END IF;
IF (in_bits & 16384::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-send-reply'];
END IF;
IF (in_bits & 32768::BIT(24)) != 0::BIT(24) THEN
out_priv := out_priv || ARRAY['caldav:schedule-send-freebusy'];
END IF;
END IF;
END IF;
RETURN out_priv;
END
$$
LANGUAGE 'PlPgSQL' IMMUTABLE STRICT;
BEGIN;
SELECT check_db_revision(1,2,5);
\i dba/better_perms.sql
-- DAV Privileges implementation
--

View File

@ -14,8 +14,10 @@ CREATE TABLE sync_tokens (
CREATE TABLE sync_changes (
sync_time TIMESTAMP WITH TIME ZONE DEFAULT current_timestamp,
status INT2,
dav_id INT8 REFERENCES calendar_item(dav_id)
collection_id INT8 REFERENCES collection(collection_id) ON DELETE CASCADE ON UPDATE CASCADE,
sync_status INT,
dav_id INT8 REFERENCES calendar_item(dav_id) ON DELETE SET NULL ON UPDATE RESTRICT,
dav_name TEXT
);
SELECT new_db_revision(1,2,7, 'Juli' );
@ -38,8 +40,8 @@ BEGIN
RETURN FALSE;
END IF;
SELECT dav_id INTO tmp_int FROM calendar_item WHERE dav_name = in_dav_name;
INSERT INTO sync_changes ( collection_id, status, dav_id)
VALUES( in_collection_id, in_status::INT2, tmp_int);
INSERT INTO sync_changes ( collection_id, sync_status, dav_id, dav_name)
VALUES( in_collection_id, in_status, tmp_int, in_dav_name);
RETURN TRUE;
END
$$ LANGUAGE 'PlPgSQL' VOLATILE STRICT;
@ -51,14 +53,16 @@ DECLARE
in_collection_id ALIAS FOR $2;
tmp_int INT8;
BEGIN
SELECT 1 INTO tmp_int FROM sync_changes
WHERE collection_id = in_collection_id
AND sync_time > (SELECT modification_time FROM sync_tokens WHERE sync_token = in_old_sync_token)
LIMIT 1;
IF NOT FOUND THEN
RETURN in_old_sync_token;
IF in_old_sync_token > 0 THEN
SELECT 1 INTO tmp_int FROM sync_changes
WHERE collection_id = in_collection_id
AND sync_time > (SELECT modification_time FROM sync_tokens WHERE sync_token = in_old_sync_token)
LIMIT 1;
IF NOT FOUND THEN
RETURN in_old_sync_token;
END IF;
END IF;
SELECT nextval('dav_id_seq') INTO tmp_int;
SELECT nextval('sync_tokens_sync_token_seq') INTO tmp_int;
INSERT INTO sync_tokens(collection_id, sync_token) VALUES( in_collection_id, tmp_int );
RETURN tmp_int;
END

View File

@ -823,6 +823,14 @@ EOSQL;
}
/**
* Returns the ID of the collection of, or containing this request
*/
function CollectionId( ) {
return $this->collection_id;
}
/**
* Returns the array of supported privileges converted into XMLElements
*/

View File

@ -81,41 +81,16 @@ class DAVResource
global $c;
foreach( $row AS $k => $v ) {
dbg_error_log( 'resource', 'Processing resource property "%s" has "%s".', $row->dav_name, $k );
switch ( $k ) {
case 'dav_name':
$this->href = ConstructURL( '/'.$this->dav_name.'/', true );
$this->{$k} = $v;
break;
case 'dav_etag':
$this->unique_tag = $this->dav_etag;
break;
case 'caldav_data':
$this->content = $this->caldav_data;
break;
case 'updated':
$this->modified = ISODateToHTTPDate($this->updated);
break;
case 'created':
$this->modified = ISODateToHTTPDate($this->joined);
break;
case 'username':
$this->owner = ConstructURL( '/'.$this->username.'/', true );;
$this->principal_url = ConstructURL( '/'.$this->username.'/', true );;
$this->unique_tag = '"'.$v.'"';
break;
default:
$this->{$k} = $v;
}
}
if ( ! isset($this->content) ) {
$this->contenttype = 'http/unix-directory';
}
}
@ -127,9 +102,13 @@ class DAVResource
if ( $reply === null ) $reply = $GLOBALS['reply'];
dbg_error_log( 'caldav', 'Processing "%s" on "%s".', $tag, $this->path );
dbg_error_log( 'resource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
switch( $tag ) {
case 'DAV::href':
$prop->NewElement('href', ConstructURL($this->dav_name) );
break;
case 'DAV::getcontenttype':
$prop->NewElement('getcontenttype', $this->contenttype );
break;
@ -142,13 +121,13 @@ class DAVResource
$prop->NewElement('displayname', $this->displayname );
break;
case 'DAV::getlastmodified':
$prop->NewElement('getlastmodified', $this->modified );
break;
// case 'DAV::getlastmodified':
// $prop->NewElement('getlastmodified', $this->modified );
// break;
case 'DAV::creationdate':
$prop->NewElement('creationdate', $this->created );
break;
// case 'DAV::creationdate':
// $prop->NewElement('creationdate', $this->created );
// break;
case 'DAV::getcontentlanguage':
$locale = (isset($c->current_locale) ? $c->current_locale : '');
@ -156,10 +135,10 @@ class DAVResource
$prop->NewElement('getcontentlanguage', $locale );
break;
case 'DAV::owner':
// After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
$reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url ) );
break;
// case 'DAV::owner':
// // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
// $reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url) ) );
// break;
// Empty tag responses.
case 'DAV::alternate-URI-set':
@ -172,7 +151,7 @@ class DAVResource
$not_found[] = $reply->Tag($tag);
}
else {
$prop->NewElement('getetag', $this->etag );
$prop->NewElement('getetag', $this->unique_tag );
}
break;
@ -189,23 +168,61 @@ class DAVResource
}
break;
case 'DAV::sync-token':
if ( $this->_is_collection ) {
$prop->NewElement('sync-token', $this->NewSyncToken() );
}
else {
$not_found[] = $reply->Tag($tag);
}
break;
default:
dbg_error_log( 'caldav', 'Request for unsupported property "%s" of path "%s".', $tag, $this->href );
dbg_error_log( 'resource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->href );
return false;
}
return true;
}
/**
* Construct XML propstat fragment for this resource
*
* @param array $properties The requested properties for this resource
*
* @return string An XML fragment with the requested properties for this resource
*/
function GetPropStat( $properties ) {
global $session, $c, $request, $reply;
dbg_error_log('resource',': GetPropStat: href "%s"', $this->dav_name );
$prop = new XMLElement('prop');
$denied = array();
$not_found = array();
foreach( $properties AS $k => $tag ) {
dbg_error_log( 'resource', 'Looking at resource "%s" for property [%s]"%s".', $this->href, $k, $tag );
if ( ! $this->ResourceProperty($tag, $prop, $reply) ) {
dbg_error_log( 'resource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->href );
$not_found[] = $reply->Tag($tag);
}
}
$status = new XMLElement('status', 'HTTP/1.1 200 OK' );
$elements = array( new XMLElement( 'propstat', array($prop,$status) ) );
if ( count($denied) > 0 ) {
$status = new XMLElement('status', 'HTTP/1.1 403 Forbidden' );
$noprop = new XMLElement('prop');
foreach( $denied AS $k => $v ) {
$noprop->NewElement( $v );
}
$elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
}
if ( count($not_found) > 0 ) {
$status = new XMLElement('status', 'HTTP/1.1 404 Not Found' );
$noprop = new XMLElement('prop');
foreach( $not_found AS $k => $v ) {
$noprop->NewElement( $v );
}
$elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
}
return $elements;
}
/**
* Render XML for this resource
*
@ -228,14 +245,6 @@ class DAVResource
dbg_error_log( 'principal', 'Request for unsupported property "%s" of principal "%s".', $tag, $this->username );
$not_found[] = $reply->Tag($tag);
}
default:
if ( ! $request->ServerProperty( $tag, $prop, $reply ) ) {
dbg_error_log( 'principal', 'Request for unsupported property "%s" of principal "%s".', $tag, $this->username );
$not_found[] = $reply->Tag($tag);
}
break;
}
}
if ( $props_only ) return $prop;

View File

@ -66,15 +66,22 @@ else {
/**
* We read the resource first, so we can check if it matches (or does not match)
*/
$qry = new PgQuery( "SELECT cd.dav_etag, ci.uid FROM caldav_data cd JOIN calendar_item ci USING (dav_id) WHERE cd.user_no = ? AND cd.dav_name = ?;", $request->user_no, $request->path );
$escaped_path = qpg($request->path);
$qry = new PgQuery( "SELECT cd.dav_etag, ci.uid, cd.collection_id FROM caldav_data cd JOIN calendar_item ci USING (dav_id) WHERE cd.user_no = ? AND cd.dav_name = $escaped_path;", $request->user_no );
if ( $qry->Exec("DELETE") && $qry->rows == 1 ) {
$delete_row = $qry->Fetch();
if ( (isset($request->etag_if_match) && $request->etag_if_match != $delete_row->dav_etag) ) {
$request->DoResponse( 412, translate("Resource has changed on server - not deleted") );
}
$qry = new PgQuery( "DELETE FROM caldav_data WHERE user_no = ? AND dav_name = ?;", $request->user_no, $request->path );
$collection_id = $delete_row->collection_id;
$sql = <<<EOSQL
DELETE FROM caldav_data WHERE collection_id = $collection_id AND dav_name = $escaped_path;
SELECT write_sync_change( $collection_id, 404, $escaped_path);
EOSQL;
$qry = new PgQuery( $sql );
if ( $qry->Exec("DELETE") ) {
$qry = new PgQuery( "DELETE FROM property WHERE dav_name = ?;", $request->path ); $qry->Exec("DELETE"); /** @todo we should write a trigger to delete property records when caldav_data or collection is deleted */
$qry = new PgQuery( "DELETE FROM property WHERE dav_name = $escaped_path;" ); $qry->Exec("DELETE"); /** @todo we should write a trigger to delete property records when caldav_data or collection is deleted */
@dbg_error_log( "DELETE", "DELETE: User: %d, ETag: %s, Path: %s", $session->user_no, $request->etag_if_match, $request->path);
if ( function_exists('log_caldav_action') ) {
log_caldav_action( 'DELETE', $delete_row->uid, $request->user_no, $request->collection_id, $request->path );

View File

@ -1,5 +1,5 @@
<?php
require("DAVObject.php");
require("DAVResource.php");
$responses = array();
/**
@ -11,13 +11,17 @@ if ( !isset($sync_token) ) $sync_token = 0;
$sync_token = intval($sync_token);
dbg_error_log( 'sync', " sync-token: %s", $sync_token );
$props = $xmltree->GetPath('/DAV::sync-collection/DAV::prop');
$props = $xmltree->GetElements('DAV::prop');
$v = $props[0];
$props = $v->GetContent();
$proplist = array();
foreach( $props AS $k => $v ) {
$proplist[] = $v->GetContent();
$proplist[] = $v->GetTag();
}
function display_status( $status_code ) {
return sprintf( 'HTTP/1.1 %03d %s', $status_code, getStatusMessage($status_code) );
return sprintf( 'HTTP/1.1 %03d %s', intval($status_code), getStatusMessage($status_code) );
}
$sql = "SELECT new_sync_token(?,?)";
@ -25,22 +29,42 @@ $qry = new PgQuery($sql, $sync_token, $request->CollectionId());
if ( !$qry->Exec("REPORT",__LINE__,__FILE__) || $qry->rows <= 0 ) {
$request->DoResponse( 500, translate("Database error") );
}
$row = $qry->Fetch();
$new_token = $row->new_sync_token;
$sql = "SELECT * FROM collection LEFT JOIN sync_changes USING(collection_id) sync_changes LEFT JOIN calendar_item USING (dav_id) LEFT JOIN caldav_data USING (dav_id) WHERE collection_id = ? AND sync_time > (SELECT modification_time FROM sync_tokens WHERE sync_token = ?)";
$qry = new PgQuery($sql, $request->CollectionId(), $sync_token);
if ( $sync_token == 0 ) {
$sql = <<<EOSQL
SELECT *, 201 AS sync_status FROM collection
LEFT JOIN caldav_data USING (collection_id)
LEFT JOIN calendar_item USING (dav_id)
WHERE collection.collection_id = ?
EOSQL;
$qry = new PgQuery($sql, $request->CollectionId());
}
else {
$sql = <<<EOSQL
SELECT * FROM collection LEFT JOIN sync_changes USING(collection_id)
LEFT JOIN calendar_item USING (dav_id)
LEFT JOIN caldav_data USING (dav_id)
WHERE collection.collection_id = ?
AND sync_time > (SELECT modification_time FROM sync_tokens WHERE sync_token = ?)
EOSQL;
$qry = new PgQuery($sql, $request->CollectionId(), $sync_token);
}
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
if ( $qry->Exec("REPORT",__LINE__,__FILE__) ) {
while( $object = $qry->Fetch() ) {
$resultset = array(
new XMLElement( 'href', $object->href ),
new XMLElement( 'status', display_status($object->status) )
new XMLElement( 'href', ConstructURL($object->dav_name) ),
new XMLElement( 'status', display_status($object->sync_status) )
);
if ( $status != 404 ) {
$dav_object = new DavObject($object);
$resultset[] = $dav_object->GetPropStat( $proplist );
$dav_resource = new DAVResource($object);
$resultset = array_merge( $resultset, $dav_resource->GetPropStat($proplist) );
}
$responses[] = new XMLElement( 'sync-response', $resultset );
}
$responses[] = new XMLElement( 'sync-token', $new_token );
}
else {
$request->DoResponse( 500, translate("Database error") );

View File

@ -0,0 +1,21 @@
HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
ETag: "d668266633188d6d2690a2eb491bba0c"
Content-Length: 404
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:">
<sync-response>
<href>/caldav.php/user1/home/3F4CF6227300FD062D9EF3CDFB30D32D-0.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"2c32a2f8aba853654eb17fe037a4db4d"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-token>1</sync-token>
</multistatus>

View File

@ -0,0 +1,19 @@
#
# Check for support of REPORT sync-collection with no sync-token
#
TYPE=REPORT
URL=http://mycaldav/caldav.php/user1/home/
HEADER=User-agent: sync-collection initial REPORT
HEADER=Content-type: text/xml
HEAD
BEGINDATA
<?xml version="1.0" encoding="utf-8" ?>
<D:sync-collection xmlns:D="DAV:">
<D:sync-token/>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
ENDDATA

View File

@ -0,0 +1,161 @@
HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
ETag: "7ab7cf2174a67bf4fc26271956be7d90"
Content-Length: 4528
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:">
<sync-response>
<href>/caldav.php/user1/home/AAA9318E-37D9-4319-8626-95ECD3D3B243.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"5f050eca5480bbebbe9428222570913d"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/da81c0ee-7871-11db-c6d6-f6927c144649.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"421abf7e4848d2fecbf64217ed205d4b"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/71e2ae82-7870-11db-c6d6-f6927c144649.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"0d7a68984bf525342d22b8924a57e8e2"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/e6eb5bc9-f7f9-4a0a-94e8-8e90eefc7d08.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"8f581a053df6d833254756dfd7553d37"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/e70576e9-c1e0-431e-a507-0386fd82f223.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"e8060931f30c1798ac58ffbe4ec0bffc"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/b1679f77-673d-4f46-b3eb-2420e1bba301.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"a2990674708634a311bb98a59865ca50"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/0575d895-a006-4ed8-9be6-0d1b6b6b1f96.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"00ad5eb1eb5507884710b0b66aa5d5c4"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/917b9e47-b748-4550-a566-657fbe672447.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"cb3d9dc3e8c157f53eba3ea0e1e0f146"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/2178279a-aec2-471f-832d-1f6df6203f2f.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"509b0f0d8a3363379f9f5727f5dd74a0"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/fbd57454-d966-4a14-8341-abe1edb1ae66.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"ac90acd649c25070b1a2a17fb31a105a"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/1906b3ca-4890-468a-9b58-1de74bf2c716.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"5def8ae2b20893a1c7f4dbaeb008f2f1"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/9d050be7-8a02-4355-8ed3-02a9fc5f473f.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"08a435c2abaf38f4a50a997343c098a7"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/4aaf8f37-f232-4c8e-a72e-e171d4c4fe54.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"a1c6404d61190f9574e2bfd69383f144"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/20061101T073004Z.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"c3658901fd4689d4a1e1d6f08601ef4f"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/3F4CF6227300FD062D9EF3CDFB30D32D-0.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"2c32a2f8aba853654eb17fe037a4db4d"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-token>2</sync-token>
</multistatus>

View File

@ -0,0 +1,19 @@
#
# Check for support of REPORT sync-collection with no sync-token
#
TYPE=REPORT
URL=http://mycaldav/caldav.php/user1/home/
HEADER=User-agent: sync-collection initial REPORT
HEADER=Content-type: text/xml
HEAD
BEGINDATA
<?xml version="1.0" encoding="utf-8" ?>
<D:sync-collection xmlns:D="DAV:">
<D:sync-token/>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
ENDDATA

View File

@ -0,0 +1,161 @@
HTTP/1.1 207 Multi-Status
Date: Dow, 01 Jan 2000 00:00:00 GMT
DAV: 1, 2, 3, access-control, calendar-access, calendar-schedule
ETag: "1e80e92dfce8e43ed452a56e350b577b"
Content-Length: 4525
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8" ?>
<multistatus xmlns="DAV:">
<sync-response>
<href>/caldav.php/user1/home/20061101T073004Z.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"c3658901fd4689d4a1e1d6f08601ef4f"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/4aaf8f37-f232-4c8e-a72e-e171d4c4fe54.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"a1c6404d61190f9574e2bfd69383f144"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/9d050be7-8a02-4355-8ed3-02a9fc5f473f.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"08a435c2abaf38f4a50a997343c098a7"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/1906b3ca-4890-468a-9b58-1de74bf2c716.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"5def8ae2b20893a1c7f4dbaeb008f2f1"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/fbd57454-d966-4a14-8341-abe1edb1ae66.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"ac90acd649c25070b1a2a17fb31a105a"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/2178279a-aec2-471f-832d-1f6df6203f2f.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"509b0f0d8a3363379f9f5727f5dd74a0"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/917b9e47-b748-4550-a566-657fbe672447.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"cb3d9dc3e8c157f53eba3ea0e1e0f146"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/0575d895-a006-4ed8-9be6-0d1b6b6b1f96.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"00ad5eb1eb5507884710b0b66aa5d5c4"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/b1679f77-673d-4f46-b3eb-2420e1bba301.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"a2990674708634a311bb98a59865ca50"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/e70576e9-c1e0-431e-a507-0386fd82f223.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"e8060931f30c1798ac58ffbe4ec0bffc"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/e6eb5bc9-f7f9-4a0a-94e8-8e90eefc7d08.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"8f581a053df6d833254756dfd7553d37"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/71e2ae82-7870-11db-c6d6-f6927c144649.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"0d7a68984bf525342d22b8924a57e8e2"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/da81c0ee-7871-11db-c6d6-f6927c144649.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"421abf7e4848d2fecbf64217ed205d4b"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/AAA9318E-37D9-4319-8626-95ECD3D3B243.ics</href>
<status>HTTP/1.1 201 Created</status>
<propstat>
<prop>
<getetag>"5f050eca5480bbebbe9428222570913d"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-response>
<href>/caldav.php/user1/home/AAA9318E-37D9-4319-8626-95ECD3D3B243.ics</href>
<status>HTTP/1.1 200 OK</status>
<propstat>
<prop>
<getetag>"5f050eca5480bbebbe9428222570913d"</getetag>
</prop>
<status>HTTP/1.1 200 OK</status>
</propstat>
</sync-response>
<sync-token>3</sync-token>
</multistatus>

View File

@ -0,0 +1,19 @@
#
# Check for support of REPORT sync-collection with no sync-token
#
TYPE=REPORT
URL=http://mycaldav/caldav.php/user1/home/
HEADER=User-agent: sync-collection changes REPORT
HEADER=Content-type: text/xml
HEAD
BEGINDATA
<?xml version="1.0" encoding="utf-8" ?>
<D:sync-collection xmlns:D="DAV:">
<D:sync-token>1</D:sync-token>
<D:prop>
<D:getetag/>
</D:prop>
</D:sync-collection>
ENDDATA