Remove case-folding of incoming XML.

This commit is contained in:
Andrew McMillan 2008-10-21 23:04:31 +13:00
parent a8ae3b1406
commit 5ec4c33670
14 changed files with 683 additions and 580 deletions

View File

@ -52,11 +52,23 @@ class CalDAVPrincipal
*/
var $by_email;
/**
* @var RFC3744: The principals that are direct members of this group.
*/
var $group_member_set;
/**
* @var RFC3744: The groups in which the principal is directly a member.
*/
var $group_membership;
/**
* Constructor
* @param mixed $parameters If null, an empty Principal is created. If it
* is an integer then that ID is read (if possible). If it is
* an array then the Principal matching the supplied elements is read.
* an array then the Principal matching the supplied elements
* is read. If it is an object then it is expected to be a 'usr'
* record that was read elsewhere.
*
* @return boolean Whether we actually read data from the DB to initialise the record.
*/
@ -65,7 +77,11 @@ class CalDAVPrincipal
if ( $parameters == null ) return false;
$this->by_email = false;
if ( is_int($parameters) ) {
if ( is_object($parameters) ) {
dbg_error_log( "principal", "Principal: record for %s", $parameters->username );
$usr = $parameters;
}
else if ( is_int($parameters) ) {
dbg_error_log( "principal", "Principal: %d", $parameters );
$usr = getUserByID($parameters);
}
@ -110,17 +126,43 @@ class CalDAVPrincipal
$this->url = ConstructURL( "/".$this->username."/" );
// $this->url = ConstructURL( "/__uuids__/" . $this->username . "/" );
$this->calendar_home_set = ConstructURL( "/".$this->username."/" );
// $qry = new PgQuery("SELECT dav_name FROM collection WHERE user_no = ?", $this->user_no);
// // Should be only one record, but this might change in future.
// $qry = new PgQuery("SELECT DISTINCT parent_container FROM collection WHERE user_no = ?", $this->user_no);
// $this->calendar_home_set = array();
// if( $qry->Exec("CalDAVPrincipal",__LINE__,__FILE__) && $qry->rows > 0 ) {
// while( $calendar = $qry->Fetch() ) {
// $this->calendar_home_set[] = ConstructURL($calendar->dav_name);
// }
// }
$this->calendar_home_set = array( $this->url );
$this->user_address_set = array(
"mailto:".$this->email,
ConstructURL( "/".$this->username."/" ),
// ConstructURL( "/~".$this->username."/" ),
// ConstructURL( "/__uuids__/".$this->username."/" ),
);
$this->schedule_inbox_url = sprintf( "%s.in/", $this->calendar_home_set);
$this->schedule_outbox_url = sprintf( "%s.out/", $this->calendar_home_set);
$this->dropbox_url = sprintf( "%s.drop/", $this->calendar_home_set);
$this->notifications_url = sprintf( "%s.notify/", $this->calendar_home_set);
$this->schedule_inbox_url = sprintf( "%s.in/", $this->url);
$this->schedule_outbox_url = sprintf( "%s.out/", $this->url);
$this->dropbox_url = sprintf( "%s.drop/", $this->url);
$this->notifications_url = sprintf( "%s.notify/", $this->url);
$this->group_member_set = array();
$qry = new PgQuery("SELECT * FROM relationship LEFT JOIN usr ON (from_user = usr.user_no) LEFT JOIN role_member ON (to_user = role_member.user_no) LEFT JOIN roles USING (role_no) WHERE to_user = ? AND role_name = 'Group';", $this->user_no );
if ( $qry->Exec("CalDAVPrincipal") && $qry->rows > 0 ) {
while( $membership = $qry->Fetch() ) {
$this->group_member_set[] = ConstructURL( "/". $membership->username . "/");
}
}
$this->group_membership = array();
$qry = new PgQuery("SELECT * FROM relationship LEFT JOIN usr ON (to_user = user_no) LEFT JOIN role_member USING (user_no) LEFT JOIN roles USING (role_no) WHERE from_user = ? AND role_name = 'Group';", $this->user_no );
if ( $qry->Exec("CalDAVPrincipal") && $qry->rows > 0 ) {
while( $membership = $qry->Fetch() ) {
$this->group_membership[] = ConstructURL( "/". $membership->username . "/");
}
}
dbg_error_log( "principal", "User: %s (%d) URL: %s, Home: %s, By Email: %d", $this->username, $this->user_no, $this->url, $this->calendar_home_set, $this->by_email );
}
@ -167,5 +209,189 @@ class CalDAVPrincipal
}
/**
* Returns the array of privilege names converted into XMLElements
*/
function RenderPrivileges($privilege_names, $container="privilege") {
$privileges = array();
foreach( $privilege_names AS $k => $v ) {
$privileges[] = new XMLElement($container, new XMLElement($k));
}
return $privileges;
}
/**
* Render XML for a single Principal (user) from the DB
*
* @param array $properties The requested properties for this principal
* @param reference $reply A reference to the XMLDocument being used for the reply
* @param boolean $props_only Default false. If true will only return the fragment with the properties, not a full response fragment.
*
* @return string An XML fragment with the requested properties for this principal
*/
function RenderAsXML( $properties, &$reply, $props_only = false ) {
global $session, $c, $request;
dbg_error_log("CalDAVPrincipal",": RenderAsXML: Principal '%s'", $this->username );
$prop = new XMLElement("prop");
$denied = array();
$not_found = array();
foreach( $properties AS $k => $tag ) {
dbg_error_log("CalDAVPrincipal",": RenderAsXML: Principal Property '%s'", $tag );
switch( $tag ) {
case 'DAV::getcontenttype':
$prop->NewElement("getcontenttype", "httpd/unix-directory" );
break;
case 'DAV::resourcetype':
$prop->NewElement("resourcetype", array( new XMLElement("principal"), new XMLElement("collection")) );
break;
case 'DAV::displayname':
$prop->NewElement("displayname", $this->fullname );
break;
case 'DAV::principal-URL':
$prop->NewElement("principal-URL", $this->url );
break;
case 'DAV::getlastmodified':
$prop->NewElement("getlastmodified", $this->modified );
break;
case 'DAV::group-member-set':
$set = array();
foreach( $this->group_member_set AS $k => $url ) {
$set[] = new XMLElement('href', $url );
}
$prop->NewElement("group-member-set", $set );
break;
case 'DAV::group-membership':
$set = array();
foreach( $this->group_membership AS $k => $url ) {
$set[] = new XMLElement('href', $url );
}
$prop->NewElement("group-membership", $set );
break;
case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL':
$prop->NewElement($reply->Caldav("schedule-inbox-URL"), new XMLElement('href', $this->schedule_inbox_url) );
break;
case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL':
$prop->NewElement($reply->Caldav("schedule-outbox-URL"), new XMLElement('href', $this->schedule_outbox_url) );
break;
case 'http://calendarserver.org/ns/:dropbox-home-URL':
$prop->NewElement($reply->Calendarserver("dropbox-home-URL"), new XMLElement('href', $this->dropbox_url) );
break;
case 'http://calendarserver.org/ns/:notifications-URL':
$prop->NewElement($reply->Calendarserver("notifications-URL"), new XMLElement('href', $this->notifications_url) );
break;
case 'urn:ietf:params:xml:ns:caldav:calendar-home-set':
$set = array();
foreach( $this->calendar_home_set AS $k => $url ) {
$set[] = new XMLElement('href', $url );
}
$prop->NewElement($reply->Caldav("calendar-home-set"), $set );
break;
case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set':
$set = array();
foreach( $this->user_address_set AS $k => $v ) {
$set[] = new XMLElement('href', $v );
}
$prop->NewElement($reply->Caldav("calendar-user-address-set"), $set );
break;
case 'DAV::getcontentlanguage':
$locale = $c->current_locale;
if ( isset($this->locale) && $this->locale != "" ) $locale = $this->locale;
$prop->NewElement("getcontentlanguage", $locale );
break;
case 'DAV::supportedlock':
$prop->NewElement("supportedlock",
new XMLElement( "lockentry",
array(
new XMLElement("lockscope", new XMLElement("exclusive")),
new XMLElement("locktype", new XMLElement("write")),
)
)
);
break;
case 'DAV::acl':
/**
* FIXME: This information is semantically valid but presents an incorrect picture.
*/
$principal = new XMLElement("principal");
$principal->NewElement("authenticated");
$grant = new XMLElement( "grant", array($this->RenderPrivileges($request->permissions)) );
$prop->NewElement("acl", new XMLElement( "ace", array( $principal, $grant ) ) );
break;
case 'DAV::current-user-privilege-set':
$prop->NewElement("current-user-privilege-set", $this->RenderPrivileges($request->permissions) );
break;
case 'DAV::supported-privilege-set':
$prop->NewElement("supported-privilege-set", $this->RenderPrivileges( $request->SupportedPrivileges(), "supported-privilege") );
break;
// Empty tag responses.
case 'DAV::creationdate':
case 'DAV::alternate-URI-set':
case 'DAV::getcontentlength':
$prop->NewElement( $reply->Tag($tag));
break;
case 'SOME-DENIED-PROPERTY': /** TODO: indicating the style for future expansion */
$denied[] = $reply->Tag($tag);
break;
default:
dbg_error_log( 'CalDAVPrincipal', "Request for unsupported property '%s' of principal.", $item->username );
$not_found[] = $reply->Tag($tag);
break;
}
}
if ( $props_only ) return $prop;
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
$propstat = new XMLElement( "propstat", array( $prop, $status) );
$href = new XMLElement("href", $this->url );
$elements = array($href,$propstat);
if ( count($denied) > 0 ) {
$status = new XMLElement("status", "HTTP/1.1 403 Forbidden" );
$noprop = new XMLElement("prop");
foreach( $denied AS $k => $v ) {
$noprop->NewElement( strtolower($v) );
}
$elements[] = new XMLElement( "propstat", array( $noprop, $status) );
}
if ( count($not_found) > 0 ) {
$status = new XMLElement("status", "HTTP/1.1 404 Not Found" );
$noprop = new XMLElement("prop");
foreach( $not_found AS $k => $v ) {
$noprop->NewElement( strtolower($v) );
}
$elements[] = new XMLElement( "propstat", array( $noprop, $status) );
}
$response = new XMLElement( "response", $elements );
return $response;
}
}

View File

@ -220,6 +220,7 @@ class CalDAVRequest
$xml_parser = xml_parser_create_ns('UTF-8');
$this->xml_tags = array();
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
xml_parse_into_struct( $xml_parser, $this->raw_post, $this->xml_tags );
xml_parser_free($xml_parser);
}

View File

@ -1,6 +1,6 @@
<?php
/**
* @package rscds
* @package davical
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @copyright Catalyst .Net Ltd
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
@ -96,7 +96,7 @@ awl_set_locale($c->default_locale);
*
*/
$c->code_version = 0;
$c->version_string = '0.9.5.2'; // The actual version # is replaced into that during the build /release process
$c->version_string = '0.9.5.4'; // The actual version # is replaced into that during the build /release process
if ( isset($c->version_string) && preg_match( '/(\d+)\.(\d+)\.(\d+)(.*)/', $c->version_string, $matches) ) {
$c->code_major = $matches[1];
$c->code_minor = $matches[2];

View File

@ -134,7 +134,7 @@ 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 * FROM usr WHERE lower(username) = lower(?) ", $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 );
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
$_known_users_name[$username] = $qry->Fetch();
$id = $_known_users_name[$username]->user_no;
@ -155,7 +155,7 @@ 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 * FROM usr WHERE user_no = ? ", intval($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) );
if ( $qry->Exec('always',__LINE__,__FILE__) && $qry->rows == 1 ) {
$_known_users_id[$user_no] = $qry->Fetch();
$name = $_known_users_id[$user_no]->username;

View File

@ -18,7 +18,7 @@ foreach( $request->xml_tags AS $k => $v ) {
$tag = $v['tag'];
dbg_error_log( "LOCK", " Handling Tag '%s' => '%s' ", $k, $v );
switch ( $tag ) {
case 'DAV::LOCKINFO':
case 'DAV::lockinfo':
dbg_error_log( "LOCK", ":Request: %s -> %s", $v['type'], $tag );
if ( $v['type'] == "open" ) {
$lockscope = "";
@ -34,11 +34,11 @@ foreach( $request->xml_tags AS $k => $v ) {
}
break;
case 'DAV::OWNER':
case 'DAV::LOCKTYPE':
case 'DAV::LOCKSCOPE':
case 'DAV::owner':
case 'DAV::locktype':
case 'DAV::lockscope':
dbg_error_log( "LOCK", ":Request: %s -> %s", $v['type'], $tag );
if ( $inside['DAV::LOCKINFO'] ) {
if ( $inside['DAV::lockinfo'] ) {
if ( $v['type'] == "open" ) {
$inside[$tag] = true;
}
@ -49,25 +49,25 @@ foreach( $request->xml_tags AS $k => $v ) {
break;
/*case 'DAV::SHARED': */ /** Shared lock is not supported yet */
case 'DAV::EXCLUSIVE':
case 'DAV::exclusive':
dbg_error_log( "LOCK", ":Request: %s -> %s", $v['type'], $tag );
if ( $inside['DAV::LOCKSCOPE'] && $v['type'] == "complete" ) {
if ( $inside['DAV::lockscope'] && $v['type'] == "complete" ) {
$lockscope = strtolower(substr($tag,5));
}
break;
/* case 'DAV::READ': */ /** RFC2518 is pretty vague about read locks */
case 'DAV::WRITE':
case 'DAV::write':
dbg_error_log( "LOCK", ":Request: %s -> %s", $v['type'], $tag );
if ( $inside['DAV::LOCKTYPE'] && $v['type'] == "complete" ) {
if ( $inside['DAV::locktype'] && $v['type'] == "complete" ) {
$locktype = strtolower(substr($tag,5));
}
break;
case 'DAV::HREF':
case 'DAV::href':
dbg_error_log( "LOCK", ":Request: %s -> %s", $v['type'], $tag );
dbg_log_array( "LOCK", "DAV:HREF", $v, true );
if ( $inside['DAV::OWNER'] && $v['type'] == "complete" ) {
dbg_log_array( "LOCK", "DAV:href", $v, true );
if ( $inside['DAV::owner'] && $v['type'] == "complete" ) {
$lockowner = $v['value'];
}
break;
@ -136,4 +136,3 @@ $prop = new XMLElement( "prop", $response, array('xmlns'=>'DAV:') );
$xmldoc = $prop->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');
$request->DoResponse( 200, $xmldoc, 'text/xml; charset="utf-8"' );
?>

View File

@ -4,8 +4,8 @@
*
* @package davical
* @subpackage caldav
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @copyright Catalyst .Net Ltd
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Morphoss Ltd - http://www.morphoss.com/
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
dbg_error_log("MKCALENDAR", "method handler");
@ -35,10 +35,10 @@ if ( isset($request->xml_tags) ) {
$position = 0;
$xmltree = BuildXMLTree( $request->xml_tags, $position);
// echo $xmltree->Render();
if ( $xmltree->GetTag() != "URN:IETF:PARAMS:XML:NS:CALDAV:MKCALENDAR" ) {
$request->DoResponse( 403, "XML is not a URN:IETF:PARAMS:XML:NS:CALDAV:MKCALENDAR document" );
if ( $xmltree->GetTag() != "urn:ietf:params:xml:ns:caldav:mkcalendar" ) {
$request->DoResponse( 403, "The supplied XML is not a 'urn:ietf:params:xml:ns:caldav:mkcalendar' document" );
}
$setprops = $xmltree->GetPath("/URN:IETF:PARAMS:XML:NS:CALDAV:MKCALENDAR/DAV::SET/DAV::PROP/*");
$setprops = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:mkcalendar/DAV::set/DAV::prop/*");
$propertysql = "";
foreach( $setprops AS $k => $setting ) {
@ -47,7 +47,7 @@ if ( isset($request->xml_tags) ) {
switch( $tag ) {
case 'DAV::DISPLAYNAME':
case 'DAV::displayname':
$displayname = $content;
/**
* TODO: This is definitely a bug in SOHO Organizer and we probably should respond
@ -61,27 +61,27 @@ if ( isset($request->xml_tags) ) {
$success[$tag] = 1;
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:SUPPORTED-CALENDAR-COMPONENT-SET': /** Ignored, since we will support all component types */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:SUPPORTED-CALENDAR-DATA': /** Ignored, since we will support iCalendar 2.0 */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-DATA': /** Ignored, since we will support iCalendar 2.0 */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:MAX-RESOURCE-SIZE': /** Ignored, since we will support arbitrary size */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:MIN-DATE-TIME': /** Ignored, since we will support arbitrary time */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:MAX-DATE-TIME': /** Ignored, since we will support arbitrary time */
case 'URN:IETF:PARAMS:XML:NS:CALDAV:MAX-INSTANCES': /** Ignored, since we will support arbitrary instances */
case 'DAV::RESOURCETYPE': /** Any value for resourcetype is ignored */
case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set': /** Ignored, since we will support all component types */
case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': /** Ignored, since we will support iCalendar 2.0 */
case 'urn:ietf:params:xml:ns:caldav:calendar-data': /** Ignored, since we will support iCalendar 2.0 */
case 'urn:ietf:params:xml:ns:caldav:max-resource-size': /** Ignored, since we will support arbitrary size */
case 'urn:ietf:params:xml:ns:caldav:min-date-time': /** Ignored, since we will support arbitrary time */
case 'urn:ietf:params:xml:ns:caldav:max-date-time': /** Ignored, since we will support arbitrary time */
case 'urn:ietf:params:xml:ns:caldav:max-instances': /** Ignored, since we will support arbitrary instances */
case 'DAV::resourcetype': /** Any value for resourcetype is ignored */
$success[$tag] = 1;
break;
/**
* The following properties are read-only, so they will cause the request to fail
*/
case 'DAV::GETETAG':
case 'DAV::GETCONTENTLENGTH':
case 'DAV::GETCONTENTTYPE':
case 'DAV::GETLASTMODIFIED':
case 'DAV::CREATIONDATE':
case 'DAV::LOCKDISCOVERY':
case 'DAV::SUPPORTEDLOCK':
case 'DAV::getetag':
case 'DAV::getcontentlength':
case 'DAV::getcontenttype':
case 'DAV::getlastmodified':
case 'DAV::creationdate':
case 'DAV::lockdiscovery':
case 'DAV::supportedlock':
$failure['set-'.$tag] = new XMLElement( 'propstat', array(
new XMLElement( 'prop', new XMLElement($tag)),
new XMLElement( 'status', 'HTTP/1.1 409 Conflict' ),
@ -182,4 +182,3 @@ else {
*
*/
?>

37
inc/caldav-POST.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* CalDAV Server - handle PUT method
*
* @package davical
* @subpackage caldav
* @author Andrew McMillan <andrew@morphoss.com>
* @copyright Morphoss Ltd - http://www.morphoss.com/
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
dbg_error_log("POST", "method handler");
require_once("iCalendar.php");
if ( ! $request->AllowedTo("CALDAV:schedule-send-freebusy")
&& ! $request->AllowedTo("CALDAV:schedule-send-invite")
&& ! $request->AllowedTo("CALDAV:schedule-send-reply") ) {
$request->DoResponse(403);
}
if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || $c->dbg['post']) ) {
$fh = fopen('/tmp/POST.txt','w');
if ( $fh ) {
fwrite($fh,$request->raw_post);
fclose($fh);
}
}
$ical = new iCalendar( array('icalendar' => $request->raw_post) );
switch ( $ical->properties['METHOD'] ) {
case 'REQUEST':
break;
default:
dbg_error_log("POST", ": Unhandled '%s' method in request.", $ical->properties['METHOD'] );
}

View File

@ -14,141 +14,28 @@ if ( ! ($request->AllowedTo('read') || $request->AllowedTo('freebusy')) ) {
$request->DoResponse( 403, translate("You may not access that calendar") );
}
require_once("XMLElement.php");
require_once("iCalendar.php");
require_once("XMLDocument.php");
$href_list = array();
$attribute_list = array();
$prop_list = array();
$unsupported = array();
$arbitrary = array();
$namespaces = array( "DAV:" => "" );
$prefixes = array();
function add_namespace( $prefix, $namespace ) {
global $namespaces;
global $prefixes;
if ( !isset($namespaces[$namespace]) ) {
if ( $prefix == "" || isset($prefixes[$prefix]) ) {
dbg_error_log("ERROR", "Cannot assign the same prefix to two different namespaces");
exit;
}
else {
$prefixes[$prefix] = $prefix;
$namespaces[$namespace] = $prefix;
}
}
else {
if ( $namespaces[$namespace] != $prefix ) {
dbg_error_log("ERROR", "Cannot use the same namespace with two different prefixes");
exit;
}
}
}
function ns_tag( $in_tag, $namespace=null, $prefix=null ) {
global $namespaces, $prefixes;
if ( $namespace == null ) {
// Attempt to split out from namespace:tag
if ( preg_match('/^(.*):([^:]+)$/', $in_tag, $matches) ) {
$namespace = $matches[1];
$tag = $matches[2];
}
else {
// There is nothing we can do here
return $in_tag;
}
}
else {
$tag = $in_tag;
}
$namespace = strtolower($namespace);
if ( $namespace == 'dav:' ) $namespace = 'DAV:'; // Special case for conventional naming
$tag = strtolower($tag);
if ( $prefix == null ) {
// Attempt to assign one
if ( isset($namespaces[$namespace]) ) {
$prefix = $namespaces[$namespace];
}
else {
// Try and build a prefix based on the first alphabetic character of the last element of the namespace
if ( preg_match('/^(.*):([^:]+)$/', $namespace, $matches) ) {
$alpha = preg_replace( '/[^a-z]/i', '', $matches[2] );
$prefix = strtoupper(substr($alpha,0,1));
}
else {
$prefix = 'x';
}
$i = "";
if ( isset($prefixes[$prefix]) ) {
for ( $i=1; $i<10 && isset($prefixes["$prefix$i"]); $i++ ) {
}
}
if ( isset($prefixes["$prefix$i"]) ) {
dbg_error_log("ERROR", "Cannot find a free prefix for this namespace");
exit;
}
$prefix = "$prefix$i";
$namespaces[$namespace] = $prefix;
$prefixes[$prefix] = 1;
}
}
if ( !isset($namespaces[$namespace]) ) {
add_namespace( $prefix, $namespace );
}
return $prefix . ($prefix == "" ? "" : ":") . $tag;
}
function namespace_array() {
global $namespaces;
$ns = array();
foreach( $namespaces AS $n => $p ) {
if ( $p == "" ) $ns["xmlns"] = $n; else $ns["xmlns:$p"] = $n;
}
return $ns;
}
function calendar_server_tag( $tag ) {
add_namespace("A", "http://calendarserver.org/ns/");
return ns_tag( $tag, 'http://calendarserver.org/ns/' );
}
function caldav_tag( $tag ) {
return ns_tag( $tag, 'urn:ietf:params:xml:ns:caldav' );
}
$reply = new XMLDocument( array( "DAV:" => "" ) );
foreach( $request->xml_tags AS $k => $v ) {
$ns_tag = $v['tag'];
if ( preg_match('/^(.*):([^:]+)$/', $ns_tag, $matches) ) {
$namespace = $matches[1];
$tag = $matches[2];
}
else {
$namespace = "";
$tag = $ns_tag;
}
dbg_error_log( "PROPFIND", " Handling Tag '%s' => '%s' ", $k, $v );
dbg_error_log( "PROPFIND", " Handling Tag '%s' => '%s' ", $k, $ns_tag );
switch ( $tag ) {
case 'PROPFIND':
case 'PROP':
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $tag );
switch ( $ns_tag ) {
case 'DAV::propfind':
case 'DAV::prop':
dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $ns_tag );
break;
case 'HREF':
// dbg_log_array( "PROPFIND", "HREF", $v, true );
case 'DAV::href':
$href_list[] = $v['value'];
dbg_error_log( "PROPFIND", "Adding href '%s'", $v['value'] );
break;
@ -157,83 +44,87 @@ foreach( $request->xml_tags AS $k => $v ) {
/**
* Handled DAV properties
*/
case 'ACL': /** acl - only vaguely supported */
case 'CREATIONDATE': /** creationdate - should work fine */
case 'GETLASTMODIFIED': /** getlastmodified - should work fine */
case 'DISPLAYNAME': /** displayname - should work fine */
case 'GETCONTENTLENGTH': /** getcontentlength- should work fine */
case 'GETCONTENTTYPE': /** getcontenttype - should work fine */
case 'GETETAG': /** getetag - should work fine */
case 'GETCTAG': /** Calendar Server extension like etag - should work fine (we just return etag) */
case 'SUPPORTEDLOCK': /** supportedlock - should work fine */
case 'PRINCIPAL-URL': /** principal-url - should work fine */
case 'RESOURCETYPE': /** resourcetype - should work fine */
case 'GETCONTENTLANGUAGE': /** resourcetype - should return the user's chosen locale, or default locale */
case 'SUPPORTED-PRIVILEGE-SET': /** supported-privilege-set - should work fine */
case 'CURRENT-USER-PRIVILEGE-SET': /** current-user-privilege-set - only vaguely supported */
case 'ALLPROP': /** allprop - limited support */
case 'DAV::acl': /** Only vaguely supported as yet. Will need work.*/
case 'DAV::creationdate': /** should work fine */
case 'DAV::getlastmodified': /** should work fine */
case 'DAV::displayname': /** should work fine */
case 'DAV::getcontentlength': /** should work fine */
case 'DAV::getcontenttype': /** should work fine */
case 'DAV::getetag': /** should work fine */
case 'DAV::supportedlock': /** should work fine */
case 'DAV::principal-URL': /** should work fine */
case 'DAV::owner': /** should work fine */
case 'DAV::resourcetype': /** should work fine */
case 'DAV::getcontentlanguage': /** should return the user's chosen locale, or default locale */
case 'DAV::current-user-privilege-set': /** only vaguely supported */
case 'DAV::allprop': /** limited support, needs to be checked for correctness at some point */
/**
* Handled CalDAV properties
*/
case 'CALENDAR-HOME-SET': /** calendar-home-set is used by iCal in Leopard - should work fine */
$attribute_list[$tag] = 1;
dbg_error_log( "PROPFIND", "Adding %s attribute '%s'", $namespace, $tag );
break;
case 'SUPPORTED-COLLATION-SET': /** fixed server definition - should work fine */
case 'SUPPORTED-CALENDAR-COMPONENT-SET': /** fixed server definition - should work fine */
case 'urn:ietf:params:xml:ns:caldav:calendar-home-set': /** Should work fine */
case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set': /** Should work fine */
case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL': /** Support in development */
case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL': /** Support in development */
/**
* Handled calendar-schedule properties
* Handled calendarserver properties
*/
case 'CALENDAR-USER-ADDRESS-SET': /** CalDAV+s: slightly supported */
// case 'SCHEDULE-INBOX-URL': /** CalDAV+s: not supported */
// case 'SCHEDULE-OUTBOX-URL': /** CalDAV+s: not supported */
// case 'DROPBOX-HOME-URL': // HTTP://CALENDARSERVER.ORG/NS/
// case 'NOTIFICATIONS-URL': // HTTP://CALENDARSERVER.ORG/NS/
case 'http://calendarserver.org/ns/:getctag': /** Calendar Server extension like etag - should work fine (we just return etag) */
$prop_list[$ns_tag] = $ns_tag;
dbg_error_log( "PROPFIND", "Adding attribute '%s'", $ns_tag );
break;
/** fixed server definitions - should work fine */
case 'DAV::supported-collation-set':
case 'DAV::supported-calendar-component-set':
case 'DAV::principal-collection-set':
case 'DAV::supported-privilege-set':
// case 'dropbox-home-URL': // HTTP://CALENDARSERVER.ORG/NS/
// case 'notifications-URL': // HTTP://CALENDARSERVER.ORG/NS/
if ( $_SERVER['PATH_INFO'] == '/' || $_SERVER['PATH_INFO'] == '' ) {
$arbitrary[$ns_tag] = $ns_tag;
dbg_error_log( "PROPFIND", "Adding arbitrary DAV property '%s'", $ns_tag );
}
else {
$attribute_list[$tag] = 1;
dbg_error_log( "PROPFIND", "Adding %s attribute '%s'", $namespace, $tag );
$prop_list[$ns_tag] = $ns_tag;
dbg_error_log( "PROPFIND", "Adding attribute '%s'", $ns_tag );
}
break;
case 'CALENDAR-TIMEZONE': // CalDAV
case 'SUPPORTED-CALENDAR-DATA': // CalDAV
case 'MAX-RESOURCE-SIZE': // CalDAV
case 'MIN-DATE-TIME': // CalDAV
case 'MAX-DATE-TIME': // CalDAV
case 'MAX-INSTANCES': // CalDAV
case 'MAX-ATTENDEES-PER-INSTANCE': // CalDAV
// case 'CHECKED-OUT': // DAV:
// case 'CHECKED-IN': // DAV:
// case 'SOURCE': // DAV:
// case 'LOCKDISCOVERY': // DAV:
// case 'EXECUTABLE': // HTTP://APACHE.ORG/DAV/PROPS/
case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': // Ignored
case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': // Ignored
case 'urn:ietf:params:xml:ns:caldav:max-resource-size': // Ignored - should be a server setting
case 'urn:ietf:params:xml:ns:caldav:min-date-time': // Ignored - should be a server setting
case 'urn:ietf:params:xml:ns:caldav:max-date-time': // Ignored - should be a server setting
case 'urn:ietf:params:xml:ns:caldav:max-instances': // Ignored - should be a server setting
case 'urn:ietf:params:xml:ns:caldav:max-attendees-per-instance': // Ignored - should be a server setting
/** These are ignored specifically */
break;
/**
* Add the ones that are specifically unsupported here.
*/
// case 'DAV::checked-out': // DAV:
// case 'DAV::checked-in': // DAV:
// case 'DAV::source': // DAV:
// case 'DAV::lockdiscovery': // DAV:
// case 'http://apache.org/dav/props/:executable': //
case 'This is not a supported property': // an impossible example
$unsupported[$tag] = "";
dbg_error_log( "PROPFIND", "Unsupported tag >>%s<< in xmlns >>%s<<", $tag, $namespace);
$unsupported[$ns_tag] = "";
dbg_error_log( "PROPFIND", "Unsupported tag >>%s<< ", $ns_tag);
break;
/**
* Arbitrary DAV properties may also be reported
*/
case 'CALENDAR-DESCRIPTION': // CalDAV, informational
case 'urn:ietf:params:xml:ns:caldav:calendar-description': // Supported purely as an arbitrary property
default:
$arbitrary[$ns_tag] = $ns_tag;
dbg_error_log( "PROPFIND", "Adding arbitrary DAV property '%s'", $ns_tag );
dbg_error_log( "PROPFIND", "Adding arbitrary property '%s'", $ns_tag );
break;
}
}
@ -281,146 +172,76 @@ function get_arbitrary_properties($dav_name) {
* Handles any properties related to the DAV::PRINCIPAL in the request
*/
function add_principal_properties( &$prop, &$not_found, &$denied ) {
global $attribute_list, $session, $c, $request;
global $prop_list, $session, $c, $request, $reply;
if ( isset($attribute_list['PRINCIPAL-URL'] ) ) {
$prop->NewElement("principal-url", new XMLElement('href', $request->principal->url ) );
$allprop = isset($prop_list['DAV::allprop']);
if ( isset($prop_list['DAV::principal-URL'] ) ) {
$prop->NewElement("principal-URL", new XMLElement('href', $request->principal->url ) );
}
if ( isset($prop_list['DAV::alternate-URI-set'] ) ) {
$prop->NewElement("alternate-URI-set" ); // Empty - there are no alternatives!
}
if ( isset($attribute_list['CALENDAR-HOME-SET'] ) ) {
$prop->NewElement(caldav_tag("calendar-home-set"), new XMLElement('href', $request->principal->calendar_home_set ) );
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-home-set'] ) ) {
$home_set = array();
$chs = $request->principal->calendar_home_set;
foreach( $chs AS $k => $url ) {
$home_set[] = new XMLElement('href', $url );
}
$prop->NewElement($reply->Caldav("calendar-home-set"), $home_set );
}
if ( isset($attribute_list['SCHEDULE-INBOX-URL'] ) ) {
$prop->NewElement(caldav_tag("schedule-inbox-url"), new XMLElement('href', $request->principal->schedule_inbox_url) );
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:schedule-inbox-URL'] ) ) {
$prop->NewElement($reply->Caldav("schedule-inbox-URL"), new XMLElement('href', $request->principal->schedule_inbox_url) );
}
if ( isset($attribute_list['SCHEDULE-OUTBOX-URL'] ) ) {
$prop->NewElement(caldav_tag("schedule-outbox-url"), new XMLElement('href', $request->principal->schedule_outbox_url) );
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:schedule-outbox-URL'] ) ) {
$prop->NewElement($reply->Caldav("schedule-outbox-URL"), new XMLElement('href', $request->principal->schedule_outbox_url) );
}
if ( isset($attribute_list['DROPBOX-HOME-URL'] ) ) {
$prop->NewElement(calendar_server_tag("dropbox-home-url"), new XMLElement('href', $request->principal->dropbox_url) );
if ( isset($prop_list['http://calendarserver.org/ns/:dropbox-home-URL'] ) ) {
$prop->NewElement($reply->Calendarserver("dropbox-home-URL"), new XMLElement('href', $request->principal->dropbox_url) );
}
if ( isset($attribute_list['NOTIFICATIONS-URL'] ) ) {
$prop->NewElement(calendar_server_tag("notifications-url"), new XMLElement('href', $request->principal->notifications_url) );
if ( isset($prop_list['http://calendarserver.org/ns/:notifications-URL'] ) ) {
$prop->NewElement($reply->Calendarserver("notifications-URL"), new XMLElement('href', $request->principal->notifications_url) );
}
if ( isset($attribute_list['CALENDAR-USER-ADDRESS-SET'] ) ) {
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-user-address-set'] ) ) {
$addr_set = array();
foreach( $request->principal->user_address_set AS $k => $v ) {
$uas = $request->principal->user_address_set;
foreach( $uas AS $k => $v ) {
$addr_set[] = new XMLElement('href', $v );
}
$prop->NewElement(caldav_tag("calendar-user-address-set"), $addr_set );
$prop->NewElement($reply->Caldav("calendar-user-address-set"), $addr_set );
}
}
/**
* Returns an XML sub-tree for a single collection record from the DB
* Handles any properties related to the DAV::PRINCIPAL in the request
*/
function collection_to_xml( $collection ) {
global $arbitrary, $attribute_list, $session, $c, $request;
function add_general_properties( &$prop, &$not_found, &$denied, $record ) {
global $prop_list, $session, $c, $request, $reply;
dbg_error_log("PROPFIND","Building XML Response for collection '%s'", $collection->dav_name );
$allprop = isset($prop_list['DAV::allprop']);
$arbitrary_results = get_arbitrary_properties($collection->dav_name);
$collection->properties = $arbitrary_results->found;
$url = ConstructURL($collection->dav_name);
$resourcetypes = array( new XMLElement("collection") );
$contentlength = false;
if ( $collection->is_calendar == 't' ) {
$resourcetypes[] = new XMLElement(caldav_tag("calendar"), false);
$lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $collection->user_no, $collection->dav_name.'[^/]+$' );
if ( $lqry->Exec("PROPFIND",__LINE__,__FILE__) && $row = $lqry->Fetch() ) {
$contentlength = $row->sum;
}
if ( $allprop || isset($prop_list['DAV::getlastmodified']) ) {
$prop->NewElement("getlastmodified", ( isset($record->modified)? $record->modified : false ));
}
if ( $collection->is_principal == 't' ) {
$resourcetypes[] = new XMLElement("principal");
if ( $allprop || isset($prop_list['DAV::creationdate']) ) {
$prop->NewElement("creationdate", $record->created );
}
$prop = new XMLElement("prop");
$not_found = new XMLElement("prop");
$denied = new XMLElement("prop");
/**
* First process any static values we do support
*/
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['SUPPORTED-COLLATION-SET']) ) {
$collations = array();
$collations[] = new XMLElement(caldav_tag("supported-collation"), 'i;ascii-casemap');
$collations[] = new XMLElement(caldav_tag("supported-collation"), 'i;octet');
$prop->NewElement(caldav_tag("supported-collation-set"), $collations );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['SUPPORTED-CALENDAR-COMPONENT-SET']) ) {
$components = array();
$components[] = new XMLElement(caldav_tag("comp"), '', array("name" => "VEVENT"));
$components[] = new XMLElement(caldav_tag("comp"), '', array("name" => "VTODO"));
$prop->NewElement(caldav_tag("supported-calendar-component-set"), $components );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTTYPE']) ) {
$prop->NewElement("getcontenttype", "httpd/unix-directory" );
if ( $allprop || isset($prop_list['DAV::getetag']) ) {
$prop->NewElement("getetag", '"'.$record->dav_etag.'"' );
}
/**
* Second process any dynamic values we do support
*/
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETLASTMODIFIED']) ) {
$prop->NewElement("getlastmodified", ( isset($collection->modified)? $collection->modified : false ));
if ( isset($prop_list['DAV::owner']) ) {
$prop->NewElement("owner", new XMLElement('href', 'mailto:'.$request->principal->email ) );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTLENGTH']) ) {
$prop->NewElement("getcontentlength", $contentlength );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['CREATIONDATE']) ) {
$prop->NewElement("creationdate", $collection->created );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['RESOURCETYPE']) ) {
$prop->NewElement("resourcetype", $resourcetypes );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['DISPLAYNAME']) ) {
$displayname = ( $collection->dav_displayname == "" ? ucfirst(trim(str_replace("/"," ", $collection->dav_name))) : $collection->dav_displayname );
$prop->NewElement("displayname", $displayname );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETETAG']) ) {
$prop->NewElement("getetag", '"'.$collection->dav_etag.'"' );
}
if ( isset($attribute_list['GETCTAG']) ) {
// Calendar Server extension which only applies to collections. We return the etag, which does the needful.
$prop->NewElement(calendar_server_tag('getctag'),$collection->dav_etag );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['CURRENT-USER-PRIVILEGE-SET']) ) {
$prop->NewElement("current-user-privilege-set", privileges($request->permissions) );
if ( isset($prop_list['DAV::principal-collection-set']) ) {
$prop->NewElement("principal-collection-set", new XMLElement('href', ConstructURL('/') ) );
}
if ( isset($attribute_list['CALENDAR-FREE-BUSY-SET'] ) ) {
if ( isset($collection->is_inbox) && $collection->is_inbox && $session->user_no == $collection->user_no ) {
$fb_set = array();
foreach( $collection->free_busy_set AS $k => $v ) {
$fb_set[] = new XMLElement('href', $v );
}
$prop->NewElement(caldav_tag("calendar-free-busy-set"), $fb_set );
}
else if ( $session->user_no == $collection->user_no ) {
$not_found->NewElement(caldav_tag("calendar-free-busy-set") );
}
else {
$denied->NewElement(caldav_tag("calendar-free-busy-set") );
}
}
/**
* Then look at any properties related to the principal
*/
add_principal_properties( $prop, $not_found, $denied );
if ( count($collection->properties) > 0 ) {
foreach( $collection->properties AS $k => $v ) {
$prop->NewElement(ns_tag($k), $v);
}
}
if ( isset($attribute_list['ACL']) ) {
if ( isset($prop_list['DAV::acl']) ) {
/**
* FIXME: This information is semantically valid but presents an incorrect picture.
*/
@ -430,12 +251,12 @@ function collection_to_xml( $collection ) {
$prop->NewElement("acl", new XMLElement( "ace", array( $principal, $grant ) ) );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTLANGUAGE']) ) {
if ( $allprop || isset($prop_list['DAV::getcontentlanguage']) ) {
$contentlength = strlen($item->caldav_data);
$prop->NewElement("getcontentlanguage", $c->current_locale );
}
if ( isset($attribute_list['SUPPORTEDLOCK']) ) {
if ( isset($prop_list['DAV::supportedlock']) ) {
$prop->NewElement("supportedlock",
new XMLElement( "lockentry",
array(
@ -446,21 +267,28 @@ function collection_to_xml( $collection ) {
);
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['SUPPORTED-PRIVILEGE-SET']) ) {
if ( isset($prop_list['DAV::current-user-privilege-set']) ) {
$prop->NewElement("current-user-privilege-set", privileges($request->permissions) );
}
if ( isset($prop_list['DAV::supported-privilege-set']) ) {
$prop->NewElement("supported-privilege-set", privileges( $request->SupportedPrivileges(), "supported-privilege") );
}
}
/**
* Build the <propstat><prop></prop><status></status></propstat> part of the response
*/
function build_propstat_response( $prop, $not_found, $denied, $url ) {
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
$propstat = new XMLElement( "propstat", array( $prop, $status) );
$href = new XMLElement("href", $url );
$response = array($href,$propstat);
if ( count($arbitrary_results->missing) > 0 ) {
foreach( $arbitrary_results->missing AS $k => $v ) {
$not_found->NewElement(ns_tag($k), '');
}
}
if ( is_array($not_found->content) && count($not_found->content) > 0 ) {
$response[] = new XMLElement( "propstat", array( $not_found, new XMLElement("status", "HTTP/1.1 404 Not Found" )) );
}
@ -475,14 +303,134 @@ function collection_to_xml( $collection ) {
}
/**
* Returns an XML sub-tree for a single collection record from the DB
*/
function collection_to_xml( $collection ) {
global $arbitrary, $prop_list, $session, $c, $request, $reply;
dbg_error_log("PROPFIND","Building XML Response for collection '%s'", $collection->dav_name );
$allprop = isset($prop_list['DAV::allprop']);
$arbitrary_results = get_arbitrary_properties($collection->dav_name);
$collection->properties = $arbitrary_results->found;
$url = ConstructURL($collection->dav_name);
$prop = new XMLElement("prop");
$not_found = new XMLElement("prop");
$denied = new XMLElement("prop");
/**
* First process any static values we do support
*/
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:supported-collation-set']) ) {
$collations = array();
$collations[] = new XMLElement($reply->Caldav("supported-collation"), 'i;ascii-casemap');
$collations[] = new XMLElement($reply->Caldav("supported-collation"), 'i;octet');
$prop->NewElement($reply->Caldav("supported-collation-set"), $collations );
}
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:supported-calendar-component-set']) ) {
$components = array();
$components[] = new XMLElement($reply->Caldav("comp"), '', array("name" => "VEVENT"));
$components[] = new XMLElement($reply->Caldav("comp"), '', array("name" => "VTODO"));
$prop->NewElement($reply->Caldav("supported-calendar-component-set"), $components );
}
if ( $allprop || isset($prop_list['DAV::getcontenttype']) ) {
$prop->NewElement("getcontenttype", "httpd/unix-directory" ); // Strictly text/icalendar perhaps
}
/**
* Process any dynamic values we do support
*/
if ( $allprop || isset($prop_list['DAV::getcontentlength'])
|| isset($prop_list['DAV::resourcetype']) ) {
$resourcetypes = array( new XMLElement("collection") );
$contentlength = false;
if ( $collection->is_calendar == 't' ) {
$resourcetypes[] = new XMLElement($reply->Caldav("calendar"), false);
$lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $collection->user_no, $collection->dav_name.'[^/]+$' );
if ( $lqry->Exec("PROPFIND",__LINE__,__FILE__) && $row = $lqry->Fetch() ) {
$contentlength = $row->sum;
}
}
if ( $collection->is_principal == 't' ) {
$resourcetypes[] = new XMLElement("principal");
}
if ( $allprop || isset($prop_list['DAV::getcontentlength']) ) {
$prop->NewElement("getcontentlength", $contentlength ); // Not strictly correct as a GET on this URL would be longer
}
if ( $allprop || isset($prop_list['DAV::resourcetype']) ) {
$prop->NewElement("resourcetype", $resourcetypes );
}
}
if ( $allprop || isset($prop_list['DAV::displayname']) ) {
$displayname = ( $collection->dav_displayname == "" ? ucfirst(trim(str_replace("/"," ", $collection->dav_name))) : $collection->dav_displayname );
$prop->NewElement("displayname", $displayname );
}
if ( isset($prop_list['http://calendarserver.org/ns/:getctag']) ) {
// Calendar Server extension which only applies to collections. We return the etag, which does the needful.
$prop->NewElement($reply->Calendarserver('getctag'),$collection->dav_etag );
}
if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-free-busy-set'] ) ) {
if ( isset($collection->is_inbox) && $collection->is_inbox && $session->user_no == $collection->user_no ) {
$fb_set = array();
foreach( $collection->free_busy_set AS $k => $v ) {
$fb_set[] = new XMLElement('href', $v );
}
$prop->NewElement($reply->Caldav("calendar-free-busy-set"), $fb_set );
}
else if ( $session->user_no == $collection->user_no ) {
$not_found->NewElement($reply->Caldav("calendar-free-busy-set") );
}
else {
$denied->NewElement($reply->Caldav("calendar-free-busy-set") );
}
}
/**
* Then look at any properties related to the principal
*/
add_principal_properties( $prop, $not_found, $denied );
/**
* And any properties that are server/request related, or standard fields
* from our query.
*/
add_general_properties( $prop, $not_found, $denied, $collection );
/**
* Arbitrary collection properties
*/
if ( count($collection->properties) > 0 ) {
foreach( $collection->properties AS $k => $v ) {
$prop->NewElement($reply->Tag($k), $v);
}
}
if ( count($arbitrary_results->missing) > 0 ) {
foreach( $arbitrary_results->missing AS $k => $v ) {
$not_found->NewElement($reply->Tag($k), '');
}
}
return build_propstat_response( $prop, $not_found, $denied, $url );
}
/**
* Return XML for a single data item from the DB
*/
function item_to_xml( $item ) {
global $attribute_list, $session, $c, $request;
global $prop_list, $session, $c, $request, $reply;
dbg_error_log("PROPFIND","Building XML Response for item '%s'", $item->dav_name );
$allprop = isset($prop_list['DAV::allprop']);
$item->properties = get_arbitrary_properties($item->dav_name);
$url = ConstructURL($item->dav_name);
@ -492,82 +440,35 @@ function item_to_xml( $item ) {
$denied = new XMLElement("prop");
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETLASTMODIFIED']) ) {
$prop->NewElement("getlastmodified", ( isset($item->modified)? $item->modified : false ));
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTLENGTH']) ) {
if ( $allprop || isset($prop_list['DAV::getcontentlength']) ) {
$contentlength = strlen($item->caldav_data);
$prop->NewElement("getcontentlength", $contentlength );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTTYPE']) ) {
if ( $allprop || isset($prop_list['DAV::getcontenttype']) ) {
$prop->NewElement("getcontenttype", "text/calendar" );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['CREATIONDATE']) ) {
$prop->NewElement("creationdate", $item->created );
if ( $allprop || isset($prop_list['DAV::displayname']) ) {
$prop->NewElement("displayname", $item->dav_displayname );
}
/**
* Non-collections should return an empty resource type, it appears from RFC2518 8.1.2
*/
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['RESOURCETYPE']) ) {
if ( $allprop || isset($prop_list['DAV::resourcetype']) ) {
$prop->NewElement("resourcetype");
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['DISPLAYNAME']) ) {
$prop->NewElement("displayname", $item->dav_displayname );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETETAG']) ) {
$prop->NewElement("getetag", '"'.$item->dav_etag.'"' );
}
/**
* Then look at any properties related to the principal
*/
add_principal_properties( $prop, $not_found, $denied );
if ( isset($attribute_list['ACL']) ) {
/**
* FIXME: This information is semantically valid but presents an incorrect picture.
*/
$principal = new XMLElement("principal");
$principal->NewElement("authenticated");
$grant = new XMLElement( "grant", array(privileges($request->permissions)) );
$prop->NewElement("acl", new XMLElement( "ace", array( $principal, $grant ) ) );
}
/**
* And any properties that are server/request related.
*/
add_general_properties( $prop, $not_found, $denied, $item );
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['GETCONTENTLANGUAGE']) ) {
$contentlength = strlen($item->caldav_data);
$prop->NewElement("getcontentlanguage", $c->current_locale );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['CURRENT-USER-PRIVILEGE-SET']) ) {
$prop->NewElement("current-user-privilege-set", privileges($request->permissions) );
}
if ( isset($attribute_list['ALLPROP']) || isset($attribute_list['SUPPORTEDLOCK']) ) {
$prop->NewElement("supportedlock",
new XMLElement( "lockentry",
array(
new XMLElement("lockscope", new XMLElement("exclusive")),
new XMLElement("locktype", new XMLElement("write")),
)
)
);
}
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
$propstat = new XMLElement( "propstat", array( $prop, $status) );
$href = new XMLElement("href", $url );
$response = array($href,$propstat);
if ( is_array($not_found->content) && count($not_found->content) > 0 ) {
$response[] = new XMLElement( "propstat", array( $not_found, new XMLElement("status", "HTTP/1.1 404 Not Found" )) );
}
if ( is_array($denied->content) && count($denied->content) > 0 ) {
$response[] = new XMLElement( "propstat", array( $denied, new XMLElement("status", "HTTP/1.1 403 Forbidden" )) );
}
$response = new XMLElement( "response", $response );
return $response;
return build_propstat_response( $prop, $not_found, $denied, $url );
}
/**
@ -576,7 +477,7 @@ function item_to_xml( $item ) {
* a list of calendars for the user which are parented by this path.
*/
function get_collection_contents( $depth, $user_no, $collection ) {
global $session, $request;
global $session, $request, $reply, $prop_list;
dbg_error_log("PROPFIND","Getting collection contents: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection->dav_name );
@ -586,7 +487,7 @@ function get_collection_contents( $depth, $user_no, $collection ) {
* Calendar collections may not contain calendar collections.
*/
if ( $collection->dav_name == '/' ) {
$sql = "SELECT user_no, user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
$sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
$sql .= "to_char(updated at time zone 'GMT',?) AS created, ";
$sql .= "to_char(updated at time zone 'GMT',?) AS modified, ";
$sql .= "fullname AS dav_displayname, FALSE AS is_calendar, TRUE AS is_principal FROM usr ";
@ -603,7 +504,13 @@ function get_collection_contents( $depth, $user_no, $collection ) {
if( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows > 0 ) {
while( $subcollection = $qry->Fetch() ) {
$responses[] = collection_to_xml( $subcollection );
if ( $subcollection->is_principal == "t" ) {
$principal = new CalDAVPrincipal($subcollection);
$responses[] = $principal->RenderAsXML($prop_list, &$reply);
}
else {
$responses[] = collection_to_xml( $subcollection );
}
if ( $depth > 0 ) {
$responses = array_merge( $responses, get_collection_contents( $depth - 1, $user_no, $subcollection ) );
}
@ -740,7 +647,8 @@ else {
$request->DoResponse( 403, translate("You do not have appropriate rights to view that resource.") );
}
$multistatus = new XMLElement( "multistatus", $responses, namespace_array() );
$multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
// dbg_log_array( "PROPFIND", "XML", $multistatus, true );
$xmldoc = $multistatus->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');

View File

@ -4,8 +4,8 @@
*
* @package davical
* @subpackage caldav
* @author Andrew McMillan <andrew@catalyst.net.nz>
* @copyright Catalyst .Net Ltd
* @author Andrew McMillan <andrew@mcmillan.net.nz>
* @copyright Morphoss Ltd - http://www.morphoss.com/
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2
*/
dbg_error_log("PROPPATCH", "method handler");
@ -19,15 +19,15 @@ $xmltree = BuildXMLTree( $request->xml_tags, $position);
// echo $xmltree->Render();
if ( $xmltree->GetTag() != "DAV::PROPERTYUPDATE" ) {
if ( $xmltree->GetTag() != "DAV::propertyupdate" ) {
$request->DoResponse( 403 );
}
/**
* Find the properties being set, and the properties being removed
*/
$setprops = $xmltree->GetPath("/DAV::PROPERTYUPDATE/DAV::SET/DAV::PROP/*");
$rmprops = $xmltree->GetPath("/DAV::PROPERTYUPDATE/DAV::REMOVE/DAV::PROP/*");
$setprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::set/DAV::prop/*");
$rmprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::remove/DAV::prop/*");
/**
* We build full status responses for failures. For success we just record
@ -50,7 +50,7 @@ foreach( $setprops AS $k => $setting ) {
switch( $tag ) {
case 'DAV::DISPLAYNAME':
case 'DAV::displayname':
/**
* Can't set displayname on resources - only collections or principals
*/
@ -74,12 +74,12 @@ foreach( $setprops AS $k => $setting ) {
}
break;
case 'DAV::RESOURCETYPE':
case 'DAV::resourcetype':
/**
* We don't allow a collection to change to/from a resource. Only collections may be CalDAV calendars.
*/
$setcollection = count($setting->GetPath('DAV::RESOURCETYPE/DAV::COLLECTION'));
$setcalendar = count($setting->GetPath('DAV::RESOURCETYPE/urn:ietf:params:xml:ns:caldav:calendar'));
$setcollection = count($setting->GetPath('DAV::resourcetype/DAV::collection'));
$setcalendar = count($setting->GetPath('DAV::resourcetype/urn:ietf:params:xml:ns:caldav:calendar'));
if ( $request->IsCollection() && ($setcollection || $setcalendar) ) {
if ( $setcalendar ) {
$sql .= sprintf( "UPDATE collection SET is_calendar = TRUE WHERE dav_name = %s;", qpg($request->path) );
@ -98,13 +98,19 @@ foreach( $setprops AS $k => $setting ) {
/**
* The following properties are read-only, so they will cause the request to fail
*/
case 'DAV::GETETAG':
case 'DAV::GETCONTENTLENGTH':
case 'DAV::GETCONTENTTYPE':
case 'DAV::GETLASTMODIFIED':
case 'DAV::CREATIONDATE':
case 'DAV::LOCKDISCOVERY':
case 'DAV::SUPPORTEDLOCK':
case 'http://calendarserver.org/ns/:getctag':
case 'DAV::owner':
case 'DAV::principal-collection-set':
case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set':
case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL':
case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL':
case 'DAV::getetag':
case 'DAV::getcontentlength':
case 'DAV::getcontenttype':
case 'DAV::getlastmodified':
case 'DAV::creationdate':
case 'DAV::lockdiscovery':
case 'DAV::supportedlock':
$failure['set-'.$tag] = new XMLElement( 'propstat', array(
new XMLElement( 'prop', new XMLElement($tag)),
new XMLElement( 'status', 'HTTP/1.1 409 Conflict' ),
@ -130,12 +136,12 @@ foreach( $rmprops AS $k => $setting ) {
switch( $tag ) {
case 'DAV::RESOURCETYPE':
case 'DAV::resourcetype':
/**
* We don't allow a collection to change to/from a resource. Only collections may be CalDAV calendars.
*/
$rmcollection = (count($setting->GetPath('DAV::RESOURCETYPE/DAV::COLLECTION')) > 0);
$rmcalendar = (count($setting->GetPath('DAV::RESOURCETYPE/urn:ietf:params:xml:ns:caldav:calendar')) > 0);
$rmcollection = (count($setting->GetPath('DAV::resourcetype/DAV::collection')) > 0);
$rmcalendar = (count($setting->GetPath('DAV::resourcetype/urn:ietf:params:xml:ns:caldav:calendar')) > 0);
if ( $request->IsCollection() && !$rmcollection ) {
dbg_error_log( 'PROPPATCH', ' RMProperty %s : IsCollection=%d, rmcoll=%d, rmcal=%d', $tag, $request->IsCollection(), $rmcollection, $rmcalendar );
if ( $rmcalendar ) {
@ -156,14 +162,20 @@ foreach( $rmprops AS $k => $setting ) {
/**
* The following properties are read-only, so they will cause the request to fail
*/
case 'DAV::GETETAG':
case 'DAV::GETCONTENTLENGTH':
case 'DAV::GETCONTENTTYPE':
case 'DAV::GETLASTMODIFIED':
case 'DAV::CREATIONDATE':
case 'DAV::DISPLAYNAME':
case 'DAV::LOCKDISCOVERY':
case 'DAV::SUPPORTEDLOCK':
case 'http://calendarserver.org/ns/:getctag':
case 'DAV::owner':
case 'DAV::principal-collection-set':
case 'urn:ietf:params:xml:ns:caldav:CALENDAR-USER-ADDRESS-SET':
case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL':
case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL':
case 'DAV::getetag':
case 'DAV::getcontentlength':
case 'DAV::getcontenttype':
case 'DAV::getlastmodified':
case 'DAV::creationdate':
case 'DAV::displayname':
case 'DAV::lockdiscovery':
case 'DAV::supportedlock':
$failure['rm-'.$tag] = new XMLElement( 'propstat', array(
new XMLElement( 'prop', new XMLElement($tag)),
new XMLElement( 'status', 'HTTP/1.1 409 Conflict' ),
@ -225,4 +237,3 @@ $request->DoResponse( 500 );
exit(0);
?>

View File

@ -3,20 +3,20 @@
/**
* Build the array of properties to include in the report output
*/
$qry_content = $xmltree->GetContent('URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY');
$qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:calendar-query');
$proptype = $qry_content[0]->GetTag();
$properties = array();
switch( $proptype ) {
case 'DAV::PROP':
$qry_props = $xmltree->GetPath('/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY/DAV::PROP/*');
case 'DAV::prop':
$qry_props = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-query/DAV::prop/*');
foreach( $qry_props AS $k => $v ) {
$propertyname = preg_replace( '/^.*:/', '', $v->GetTag() );
$properties[$propertyname] = 1;
}
break;
case 'DAV::ALLPROP':
$properties['ALLPROP'] = 1;
case 'DAV::allprop':
$properties['allprop'] = 1;
break;
default:
@ -30,13 +30,13 @@ switch( $proptype ) {
* VCALENDAR, but perhaps there are others. In our case we strip it if that is
* the case and leave it alone otherwise.
*/
$qry_filters = $xmltree->GetPath('/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY/URN:IETF:PARAMS:XML:NS:CALDAV:FILTER/*');
$qry_filters = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-query/urn:ietf:params:xml:ns:caldav:filter/*');
if ( count($qry_filters) == 1 ) {
$qry_filters = $qry_filters[0]; // There can only be one FILTER element
if ( $qry_filters->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:COMP-FILTER" && $qry_filters->GetAttribute("NAME") == "VCALENDAR" )
if ( $qry_filters->GetTag() == "urn:ietf:params:xml:ns:caldav:comp-filter" && $qry_filters->GetAttribute("name") == "VCALENDAR" )
$qry_filters = $qry_filters->GetContent(); // Everything is inside a VCALENDAR AFAICS
else {
dbg_error_log("calquery", "Got bizarre CALDAV:FILTER[%s=%s]] which does not contain COMP-FILTER = VCALENDAR!!", $qry_filters->GetTag(), $qry_filters->GetAttribute("NAME") );
dbg_error_log("calquery", "Got bizarre CALDAV:FILTER[%s=%s]] which does not contain comp-filter = VCALENDAR!!", $qry_filters->GetTag(), $qry_filters->GetAttribute("name") );
$qry_filters = false;
}
}
@ -82,12 +82,12 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
$not_defined = "";
switch( $tag ) {
case 'URN:IETF:PARAMS:XML:NS:CALDAV:IS-NOT-DEFINED':
$not_defined = "NOT "; // then fall through to IS-DEFINED case
case 'URN:IETF:PARAMS:XML:NS:CALDAV:IS-DEFINED':
case 'urn:ietf:params:xml:ns:caldav:is-not-defined':
$not_defined = "not-"; // then fall through to IS-DEFINED case
case 'urn:ietf:params:xml:ns:caldav:is-defined':
if ( isset( $parameter ) ) {
$need_post_filter = true;
dbg_error_log("calquery", "Could not handle IS-%sDEFINED on property %s, parameter %s in SQL", $not_defined, $property, $parameter );
dbg_error_log("calquery", "Could not handle 'is-%sdefined' on property %s, parameter %s in SQL", $not_defined, $property, $parameter );
return false; // Not handled in SQL
}
if ( isset( $property ) ) {
@ -111,14 +111,14 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
}
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:TIME-RANGE':
case 'urn:ietf:params:xml:ns:caldav:time-range':
/**
* TODO: We should probably allow time range queries against other properties, since eventually some client may want to do this.
*/
$start_column = ($components[sizeof($components)-1] == 'VTODO' ? "due" : 'dtend'); // The column we compare against the START attribute
$finish_column = 'dtstart'; // The column we compare against the END attribute
$start = $v->GetAttribute("START");
$finish = $v->GetAttribute("END");
$start = $v->GetAttribute("start");
$finish = $v->GetAttribute("end");
if ( isset($start) && isset($finish) ) {
$sql .= sprintf( "AND ( (%s >= %s::timestamp with time zone AND %s <= %s::timestamp with time zone) ",
$start_column, qpg($start), $finish_column, qpg($finish));
@ -137,10 +137,10 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
}
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:TEXT-MATCH':
case 'urn:ietf:params:xml:ns:caldav:text-match':
$search = $v->GetContent();
$negate = $v->GetAttribute("NEGATE-CONDITION");
$collation = $v->GetAttribute("COLLATION");
$negate = $v->GetAttribute("negate-condition");
$collation = $v->GetAttribute("collation");
switch( strtolower($collation) ) {
case 'i;octet':
$comparison = 'LIKE';
@ -150,12 +150,14 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
$comparison = 'ILIKE';
break;
}
$sql .= sprintf( "AND %s%s %s %s ", (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""),
dbg_error_log("calquery", " text-match: (%s IS NULL OR %s%s %s %s) ", $property, (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""),
$property, $comparison, qpg("%".$search."%") );
$sql .= sprintf( "AND (%s IS NULL OR %s%s %s %s) ", $property, (isset($negate) && strtolower($negate) == "yes" ? "NOT ": ""),
$property, $comparison, qpg("%".$search."%") );
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:COMP-FILTER':
$comp_filter_name = $v->GetAttribute("NAME");
case 'urn:ietf:params:xml:ns:caldav:comp-filter':
$comp_filter_name = $v->GetAttribute("name");
if ( count($components) == 0 ) {
$sql .= "AND caldav_data.caldav_type = ".qpg($comp_filter_name)." ";
}
@ -167,8 +169,8 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
}
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:PROP-FILTER':
$propertyname = $v->GetAttribute("NAME");
case 'urn:ietf:params:xml:ns:caldav:prop-filter':
$propertyname = $v->GetAttribute("name");
switch( $propertyname ) {
case 'PERCENT-COMPLETE':
$property = 'percent_complete';
@ -195,18 +197,18 @@ function SqlFilterFragment( $filter, $components, $property = null, $parameter =
case 'COMPLETED': /** TODO: this should be moved into the properties supported in SQL. */
default:
$need_post_filter = true;
dbg_error_log("calquery", "Could not handle PROP-FILTER on %s in SQL", $propertyname );
return false; // Can't handle PROP-FILTER conditions in the SQL for this property
dbg_error_log("calquery", "Could not handle 'prop-filter' on %s in SQL", $propertyname );
continue;
}
$subfilter = $v->GetContent();
$success = SqlFilterFragment( $subfilter, $components, $property, $parameter );
if ( $success === false ) continue; else $sql .= $success;
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:PARAM-FILTER':
case 'urn:ietf:params:xml:ns:caldav:param-filter':
$need_post_filter = true;
return false; // Can't handle PARAM-FILTER conditions in the SQL
$parameter = $v->GetAttribute("NAME");
$parameter = $v->GetAttribute("name");
$subfilter = $v->GetContent();
$success = SqlFilterFragment( $subfilter, $components, $property, $parameter );
if ( $success === false ) continue; else $sql .= $success;
@ -270,6 +272,6 @@ if ( $qry->Exec("calquery",__LINE__,__FILE__) && $qry->rows > 0 ) {
}
}
}
$multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') );
$multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
$request->XMLResponse( 207, $multistatus );

View File

@ -5,9 +5,9 @@
include_once("iCalendar.php");
include_once("RRule.php");
$fbq_content = $xmltree->GetContent('URN:IETF:PARAMS:XML:NS:CALDAV:FREE-BUSY-QUERY');
$fbq_start = $fbq_content[0]->GetAttribute('START');
$fbq_end = $fbq_content[0]->GetAttribute('END');
$fbq_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:free-busy-query');
$fbq_start = $fbq_content[0]->GetAttribute('start');
$fbq_end = $fbq_content[0]->GetAttribute('end');
if ( ! ( isset($fbq_start) || isset($fbq_end) ) ) {
$request->DoResponse( 400, 'All valid freebusy requests MUST contain a time-range filter' );

View File

@ -9,20 +9,20 @@ $responses = array();
/**
* Build the array of properties to include in the report output
*/
$mg_content = $xmltree->GetContent('URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET');
$mg_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:calendar-multiget');
$proptype = $mg_content[0]->GetTag();
$properties = array();
switch( $proptype ) {
case 'DAV::PROP':
$mg_props = $xmltree->GetPath('/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET/DAV::PROP/*');
case 'DAV::prop':
$mg_props = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-multiget/DAV::prop/*');
foreach( $mg_props AS $k => $v ) {
$propertyname = preg_replace( '/^.*:/', '', $v->GetTag() );
$properties[$propertyname] = 1;
}
break;
case 'DAV::ALLPROP':
$properties['ALLPROP'] = 1;
case 'DAV::allprop':
$properties['allprop'] = 1;
break;
default:
@ -33,7 +33,7 @@ switch( $proptype ) {
/**
* Build the href list for the IN ( href, href, href, ... ) clause.
*/
$mg_hrefs = $xmltree->GetPath('/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET/DAV::HREF');
$mg_hrefs = $xmltree->GetPath('/urn:ietf:params:xml:ns:caldav:calendar-multiget/DAV::href');
$href_in = '';
foreach( $mg_hrefs AS $k => $v ) {
/**
@ -65,6 +65,6 @@ if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
}
}
$multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') );
$multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
$request->XMLResponse( 207, $multistatus );

View File

@ -2,108 +2,24 @@
$responses = array();
/**
* Return XML for a single Principal (user) from the DB
* TODO: Refactor this functionality into the CalDAVPrincipal object
*
* @param array $properties The requested properties for this principal
* @param string $item The user data for this calendar
*
* @return string An XML document which is the response for the principal
*/
function principal_to_xml( $properties, $item ) {
global $session, $c, $request;
dbg_error_log("REPORT","Building XML Response for principal '%s'", $item->username );
$this_url = ConstructURL( $request->dav_name );
$principal_url = ConstructURL( "/".$item->username."/");
$home_calendar = ConstructURL( "/".$item->username."/");
$prop = new XMLElement("prop");
$denied = array();
foreach( $properties AS $k => $v ) {
switch( $v ) {
case 'DAV::RESOURCETYPE':
$prop->NewElement("resourcetype", new XMLElement("principal") );
break;
case 'DAV::DISPLAYNAME':
$prop->NewElement("displayname", $item->username );
break;
case 'DAV::PRINCIPAL-URL':
$prop->NewElement("principal-url", $principal_url );
break;
case 'DAV::ALTERNATE-URI':
$prop->NewElement("alternate-uri" );
break;
case 'DAV::GROUP-MEMBER-SET':
$qry = new PgQuery("SELECT * FROM relationship LEFT JOIN usr ON (from_user = usr.user_no) LEFT JOIN role_member ON (to_user = role_member.user_no) LEFT JOIN roles USING (role_no) WHERE to_user = ? AND role_name = 'Group';", $item->user_no );
$group = array();
if ( $qry->Exec("REPORT-principal") && $qry->rows > 0 ) {
while( $membership = $qry->Fetch() ) {
$group[] = new XMLElement("href", ConstructURL( "/". $membership->username . "/") );
}
}
$prop->NewElement("group-member-set", $group );
break;
case 'DAV::GROUP-MEMBERSHIP':
$qry = new PgQuery("SELECT * FROM relationship LEFT JOIN usr ON (to_user = user_no) LEFT JOIN role_member USING (user_no) LEFT JOIN roles USING (role_no) WHERE from_user = ? AND role_name = 'Group';", $item->user_no );
$group = array();
if ( $qry->Exec("REPORT-principal") && $qry->rows > 0 ) {
while( $membership = $qry->Fetch() ) {
$group[] = new XMLElement("href", ConstructURL( "/". $membership->username . "/") );
}
}
$prop->NewElement("group-membership", $group );
break;
case 'URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-HOME-SET':
$prop->NewElement("calendar-home-set", $home_calendar, array("xmlns" => "urn:ietf:params:xml:ns:caldav") );
break;
case 'SOME-DENIED-PROPERTY': /** TODO: indicating the style for future expansion */
$denied[] = $v;
break;
default:
dbg_error_log( 'REPORT', "Request for unsupported property '%s' of principal.", $item->username );
break;
}
}
$status = new XMLElement("status", "HTTP/1.1 200 OK" );
$propstat = new XMLElement( "propstat", array( $prop, $status) );
$href = new XMLElement("href", $principal_url );
$elements = array($href,$propstat);
if ( count($denied) > 0 ) {
$status = new XMLElement("status", "HTTP/1.1 403 Forbidden" );
$noprop = new XMLElement("prop");
foreach( $denied AS $k => $v ) {
$noprop->NewElement( strtolower($v) );
}
$elements[] = new XMLElement( "propstat", array( $noprop, $status) );
}
$response = new XMLElement( "response", $elements );
return $response;
}
/**
* Build the array of properties to include in the report output
*/
$searches = $xmltree->GetPath('/DAV::PRINCIPAL-PROPERTY-SEARCH/DAV::PROPERTY-SEARCH');
$searches = $xmltree->GetPath('/DAV::principal-property-search/DAV::property-search');
dbg_log_array( "principal", "SEARCH", $searches, true );
$where = "";
foreach( $searches AS $k => $search ) {
$qry_props = $search->GetPath('/DAV::PROPERTY-SEARCH/DAV::PROP/*'); // There may be many
$match = $search->GetPath('/DAV::PROPERTY-SEARCH/DAV::MATCH'); // There may only be one
$qry_props = $search->GetPath('/DAV::property-search/DAV::prop/*'); // There may be many
$match = $search->GetPath('/DAV::property-search/DAV::match'); // There may only be one
dbg_log_array( "principal", "MATCH", $match, true );
$match = qpg($match[0]->GetContent());
$subwhere = "";
foreach( $qry_props AS $k1 => $v1 ) {
if ( $subwhere != "" ) $subwhere .= " OR ";
switch( $v1->GetTag() ) {
case 'DAV::DISPLAYNAME':
case 'DAV::displayname':
$subwhere .= "username = ".$match;
break;
default:
@ -119,18 +35,19 @@ $sql = "SELECT * FROM usr $where";
$qry = new PgQuery($sql);
$get_props = $xmltree->GetPath('/DAV::PRINCIPAL-PROPERTY-SEARCH/DAV::PROP/*');
$get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*');
$properties = array();
foreach( $get_props AS $k1 => $v1 ) {
$properties[] = $v1->GetTag();
}
if ( $qry->Exec("REPORT",__LINE__,__FILE__) && $qry->rows > 0 ) {
while( $principal_object = $qry->Fetch() ) {
$responses[] = principal_to_xml( $properties, $principal_object );
while( $row = $qry->Fetch() ) {
$principal = new CalDAVPrincipal($row);
$responses[] = $principal->RenderAsXML( $properties, &$reply );
}
}
$multistatus = new XMLElement( "multistatus", $responses, array('xmlns'=>'DAV:') );
$multistatus = new XMLElement( "multistatus", $responses, $reply->GetXmlNsArray() );
$request->XMLResponse( 207, $multistatus );

View File

@ -10,6 +10,8 @@
*/
dbg_error_log("REPORT", "method handler");
require_once("XMLDocument.php");
if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || $c->dbg['report']) ) {
$fh = fopen('/tmp/REPORT.txt','w');
if ( $fh ) {
@ -40,11 +42,13 @@ $denied = array();
$unsupported = array();
if ( isset($prop_filter) ) unset($prop_filter);
if ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:FREE-BUSY-QUERY" ) {
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:free-busy-query" ) {
include("caldav-REPORT-freebusy.php");
exit; // Not that the above include should return anyway
}
if ( $xmltree->GetTag() == "DAV::PRINCIPAL-PROPERTY-SEARCH" ) {
$reply = new XMLDocument( array( "DAV:" => "" ) );
if ( $xmltree->GetTag() == "DAV::principal-property-search" ) {
include("caldav-REPORT-principal.php");
exit; // Not that the above include should return anyway
}
@ -66,14 +70,14 @@ if ( ! ($request->AllowedTo('read') ) ) {
* @return string An XML document which is the response for the calendar
*/
function calendar_to_xml( $properties, $item ) {
global $session, $c, $request;
global $session, $c, $request, $reply;
dbg_error_log("REPORT","Building XML Response for item '%s'", $item->dav_name );
$denied = array();
$caldav_data = $item->caldav_data;
$displayname = $item->summary;
if ( isset($properties['CALENDAR-DATA']) || isset($properties['DISPLAYNAME']) ) {
if ( isset($properties['calendar-data']) || isset($properties['displayname']) ) {
if ( !$request->AllowedTo('all') && $session->user_no != $item->user_no ){
// the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal
if ( $item->class == 'CONFIDENTIAL' ) {
@ -108,27 +112,27 @@ function calendar_to_xml( $properties, $item ) {
$prop = new XMLElement("prop");
foreach( $properties AS $k => $v ) {
switch( $k ) {
case 'GETCONTENTLENGTH':
case 'getcontentlength':
$contentlength = strlen($caldav_data);
$prop->NewElement("getcontentlength", $contentlength );
$prop->NewElement($k, $contentlength );
break;
case 'CALENDAR-DATA':
$prop->NewElement("calendar-data","$caldav_data" , array("xmlns" => "urn:ietf:params:xml:ns:caldav") );
case 'calendar-data':
$prop->NewElement($reply->Caldav($k), $caldav_data );
break;
case 'GETCONTENTTYPE':
$prop->NewElement("getcontenttype", "text/calendar" );
case 'getcontenttype':
$prop->NewElement($k, "text/calendar" );
break;
case 'RESOURCETYPE':
$prop->NewElement("resourcetype", new XMLElement("calendar", false, array("xmlns" => "urn:ietf:params:xml:ns:caldav")) );
case 'resourcetype':
$prop->NewElement($k, new XMLElement($reply->Caldav("calendar"), false) );
break;
case 'DISPLAYNAME':
$prop->NewElement("displayname", $displayname );
case 'displayname':
$prop->NewElement($k, $displayname );
break;
case 'GETETAG':
$prop->NewElement("getetag", '"'.$item->dav_etag.'"' );
case 'getetag':
$prop->NewElement($k, '"'.$item->dav_etag.'"' );
break;
case 'CURRENT-USER-PRIVILEGE-SET':
$prop->NewElement("current-user-privilege-set", privileges($request->permissions) );
case '"current-user-privilege-set"':
$prop->NewElement($k, privileges($request->permissions) );
break;
case 'SOME-DENIED-PROPERTY': /** TODO: indicating the style for future expansion */
$denied[] = $v;
@ -158,16 +162,15 @@ function calendar_to_xml( $properties, $item ) {
return $response;
}
if ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY" ) {
$calquery = $xmltree->GetPath("/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-QUERY/*");
if ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) {
$calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*");
include("caldav-REPORT-calquery.php");
}
elseif ( $xmltree->GetTag() == "URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET" ) {
$multiget = $xmltree->GetPath("/URN:IETF:PARAMS:XML:NS:CALDAV:CALENDAR-MULTIGET/*");
elseif ( $xmltree->GetTag() == "urn:ietf:params:xml:ns:caldav:calendar-multiget" ) {
$multiget = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-multiget/*");
include("caldav-REPORT-multiget.php");
}
else {
$request->DoResponse( 501, "XML is not a supported REPORT query document" );
}
?>