1<?php 2/** 3* CalDAV Server - handle REPORT method 4* 5* @package davical 6* @subpackage caldav 7* @author Andrew McMillan <andrew@morphoss.com> 8* @copyright Catalyst .Net Ltd, Morphoss Ltd 9* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 10*/ 11dbg_error_log("REPORT", "method handler"); 12 13require_once("XMLDocument.php"); 14require_once('DAVResource.php'); 15 16require_once('RRule.php'); 17 18if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || (isset($c->dbg['report']) && $c->dbg['report'])) ) { 19 $fh = fopen('/var/log/davical/REPORT.debug','w'); 20 if ( $fh ) { 21 fwrite($fh,$request->raw_post); 22 fclose($fh); 23 } 24} 25 26if ( !isset($request->xml_tags) ) { 27 $request->DoResponse( 406, translate("REPORT body contains no XML data!") ); 28} 29$position = 0; 30$xmltree = BuildXMLTree( $request->xml_tags, $position); 31if ( !is_object($xmltree) ) { 32 $request->DoResponse( 406, translate("REPORT body is not valid XML data!") ); 33} 34 35$target = new DAVResource($request->path); 36 37if ( $xmltree->GetNSTag() != 'DAV::principal-property-search' 38 && $xmltree->GetNSTag() != 'DAV::principal-property-search-set' ) { 39 $target->NeedPrivilege( array('DAV::read', 'urn:ietf:params:xml:ns:caldav:read-free-busy'), true ); // They may have either 40} 41 42require_once("vCalendar.php"); 43 44$reportnum = -1; 45$report = array(); 46$denied = array(); 47$unsupported = array(); 48if ( isset($prop_filter) ) unset($prop_filter); 49 50if ( $xmltree->GetNSTag() == 'urn:ietf:params:xml:ns:caldav:free-busy-query' ) { 51 include("caldav-REPORT-freebusy.php"); 52 exit; // Not that the above include should return anyway 53} 54 55$reply = new XMLDocument( array( "DAV:" => "" ) ); 56switch( $xmltree->GetNSTag() ) { 57 case 'DAV::principal-property-search': 58 include("caldav-REPORT-principal.php"); 59 exit; // Not that it should return anyway. 60 case 'DAV::principal-search-property-set': 61 include("caldav-REPORT-pps-set.php"); 62 exit; // Not that it should return anyway. 63 case 'DAV::sync-collection': 64 if ( $target->IsExternal() ) { 65 require_once("external-fetch.php"); 66 update_external ( $target ); 67 } 68 include("caldav-REPORT-sync-collection.php"); 69 exit; // Not that it should return anyway. 70 case 'DAV::expand-property': 71 if ( $target->IsExternal() ) { 72 require_once("external-fetch.php"); 73 update_external ( $target ); 74 } 75 include("caldav-REPORT-expand-property.php"); 76 exit; // Not that it should return anyway. 77 case 'DAV::principal-match': 78 include("caldav-REPORT-principal-match.php"); 79 exit; // Not that it should return anyway. 80} 81 82/** 83* check if we need to do expansion of recurring events 84* @param object $calendar_data_node 85*/ 86function check_for_expansion( $calendar_data_node ) { 87 global $need_expansion, $expand_range_start, $expand_range_end, $expand_as_floating; 88 89 if ( !class_exists('DateTime') ) return; /** We don't support expansion on PHP5.1 */ 90 91 $expansion = $calendar_data_node->GetElements('urn:ietf:params:xml:ns:caldav:expand'); 92 if ( isset($expansion[0]) ) { 93 $need_expansion = true; 94 $expand_range_start = $expansion[0]->GetAttribute('start'); 95 $expand_range_end = $expansion[0]->GetAttribute('end'); 96 $expand_as_floating = $expansion[0]->GetAttribute('floating'); 97 if ( isset($expand_range_start) ) $expand_range_start = new RepeatRuleDateTime($expand_range_start); 98 if ( isset($expand_range_end) ) $expand_range_end = new RepeatRuleDateTime($expand_range_end); 99 if ( isset($expand_as_floating) && $expand_as_floating == "yes" ) 100 $expand_as_floating = true; 101 else 102 $expand_as_floating = false; 103 } 104} 105 106 107/** 108* Return XML for a single component from the DB 109* 110* @param array $properties The properties for this component 111* @param string $item The DB row data for this component 112* 113* @return string An XML document which is the response for the component 114*/ 115function component_to_xml( $properties, $item ) { 116 global $session, $c, $request, $reply; 117 118 dbg_error_log("REPORT","Building XML Response for item '%s'", $item->dav_name ); 119 120 $denied = array(); 121 $unsupported = array(); 122 $caldav_data = $item->caldav_data; 123 $displayname = preg_replace( '{^.*/}', '', $item->dav_name ); 124 $type = 'unknown'; 125 $contenttype = 'text/plain'; 126 switch( strtoupper($item->caldav_type) ) { 127 case 'VJOURNAL': 128 case 'VEVENT': 129 case 'VTODO': 130 $displayname = $item->summary; 131 $type = 'calendar'; 132 $contenttype = 'text/calendar'; 133 if ( isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) || isset($properties['DAV::displayname']) ) { 134 if ( !$request->AllowedTo('all') && $session->user_no != $item->user_no ) { 135 // the user is not admin / owner of this calendar looking at his calendar and can not admin the other cal 136 if ( $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read') ) { 137 dbg_error_log("REPORT","Anonymising confidential event for: %s", $item->dav_name ); 138 $vcal = new vCalendar( $caldav_data ); 139 $caldav_data = $vcal->Confidential()->Render(); 140 $displayname = translate('Busy'); 141 } 142 } 143 } 144 145 if ( isset($c->hide_alarm) && $c->hide_alarm ) { 146 $dav_resource = new DAVResource($item->dav_name); 147 if ( isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) && !$dav_resource->HavePrivilegeTo('write') ) { 148 dbg_error_log("REPORT","Stripping event alarms for: %s", $item->dav_name ); 149 $vcal = new vCalendar($caldav_data); 150 $vcal->ClearComponents('VALARM'); 151 $caldav_data = $vcal->Render(); 152 } 153 } 154 break; 155 156 case 'VCARD': 157 $displayname = $item->fn; 158 $type = 'vcard'; 159 $contenttype = 'text/vcard'; 160 break; 161 } 162 163 $url = ConstructURL($item->dav_name); 164 165 $prop = new XMLElement("prop"); 166 $need_resource = false; 167 foreach( $properties AS $full_tag => $v ) { 168 $base_tag = preg_replace('{^.*:}', '', $full_tag ); 169 switch( $full_tag ) { 170 case 'DAV::getcontentlength': 171 $contentlength = strlen($caldav_data); 172 $prop->NewElement($base_tag, $contentlength ); 173 break; 174 case 'DAV::getlastmodified': 175 $prop->NewElement($base_tag, ISODateToHTTPDate($item->modified) ); 176 break; 177 case 'urn:ietf:params:xml:ns:caldav:calendar-data': 178 if ( $type == 'calendar' ) $reply->CalDAVElement($prop, $base_tag, $caldav_data ); 179 else $unsupported[] = $base_tag; 180 break; 181 case 'urn:ietf:params:xml:ns:carddav:address-data': 182 if ( $type == 'vcard' ) $reply->CardDAVElement($prop, $base_tag, $caldav_data ); 183 else $unsupported[] = $base_tag; 184 break; 185 case 'DAV::getcontenttype': 186 $prop->NewElement($base_tag, $contenttype ); 187 break; 188 case 'DAV::current-user-principal': 189 $prop->NewElement("current-user-principal", $request->current_user_principal_xml); 190 break; 191 case 'DAV::displayname': 192 $prop->NewElement($base_tag, $displayname ); 193 break; 194 case 'DAV::resourcetype': 195 $prop->NewElement($base_tag); // Just an empty resourcetype for a non-collection. 196 break; 197 case 'DAV::getetag': 198 $prop->NewElement($base_tag, '"'.$item->dav_etag.'"' ); 199 break; 200 case '"current-user-privilege-set"': 201 $prop->NewElement($base_tag, privileges($request->permissions) ); 202 break; 203 default: 204 // It's harder. We need the DAVResource() to get this one. 205 $need_resource = true; 206 } 207 if ( $need_resource ) break; 208 } 209 $href = new XMLElement("href", $url ); 210 if ( $need_resource ) { 211 if ( !isset($dav_resource) ) $dav_resource = new DAVResource($item->dav_name); 212 $elements = $dav_resource->GetPropStat(array_keys($properties), $reply); 213 array_unshift($elements, $href); 214 } 215 else { 216 $elements = array($href); 217 $status = new XMLElement("status", "HTTP/1.1 200 OK" ); 218 $elements[] = new XMLElement( "propstat", array( $prop, $status) ); 219 if ( count($denied) > 0 ) { 220 $status = new XMLElement("status", "HTTP/1.1 403 Forbidden" ); 221 $noprop = new XMLElement("prop"); 222 foreach( $denied AS $k => $v ) { 223 $reply->NSElement($noprop, $v); 224 } 225 $elements[] = new XMLElement( "propstat", array( $noprop, $status) ); 226 } 227 228 if ( ! $request->PreferMinimal() && count($unsupported) > 0 ) { 229 $status = new XMLElement("status", "HTTP/1.1 404 Not Found" ); 230 $noprop = new XMLElement("prop"); 231 foreach( $unsupported AS $k => $v ) { 232 $reply->NSElement($noprop, $v); 233 } 234 $elements[] = new XMLElement( "propstat", array( $noprop, $status) ); 235 } 236 } 237 238 $response = new XMLElement( "response", $elements ); 239 240 return $response; 241} 242 243if ( $target->IsExternal() ) { 244 require_once("external-fetch.php"); 245 update_external ( $target ); 246} 247 248// These reports are always allowed to see the resource_data because they are special 249$c->sync_resource_data_ok = true; 250 251if ( $xmltree->GetNSTag() == "urn:ietf:params:xml:ns:caldav:calendar-query" ) { 252 $calquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:caldav:calendar-query/*"); 253 include("caldav-REPORT-calquery.php"); 254} 255elseif ( $xmltree->GetNSTag() == "urn:ietf:params:xml:ns:caldav:calendar-multiget" ) { 256 $mode = 'caldav'; 257 $qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:calendar-multiget'); 258 include("caldav-REPORT-multiget.php"); 259} 260elseif ( $xmltree->GetNSTag() == "urn:ietf:params:xml:ns:carddav:addressbook-multiget" ) { 261 $mode = 'carddav'; 262 $qry_content = $xmltree->GetContent('urn:ietf:params:xml:ns:carddav:addressbook-multiget'); 263 include("caldav-REPORT-multiget.php"); 264} 265elseif ( $xmltree->GetNSTag() == "urn:ietf:params:xml:ns:carddav:addressbook-query" ) { 266 $cardquery = $xmltree->GetPath("/urn:ietf:params:xml:ns:carddav:addressbook-query/*"); 267 include("caldav-REPORT-cardquery.php"); 268} 269else { 270 dbg_error_log( 'ERROR', "Request for unsupported report type '%s'.", $xmltree->GetNSTag() ); 271 $request->PreconditionFailed( 403, 'DAV::supported-report', sprintf( '"%s" is not a supported report type', $xmltree->GetNSTag()) ); 272} 273 274