diff --git a/dba/appuser_permissions.txt b/dba/appuser_permissions.txt
index 325af656..ed3da51a 100644
--- a/dba/appuser_permissions.txt
+++ b/dba/appuser_permissions.txt
@@ -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
diff --git a/dba/caldav_functions.sql b/dba/caldav_functions.sql
index 45333372..daf92d27 100644
--- a/dba/caldav_functions.sql
+++ b/dba/caldav_functions.sql
@@ -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, '[^/]*$', '');
diff --git a/dba/davical.sql b/dba/davical.sql
index e5173184..a5ffffc2 100644
--- a/dba/davical.sql
+++ b/dba/davical.sql
@@ -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 (
diff --git a/htdocs/caldav.php b/htdocs/caldav.php
index cf5fc13c..1da98261 100644
--- a/htdocs/caldav.php
+++ b/htdocs/caldav.php
@@ -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;
diff --git a/inc/AwlQuery.php b/inc/AwlQuery.php
index 7b3586c6..3bd1ef49 100644
--- a/inc/AwlQuery.php
+++ b/inc/AwlQuery.php
@@ -350,6 +350,14 @@ class AwlQuery
}
+ /**
+ * Return the count of rows retrieved/affected
+ */
+ function rows() {
+ return $this->rows;
+ }
+
+
/**
* Execute the query, logging any debugging.
*
diff --git a/inc/CalDAVPrincipal.php b/inc/CalDAVPrincipal.php
index a9640b5c..d86fc460 100644
--- a/inc/CalDAVPrincipal.php
+++ b/inc/CalDAVPrincipal.php
@@ -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
*/
diff --git a/inc/CalDAVRequest.php b/inc/CalDAVRequest.php
index 6951dd6e..b932cb7d 100644
--- a/inc/CalDAVRequest.php
+++ b/inc/CalDAVRequest.php
@@ -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.
*/
diff --git a/inc/DAVResource.php b/inc/DAVResource.php
index 9ad0960f..73feaed6 100644
--- a/inc/DAVResource.php
+++ b/inc/DAVResource.php
@@ -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 = '';
+ if ( $this->_is_principal )
+ $this->resourcetype .= '';
+ 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 = '';
+ }
}
@@ -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);
diff --git a/inc/HTTPAuthSession.php b/inc/HTTPAuthSession.php
index 963b54a2..57c2b409 100644
--- a/inc/HTTPAuthSession.php
+++ b/inc/HTTPAuthSession.php
@@ -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;
}
}
diff --git a/inc/always.php b/inc/always.php
index 09e73e02..381cd171 100644
--- a/inc/always.php
+++ b/inc/always.php
@@ -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;
diff --git a/inc/always.php.in b/inc/always.php.in
index 5231f672..21f6bcc2 100644
--- a/inc/always.php.in
+++ b/inc/always.php.in
@@ -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;
diff --git a/inc/caldav-MOVE.php b/inc/caldav-MOVE.php
index 264614a0..5b3242c3 100644
--- a/inc/caldav-MOVE.php
+++ b/inc/caldav-MOVE.php
@@ -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 );
diff --git a/inc/caldav-OPTIONS.php b/inc/caldav-OPTIONS.php
index d0b153b0..6fcb4845 100644
--- a/inc/caldav-OPTIONS.php
+++ b/inc/caldav-OPTIONS.php
@@ -4,9 +4,9 @@
*
* @package davical
* @subpackage caldav
-* @author Andrew McMillan
-* @copyright Catalyst .Net Ltd
-* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
+* @author Andrew McMillan
+* @copyright Catalyst .Net Ltd, Morphoss Ltd
+* @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, "" );
-?>
diff --git a/inc/caldav-PUT-functions.php b/inc/caldav-PUT-functions.php
index 39516dad..56c0b199 100644
--- a/inc/caldav-PUT-functions.php
+++ b/inc/caldav-PUT-functions.php
@@ -365,9 +365,9 @@ function import_collection( $ics_content, $user_no, $path, $caldav_context ) {
}
$sql .= <<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 .= <<