diff --git a/inc/CalDAVPrincipal.php b/inc/CalDAVPrincipal.php index d9666050..33978a79 100644 --- a/inc/CalDAVPrincipal.php +++ b/inc/CalDAVPrincipal.php @@ -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; + } } \ No newline at end of file diff --git a/inc/CalDAVRequest.php b/inc/CalDAVRequest.php index 843dd624..ff86d989 100644 --- a/inc/CalDAVRequest.php +++ b/inc/CalDAVRequest.php @@ -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); } diff --git a/inc/always.php b/inc/always.php index 32a8b936..6e142765 100644 --- a/inc/always.php +++ b/inc/always.php @@ -1,6 +1,6 @@ * @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]; diff --git a/inc/always.php.in b/inc/always.php.in index 2aceb9fb..d7b2d7d8 100644 --- a/inc/always.php.in +++ b/inc/always.php.in @@ -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; diff --git a/inc/caldav-LOCK.php b/inc/caldav-LOCK.php index c1a99762..caead4e8 100644 --- a/inc/caldav-LOCK.php +++ b/inc/caldav-LOCK.php @@ -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,''); $request->DoResponse( 200, $xmldoc, 'text/xml; charset="utf-8"' ); -?> \ No newline at end of file diff --git a/inc/caldav-MKCALENDAR.php b/inc/caldav-MKCALENDAR.php index 6bc958f6..13dd797d 100644 --- a/inc/caldav-MKCALENDAR.php +++ b/inc/caldav-MKCALENDAR.php @@ -4,8 +4,8 @@ * * @package davical * @subpackage caldav -* @author Andrew McMillan -* @copyright Catalyst .Net Ltd +* @author Andrew McMillan +* @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 { * */ -?> diff --git a/inc/caldav-POST.php b/inc/caldav-POST.php new file mode 100644 index 00000000..89a201e8 --- /dev/null +++ b/inc/caldav-POST.php @@ -0,0 +1,37 @@ + +* @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'] ); +} \ No newline at end of file diff --git a/inc/caldav-PROPFIND.php b/inc/caldav-PROPFIND.php index 1261d2d0..02dfbd4c 100644 --- a/inc/caldav-PROPFIND.php +++ b/inc/caldav-PROPFIND.php @@ -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 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,''); diff --git a/inc/caldav-PROPPATCH.php b/inc/caldav-PROPPATCH.php index bc13bd3e..fe5896d5 100644 --- a/inc/caldav-PROPPATCH.php +++ b/inc/caldav-PROPPATCH.php @@ -4,8 +4,8 @@ * * @package davical * @subpackage caldav -* @author Andrew McMillan -* @copyright Catalyst .Net Ltd +* @author Andrew McMillan +* @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); -?> diff --git a/inc/caldav-REPORT-calquery.php b/inc/caldav-REPORT-calquery.php index d41a9e90..2c273bfd 100644 --- a/inc/caldav-REPORT-calquery.php +++ b/inc/caldav-REPORT-calquery.php @@ -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 ); diff --git a/inc/caldav-REPORT-freebusy.php b/inc/caldav-REPORT-freebusy.php index 7108cf33..52d6354b 100644 --- a/inc/caldav-REPORT-freebusy.php +++ b/inc/caldav-REPORT-freebusy.php @@ -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' ); diff --git a/inc/caldav-REPORT-multiget.php b/inc/caldav-REPORT-multiget.php index 44ca771c..5877442a 100644 --- a/inc/caldav-REPORT-multiget.php +++ b/inc/caldav-REPORT-multiget.php @@ -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 ); diff --git a/inc/caldav-REPORT-principal.php b/inc/caldav-REPORT-principal.php index 57241be3..b811780b 100644 --- a/inc/caldav-REPORT-principal.php +++ b/inc/caldav-REPORT-principal.php @@ -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 ); diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php index 48f9d098..0f69683a 100644 --- a/inc/caldav-REPORT.php +++ b/inc/caldav-REPORT.php @@ -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" ); } -?>