mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-05-26 02:44:29 +00:00
Getting 'MOVE' working has proven surprisingly complex.
This commit is contained in:
parent
a7d7bb63d5
commit
3df6ccc4ba
@ -35,6 +35,7 @@ GRANT SELECT,INSERT,UPDATE,DELETE
|
||||
ON relationship_type
|
||||
ON sync_tokens
|
||||
ON sync_changes
|
||||
ON grants
|
||||
|
||||
GRANT SELECT,UPDATE
|
||||
ON relationship_type_rt_id_seq
|
||||
|
||||
@ -417,7 +417,8 @@ BEGIN
|
||||
IF TG_OP = 'UPDATE' THEN
|
||||
IF NEW.dav_name != OLD.dav_name THEN
|
||||
UPDATE caldav_data
|
||||
SET dav_name = replace( dav_name, OLD.dav_name, NEW.dav_name)
|
||||
SET dav_name = replace( dav_name, OLD.dav_name, NEW.dav_name),
|
||||
user_no = NEW.user_no
|
||||
WHERE substring(dav_name from 1 for char_length(OLD.dav_name)) = OLD.dav_name;
|
||||
END IF;
|
||||
END IF;
|
||||
@ -475,6 +476,44 @@ CREATE TRIGGER caldav_data_modified AFTER INSERT OR UPDATE OR DELETE ON caldav_d
|
||||
FOR EACH ROW EXECUTE PROCEDURE caldav_data_modified();
|
||||
|
||||
|
||||
DROP TRIGGER caldav_data_sync_dav_id ON caldav_data CASCADE;
|
||||
DROP TRIGGER calendar_item_sync_dav_id ON calendar_item CASCADE;
|
||||
CREATE or REPLACE FUNCTION sync_dav_id ( ) RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
-- Just let the ON DELETE CASCADE handle this case
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
IF NEW.dav_id IS NULL THEN
|
||||
NEW.dav_id = nextval('dav_id_seq');
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'UPDATE' THEN
|
||||
IF OLD.dav_id != NEW.dav_id OR OLD.collection_id != NEW.collection_id
|
||||
OR OLD.user_no != NEW.user_no OR OLD.dav_name != NEW.dav_name THEN
|
||||
UPDATE calendar_item SET dav_id = NEW.dav_id, user_no = NEW.user_no,
|
||||
collection_id = NEW.collection_id, dav_name = NEW.dav_name
|
||||
WHERE dav_name = OLD.dav_name OR dav_id = OLD.dav_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
UPDATE calendar_item SET dav_id = NEW.dav_id, user_no = NEW.user_no,
|
||||
collection_id = NEW.collection_id, dav_name = NEW.dav_name
|
||||
WHERE dav_name = NEW.dav_name OR dav_id = NEW.dav_id;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
CREATE TRIGGER caldav_data_sync_dav_id AFTER INSERT OR UPDATE ON caldav_data
|
||||
FOR EACH ROW EXECUTE PROCEDURE sync_dav_id();
|
||||
|
||||
|
||||
|
||||
-- New in 1.2.6
|
||||
|
||||
CREATE or REPLACE FUNCTION legacy_privilege_to_bits( TEXT ) RETURNS BIT(24) AS $$
|
||||
@ -927,14 +966,14 @@ BEGIN
|
||||
-- We need to canonicalise the path, so:
|
||||
-- If it matches '/' + some characters (+ optional '/') => a principal URL
|
||||
IF in_path ~ '^/[^/]+/?$' THEN
|
||||
alt1_path := replace(in_path, '/', '')
|
||||
alt1_path := replace(in_path, '/', '');
|
||||
SELECT principal_privileges(in_accessor,principal_id) INTO out_conferred FROM usr JOIN principal USING(user_no) WHERE username = alt1_path;
|
||||
RETURN out_conferred;
|
||||
END IF;
|
||||
|
||||
-- Otherwise look for the longest segment matching up to the last '/', or if we append one, or if we replace a final '.ics' with one.
|
||||
alt1_path := in_path;
|
||||
IF alt1_path ~ '\.ics$' THEN
|
||||
IF alt1_path ~ E'\\.ics$' THEN
|
||||
alt1_path := substr(alt1_path, 1, length(alt1_path) - 4) || '/';
|
||||
END IF;
|
||||
alt2_path := regexp_replace( in_path, '[^/]*$', '');
|
||||
|
||||
@ -209,43 +209,41 @@ CREATE TABLE freebusy_ticket (
|
||||
);
|
||||
|
||||
|
||||
CREATE or REPLACE FUNCTION sync_dav_id ( ) RETURNS TRIGGER AS '
|
||||
|
||||
CREATE or REPLACE FUNCTION sync_dav_id ( ) RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
|
||||
IF TG_OP = ''DELETE'' THEN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
-- Just let the ON DELETE CASCADE handle this case
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
IF NEW.dav_id IS NULL THEN
|
||||
NEW.dav_id = nextval(''dav_id_seq'');
|
||||
NEW.dav_id = nextval('dav_id_seq');
|
||||
END IF;
|
||||
|
||||
IF TG_OP = ''UPDATE'' THEN
|
||||
IF OLD.dav_id = NEW.dav_id THEN
|
||||
-- Nothing to do
|
||||
RETURN NEW;
|
||||
IF TG_OP = 'UPDATE' THEN
|
||||
IF OLD.dav_id != NEW.dav_id OR OLD.collection_id != NEW.collection_id
|
||||
OR OLD.user_no != NEW.user_no OR OLD.dav_name != NEW.dav_name THEN
|
||||
UPDATE calendar_item SET dav_id = NEW.dav_id, user_no = NEW.user_no,
|
||||
collection_id = NEW.collection_id, dav_name = NEW.dav_name
|
||||
WHERE dav_name = OLD.dav_name OR dav_id = OLD.dav_id;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF TG_RELNAME = ''caldav_data'' THEN
|
||||
UPDATE calendar_item SET dav_id = NEW.dav_id WHERE user_no = NEW.user_no AND dav_name = NEW.dav_name;
|
||||
ELSE
|
||||
UPDATE caldav_data SET dav_id = NEW.dav_id WHERE user_no = NEW.user_no AND dav_name = NEW.dav_name;
|
||||
END IF;
|
||||
UPDATE calendar_item SET dav_id = NEW.dav_id, user_no = NEW.user_no,
|
||||
collection_id = NEW.collection_id, dav_name = NEW.dav_name
|
||||
WHERE dav_name = NEW.dav_name OR dav_id = NEW.dav_id;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
' LANGUAGE 'plpgsql';
|
||||
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
CREATE TRIGGER caldav_data_sync_dav_id AFTER INSERT OR UPDATE ON caldav_data
|
||||
FOR EACH ROW EXECUTE PROCEDURE sync_dav_id();
|
||||
|
||||
CREATE TRIGGER calendar_item_sync_dav_id AFTER INSERT OR UPDATE ON calendar_item
|
||||
FOR EACH ROW EXECUTE PROCEDURE sync_dav_id();
|
||||
|
||||
|
||||
-- Only needs SELECT access by website.
|
||||
CREATE TABLE principal_type (
|
||||
|
||||
@ -53,6 +53,7 @@ switch ( $request->method ) {
|
||||
case 'MKCOL': include_once("caldav-MKCOL.php"); break;
|
||||
case 'PUT': include_once("caldav-PUT.php"); break;
|
||||
case 'POST': include_once("caldav-POST.php"); break;
|
||||
case 'MOVE': include_once("caldav-MOVE.php"); break;
|
||||
case 'GET': include_once("caldav-GET.php"); break;
|
||||
case 'HEAD': include_once("caldav-GET.php"); break;
|
||||
case 'DELETE': include_once("caldav-DELETE.php"); break;
|
||||
|
||||
@ -350,6 +350,14 @@ class AwlQuery
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the count of rows retrieved/affected
|
||||
*/
|
||||
function rows() {
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute the query, logging any debugging.
|
||||
*
|
||||
|
||||
@ -163,6 +163,8 @@ class CalDAVPrincipal
|
||||
if ( !isset($this->modified) ) $this->modified = ISODateToHTTPDate($this->updated);
|
||||
if ( !isset($this->created) ) $this->created = ISODateToHTTPDate($this->joined);
|
||||
|
||||
$this->dav_etag = md5($this->username . $this->updated);
|
||||
|
||||
$this->by_email = false;
|
||||
$this->principal_url = ConstructURL( '/'.$this->username.'/', true );
|
||||
$this->url = $this->principal_url;
|
||||
@ -180,9 +182,9 @@ class CalDAVPrincipal
|
||||
$this->dropbox_url = sprintf( '%s.drop/', $this->url);
|
||||
$this->notifications_url = sprintf( '%s.notify/', $this->url);
|
||||
|
||||
if ( isset ( $c->notifications_server ) ) {
|
||||
if ( isset ( $c->notifications_server ) ) {
|
||||
$this->xmpp_uri = 'xmpp:pubsub.'.$c->notifications_server['host'].'?pubsub;node=/home/'.$c->notifications_server['host'];
|
||||
$this->xmpp_uri .= '/'.preg_replace ( '/@.*$/', '', $c->notifications_server['jid'] ).'/DAViCal'.$this->url;
|
||||
$this->xmpp_uri .= '/'.preg_replace ( '/@.*$/', '', $c->notifications_server['jid'] ).'/DAViCal'.$this->url;
|
||||
$this->xmpp_server = $c->notifications_server['host'];
|
||||
}
|
||||
|
||||
@ -284,7 +286,7 @@ class CalDAVPrincipal
|
||||
$username = $user->username;
|
||||
}
|
||||
}
|
||||
elseif( $user = getUserByName( $username, 'caldav') ) {
|
||||
elseif( $user = getUserByName( $username, 'principal') ) {
|
||||
$user_no = $user->user_no;
|
||||
}
|
||||
return $username;
|
||||
@ -315,6 +317,14 @@ class CalDAVPrincipal
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the privileges bits for the current session user to this resource
|
||||
*/
|
||||
function Privileges() {
|
||||
return $this->privileges;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a representation of the principal as a collection
|
||||
*/
|
||||
|
||||
@ -77,6 +77,12 @@ class CalDAVRequest
|
||||
*/
|
||||
var $collection_type;
|
||||
|
||||
/**
|
||||
* The type of collection being requested:
|
||||
* calendar, schedule-inbox, schedule-outbox
|
||||
*/
|
||||
protected $exists;
|
||||
|
||||
/**
|
||||
* A static structure of supported privileges.
|
||||
*/
|
||||
|
||||
@ -26,25 +26,25 @@ function privilege_to_bits( $raw_privs ) {
|
||||
foreach( $raw_privs AS $priv ) {
|
||||
$trim_priv = trim(strtolower(preg_replace( '/^.*:/', '', $priv)));
|
||||
switch( $trim_priv ) {
|
||||
case 'read' : $out_priv &= 4609; break; // 1 + 512 + 4096
|
||||
case 'write' : $out_priv &= 198; break; // 2 + 4 + 64 + 128
|
||||
case 'write-properties' : $out_priv &= 2; break;
|
||||
case 'write-content' : $out_priv &= 4; break;
|
||||
case 'unlock' : $out_priv &= 8; break;
|
||||
case 'read-acl' : $out_priv &= 16; break;
|
||||
case 'read-current-user-privilege-set' : $out_priv &= 32; break;
|
||||
case 'bind' : $out_priv &= 64; break;
|
||||
case 'unbind' : $out_priv &= 128; break;
|
||||
case 'write-acl' : $out_priv &= 256; break;
|
||||
case 'read-free-busy' : $out_priv &= 4608; break; // 512 + 4096
|
||||
case 'schedule-deliver' : $out_priv &= 7168; break; // 1024 + 2048 + 4096
|
||||
case 'schedule-deliver-invite' : $out_priv &= 1024; break;
|
||||
case 'schedule-deliver-reply' : $out_priv &= 2048; break;
|
||||
case 'schedule-query-freebusy' : $out_priv &= 4096; break;
|
||||
case 'schedule-send' : $out_priv &= 57344; break; // 8192 + 16384 + 32768
|
||||
case 'schedule-send-invite' : $out_priv &= 8192; break;
|
||||
case 'schedule-send-reply' : $out_priv &= 16384; break;
|
||||
case 'schedule-send-freebusy' : $out_priv &= 32768; break;
|
||||
case 'read' : $out_priv |= 4609; break; // 1 + 512 + 4096
|
||||
case 'write' : $out_priv |= 198; break; // 2 + 4 + 64 + 128
|
||||
case 'write-properties' : $out_priv |= 2; break;
|
||||
case 'write-content' : $out_priv |= 4; break;
|
||||
case 'unlock' : $out_priv |= 8; break;
|
||||
case 'read-acl' : $out_priv |= 16; break;
|
||||
case 'read-current-user-privilege-set' : $out_priv |= 32; break;
|
||||
case 'bind' : $out_priv |= 64; break;
|
||||
case 'unbind' : $out_priv |= 128; break;
|
||||
case 'write-acl' : $out_priv |= 256; break;
|
||||
case 'read-free-busy' : $out_priv |= 4608; break; // 512 + 4096
|
||||
case 'schedule-deliver' : $out_priv |= 7168; break; // 1024 + 2048 + 4096
|
||||
case 'schedule-deliver-invite' : $out_priv |= 1024; break;
|
||||
case 'schedule-deliver-reply' : $out_priv |= 2048; break;
|
||||
case 'schedule-query-freebusy' : $out_priv |= 4096; break;
|
||||
case 'schedule-send' : $out_priv |= 57344; break; // 8192 + 16384 + 32768
|
||||
case 'schedule-send-invite' : $out_priv |= 8192; break;
|
||||
case 'schedule-send-reply' : $out_priv |= 16384; break;
|
||||
case 'schedule-send-freebusy' : $out_priv |= 32768; break;
|
||||
default:
|
||||
dbg_error_log( 'ERROR', 'Cannot convert privilege of "%s" into bits.', $priv );
|
||||
|
||||
@ -123,7 +123,7 @@ class DAVResource
|
||||
/**
|
||||
* @var The actual resource content, if it exists and is not a collection
|
||||
*/
|
||||
protected $content;
|
||||
protected $resource;
|
||||
|
||||
/**
|
||||
* @var The type of the resource, possibly multiple
|
||||
@ -195,7 +195,7 @@ class DAVResource
|
||||
$this->exists = null;
|
||||
$this->dav_name = null;
|
||||
$this->unique_tag = null;
|
||||
$this->content = null;
|
||||
$this->resource = null;
|
||||
$this->collection = null;
|
||||
$this->principal = null;
|
||||
$this->resourcetype = null;
|
||||
@ -231,7 +231,7 @@ class DAVResource
|
||||
|
||||
$this->exists = true;
|
||||
foreach( $row AS $k => $v ) {
|
||||
dbg_error_log( 'resource', 'Processing resource property "%s" has "%s".', $row->dav_name, $k );
|
||||
dbg_error_log( 'DAVResource', 'Processing resource property "%s" has "%s".', $row->dav_name, $k );
|
||||
switch ( $k ) {
|
||||
case 'dav_etag':
|
||||
$this->unique_tag = '"'.$v.'"';
|
||||
@ -258,16 +258,20 @@ class DAVResource
|
||||
$ourpath = $matches[1]. '/';
|
||||
}
|
||||
|
||||
/** remove any leading protocol/server/port/prefix... */
|
||||
$base_path = ConstructURL('/');
|
||||
if ( preg_match( '%^(.*?)'.str_replace('%', '\\%',$base_path).'(.*)$%', $ourpath, $matches ) ) {
|
||||
if ( $matches[1] == '' || $matches[1] == $c->protocol_server_port ) {
|
||||
$ourpath = $matches[2];
|
||||
}
|
||||
}
|
||||
|
||||
/** strip doubled slashes */
|
||||
if ( strstr($ourpath,'//') ) $ourpath = preg_replace( '#//+#', '/', $ourpath);
|
||||
|
||||
/** remove any leading protocol/server/port/prefix... */
|
||||
$base_path = ConstructURL('/');
|
||||
$this->dav_name = str_replace( $base_path, '/', $ourpath );
|
||||
if ( substr($ourpath,0,1) != '/' ) $ourpath = '/'.$ourpath;
|
||||
|
||||
if ( substr($this->dav_name,0,1) != '/' ) {
|
||||
$this->dav_name = '/'.$this->dav_name;
|
||||
}
|
||||
$this->dav_name = $ourpath;
|
||||
}
|
||||
|
||||
|
||||
@ -300,13 +304,13 @@ class DAVResource
|
||||
$sql = $base_sql .'dav_name = :raw_path ';
|
||||
$params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id );
|
||||
if ( !preg_match( '#/$#', $this->dav_name ) ) {
|
||||
$sql .= ' OR dav_name = :up_to_slash OR dav_name = :plus_slash';
|
||||
$sql .= ' OR dav_name = :up_to_slash OR dav_name = :plus_slash ';
|
||||
$params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
|
||||
$params[':plus_slash'] = $this->dav_name.'/';
|
||||
}
|
||||
$sql .= 'ORDER BY LENGTH(dav_name) DESC LIMIT 1';
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows == 1 && ($row = $qry->Fetch()) ) {
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
|
||||
$this->collection = $row;
|
||||
if ( $row->is_calendar == 't' )
|
||||
$this->collection->type = 'calendar';
|
||||
@ -331,8 +335,9 @@ EOSQL;
|
||||
$qry->Exec('DAVResource');
|
||||
dbg_error_log( 'DAVResource', 'Created new collection as "$displayname".' );
|
||||
|
||||
$qry = new AwlQuery( $base_sql . 'user_no = :user_no AND dav_name = :dav_name', $params );
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows == 1 && ($row = $qry->Fetch()) ) {
|
||||
$params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id );
|
||||
$qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
|
||||
$this->collection = $row;
|
||||
}
|
||||
}
|
||||
@ -342,12 +347,6 @@ EOSQL;
|
||||
$this->proxy_type = $matches[3];
|
||||
$this->collection->dav_name = $matches[1].'/';
|
||||
}
|
||||
else if ( $this->options['allow_by_email'] && preg_match( '#^/(\S+@\S+[.]\S+)/?$#', $this->dav_name, $matches) ) {
|
||||
/** @TODO: perhaps we should deprecate this in favour of scheduling extensions */
|
||||
$this->collection->type = 'principal_email';
|
||||
$this->collection->dav_name = $matches[1].'/';
|
||||
$this->_is_principal = true;
|
||||
}
|
||||
else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches) ) {
|
||||
$this->collection->dav_name = $matches[1].'/';
|
||||
$this->collection->type = 'principal';
|
||||
@ -362,12 +361,31 @@ EOSQL;
|
||||
$this->collection->dav_name = '/';
|
||||
$this->collection->type = 'root';
|
||||
}
|
||||
else {
|
||||
dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
|
||||
$this->collection->exists = false;
|
||||
$this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
|
||||
}
|
||||
|
||||
$this->_is_collection = ( $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' );
|
||||
if ( $this->_is_collection ) {
|
||||
$this->_is_calendar = $this->collection->is_calendar;
|
||||
$this->_is_addressbook = $this->collection->is_addressbook;
|
||||
$this->dav_name = $this->collection->dav_name;
|
||||
$this->_is_calendar = ($this->collection->type == 'calendar');
|
||||
$this->_is_addressbook = ($this->collection->type == 'addressbook');
|
||||
$this->contenttype = 'httpd/unix-directory';
|
||||
if ( isset($this->collection->dav_etag) ) $this->unique_tag = $this->collection->dav_etag;
|
||||
if ( isset($this->collection->created) ) $this->created = $this->collection->created;
|
||||
if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
|
||||
if ( isset($this->collection->resourcetype) )
|
||||
$this->resourcetype = $this->collection->resourcetype;
|
||||
else {
|
||||
$this->resourcetype = '<DAV::collection/>';
|
||||
if ( $this->_is_principal )
|
||||
$this->resourcetype .= '<DAV::principal/>';
|
||||
else {
|
||||
$this->exists = (!isset($this->collection->exists) || $this->collection->exists);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,6 +396,13 @@ EOSQL;
|
||||
function FetchPrincipal() {
|
||||
global $c, $session;
|
||||
$this->principal = new CalDAVPrincipal( array( "path" => $this->dav_name ) );
|
||||
if ( $this->IsPrincipal() ) {
|
||||
$this->contenttype = 'httpd/unix-directory';
|
||||
$this->unique_tag = $this->principal->dav_etag;
|
||||
$this->created = $this->principal->created;
|
||||
$this->modified = $this->principal->modified;
|
||||
$this->resourcetype = '<DAV::principal/>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -398,9 +423,14 @@ EOQRY;
|
||||
$params = array( ':dav_name' => $this->dav_name );
|
||||
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows > 0 ) {
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
|
||||
$this->exists = true;
|
||||
$this->resource = $qry->Fetch();
|
||||
$this->unique_tag = $this->resource->dav_etag;
|
||||
$this->created = $this->resource->created;
|
||||
$this->modified = $this->resource->modified;
|
||||
$this->contenttype = 'text/calendar';
|
||||
$this->resourcetype = '';
|
||||
}
|
||||
else {
|
||||
$this->exists = false;
|
||||
@ -416,29 +446,54 @@ EOQRY;
|
||||
|
||||
if ( $this->dav_name == '/' || $this->dav_name == '' ) {
|
||||
$this->privileges = 1; // read
|
||||
dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' );
|
||||
// dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $session->AllowedTo('Admin') || $session->user_no == $this->user_no ) {
|
||||
if ( $session->AllowedTo('Admin') ) {
|
||||
$this->privileges = privilege_to_bits('all');
|
||||
dbg_error_log( 'DAVResource', 'Full permissions for %s', ( $session->user_no == $this->user_no ? 'user accessing their own hierarchy' : 'an administrator') );
|
||||
// dbg_error_log( 'DAVResource', 'Full permissions for an administrator.' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->IsPrincipal() ) {
|
||||
if ( !isset($this->principal) ) $this->FetchPrincipal();
|
||||
$this->privileges = $this->principal->Privileges();
|
||||
// dbg_error_log( 'DAVResource', 'Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$this->privileges = 0;
|
||||
if ( !isset($this->collection) ) $this->FetchCollection();
|
||||
if ( !isset($this->collection->path_privileges) ) {
|
||||
$parent_path = preg_replace('{/[^/]*/$}', '/', $this->collection->dav_name );
|
||||
// dbg_error_log( 'DAVResource', 'Checking privileges of "%s" - parent of "%s"', $parent_path, $this->collection->dav_name );
|
||||
$parent = new DAVResource( $parent_path );
|
||||
|
||||
$this->collection->path_privileges = $parent->Privileges();
|
||||
}
|
||||
|
||||
$this->privileges = $this->collection->path_privileges;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the privileges bits for the current session user to this resource
|
||||
*/
|
||||
function Privileges() {
|
||||
if ( !isset($this->privileges) ) $this->FetchPrivileges();
|
||||
return $this->privileges;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the user has the privileges to do what is requested.
|
||||
*/
|
||||
function HavePrivilegeTo( $do_what ) {
|
||||
if ( !isset($this->privileges) ) $this->FetchPrivileges();
|
||||
$test_bits = privilege_to_bits( $do_what );
|
||||
// dbg_error_log( 'DAVResource', 'Testing privileges of "%s"(%d) against allowed "%s" => "%s"', $do_what, $test_bits, $this->privileges, ($this->privileges & $test_bits) );
|
||||
return ($this->privileges & $test_bits) > 0;
|
||||
}
|
||||
|
||||
@ -454,7 +509,7 @@ EOQRY;
|
||||
if ( !isset($xmldoc) && isset($GLOBALS['reply']) ) $xmldoc = $GLOBALS['reply'];
|
||||
$privileges = array();
|
||||
foreach( $privilege_names AS $k ) {
|
||||
dbg_error_log( 'DAVResource', 'Adding privilege "%s".', $k );
|
||||
// dbg_error_log( 'DAVResource', 'Adding privilege "%s".', $k );
|
||||
$privilege = new XMLElement('privilege');
|
||||
if ( isset($xmldoc) )
|
||||
$xmldoc->NSElement($privilege,$k);
|
||||
@ -572,15 +627,56 @@ EOQRY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resource is a collection
|
||||
*/
|
||||
function IsCollection() {
|
||||
return $this->_is_collection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resource is a principal
|
||||
*/
|
||||
function IsPrincipal() {
|
||||
return $this->_is_collection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resource is a calendar
|
||||
*/
|
||||
function IsCalendar() {
|
||||
return $this->_is_calendar;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resource is an addressbook
|
||||
*/
|
||||
function IsAddressbook() {
|
||||
return $this->_is_addressbook;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether this resource actually exists, in the virtual sense, within the hierarchy
|
||||
*/
|
||||
function Exists() {
|
||||
if ( isset($this->exists) ) return $this->exists;
|
||||
if ( isset($this->collection) && isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' ) {
|
||||
return true;
|
||||
if ( ! isset($this->exists) ) {
|
||||
if ( $this->IsPrincipal() ) {
|
||||
if ( !isset($this->principal) ) $this->FetchPrincipal();
|
||||
$this->exists = $this->principal->Exists();
|
||||
}
|
||||
else if ( $this->IsCollection() ) {
|
||||
if ( !isset($this->collection) ) $this->FetchCollection();
|
||||
}
|
||||
else {
|
||||
if ( !isset($this->resource) ) $this->FetchResource();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
dbg_error_log('DAVResource',' Checking whether "%s" exists. It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
|
||||
return $this->exists;
|
||||
}
|
||||
|
||||
|
||||
@ -605,6 +701,23 @@ EOQRY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the principal-URL for this resource
|
||||
*/
|
||||
function unique_tag() {
|
||||
if ( isset($this->unique_tag) ) return $this->unique_tag;
|
||||
if ( $this->IsCollection() && !isset($this->collection) ) {
|
||||
$this->FetchCollection();
|
||||
if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
|
||||
}
|
||||
else if ( !isset($this->resource) ) $this->FetchResource();
|
||||
|
||||
if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
|
||||
|
||||
return $this->unique_tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the target collection is publicly_readable
|
||||
*/
|
||||
@ -632,7 +745,7 @@ EOQRY;
|
||||
else {
|
||||
$qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
|
||||
array( ':parent_name' => $this->collection->parent_container ) );
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows > 0 && $parent = $qry->Fetch() ) {
|
||||
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
|
||||
if ( $parent->is_calendar == 't' )
|
||||
$this->parent_container_type = 'calendar';
|
||||
else if ( $parent->is_addressbook == 't' )
|
||||
@ -649,15 +762,56 @@ EOQRY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return general server-related properties, in plain form
|
||||
*/
|
||||
function GetProperty( $name ) {
|
||||
global $c, $session;
|
||||
|
||||
// dbg_error_log( 'DAVResource', 'Processing "%s".', $name );
|
||||
$value = null;
|
||||
|
||||
switch( $name ) {
|
||||
case 'collection_id':
|
||||
if ( !isset($this->collection) ) $this->FetchCollection();
|
||||
return $this->collection->collection_id;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( $this->_is_principal ) {
|
||||
if ( !isset($this->principal) ) $this->FetchPrincipal();
|
||||
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
|
||||
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
|
||||
}
|
||||
else if ( $this->_is_collection ) {
|
||||
if ( !isset($this->collection) ) $this->FetchCollection();
|
||||
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
|
||||
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
|
||||
}
|
||||
else {
|
||||
if ( !isset($this->resource) ) $this->FetchResource();
|
||||
if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
|
||||
if ( !isset($this->principal) ) $this->FetchPrincipal();
|
||||
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
|
||||
if ( !isset($this->collection) ) $this->FetchCollection();
|
||||
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
|
||||
}
|
||||
dbg_error_log( 'ERROR', 'Request for property "%s" which is not understood.', $name );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return general server-related properties for this URL
|
||||
*/
|
||||
function ResourceProperty( $tag, $prop, $reply = null ) {
|
||||
function ResourceProperty( $tag, $prop, $reply = null, &$denied ) {
|
||||
global $c, $session;
|
||||
|
||||
if ( $reply === null ) $reply = $GLOBALS['reply'];
|
||||
|
||||
dbg_error_log( 'resource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
|
||||
dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
|
||||
|
||||
switch( $tag ) {
|
||||
case 'DAV::href':
|
||||
@ -677,7 +831,7 @@ EOQRY;
|
||||
break;
|
||||
|
||||
case 'DAV::getlastmodified':
|
||||
$prop->NewElement('getlastmodified', $this->last_modified );
|
||||
$prop->NewElement('getlastmodified', $this->modified );
|
||||
break;
|
||||
|
||||
case 'DAV::creationdate':
|
||||
@ -703,7 +857,7 @@ EOQRY;
|
||||
|
||||
case 'DAV::getetag':
|
||||
if ( $this->_is_collection ) {
|
||||
$not_found[] = $reply->Tag($tag);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$prop->NewElement('getetag', $this->unique_tag );
|
||||
@ -719,17 +873,22 @@ EOQRY;
|
||||
$prop->NewElement('http://calendarserver.org/ns/:getctag', $this->unique_tag );
|
||||
}
|
||||
else {
|
||||
$not_found[] = $reply->Tag($tag);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'urn:ietf:params:xml:ns:caldav:calendar-data':
|
||||
if ( isset($this->caldav_data) ) {
|
||||
if ( $this->_is_collection ) {
|
||||
if ( !isset($this->resource) ) $this->FetchResource();
|
||||
$reply->CalDAVElement($prop, $k, $this->resource->caldav_data );
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dbg_error_log( 'resource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
|
||||
dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -746,15 +905,15 @@ EOQRY;
|
||||
function GetPropStat( $properties ) {
|
||||
global $session, $c, $request, $reply;
|
||||
|
||||
dbg_error_log('resource',': GetPropStat: href "%s"', $this->dav_name );
|
||||
dbg_error_log('DAVResource',': 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->dav_name, $k, $tag );
|
||||
if ( ! $this->ResourceProperty($tag, $prop, $reply) ) {
|
||||
dbg_error_log( 'resource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
|
||||
// dbg_error_log( 'DAVResource', 'Looking at resource "%s" for property [%s]"%s".', $this->dav_name, $k, $tag );
|
||||
if ( ! $this->ResourceProperty($tag, $prop, $reply, $denied ) ) {
|
||||
dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
|
||||
$not_found[] = $reply->Tag($tag);
|
||||
}
|
||||
}
|
||||
@ -795,7 +954,7 @@ EOQRY;
|
||||
function RenderAsXML( $properties, &$reply, $props_only = false ) {
|
||||
global $session, $c, $request;
|
||||
|
||||
dbg_error_log('principal',': RenderAsXML: Principal "%s"', $this->username );
|
||||
dbg_error_log('DAVResource',': RenderAsXML: Principal "%s"', $this->username );
|
||||
|
||||
$prop = new XMLElement('prop');
|
||||
$denied = array();
|
||||
@ -812,7 +971,7 @@ EOQRY;
|
||||
$status = new XMLElement('status', 'HTTP/1.1 200 OK' );
|
||||
|
||||
$propstat = new XMLElement( 'propstat', array( $prop, $status) );
|
||||
$href = $reply->href( ConstructURL($this->dav_name) ); /** TODO: make ::href() into an accessor */
|
||||
$href = $reply->href( ConstructURL($this->dav_name) ); /** @TODO: make ::href() into an accessor */
|
||||
|
||||
$elements = array($href,$propstat);
|
||||
|
||||
|
||||
@ -23,25 +23,25 @@ class HTTPAuthSession {
|
||||
* User ID number
|
||||
* @var user_no int
|
||||
*/
|
||||
var $user_no;
|
||||
public $user_no;
|
||||
|
||||
/**
|
||||
* User e-mail
|
||||
* @var email string
|
||||
*/
|
||||
var $email;
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* User full name
|
||||
* @var fullname string
|
||||
*/
|
||||
var $fullname;
|
||||
public $fullname;
|
||||
|
||||
/**
|
||||
* Group rights
|
||||
* @var groups array
|
||||
*/
|
||||
var $groups;
|
||||
public $groups;
|
||||
/**#@-*/
|
||||
|
||||
/**
|
||||
@ -228,7 +228,7 @@ class HTTPAuthSession {
|
||||
if (isset($c->authenticate_hook['optional']) && $c->authenticate_hook['optional']) {
|
||||
if ($hook_response !== false) { return $hook_response; }
|
||||
} else {
|
||||
return $hook_response;
|
||||
return $hook_response;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ if ( !isset($_SERVER['SERVER_NAME']) ) {
|
||||
/**
|
||||
* Calculate the simplest form of reference to this page, excluding the PATH_INFO following the script name.
|
||||
*/
|
||||
$c->protocol_server_port_script = sprintf( '%s://%s%s%s',
|
||||
$c->protocol_server_port = sprintf( '%s://%s%s',
|
||||
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'? 'https' : 'http'),
|
||||
$_SERVER['SERVER_NAME'],
|
||||
(
|
||||
@ -76,8 +76,8 @@ $c->protocol_server_port_script = sprintf( '%s://%s%s%s',
|
||||
|| (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && $_SERVER['SERVER_PORT'] == 443 )
|
||||
? ''
|
||||
: ':'.$_SERVER['SERVER_PORT']
|
||||
),
|
||||
($_SERVER['SCRIPT_NAME'] == '/index.php' ? '' : $_SERVER['SCRIPT_NAME']) );
|
||||
) );
|
||||
$c->protocol_server_port_script = $c->protocol_server_port . ($_SERVER['SCRIPT_NAME'] == '/index.php' ? '' : $_SERVER['SCRIPT_NAME']);
|
||||
|
||||
init_gettext( 'davical', '../locale' );
|
||||
|
||||
@ -167,7 +167,11 @@ function getUserByName( $username, $use_cache = true ) {
|
||||
// Provide some basic caching in case this ends up being overused.
|
||||
if ( $use_cache && isset( $_known_users_name[$username] ) ) return $_known_users_name[$username];
|
||||
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified FROM usr WHERE lower(username) = lower(?) ", $username );
|
||||
global $session;
|
||||
if ( isset($session->user_no) )
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, user_privileges(?,usr.user_no) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE lower(username) = lower(?) ", $session->user_no, $username );
|
||||
else
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, 0::BIT(24) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE lower(username) = lower(?) ", $username );
|
||||
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
|
||||
$_known_users_name[$username] = $qry->Fetch();
|
||||
$id = $_known_users_name[$username]->user_no;
|
||||
@ -188,7 +192,8 @@ function getUserByID( $user_no, $use_cache = true ) {
|
||||
// Provide some basic caching in case this ends up being overused.
|
||||
if ( $use_cache && isset( $_known_users_id[$user_no] ) ) return $_known_users_id[$user_no];
|
||||
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified FROM usr WHERE user_no = ? ", intval($user_no) );
|
||||
global $session;
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, user_privileges(?,usr.user_no) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE user_no = ? ", $session->user_no, intval($user_no) );
|
||||
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
|
||||
$_known_users_id[$user_no] = $qry->Fetch();
|
||||
$name = $_known_users_id[$user_no]->username;
|
||||
|
||||
@ -68,7 +68,7 @@ if ( !isset($_SERVER['SERVER_NAME']) ) {
|
||||
/**
|
||||
* Calculate the simplest form of reference to this page, excluding the PATH_INFO following the script name.
|
||||
*/
|
||||
$c->protocol_server_port_script = sprintf( '%s://%s%s%s',
|
||||
$c->protocol_server_port = sprintf( '%s://%s%s',
|
||||
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'? 'https' : 'http'),
|
||||
$_SERVER['SERVER_NAME'],
|
||||
(
|
||||
@ -76,8 +76,8 @@ $c->protocol_server_port_script = sprintf( '%s://%s%s%s',
|
||||
|| (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && $_SERVER['SERVER_PORT'] == 443 )
|
||||
? ''
|
||||
: ':'.$_SERVER['SERVER_PORT']
|
||||
),
|
||||
($_SERVER['SCRIPT_NAME'] == '/index.php' ? '' : $_SERVER['SCRIPT_NAME']) );
|
||||
) );
|
||||
$c->protocol_server_port_script = $c->protocol_server_port . ($_SERVER['SCRIPT_NAME'] == '/index.php' ? '' : $_SERVER['SCRIPT_NAME']);
|
||||
|
||||
init_gettext( 'davical', '../locale' );
|
||||
|
||||
@ -167,7 +167,11 @@ function getUserByName( $username, $use_cache = true ) {
|
||||
// Provide some basic caching in case this ends up being overused.
|
||||
if ( $use_cache && isset( $_known_users_name[$username] ) ) return $_known_users_name[$username];
|
||||
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified FROM usr WHERE lower(username) = lower(?) ", $username );
|
||||
global $session;
|
||||
if ( isset($session->user_no) )
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, user_privileges(?,usr.user_no) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE lower(username) = lower(?) ", $session->user_no, $username );
|
||||
else
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, 0::BIT(24) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE lower(username) = lower(?) ", $username );
|
||||
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
|
||||
$_known_users_name[$username] = $qry->Fetch();
|
||||
$id = $_known_users_name[$username]->user_no;
|
||||
@ -188,7 +192,8 @@ function getUserByID( $user_no, $use_cache = true ) {
|
||||
// Provide some basic caching in case this ends up being overused.
|
||||
if ( $use_cache && isset( $_known_users_id[$user_no] ) ) return $_known_users_id[$user_no];
|
||||
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified FROM usr WHERE user_no = ? ", intval($user_no) );
|
||||
global $session;
|
||||
$qry = new PgQuery( "SELECT *, to_char(updated at time zone 'GMT','Dy, DD Mon IYYY HH24:MI:SS \"GMT\"') AS modified, principal.*, user_privileges(?,usr.user_no) AS privileges FROM usr LEFT JOIN principal USING(user_no) WHERE user_no = ? ", $session->user_no, intval($user_no) );
|
||||
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
|
||||
$_known_users_id[$user_no] = $qry->Fetch();
|
||||
$name = $_known_users_id[$user_no]->username;
|
||||
|
||||
@ -10,6 +10,8 @@
|
||||
*/
|
||||
dbg_error_log("MOVE", "method handler");
|
||||
|
||||
require_once('DAVResource.php');
|
||||
|
||||
if ( ! $request->AllowedTo("read") ) {
|
||||
$request->DoResponse(403);
|
||||
}
|
||||
@ -28,18 +30,26 @@ if ( $request->path == '/' || $request->IsPrincipal() || $request->destination =
|
||||
$request->DoResponse( 403 );
|
||||
}
|
||||
|
||||
if ( !class_exists('DAVResource') ) require('DAVResource.php');
|
||||
$dest = new DAVResource($request->destination);
|
||||
|
||||
if ( $dest->path == '/' || $dest->IsPrincipal() ) {
|
||||
if ( $dest->dav_name() == '/' || $dest->IsPrincipal() ) {
|
||||
$request->DoResponse( 403 );
|
||||
}
|
||||
|
||||
if ( ! $request->overwrite && $dest->Exists() ) {
|
||||
$request->DoResponse( 412 );
|
||||
$request->DoResponse( 412, translate('Not overwriting existing destination resource') );
|
||||
}
|
||||
|
||||
if ( $request->IsCollection() ) {
|
||||
if ( isset($request->etag_none_match) && $request->etag_none_match != '*' ) {
|
||||
$request->DoResponse( 412 ); /** request to move, but only if there is no source? WTF! */
|
||||
}
|
||||
|
||||
$src = new DAVResource($request->path);
|
||||
if ( ! $src->Exists() ) {
|
||||
$request->DoResponse( 412, translate('Source resource does not exist.') );
|
||||
}
|
||||
|
||||
if ( $src->IsCollection() ) {
|
||||
switch( $dest->ContainerType() ) {
|
||||
case 'calendar':
|
||||
case 'addressbook':
|
||||
@ -48,14 +58,41 @@ if ( $request->IsCollection() ) {
|
||||
$request->DoResponse( 412, translate('Special collections may not contain a calendar or other special collection.') );
|
||||
};
|
||||
}
|
||||
else {
|
||||
if ( (isset($request->etag_if_match) && $request->etag_if_match != '' )
|
||||
|| ( isset($request->etag_none_match) && $request->etag_none_match != '') ) {
|
||||
|
||||
if ( ! $request->AllowedTo('delete') ) $request->DoResponse( 403 );
|
||||
if ( ! $dest->HaveRightsTo('DAV::write') ) $request->DoResponse( 403 );
|
||||
if ( ! $dest->Exists() && !$dest->HaveRightsTo('DAV::bind') ) $request->DoResponse( 403 );
|
||||
// if ( ! $request->HaveRightsTo('DAV::unbind') ) $request->DoResponse( 403 );
|
||||
/**
|
||||
* RFC2068, 14.25:
|
||||
* If none of the entity tags match, or if "*" is given and no current
|
||||
* entity exists, the server MUST NOT perform the requested method, and
|
||||
* MUST return a 412 (Precondition Failed) response.
|
||||
*
|
||||
* RFC2068, 14.26:
|
||||
* If any of the entity tags match the entity tag of the entity that
|
||||
* would have been returned in the response to a similar GET request
|
||||
* (without the If-None-Match header) on that resource, or if "*" is
|
||||
* given and any current entity exists for that resource, then the
|
||||
* server MUST NOT perform the requested method.
|
||||
*/
|
||||
$error = '';
|
||||
if ( isset($request->etag_if_match) && $request->etag_if_match != $src->unique_tag() ) {
|
||||
$error = translate( 'Existing resource does not match "If-Match" header - not accepted.');
|
||||
}
|
||||
else if ( isset($request->etag_none_match) && $request->etag_none_match != '' && $request->etag_none_match == $src->unique_tag() ) {
|
||||
$error = translate( 'Existing resource matches "If-None-Match" header - not accepted.');
|
||||
}
|
||||
if ( $error != '' ) $request->DoResponse( 412, $error );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $src->HavePrivilegeTo('DAV::unbind') ) $request->DoResponse( 403 );
|
||||
if ( ! $dest->HavePrivilegeTo('DAV::write') ) $request->DoResponse( 403 );
|
||||
if ( ! $dest->Exists() && !$dest->HavePrivilegeTo('DAV::bind') ) $request->DoResponse( 403 );
|
||||
|
||||
|
||||
function rollback( $response_code = 412 ) {
|
||||
global $request;
|
||||
$qry = new AwlQuery('ROLLBACK');
|
||||
$qry->Exec('move'); // Just in case
|
||||
$request->DoResponse( $response_code );
|
||||
@ -66,21 +103,73 @@ function rollback( $response_code = 412 ) {
|
||||
$qry = new AwlQuery('BEGIN');
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
|
||||
if ( $request->IsCollection() ) {
|
||||
$src_name = $src->dav_name();
|
||||
$dst_name = $dest->dav_name();
|
||||
$src_collection = $src->GetProperty('collection_id');
|
||||
$dst_collection = $dest->GetProperty('collection_id');
|
||||
$src_user_no = $src->GetProperty('user_no');
|
||||
$dst_user_no = $dest->GetProperty('user_no');
|
||||
|
||||
|
||||
if ( $src->IsCollection() ) {
|
||||
if ( $dest->Exists() ) {
|
||||
$qry = new AwlQuery( 'DELETE FROM collection WHERE dav_name = :dst_name', array( ':dst_name' => $dst_name ) );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
}
|
||||
/** @TODO: Need to confirm this will work correctly if we move this into another user's hierarchy. */
|
||||
$qry = new AwlQuery( 'UPDATE collection SET dav_name = :new_dav_name WHERE collection_id = :collection_id', array(
|
||||
':new_dav_name' => $dest->dav_name(),
|
||||
':collection_id' => $request->collection
|
||||
);
|
||||
$sql = 'UPDATE collection SET dav_name = :dst_name ';
|
||||
$params = array(':dst_name' => $dst_name);
|
||||
if ( $src_user_no != $dst_user_no ) {
|
||||
$sql .= ', user_no = :dst_user_no';
|
||||
$params[':dst_user_no'] = $dst_user_no;
|
||||
}
|
||||
$sql .= 'WHERE collection_id = :src_collection';
|
||||
$params[':src_collection'] = $src_collection;
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
}
|
||||
else {
|
||||
$qry = new AwlQuery( 'UPDATE caldav_data SET dav_name = :new_dav_name WHERE dav_name = :old_dav_name', array(
|
||||
':old_dav_name' => $request->dav_name(),
|
||||
':new_dav_name' => $dest->dav_name()
|
||||
);
|
||||
if ( $dest->Exists() ) {
|
||||
$qry = new AwlQuery( 'DELETE FROM caldav_data WHERE dav_name = :dst_name', array( ':dst_name' => $dst_name) );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
}
|
||||
$sql = 'UPDATE caldav_data SET dav_name = :dst_name';
|
||||
$params = array( ':dst_name' => $dst_name );
|
||||
if ( $src_user_no != $dst_user_no ) {
|
||||
$sql .= ', user_no = :dst_user_no';
|
||||
$params[':dst_user_no'] = $dst_user_no;
|
||||
}
|
||||
if ( $src_collection != $dst_collection ) {
|
||||
$sql .= ', collection_id = :dst_collection';
|
||||
$params[':dst_collection'] = $dst_collection;
|
||||
}
|
||||
$sql .=' WHERE dav_name = :src_name';
|
||||
$params[':src_name'] = $src_name;
|
||||
$qry = new AwlQuery( $sql, $params );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
|
||||
$qry = new AwlQuery( 'SELECT write_sync_change( :src_collection, 404, :src_name );', array(
|
||||
':src_name' => $src_name,
|
||||
':src_collection' => $src_collection
|
||||
) );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
if ( function_exists('log_caldav_action') ) {
|
||||
log_caldav_action( 'DELETE', $src->GetProperty('uid'), $src_user_no, $src_collection, $src_name );
|
||||
}
|
||||
|
||||
$qry = new AwlQuery( 'SELECT write_sync_change( :dst_collection, :sync_type, :dst_name );', array(
|
||||
':dst_name' => $dst_name,
|
||||
':dst_collection' => $dst_collection,
|
||||
':sync_type' => ( $dest->Exists() ? 200 : 201 )
|
||||
) );
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
if ( function_exists('log_caldav_action') ) {
|
||||
log_caldav_action( ( $dest->Exists() ? 'UPDATE' : 'INSERT' ), $src->GetProperty('uid'), $dst_user_no, $dst_collection, $dst_name );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$qry = new PgQuery('COMMIT');
|
||||
if ( !$qry->Exec('move') ) rollback(500);
|
||||
|
||||
$request->DoResponse( ($put_action_type == 'INSERT' ? 201 : 204) );
|
||||
$request->DoResponse( 200 );
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
*
|
||||
* @package davical
|
||||
* @subpackage caldav
|
||||
* @author Andrew McMillan <andrew@catalyst.net.nz>
|
||||
* @copyright Catalyst .Net Ltd
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
|
||||
* @author Andrew McMillan <andrew@mcmillan.net.nz>
|
||||
* @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/>
|
||||
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
|
||||
*/
|
||||
dbg_error_log("OPTIONS", "method handler");
|
||||
|
||||
@ -53,8 +53,11 @@ if ( !$exists ) {
|
||||
*/
|
||||
if ( isset($c->override_allowed_methods) )
|
||||
$allowed = $c->override_allowed_methods;
|
||||
else if ( isset($request->supported_methods) ) {
|
||||
$allowed = implode( ', ', array_keys($request->supported_methods) );
|
||||
}
|
||||
else {
|
||||
$allowed = "OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, MKCOL, MKCALENDAR, LOCK, UNLOCK, REPORT, PROPPATCH, POST";
|
||||
$allowed = "OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, MKCOL, MKCALENDAR, LOCK, UNLOCK, REPORT, PROPPATCH, POST, MOVE";
|
||||
if ( $request->path == '/' ) {
|
||||
$exists = true;
|
||||
$allowed = "OPTIONS, GET, HEAD, PROPFIND, REPORT";
|
||||
@ -65,4 +68,3 @@ header( "Allow: $allowed");
|
||||
|
||||
$request->DoResponse( 200, "" );
|
||||
|
||||
?>
|
||||
|
||||
@ -365,9 +365,9 @@ function import_collection( $ics_content, $user_no, $path, $caldav_context ) {
|
||||
}
|
||||
|
||||
$sql .= <<<EOSQL
|
||||
INSERT INTO calendar_item (user_no, dav_name, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
|
||||
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
|
||||
description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id )
|
||||
VALUES ( ?, ?, ?, ?, ?, ?, $dtend, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
VALUES ( ?, ?, currval('dav_id_seq'), ?, ?, ?, ?, $dtend, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
EOSQL;
|
||||
|
||||
$qry = new PgQuery( $sql, $user_no, $resource_path, $etag, $first->GetPValue('UID'), $dtstamp,
|
||||
@ -417,13 +417,13 @@ function putCalendarResource( &$request, $author, $caldav_context ) {
|
||||
* entity exists, the server MUST NOT perform the requested method, and
|
||||
* MUST return a 412 (Precondition Failed) response.
|
||||
*/
|
||||
rollback_on_error( $caldav_context, $request->user_no, $request->path, 412, translate('Resource changed on server - not changed.') );
|
||||
rollback_on_error( $caldav_context, $request->user_no, $request->path, translate('Resource changed on server - not changed.'), 412 );
|
||||
}
|
||||
|
||||
$put_action_type = 'INSERT';
|
||||
|
||||
if ( ! $request->AllowedTo('create') ) {
|
||||
rollback_on_error( $caldav_context, $request->user_no, $request->path, 403, translate('You may not add entries to this calendar.') );
|
||||
rollback_on_error( $caldav_context, $request->user_no, $request->path, translate('You may not add entries to this calendar.'), 403 );
|
||||
}
|
||||
}
|
||||
elseif ( $qry->rows == 1 ) {
|
||||
@ -447,7 +447,7 @@ function putCalendarResource( &$request, $author, $caldav_context ) {
|
||||
if ( isset($request->etag_if_match) && $request->etag_if_match != $icalendar->dav_etag ) {
|
||||
$error = translate( 'Existing resource does not match "If-Match" header - not accepted.');
|
||||
}
|
||||
if ( isset($etag_none_match) && $etag_none_match != '' && ($etag_none_match == $icalendar->dav_etag || $etag_none_match == '*') ) {
|
||||
if ( isset($request->etag_none_match) && $request->etag_none_match != '' && ($request->etag_none_match == $icalendar->dav_etag || $request->etag_none_match == '*') ) {
|
||||
$error = translate( 'Existing resource matches "If-None-Match" header - not accepted.');
|
||||
}
|
||||
$request->DoResponse( 412, $error );
|
||||
@ -490,12 +490,21 @@ function write_resource( $user_no, $path, $caldav_data, $collection_id, $author,
|
||||
global $tz_regex;
|
||||
|
||||
$resources = $ic->GetComponents('VTIMEZONE',false); // Not matching VTIMEZONE
|
||||
$first = $resources[0];
|
||||
if ( !isset($resources[0]) ) {
|
||||
$resource_type = 'Unknown';
|
||||
/** @TODO: Handle writing non-calendar resources, like address book entries or random file data */
|
||||
rollback_on_error( $caldav_context, $user_no, $path, translate('No calendar content'), 412 );
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
$first = $resources[0];
|
||||
$resource_type = $first->GetType();
|
||||
}
|
||||
|
||||
if ( $put_action_type == 'INSERT' ) {
|
||||
create_scheduling_requests($vcal);
|
||||
$qry = new PgQuery( 'BEGIN; INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id ) VALUES( ?, ?, ?, ?, ?, ?, current_timestamp, current_timestamp, ? )',
|
||||
$user_no, $path, $etag, $caldav_data, $first->GetType(), $author, $collection_id );
|
||||
$user_no, $path, $etag, $caldav_data, $resource_type, $author, $collection_id );
|
||||
if ( !$qry->Exec('PUT') ) {
|
||||
rollback_on_error( $caldav_context, $user_no, $path);
|
||||
return false;
|
||||
@ -504,7 +513,7 @@ function write_resource( $user_no, $path, $caldav_data, $collection_id, $author,
|
||||
else {
|
||||
update_scheduling_requests($vcal);
|
||||
$qry = new PgQuery( 'BEGIN;UPDATE caldav_data SET caldav_data=?, dav_etag=?, caldav_type=?, logged_user=?, modified=current_timestamp WHERE user_no=? AND dav_name=?',
|
||||
$caldav_data, $etag, $first->GetType(), $author, $user_no, $path );
|
||||
$caldav_data, $etag, $resource_type, $author, $user_no, $path );
|
||||
if ( !$qry->Exec('PUT') ) {
|
||||
rollback_on_error( $caldav_context, $user_no, $path);
|
||||
return false;
|
||||
@ -639,11 +648,11 @@ EOSQL;
|
||||
}
|
||||
else {
|
||||
$sql .= <<<EOSQL
|
||||
INSERT INTO calendar_item (user_no, dav_name, dav_etag, uid, dtstamp,
|
||||
INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
|
||||
dtstart, dtend, summary, location, class, transp,
|
||||
description, rrule, tz_id, last_modified, url, priority,
|
||||
created, due, percent_complete, status, collection_id )
|
||||
VALUES ( $user_no, $escaped_path, ?, ?, ?,
|
||||
VALUES ( $user_no, $escaped_path, currval('dav_id_seq'), ?, ?, ?,
|
||||
?, $dtend, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, $collection_id );
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user