DAViCal
DAVResource.php
1<?php
12require_once('AwlCache.php');
13require_once('AwlQuery.php');
14require_once('DAVPrincipal.php');
15require_once('DAVTicket.php');
16require_once('iCalendar.php');
17
18
25{
29 protected $dav_name;
30
34 protected $exists;
35
39 protected $unique_tag;
40
44 protected $resource;
45
49 protected $parent;
50
54 protected $resourcetypes;
55
59 protected $contenttype;
60
64 protected $bound_from;
65
69 private $collection;
70
74 private $principal;
75
79 private $privileges;
80
84 private $_is_collection;
85
89 private $_is_principal;
90
94 private $_is_calendar;
95
99 private $_is_binding;
100
104 private $_is_external;
105
109 private $_is_addressbook;
110
114 private $_is_proxy_resource;
115
119 private $proxy_type;
120
124 private $supported_methods;
125
129 private $supported_reports;
130
134 private $dead_properties;
135
139 private $supported_components;
140
144 private $tickets;
145
155 function __construct( $parameters = null, DAVResource $prefetched_collection = null ) {
156 $this->exists = null;
157 $this->bound_from = null;
158 $this->dav_name = null;
159 $this->unique_tag = null;
160 $this->resource = null;
161 $this->collection = null;
162 $this->principal = null;
163 $this->parent = null;
164 $this->resourcetypes = null;
165 $this->contenttype = null;
166 $this->privileges = null;
167 $this->dead_properties = null;
168 $this->supported_methods = null;
169 $this->supported_reports = null;
170
171 $this->_is_collection = false;
172 $this->_is_principal = false;
173 $this->_is_calendar = false;
174 $this->_is_binding = false;
175 $this->_is_external = false;
176 $this->_is_addressbook = false;
177 $this->_is_proxy_resource = false;
178
179 if ( isset($prefetched_collection) ) {
180 $this->collection = $prefetched_collection;
181 }
182
183 if ( isset($parameters) && is_object($parameters) ) {
184 $this->FromRow($parameters);
185 }
186 else if ( isset($parameters) && is_array($parameters) ) {
187 if ( isset($parameters['path']) ) {
188 $this->FromPath($parameters['path']);
189 }
190 }
191 else if ( isset($parameters) && is_string($parameters) ) {
192 $this->FromPath($parameters);
193 }
194 }
195
196
201 function FromRow($row) {
202 global $c, $session;
203
204 if ( $row == null ) return;
205
206 $this->exists = true;
207 $this->dav_name = $row->dav_name;
208 $this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
209 $this->_is_collection = preg_match( '{/$}', $this->dav_name );
210
211 if ( $this->_is_collection ) {
212 $this->contenttype = 'httpd/unix-directory';
213 $this->collection = (object) array();
214 $this->resource_id = $row->collection_id;
215
216 $this->_is_principal = preg_match( '{^/[^/]+/$}', $this->dav_name );
217 if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->dav_name, $matches) ) {
218 $this->collection->dav_name = $matches[1].'/';
219 $this->collection->type = 'principal_link';
220 $this->_is_principal = true;
221 }
222 }
223 else {
224 $this->resource = (object) array();
225 if ( isset($row->dav_id) ) $this->resource_id = $row->dav_id;
226 }
227
228 dbg_error_log( 'DAVResource', ':FromRow: Named "%s" is%s a collection.', $this->dav_name, ($this->_is_collection?'':' not') );
229
230 foreach( $row AS $k => $v ) {
231 if ( $this->_is_collection )
232 $this->collection->{$k} = $v;
233 else
234 $this->resource->{$k} = $v;
235 switch ( $k ) {
236 case 'created':
237 case 'modified':
238 $this->{$k} = $v;
239 break;
240
241 case 'resourcetypes':
242 if ( $this->_is_collection ) $this->{$k} = $v;
243 break;
244
245 case 'dav_etag':
246 $this->unique_tag = '"'.$v.'"';
247 break;
248
249 }
250 }
251
252 if ( $this->_is_collection ) {
253 if ( !isset( $this->collection->type ) || $this->collection->type == 'collection' ) {
254 if ( $this->_is_principal )
255 $this->collection->type = 'principal';
256 else if ( $row->is_calendar == 't' ) {
257 $this->collection->type = 'calendar';
258 }
259 else if ( $row->is_addressbook == 't' ) {
260 $this->collection->type = 'addressbook';
261 }
262 else if ( isset($row->is_proxy) && $row->is_proxy == 't' ) {
263 $this->collection->type = 'proxy';
264 }
265 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
266 $this->collection->type = 'schedule-'. $matches[3]. 'box';
267 else if ( $this->dav_name == '/' )
268 $this->collection->type = 'root';
269 else
270 $this->collection->type = 'collection';
271 }
272
273 $this->_is_calendar = ($this->collection->is_calendar == 't');
274 $this->_is_addressbook = ($this->collection->is_addressbook == 't');
275 $this->_is_proxy_resource = ($this->collection->type == 'proxy');
276 if ( $this->_is_principal && !isset($this->resourcetypes) ) {
277 $this->resourcetypes = '<DAV::collection/><DAV::principal/>';
278 }
279 else if ( $this->_is_proxy_resource ) {
280 $this->resourcetypes = $this->collection->resourcetypes;
281 preg_match( '#^/[^/]+/calendar-proxy-(read|write)/?[^/]*$#', $this->dav_name, $matches );
282 $this->proxy_type = $matches[1];
283 }
284 if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
285 }
286 else {
287 $this->resourcetypes = '';
288 if ( isset($this->resource->caldav_data) ) {
289 if ( isset($this->resource->summary) )$this->resource->displayname = $this->resource->summary;
290 if ( strtoupper(substr($this->resource->caldav_data,0,15)) == 'BEGIN:VCALENDAR' ) {
291 $this->contenttype = 'text/calendar';
292 if ( isset($this->resource->caldav_type) ) $this->contenttype .= "; component=" . strtolower($this->resource->caldav_type);
293 if ( !$this->HavePrivilegeTo('read') && $this->HavePrivilegeTo('read-free-busy') ) {
294 $vcal = new iCalComponent($this->resource->caldav_data);
295 $confidential = $vcal->CloneConfidential();
296 $this->resource->caldav_data = $confidential->Render();
297 $this->resource->displayname = $this->resource->summary = translate('Busy');
298 $this->resource->description = null;
299 $this->resource->location = null;
300 $this->resource->url = null;
301 }
302 else {
303 if ( isset($this->resource->class) && strtoupper($this->resource->class)=='CONFIDENTIAL' && !$this->HavePrivilegeTo('all') && $session->user_no != $this->resource->user_no ) {
304 $vcal = new iCalComponent($this->resource->caldav_data);
305 $confidential = $vcal->CloneConfidential();
306 $this->resource->caldav_data = $confidential->Render();
307 }
308 if ( isset($c->hide_alarm) && $c->hide_alarm && !$this->HavePrivilegeTo('write') ) {
309 $vcal1 = new iCalComponent($this->resource->caldav_data);
310 $comps = $vcal1->GetComponents();
311 $vcal2 = new iCalComponent();
312 $vcal2->VCalendar();
313 foreach( $comps AS $comp ) {
314 $comp->ClearComponents('VALARM');
315 $vcal2->AddComponent($comp);
316 }
317 $this->resource->displayname = $this->resource->summary = $vcal2->GetPValue('SUMMARY');
318 $this->resource->caldav_data = $vcal2->Render();
319 }
320 }
321 }
322 else if ( strtoupper(substr($this->resource->caldav_data,0,11)) == 'BEGIN:VCARD' ) {
323 $this->contenttype = 'text/vcard';
324 }
325 else if ( strtoupper(substr($this->resource->caldav_data,0,11)) == 'BEGIN:VLIST' ) {
326 $this->contenttype = 'text/x-vlist';
327 }
328 }
329 }
330 }
331
332
337 function FromPath($inpath) {
338 global $c;
339
340 $this->dav_name = DeconstructURL($inpath);
341
342 $this->FetchCollection();
343 if ( $this->_is_collection ) {
344 if ( $this->_is_principal || $this->collection->type == 'principal' ) $this->FetchPrincipal();
345 }
346 else {
347 $this->FetchResource();
348 }
349 dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
350 $this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') );
351 }
352
353
354 private function ReadCollectionFromDatabase() {
355 global $c, $session;
356
357 $this->collection = (object) array(
358 'collection_id' => -1,
359 'type' => 'nonexistent',
360 'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false
361 );
362
363 $base_sql = 'SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), ';
364 $base_sql .= 'p.principal_id, p.type_id AS principal_type_id, ';
365 $base_sql .= 'p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges, ';
366 $base_sql .= 'timezones.vtimezone ';
367 $base_sql .= 'FROM collection LEFT JOIN principal p USING (user_no) ';
368 $base_sql .= 'LEFT JOIN timezones ON (collection.timezone=timezones.tzid) ';
369 $base_sql .= 'WHERE ';
370 $sql = $base_sql .'collection.dav_name = :raw_path ';
371 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
372 if ( !preg_match( '#/$#', $this->dav_name ) ) {
373 $sql .= ' OR collection.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
374 $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
375 $params[':plus_slash'] = $this->dav_name.'/';
376 }
377 $sql .= 'ORDER BY LENGTH(collection.dav_name) DESC LIMIT 1';
378 $qry = new AwlQuery( $sql, $params );
379 if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
380 $this->collection = $row;
381 $this->collection->exists = true;
382 if ( $row->is_calendar == 't' )
383 $this->collection->type = 'calendar';
384 else if ( $row->is_addressbook == 't' )
385 $this->collection->type = 'addressbook';
386 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
387 $this->collection->type = 'schedule-'. $matches[3]. 'box';
388 else
389 $this->collection->type = 'collection';
390 }
391 else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->dav_name, $matches ) ) {
392 // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
393 $params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
394 $params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
395 $this->collection_type = 'schedule-'. $matches[4]. 'box';
396 $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type );
397 $sql = <<<EOSQL
398INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
399 VALUES( (SELECT user_no FROM usr WHERE username = text(:username)),
400 :parent_container, :dav_name,
401 (SELECT fullname FROM usr WHERE username = text(:username)) || :boxname,
402 FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
403EOSQL;
404 $qry = new AwlQuery( $sql, $params );
405 $qry->Exec('DAVResource');
406 dbg_error_log( 'DAVResource', 'Created new collection as "%s".', trim($params[':boxname']) );
407
408 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
409 $qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
410 if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
411 $this->collection = $row;
412 $this->collection->exists = true;
413 $this->collection->type = $this->collection_type;
414 }
415 }
416 else if ( preg_match( '#^(/([^/]+)/calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) {
417 $this->collection->type = 'proxy';
418 $this->_is_proxy_resource = true;
419 $this->proxy_type = $matches[3];
420 $this->collection->dav_name = $this->dav_name;
421 $this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] );
422 $this->collection->exists = true;
423 $this->collection->parent_container = '/' . $matches[2] . '/';
424 }
425 else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
426 || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
427 $this->_is_principal = true;
428 $this->FetchPrincipal();
429 $this->collection->is_principal = true;
430 $this->collection->type = 'principal';
431 }
432 else if ( $this->dav_name == '/' ) {
433 $this->collection->dav_name = '/';
434 $this->collection->type = 'root';
435 $this->collection->exists = true;
436 $this->collection->displayname = $c->system_name;
437 $this->collection->default_privileges = (1 | 16 | 32);
438 $this->collection->parent_container = '/';
439 }
440 else {
441 $sql = <<<EOSQL
442SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
443 p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
444 timezones.vtimezone, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
445 dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to,
446 dav_binding.external_url AS external_url, dav_binding.type AS external_type, dav_binding.bind_id AS bind_id
447FROM dav_binding
448 LEFT JOIN collection ON (collection.collection_id=bound_source_id)
449 LEFT JOIN principal p USING (user_no)
450 LEFT JOIN dav_principal owner ON (dav_binding.dav_owner_id=owner.principal_id)
451 LEFT JOIN timezones ON (collection.timezone=timezones.tzid)
452 WHERE dav_binding.dav_name = :raw_path
453EOSQL;
454 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
455 if ( !preg_match( '#/$#', $this->dav_name ) ) {
456 $sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash OR dav_binding.dav_name = :plus_slash ';
457 $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
458 $params[':plus_slash'] = $this->dav_name.'/';
459 }
460 $sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1';
461 $qry = new AwlQuery( $sql, $params );
462 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
463 $this->collection = $row;
464 $this->collection->exists = true;
465 $this->collection->parent_set = $row->parent_container;
466 $this->collection->parent_container = $row->bind_parent_container;
467 $this->collection->bound_from = $row->dav_name;
468 $this->collection->dav_name = $row->bound_to;
469 if ( $row->is_calendar == 't' )
470 $this->collection->type = 'calendar';
471 else if ( $row->is_addressbook == 't' )
472 $this->collection->type = 'addressbook';
473 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
474 $this->collection->type = 'schedule-'. $matches[3]. 'box';
475 else
476 $this->collection->type = 'collection';
477 if ( isset($row->external_url) && strlen($row->external_url) > 8 ) {
478 $this->_is_external = true;
479 if ( $row->external_type == 'calendar' )
480 $this->collection->type = 'calendar';
481 else if ( $row->external_type == 'addressbook' )
482 $this->collection->type = 'addressbook';
483 else
484 $this->collection->type = 'collection';
485 }
486 $this->_is_binding = true;
487 $this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
488 if ( isset($row->access_ticket_id) ) {
489 if ( !isset($this->tickets) ) $this->tickets = array();
490 $this->tickets[] = new DAVTicket($row->access_ticket_id);
491 }
492 }
493 else {
494 dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
495 $this->collection->exists = false;
496 $this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
497 }
498 }
499
500 }
501
505 protected function FetchCollection() {
506 global $session;
507
519 dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
520
521 // Try and pull the answer out of a hat
522 $cache = getCacheInstance();
523 $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name);
524 $cache_key = 'dav_resource'.$session->user_no;
525 $this->collection = $cache->get( $cache_ns, $cache_key );
526 if ( $this->collection === false ) {
527 $this->ReadCollectionFromDatabase();
528 if ( $this->collection->type != 'principal' ) {
529 $cache_ns = 'collection-'.$this->collection->dav_name;
530 @dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
531 $cache->set( $cache_ns, $cache_key, $this->collection );
532 }
533 @dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
534 }
535 else {
536 @dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
537 if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
538 || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
539 $this->_is_principal = true;
540 $this->FetchPrincipal();
541 $this->collection->is_principal = true;
542 $this->collection->type = 'principal';
543 }
544 @dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
545 }
546
547 if ( isset($this->collection->bound_from) ) {
548 $this->_is_binding = true;
549 $this->bound_from = str_replace( $this->collection->bound_to, $this->collection->bound_from, $this->dav_name);
550 if ( isset($this->collection->access_ticket_id) ) {
551 if ( !isset($this->tickets) ) $this->tickets = array();
552 $this->tickets[] = new DAVTicket($this->collection->access_ticket_id);
553 }
554 }
555
556 $this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' );
557 if ( $this->_is_collection ) {
558 $this->dav_name = $this->collection->dav_name;
559 $this->resource_id = $this->collection->collection_id;
560 $this->_is_calendar = ($this->collection->type == 'calendar');
561 $this->_is_addressbook = ($this->collection->type == 'addressbook');
562 $this->contenttype = 'httpd/unix-directory';
563 if ( !isset($this->exists) && isset($this->collection->exists) ) {
564 // If this seems peculiar it's because we only set it to false above...
565 $this->exists = $this->collection->exists;
566 }
567 if ( $this->exists ) {
568 if ( isset($this->collection->dav_etag) ) $this->unique_tag = '"'.$this->collection->dav_etag.'"';
569 if ( isset($this->collection->created) ) $this->created = $this->collection->created;
570 if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
571 if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
572 }
573 else {
574 if ( !isset($this->parent) ) $this->GetParentContainer();
575 $this->user_no = $this->parent->GetProperty('user_no');
576 }
577 if ( isset($this->collection->resourcetypes) )
578 $this->resourcetypes = $this->collection->resourcetypes;
579 else {
580 $this->resourcetypes = '<DAV::collection/>';
581 if ( $this->_is_principal ) $this->resourcetypes .= '<DAV::principal/>';
582 if ( $this->_is_addressbook ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:carddav:addressbook/>';
583 if ( $this->_is_calendar ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
584 }
585 }
586 }
587
588
592 protected function FetchPrincipal() {
593 if ( isset($this->principal) ) return;
594 $this->principal = new DAVPrincipal( array( "path" => $this->bound_from() ) );
595 if ( $this->_is_principal ) {
596 $this->exists = $this->principal->Exists();
597 $this->collection->dav_name = $this->dav_name();
598 $this->collection->type = 'principal';
599 if ( $this->exists ) {
600 $this->collection = $this->principal->AsCollection();
601 $this->displayname = $this->principal->GetProperty('displayname');
602 $this->user_no = $this->principal->user_no();
603 $this->resource_id = $this->principal->principal_id();
604 $this->created = $this->principal->created;
605 $this->modified = $this->principal->modified;
606 $this->resourcetypes = $this->principal->resourcetypes;
607 }
608 }
609 }
610
611
615 protected function FetchResource() {
616 if ( isset($this->exists) ) return; // True or false, we've got what we can already
617 if ( $this->_is_collection ) return; // We have all we're going to read
618
619 $sql = <<<EOQRY
620SELECT calendar_item.*, addressbook_resource.*, caldav_data.*
621 FROM caldav_data LEFT OUTER JOIN calendar_item USING (collection_id,dav_id)
622 LEFT OUTER JOIN addressbook_resource USING (dav_id)
623 WHERE caldav_data.dav_name = :dav_name
624EOQRY;
625 $params = array( ':dav_name' => $this->bound_from() );
626
627 $qry = new AwlQuery( $sql, $params );
628 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
629 $this->exists = true;
630 $row = $qry->Fetch();
631 $this->FromRow($row);
632 }
633 else {
634 $this->exists = false;
635 }
636 }
637
638
642 protected function FetchDeadProperties() {
643 if ( isset($this->dead_properties) ) return;
644
645 $this->dead_properties = array();
646 if ( !$this->exists || !$this->_is_collection ) return;
647
648 $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name) );
649 if ( $qry->Exec('DAVResource') ) {
650 while ( $property = $qry->Fetch() ) {
651 $this->dead_properties[$property->property_name] = self::BuildDeadPropertyXML($property->property_name,$property->property_value);
652 }
653 }
654 }
655
662 public static function BuildDeadPropertyXML($property_name, $raw_string) {
663 if ( !preg_match('{^\s*<.*>\s*$}s', $raw_string) ) return $raw_string;
664 $xmlns = null;
665 if ( preg_match( '{^(.*):([^:]+)$}', $property_name, $matches) ) {
666 $xmlns = $matches[1];
667 $property_name = $matches[2];
668 }
669 $xml = sprintf('<%s%s>%s</%s>', $property_name, (isset($xmlns)?' xmlns="'.$xmlns.'"':''), $raw_string, $property_name);
670 $xml_parser = xml_parser_create_ns('UTF-8');
671 $xml_tags = array();
672 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
673 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
674 $rc = xml_parse_into_struct( $xml_parser, $xml, $xml_tags );
675 if ( $rc == false ) {
676 $errno = xml_get_error_code($xml_parser);
677 dbg_error_log( 'ERROR', 'XML parsing error: %s (%d) at line %d, column %d',
678 xml_error_string($errno), $errno,
679 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
680 dbg_error_log( 'ERROR', "Error occurred in:\n%s\n",$xml);
681 if ($errno >= 200 && $errno < 300 && count($xml_tags) >= 3) {
682 // XML namespace error, but parsing was probably fine: continue and return tags (cf. #9)
683 dbg_error_log( 'ERROR', 'XML namespace error but tags extracted, trying to continue');
684 } else {
685 return $raw_string;
686 }
687 }
688 xml_parser_free($xml_parser);
689 $position = 0;
690 $xmltree = BuildXMLTree( $xml_tags, $position);
691 return $xmltree->GetContent();
692 }
693
697 protected function FetchPrivileges() {
698 global $session, $request;
699
700 if ( $this->dav_name == '/' || $this->dav_name == '' || $this->_is_external ) {
701 $this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set
702 dbg_error_log( 'DAVResource', ':FetchPrivileges: Read permissions for user accessing /' );
703 return;
704 }
705
706 if ( $session->AllowedTo('Admin') ) {
707 $this->privileges = privilege_to_bits('all');
708 dbg_error_log( 'DAVResource', ':FetchPrivileges: Full permissions for an administrator.' );
709 return;
710 }
711
712 if ( $this->IsPrincipal() ) {
713 if ( !isset($this->principal) ) $this->FetchPrincipal();
714 $this->privileges = $this->principal->Privileges();
715 dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username() );
716 return;
717 }
718
719 if ( ! isset($this->collection) ) $this->FetchCollection();
720 $this->privileges = 0;
721 if ( !isset($this->collection->path_privs) ) {
722 if ( !isset($this->parent) ) $this->GetParentContainer();
723
724 $this->collection->path_privs = $this->parent->Privileges();
725 $this->collection->user_no = $this->parent->GetProperty('user_no');
726 $this->collection->principal_id = $this->parent->GetProperty('principal_id');
727 }
728
729 $this->privileges = $this->collection->path_privs;
730 if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges );
731
732 dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user "%s" accessing "%s"',
733 decbin($this->privileges), $session->username, $this->dav_name() );
734
735 if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) {
736 $this->privileges |= $request->ticket->privileges();
737 dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) );
738 }
739
740 if ( isset($this->tickets) ) {
741 if ( !isset($this->resource_id) ) $this->FetchResource();
742 foreach( $this->tickets AS $k => $ticket ) {
743 if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) {
744 $this->privileges |= $ticket->privileges();
745 dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) );
746 }
747 }
748 }
749 }
750
751
756 if ( $this->dav_name == '/' ) return null;
757 if ( !isset($this->parent) ) {
758 if ( $this->_is_collection ) {
759 dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
760 $this->parent = new DAVResource( $this->parent_path() );
761 }
762 else {
763 dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
764 $this->parent = new DAVResource($this->collection->dav_name);
765 }
766 }
767 return $this->parent;
768 }
769
770
776 deprecated('DAVResource::FetchParentContainer');
777 return $this->GetParentContainer();
778 }
779
780
784 function Privileges() {
785 if ( !isset($this->privileges) ) $this->FetchPrivileges();
786 return $this->privileges;
787 }
788
789
796 function HavePrivilegeTo( $do_what, $any = null ) {
797 if ( !isset($this->privileges) ) $this->FetchPrivileges();
798 if ( !isset($any) ) $any = ($do_what != 'all');
799 $test_bits = privilege_to_bits( $do_what );
800 dbg_error_log( 'DAVResource', 'Testing %s privileges of "%s" (%s) against allowed "%s" => "%s" (%s)', ($any?'any':'exactly'),
801 $do_what, decbin($test_bits), decbin($this->privileges), ($this->privileges & $test_bits), decbin($this->privileges & $test_bits) );
802 if ( $any ) {
803 return ($this->privileges & $test_bits) > 0;
804 }
805 else {
806 return ($this->privileges & $test_bits) == $test_bits;
807 }
808 }
809
810
818 function NeedPrivilege( $privilege, $any = null ) {
819 global $request;
820
821 // Do the test
822 if ( $this->HavePrivilegeTo($privilege, $any) ) return;
823
824 // They failed, so output the error
825 $request->NeedPrivilege( $privilege, $this->dav_name );
826 exit(0); // Unecessary, but might clarify things
827 }
828
829
833 function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) {
834 if ( $privilege_names == null ) {
835 if ( !isset($this->privileges) ) $this->FetchPrivileges();
836 $privilege_names = bits_to_privilege($this->privileges, ($this->_is_collection ? $this->collection->type : null ) );
837 }
838 return privileges_to_XML( $privilege_names, $xmldoc);
839 }
840
841
846 if ( isset($this->supported_methods) ) return $this->supported_methods;
847
848 $this->supported_methods = array(
849 'OPTIONS' => '',
850 'PROPFIND' => '',
851 'REPORT' => '',
852 'DELETE' => '',
853 'LOCK' => '',
854 'UNLOCK' => '',
855 'MOVE' => ''
856 );
857 if ( $this->IsCollection() ) {
858/* if ( $this->IsPrincipal() ) {
859 $this->supported_methods['MKCALENDAR'] = '';
860 $this->supported_methods['MKCOL'] = '';
861 } */
862 switch ( $this->collection->type ) {
863 case 'root':
864 case 'email':
865 // We just override the list completely here.
866 $this->supported_methods = array(
867 'OPTIONS' => '',
868 'PROPFIND' => '',
869 'REPORT' => ''
870 );
871 break;
872
873 case 'schedule-outbox':
874 $this->supported_methods = array_merge(
875 $this->supported_methods,
876 array(
877 'POST' => '', 'PROPPATCH' => '', 'MKTICKET' => '', 'DELTICKET' => ''
878 )
879 );
880 break;
881 case 'schedule-inbox':
882 case 'calendar':
883 $this->supported_methods['GET'] = '';
884 $this->supported_methods['PUT'] = '';
885 $this->supported_methods['HEAD'] = '';
886 $this->supported_methods['MKTICKET'] = '';
887 $this->supported_methods['DELTICKET'] = '';
888 $this->supported_methods['ACL'] = '';
889 break;
890 case 'collection':
891 $this->supported_methods['MKTICKET'] = '';
892 $this->supported_methods['DELTICKET'] = '';
893 $this->supported_methods['BIND'] = '';
894 $this->supported_methods['ACL'] = '';
895 case 'principal':
896 $this->supported_methods['GET'] = '';
897 $this->supported_methods['HEAD'] = '';
898 $this->supported_methods['MKCOL'] = '';
899 $this->supported_methods['MKCALENDAR'] = '';
900 $this->supported_methods['PROPPATCH'] = '';
901 $this->supported_methods['BIND'] = '';
902 $this->supported_methods['ACL'] = '';
903 break;
904 }
905 }
906 else {
907 $this->supported_methods = array_merge(
908 $this->supported_methods,
909 array(
910 'GET' => '', 'HEAD' => '', 'PUT' => '', 'MKTICKET' => '', 'DELTICKET' => ''
911 )
912 );
913 }
914
915 return $this->supported_methods;
916 }
917
918
923 if ( !isset($this->supported_methods) ) $this->FetchSupportedMethods();
924 $methods = array();
925 foreach( $this->supported_methods AS $k => $v ) {
926// dbg_error_log( 'DAVResource', ':BuildSupportedMethods: Adding method "%s" which is "%s".', $k, $v );
927 $methods[] = new XMLElement( 'supported-method', null, array('name' => $k) );
928 }
929 return $methods;
930 }
931
932
937 if ( isset($this->supported_reports) ) return $this->supported_reports;
938
939 $this->supported_reports = array(
940 'DAV::principal-property-search' => '',
941 'DAV::principal-search-property-set' => '',
942 'DAV::expand-property' => '',
943 'DAV::principal-match' => '',
944 'DAV::sync-collection' => ''
945 );
946
947 if ( !isset($this->collection) ) $this->FetchCollection();
948
949 if ( $this->collection->is_calendar ) {
950 $this->supported_reports = array_merge(
951 $this->supported_reports,
952 array(
953 'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
954 'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
955 'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
956 )
957 );
958 }
959 if ( $this->collection->is_addressbook ) {
960 $this->supported_reports = array_merge(
961 $this->supported_reports,
962 array(
963 'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
964 'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
965 )
966 );
967 }
968 return $this->supported_reports;
969 }
970
971
975 function BuildSupportedReports( &$reply ) {
976 if ( !isset($this->supported_reports) ) $this->FetchSupportedReports();
977 $reports = array();
978 foreach( $this->supported_reports AS $k => $v ) {
979 dbg_error_log( 'DAVResource', ':BuildSupportedReports: Adding supported report "%s" which is "%s".', $k, $v );
980 $report = new XMLElement('report');
981 $reply->NSElement($report, $k );
982 $reports[] = new XMLElement('supported-report', $report );
983 }
984 return $reports;
985 }
986
987
991 function FetchTickets( ) {
992 global $c;
993 if ( isset($this->access_tickets) ) return;
994 $this->access_tickets = array();
995
996 $sql =
997'SELECT access_ticket.*, COALESCE( resource.dav_name, collection.dav_name) AS target_dav_name,
998 (access_ticket.expires < current_timestamp) AS expired,
999 dav_principal.dav_name AS principal_dav_name,
1000 EXTRACT( \'epoch\' FROM (access_ticket.expires - current_timestamp)) AS seconds,
1001 path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges
1002 FROM access_ticket JOIN collection ON (target_collection_id = collection_id)
1003 JOIN dav_principal ON (dav_owner_id = principal_id)
1004 LEFT JOIN caldav_data resource ON (resource.dav_id = access_ticket.target_resource_id)
1005 WHERE target_collection_id = :collection_id ';
1006 $params = array(':collection_id' => $this->collection->collection_id, ':scan_depth' => $c->permission_scan_depth);
1007 if ( $this->IsCollection() ) {
1008 $sql .= 'AND target_resource_id IS NULL';
1009 }
1010 else {
1011 if ( !isset($this->exists) ) $this->FetchResource();
1012 $sql .= 'AND target_resource_id = :dav_id';
1013 $params[':dav_id'] = $this->resource->dav_id;
1014 }
1015 if ( isset($this->exists) && !$this->exists ) return;
1016
1017 $qry = new AwlQuery( $sql, $params );
1018 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() ) {
1019 while( $ticket = $qry->Fetch() ) {
1020 $this->access_tickets[] = $ticket;
1021 }
1022 }
1023 }
1024
1025
1036 function BuildTicketinfo( &$reply ) {
1037 global $session, $request;
1038
1039 if ( !isset($this->access_tickets) ) $this->FetchTickets();
1040 $tickets = array();
1041 $show_all = $this->HavePrivilegeTo('DAV::read-acl');
1042 foreach( $this->access_tickets AS $meh => $trow ) {
1043 if ( !$show_all && ( $trow->dav_owner_id == $session->principal_id || $request->ticket->id() == $trow->ticket_id ) ) continue;
1044 dbg_error_log( 'DAVResource', ':BuildTicketinfo: Adding access_ticket "%s" which is "%s".', $trow->ticket_id, $trow->privileges );
1045 $ticket = new XMLElement( $reply->Tag( 'ticketinfo', 'http://www.xythos.com/namespaces/StorageServer', 'TKT' ) );
1046 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:id', $trow->ticket_id );
1047 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:owner', $reply->href( ConstructURL($trow->principal_dav_name)) );
1048 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:timeout', (isset($trow->seconds) ? sprintf( 'Seconds-%d', $trow->seconds) : 'infinity') );
1049 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:visits', 'infinity' );
1050 $privs = array();
1051 foreach( bits_to_privilege(bindec($trow->privileges) & bindec($trow->grantor_collection_privileges) ) AS $k => $v ) {
1052 $privs[] = $reply->NewXMLElement($v);
1053 }
1054 $reply->NSElement($ticket, 'DAV::privilege', $privs );
1055 $tickets[] = $ticket;
1056 }
1057 return $tickets;
1058 }
1059
1060
1068 function IsLocked( $depth = 0 ) {
1069 if ( !isset($this->_locks_found) ) {
1070 $this->_locks_found = array();
1074 $sql = 'SELECT * FROM locks WHERE :this_path::text ~ (\'^\'||dav_name||:match_end)::text';
1075 $qry = new AwlQuery($sql, array( ':this_path' => $this->dav_name, ':match_end' => ($depth == DEPTH_INFINITY ? '' : '$') ) );
1076 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) ) {
1077 while( $lock_row = $qry->Fetch() ) {
1078 $this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
1079 }
1080 }
1081 else {
1082 $this->DoResponse(500,i18n("Database Error"));
1083 // Does not return.
1084 }
1085 }
1086
1087 foreach( $this->_locks_found AS $lock_token => $lock_row ) {
1088 if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->dav_name ) {
1089 return $lock_token;
1090 }
1091 }
1092
1093 return false; // Nothing matched
1094 }
1095
1096
1100 function IsCollection() {
1101 return $this->_is_collection;
1102 }
1103
1104
1108 function IsPrincipal() {
1109 return $this->_is_collection && $this->_is_principal;
1110 }
1111
1112
1116 function IsCalendar() {
1117 return $this->_is_collection && $this->_is_calendar;
1118 }
1119
1120
1125 function IsProxyCollection( $type = 'any' ) {
1126 if ( $this->_is_proxy_resource ) {
1127 return ($type == 'any' || $type == $this->proxy_type);
1128 }
1129 return false;
1130 }
1131
1132
1137 function IsSchedulingCollection( $type = 'any' ) {
1138 if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1139 return ($type == 'any' || $type == $matches[1]);
1140 }
1141 return false;
1142 }
1143
1144
1149 function IsInSchedulingCollection( $type = 'any' ) {
1150 if ( !$this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1151 return ($type == 'any' || $type == $matches[1]);
1152 }
1153 return false;
1154 }
1155
1156
1160 function IsAddressbook() {
1161 return $this->_is_collection && $this->_is_addressbook;
1162 }
1163
1164
1168 function IsBinding() {
1169 return $this->_is_binding;
1170 }
1171
1172
1176 function IsExternal() {
1177 return $this->_is_external;
1178 }
1179
1180
1184 function Exists() {
1185 if ( ! isset($this->exists) ) {
1186 if ( $this->IsPrincipal() ) {
1187 if ( !isset($this->principal) ) $this->FetchPrincipal();
1188 $this->exists = $this->principal->Exists();
1189 }
1190 else if ( ! $this->IsCollection() ) {
1191 if ( !isset($this->resource) ) $this->FetchResource();
1192 }
1193 }
1194// dbg_error_log('DAVResource',' Checking whether "%s" exists. It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
1195 return $this->exists;
1196 }
1197
1198
1202 function ContainerExists() {
1203 if ( $this->collection->dav_name != $this->dav_name ) {
1204 return $this->collection->exists;
1205 }
1206 $parent = $this->GetParentContainer();
1207 return $parent->Exists();
1208 }
1209
1210
1215 function url() {
1216 if ( !isset($this->dav_name) ) {
1217 throw Exception("What! How can dav_name not be set?");
1218 }
1219 return ConstructURL($this->dav_name);
1220 }
1221
1222
1227 function dav_name() {
1228 if ( isset($this->dav_name) ) return $this->dav_name;
1229 return null;
1230 }
1231
1232
1237 function bound_from() {
1238 if ( isset($this->bound_from) ) return $this->bound_from;
1239 return $this->dav_name();
1240 }
1241
1242
1246 function set_bind_location( $new_dav_name ) {
1247 if ( !isset($this->bound_from) && isset($this->dav_name) ) {
1248 $this->bound_from = $this->dav_name;
1249 }
1250 $this->dav_name = $new_dav_name;
1251 return $this->dav_name;
1252 }
1253
1254
1258 function parent_path() {
1259 if ( $this->IsCollection() ) {
1260 if ( !isset($this->collection) ) $this->FetchCollection();
1261 if ( !isset($this->collection->parent_container) ) {
1262 $this->collection->parent_container = preg_replace( '{[^/]+/$}', '', $this->bound_from());
1263 }
1264 return $this->collection->parent_container;
1265 }
1266 return preg_replace( '{[^/]+$}', '', $this->bound_from());
1267 }
1268
1269
1270
1274 function principal_url() {
1275 if ( !isset($this->principal) ) $this->FetchPrincipal();
1276 return $this->principal->url();
1277 }
1278
1279
1283 function user_no() {
1284 if ( !isset($this->principal) ) $this->FetchPrincipal();
1285 return $this->principal->user_no();
1286 }
1287
1288
1292 function collection_id() {
1293 if ( !isset($this->collection) ) $this->FetchCollection();
1294 return $this->collection->collection_id;
1295 }
1296
1297
1301 function timezone_name() {
1302 if ( !isset($this->collection) ) $this->FetchCollection();
1303 return $this->collection->timezone;
1304 }
1305
1306
1310 function resource() {
1311 if ( !isset($this->resource) ) $this->FetchResource();
1312 return $this->resource;
1313 }
1314
1315
1319 function unique_tag() {
1320 if ( isset($this->unique_tag) ) return $this->unique_tag;
1321 if ( $this->IsPrincipal() && !isset($this->principal) ) {
1322 $this->FetchPrincipal();
1323 $this->unique_tag = $this->principal->unique_tag();
1324 }
1325 else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1326
1327 if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
1328
1329 return $this->unique_tag;
1330 }
1331
1332
1336 function resource_id() {
1337 if ( isset($this->resource_id) ) return $this->resource_id;
1338 if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1339 else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1340
1341 if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null;
1342
1343 return $this->resource_id;
1344 }
1345
1346
1350 function sync_token( $cachedOK = true ) {
1351 dbg_error_log('DAVResource', 'Request for a%scached sync-token', ($cachedOK ? ' ' : 'n un') );
1352 if ( $this->IsPrincipal() ) return null;
1353 if ( $this->collection_id() == 0 ) return null;
1354 if ( !isset($this->sync_token) || !$cachedOK ) {
1355 $sql = 'SELECT new_sync_token( 0, :collection_id) AS sync_token';
1356 $params = array( ':collection_id' => $this->collection_id());
1357 $qry = new AwlQuery($sql, $params );
1358 if ( !$qry->Exec() || !$row = $qry->Fetch() ) {
1359 if ( !$qry->QDo('SELECT new_sync_token( 0, :collection_id) AS sync_token', $params) ) throw new Exception('Problem with database query');
1360 $row = $qry->Fetch();
1361 }
1362 $this->sync_token = 'data:,'.$row->sync_token;
1363 }
1364 dbg_error_log('DAVResource', 'Returning sync token of "%s"', $this->sync_token );
1365 return $this->sync_token;
1366 }
1367
1371 function IsPublic() {
1372 return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' );
1373 }
1374
1375
1379 function IsPublicOnly() {
1380 return ( isset($this->collection->publicly_events_only) && $this->collection->publicly_events_only == 't' );
1381 }
1382
1383
1387 function ContainerType() {
1388 if ( $this->IsPrincipal() ) return 'root';
1389 if ( !$this->IsCollection() ) return $this->collection->type;
1390
1391 if ( ! isset($this->collection->parent_container) ) return null;
1392
1393 if ( isset($this->parent_container_type) ) return $this->parent_container_type;
1394
1395 if ( preg_match('#/[^/]+/#', $this->collection->parent_container) ) {
1396 $this->parent_container_type = 'principal';
1397 }
1398 else {
1399 $qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
1400 array( ':parent_name' => $this->collection->parent_container ) );
1401 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
1402 if ( $parent->is_calendar == 't' )
1403 $this->parent_container_type = 'calendar';
1404 else if ( $parent->is_addressbook == 't' )
1405 $this->parent_container_type = 'addressbook';
1406 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
1407 $this->parent_container_type = 'schedule-'. $matches[3]. 'box';
1408 else
1409 $this->parent_container_type = 'collection';
1410 }
1411 else
1412 $this->parent_container_type = null;
1413 }
1414 return $this->parent_container_type;
1415 }
1416
1417
1421 function BuildACE( &$xmldoc, $privs, $principal ) {
1422 $privilege_names = bits_to_privilege($privs, ($this->_is_collection ? $this->collection->type : 'resource'));
1423 $privileges = array();
1424 foreach( $privilege_names AS $k ) {
1425 $privilege = new XMLElement('privilege');
1426 if ( isset($xmldoc) )
1427 $xmldoc->NSElement($privilege,$k);
1428 else
1429 $privilege->NewElement($k);
1430 $privileges[] = $privilege;
1431 }
1432 $ace = new XMLElement('ace', array(
1433 new XMLElement('principal', $principal),
1434 new XMLElement('grant', $privileges ) )
1435 );
1436 return $ace;
1437 }
1438
1442 function GetACL( &$xmldoc ) {
1443 if ( !isset($this->principal) ) $this->FetchPrincipal();
1444 $default_privs = $this->principal->default_privileges;
1445 if ( isset($this->collection->default_privileges) ) $default_privs = $this->collection->default_privileges;
1446
1447 $acl = array();
1448 $acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) );
1449
1450 $qry = new AwlQuery('SELECT dav_principal.dav_name, grants.* FROM grants JOIN dav_principal ON (to_principal=principal_id) WHERE by_collection = :collection_id OR by_principal = :principal_id ORDER BY by_collection',
1451 array( ':collection_id' => $this->collection->collection_id,
1452 ':principal_id' => $this->principal->principal_id() ) );
1453 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
1454 $by_collection = null;
1455 while( $grant = $qry->Fetch() ) {
1456 if ( !isset($by_collection) ) $by_collection = isset($grant->by_collection);
1457 if ( $by_collection && !isset($grant->by_collection) ) break;
1458 $acl[] = $this->BuildACE($xmldoc, $grant->privileges, $xmldoc->href(ConstructURL($grant->dav_name)) );
1459 }
1460 }
1461
1462 $acl[] = $this->BuildACE($xmldoc, $default_privs, new XMLElement('authenticated') );
1463
1464 return $acl;
1465
1466 }
1467
1468
1472 function GetProperty( $name ) {
1473// dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name );
1474 $value = null;
1475
1476 switch( $name ) {
1477 case 'collection_id':
1478 return $this->collection_id();
1479 break;
1480
1481 case 'principal_id':
1482 if ( !isset($this->principal) ) $this->FetchPrincipal();
1483 return $this->principal->principal_id();
1484 break;
1485
1486 case 'resourcetype':
1487 if ( isset($this->resourcetypes) ) {
1488 $this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes);
1489 $type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes);
1490 foreach( $type_list AS $k => $resourcetype ) {
1491 if ( preg_match( '{^([^:]+):([^:]+) \s+ xmlns:([^=]+)="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1492 $type_list[$k] = $matches[4] .':' .$matches[2];
1493 }
1494 else if ( preg_match( '{^([^:]+) \s+ xmlns="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1495 $type_list[$k] = $matches[2] .':' .$matches[1];
1496 }
1497 }
1498 return $type_list;
1499 }
1500
1501 case 'resource':
1502 if ( !isset($this->resource) ) $this->FetchResource();
1503 return clone($this->resource);
1504 break;
1505
1506 case 'dav-data':
1507 if ( !isset($this->resource) ) $this->FetchResource();
1508 dbg_error_log( 'DAVResource', ':GetProperty: dav-data: fetched resource does%s exist.', ($this->exists?'':' not') );
1509 return $this->resource->caldav_data;
1510 break;
1511
1512 case 'principal':
1513 if ( !isset($this->principal) ) $this->FetchPrincipal();
1514 return clone($this->principal);
1515 break;
1516
1517 default:
1518 if ( isset($this->{$name}) ) {
1519 if ( ! is_object($this->{$name}) ) return $this->{$name};
1520 return clone($this->{$name});
1521 }
1522 if ( $this->_is_principal ) {
1523 if ( !isset($this->principal) ) $this->FetchPrincipal();
1524 if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1525 if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1526 }
1527 else if ( $this->_is_collection ) {
1528 if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1529 if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1530 }
1531 else {
1532 if ( !isset($this->resource) ) $this->FetchResource();
1533 if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
1534 if ( !isset($this->principal) ) $this->FetchPrincipal();
1535 if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1536 if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1537 }
1538 if ( isset($this->{$name}) ) {
1539 if ( ! is_object($this->{$name}) ) return $this->{$name};
1540 return clone($this->{$name});
1541 }
1542 // dbg_error_log( 'DAVResource', ':GetProperty: Failed to find property "%s" on "%s".', $name, $this->dav_name );
1543 }
1544
1545 return $value;
1546 }
1547
1548
1553 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1554 $allprop = array_merge( (isset($this->dead_properties)?array_keys($this->dead_properties):array()),
1555 (isset($include_properties)?$include_properties:array()),
1556 array(
1557 'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
1558 'DAV::creationdate', 'DAV::getetag', 'DAV::getcontentlanguage', 'DAV::supportedlock', 'DAV::lockdiscovery',
1559 'DAV::owner', 'DAV::principal-URL', 'DAV::current-user-principal',
1560 'urn:ietf:params:xml:ns:carddav:max-resource-size', 'urn:ietf:params:xml:ns:carddav:supported-address-data',
1561 'urn:ietf:params:xml:ns:carddav:addressbook-description', 'urn:ietf:params:xml:ns:carddav:addressbook-home-set'
1562 ) );
1563
1564 return $allprop;
1565 }
1566
1567
1571 function ResourceProperty( $tag, $prop, &$reply, &$denied ) {
1572 global $c, $session, $request;
1573
1574// dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
1575
1576 if ( $reply === null ) $reply = $GLOBALS['reply'];
1577
1578 switch( $tag ) {
1579 case 'DAV::allprop':
1580 $property_list = $this->DAV_AllProperties();
1581 $discarded = array();
1582 foreach( $property_list AS $k => $v ) {
1583 $this->ResourceProperty($v, $prop, $reply, $discarded);
1584 }
1585 break;
1586
1587 case 'DAV::href':
1588 $prop->NewElement('href', ConstructURL($this->dav_name) );
1589 break;
1590
1591 case 'DAV::resource-id':
1592 if ( $this->resource_id > 0 )
1593 $reply->DAVElement( $prop, 'resource-id', $reply->href(ConstructURL('/.resources/'.$this->resource_id) ) );
1594 else
1595 return false;
1596 break;
1597
1598 case 'DAV::parent-set':
1599 $sql = <<<EOQRY
1600SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
1601 WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
1602EOQRY;
1603 $qry = new AwlQuery($sql, array( ':bound_from' => $this->bound_from() ) );
1604 $parents = array();
1605 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() > 0 ) {
1606 while( $row = $qry->Fetch() ) {
1607 $parents[$row->parent_container] = true;
1608 }
1609 }
1610 $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->bound_from())] = true;
1611 $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->dav_name())] = true;
1612
1613 $parent_set = $reply->DAVElement( $prop, 'parent-set' );
1614 foreach( $parents AS $parent => $v ) {
1615 if ( preg_match( '{^(.*)?/([^/]+)/?$}', $parent, $matches ) ) {
1616 $reply->DAVElement($parent_set, 'parent', array(
1617 new XMLElement( 'href', ConstructURL($matches[1])),
1618 new XMLElement( 'segment', $matches[2])
1619 ));
1620 }
1621 else if ( $parent == '/' ) {
1622 $reply->DAVElement($parent_set, 'parent', array(
1623 new XMLElement( 'href', '/'),
1624 new XMLElement( 'segment', ( ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))
1625 ));
1626 }
1627 }
1628 break;
1629
1630 case 'DAV::getcontenttype':
1631 if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1632 $prop->NewElement('getcontenttype', $this->contenttype );
1633 break;
1634
1635 case 'DAV::resourcetype':
1636 $resourcetypes = $prop->NewElement('resourcetype' );
1637 if ( $this->_is_collection ) {
1638 $type_list = $this->GetProperty('resourcetype');
1639 if ( !is_array($type_list) ) return true;
1640 // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
1641 foreach( $type_list AS $k => $v ) {
1642 if ( $v == '' ) continue;
1643 $reply->NSElement( $resourcetypes, $v );
1644 }
1645 if ( $this->_is_binding ) {
1646 $reply->NSElement( $resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding' );
1647 }
1648 }
1649 break;
1650
1651 case 'DAV::getlastmodified':
1653 $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) );
1654 break;
1655
1656 case 'DAV::creationdate':
1658 $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true) );
1659 break;
1660
1661 case 'DAV::getcontentlength':
1662 if ( $this->_is_collection ) return false;
1663 if ( !isset($this->resource) ) $this->FetchResource();
1664 if ( isset($this->resource) ) {
1665 $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data) );
1666 }
1667 break;
1668
1669 case 'DAV::getcontentlanguage':
1670 $locale = (isset($c->current_locale) ? $c->current_locale : '');
1671 if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale;
1672 $reply->NSElement($prop, $tag, $locale );
1673 break;
1674
1675 case 'DAV::acl-restrictions':
1676 $reply->NSElement($prop, $tag, array( new XMLElement('grant-only'), new XMLElement('no-invert') ) );
1677 break;
1678
1679 case 'DAV::inherited-acl-set':
1680 $inherited_acls = array();
1681 if ( ! $this->_is_collection ) {
1682 $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
1683 }
1684 $reply->NSElement($prop, $tag, $inherited_acls );
1685 break;
1686
1687 case 'DAV::owner':
1688 // The principal-URL of the owner
1689 if ( $this->IsExternal() ){
1690 $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL($this->collection->bound_from )) );
1691 }
1692 else {
1693 $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL(DeconstructURL($this->principal_url())) ) );
1694 }
1695 break;
1696
1697 case 'DAV::add-member':
1698 if ( ! $this->_is_collection ) return false;
1699 if ( $this->_is_principal ) return false;
1700 if ( isset($c->post_add_member) && $c->post_add_member === false ) return false;
1701 $reply->DAVElement( $prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())).'?add_member') );
1702 break;
1703
1704 // Empty tag responses.
1705 case 'DAV::group':
1706 case 'DAV::alternate-URI-set':
1707 $reply->NSElement($prop, $tag );
1708 break;
1709
1710 case 'DAV::getetag':
1711 if ( $this->_is_collection ) return false;
1712 $reply->NSElement($prop, $tag, $this->unique_tag() );
1713 break;
1714
1715 case 'http://calendarserver.org/ns/:getctag':
1716 if ( ! $this->_is_collection ) return false;
1717 $reply->NSElement($prop, $tag, $this->unique_tag() );
1718 break;
1719
1720 case 'DAV::sync-token':
1721 if ( ! $this->_is_collection ) return false;
1722 $sync_token = $this->sync_token();
1723 if ( empty($sync_token) ) return false;
1724 $reply->NSElement($prop, $tag, $sync_token );
1725 break;
1726
1727 case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
1728 $proxy_type = 'read';
1729 case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
1730 if ( isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy ) return false;
1731 if ( !isset($proxy_type) ) $proxy_type = 'write';
1732 // ProxyFor is an already constructed URL
1733 $this->FetchPrincipal();
1734 $reply->CalendarserverElement($prop, 'calendar-proxy-'.$proxy_type.'-for', $reply->href( $this->principal->ProxyFor($proxy_type) ) );
1735 break;
1736
1737 case 'http://calendarserver.org/ns/:group-member-set':
1738 case 'DAV::group-member-set':
1739 if ( $this->_is_proxy_resource ) {
1740 $this->FetchPrincipal();
1741 if ( $this->proxy_type == 'read' ) {
1742 $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->ReadProxyGroup() ) );
1743 } else {
1744 $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->WriteProxyGroup() ) );
1745 }
1746 } else {
1747 return false; // leave this to DAVPrincipal
1748 }
1749 break;
1750
1751 case 'http://calendarserver.org/ns/:group-membership':
1752 case 'DAV::group-membership':
1753 if ( $this->_is_proxy_resource ) {
1754 /* the calendar-proxy-{read,write} pseudo-principal should not be a member of any group */
1755 $reply->NSElement($prop, $tag );
1756 } else {
1757 return false; // leave this to DAVPrincipal
1758 }
1759 break;
1760
1761 case 'DAV::current-user-privilege-set':
1762 if ( $this->HavePrivilegeTo('DAV::read-current-user-privilege-set') ) {
1763 $reply->NSElement($prop, $tag, $this->BuildPrivileges() );
1764 }
1765 else {
1766 $denied[] = $tag;
1767 }
1768 break;
1769
1770 case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
1771 if ( ! $this->IsCalendar() && ! $this->IsSchedulingCollection() ) return false;
1772 $reply->NSElement($prop, $tag, 'text/calendar' );
1773 break;
1774
1775 case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
1776 if ( ! $this->_is_collection ) return false;
1777 if ( $this->IsCalendar() ) {
1778 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1779 if ( isset($this->dead_properties[$tag]) ) {
1780 $set_of_components = $this->dead_properties[$tag];
1781 foreach( $set_of_components AS $k => $v ) {
1782 if ( preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches) ) {
1783 $set_of_components[$k] = $matches[1];
1784 }
1785 else {
1786 unset( $set_of_components[$k] );
1787 }
1788 }
1789 }
1790 else if ( isset($c->default_calendar_components) && is_array($c->default_calendar_components) ) {
1791 $set_of_components = $c->default_calendar_components;
1792 }
1793 else {
1794 $set_of_components = array( 'VEVENT', 'VTODO', 'VJOURNAL' );
1795 }
1796 }
1797 else if ( $this->IsSchedulingCollection() )
1798 $set_of_components = array( 'VEVENT', 'VTODO', 'VFREEBUSY' );
1799 else return false;
1800 $components = array();
1801 foreach( $set_of_components AS $v ) {
1802 $components[] = $reply->NewXMLElement( 'comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
1803 }
1804 $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components );
1805 break;
1806
1807 case 'DAV::supported-method-set':
1808 $prop->NewElement('supported-method-set', $this->BuildSupportedMethods() );
1809 break;
1810
1811 case 'DAV::supported-report-set':
1812 $prop->NewElement('supported-report-set', $this->BuildSupportedReports( $reply ) );
1813 break;
1814
1815 case 'DAV::supportedlock':
1816 $prop->NewElement('supportedlock',
1817 new XMLElement( 'lockentry',
1818 array(
1819 new XMLElement('lockscope', new XMLElement('exclusive')),
1820 new XMLElement('locktype', new XMLElement('write')),
1821 )
1822 )
1823 );
1824 break;
1825
1826 case 'DAV::supported-privilege-set':
1827 $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply) );
1828 break;
1829
1830 case 'DAV::principal-collection-set':
1831 $prop->NewElement( 'principal-collection-set', $reply->href( ConstructURL('/') ) );
1832 break;
1833
1834 case 'DAV::current-user-principal':
1835 $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) );
1836 break;
1837
1838 case 'SOME-DENIED-PROPERTY':
1839 $denied[] = $reply->Tag($tag);
1840 break;
1841
1842 case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
1843 if ( ! $this->_is_collection ) return false;
1844 if ( !isset($this->collection->vtimezone) || $this->collection->vtimezone == '' ) return false;
1845
1846 $cal = new iCalComponent();
1847 $cal->VCalendar();
1848 $cal->AddComponent( new iCalComponent($this->collection->vtimezone) );
1849 $reply->NSElement($prop, $tag, $cal->Render() );
1850 break;
1851
1852 case 'urn:ietf:params:xml:ns:carddav:address-data':
1853 case 'urn:ietf:params:xml:ns:caldav:calendar-data':
1854 if ( $this->_is_collection ) return false;
1855 if ( !isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false ) return false;
1856 if ( !isset($this->resource) ) $this->FetchResource();
1857 $reply->NSElement($prop, $tag, $this->resource->caldav_data );
1858 break;
1859
1860 case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
1861 if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1862 $reply->NSElement($prop, $tag, $c->carddav_max_resource_size );
1863 break;
1864
1865 case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
1866 if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1867 $address_data = $reply->NewXMLElement( 'address-data', false,
1868 array( 'content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
1869 $reply->NSElement($prop, $tag, $address_data );
1870 break;
1871
1872 case 'DAV::acl':
1873 if ( $this->HavePrivilegeTo('DAV::read-acl') ) {
1874 $reply->NSElement($prop, $tag, $this->GetACL( $reply ) );
1875 }
1876 else {
1877 $denied[] = $tag;
1878 }
1879 break;
1880
1881 case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
1882 case 'DAV::ticketdiscovery':
1883 $reply->NSElement($prop,'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply) );
1884 break;
1885
1886 default:
1887 $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag));
1888 if ( isset($property_value) ) {
1889 $reply->NSElement($prop, $tag, $property_value );
1890 }
1891 else {
1892 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1893 if ( isset($this->dead_properties[$tag]) ) {
1894 $reply->NSElement($prop, $tag, $this->dead_properties[$tag] );
1895 }
1896 else {
1897// dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
1898 return false;
1899 }
1900 }
1901 }
1902
1903 return true;
1904 }
1905
1906
1914 function GetPropStat( $properties, &$reply, $props_only = false ) {
1915 global $request;
1916
1917 dbg_error_log('DAVResource',':GetPropStat: propstat for href "%s"', $this->dav_name );
1918
1919 $prop = new XMLElement('prop', null, null, 'DAV:');
1920 $denied = array();
1921 $not_found = array();
1922 foreach( $properties AS $k => $tag ) {
1923 if ( is_object($tag) ) {
1924 dbg_error_log( 'DAVResource', ':GetPropStat: "$properties" should be an array of text. Assuming this object is an XMLElement!.' );
1925 $tag = $tag->GetNSTag();
1926 }
1927 $found = $this->ResourceProperty($tag, $prop, $reply, $denied );
1928 if ( !$found ) {
1929 if ( !isset($this->principal) ) $this->FetchPrincipal();
1930 $found = $this->principal->PrincipalProperty( $tag, $prop, $reply, $denied );
1931 }
1932 if ( ! $found ) {
1933// dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
1934 $not_found[] = $tag;
1935 }
1936 }
1937 if ( $props_only ) return $prop;
1938
1939 $status = new XMLElement('status', 'HTTP/1.1 200 OK', null, 'DAV:' );
1940
1941 $elements = array( new XMLElement( 'propstat', array($prop,$status), null, 'DAV:' ) );
1942
1943 if ( count($denied) > 0 ) {
1944 $status = new XMLElement('status', 'HTTP/1.1 403 Forbidden', null, 'DAV:' );
1945 $noprop = new XMLElement('prop', null, null, 'DAV:');
1946 foreach( $denied AS $k => $v ) {
1947 $reply->NSElement($noprop, $v);
1948 }
1949 $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
1950 }
1951
1952 if ( !$request->PreferMinimal() && count($not_found) > 0 ) {
1953 $status = new XMLElement('status', 'HTTP/1.1 404 Not Found', null, 'DAV:' );
1954 $noprop = new XMLElement('prop', null, null, 'DAV:');
1955 foreach( $not_found AS $k => $v ) {
1956 $reply->NSElement($noprop,$v);
1957 }
1958 $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
1959 }
1960 return $elements;
1961 }
1962
1963
1972 function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) {
1973 dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() );
1974
1975 if ( !$this->Exists() ) return null;
1976
1977 $elements = $this->GetPropStat( $properties, $reply );
1978 if ( isset($bound_parent_path) ) {
1979 $dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name );
1980 }
1981 else {
1982 $dav_name = $this->dav_name;
1983 }
1984
1985 array_unshift( $elements, $reply->href(ConstructURL($dav_name)));
1986
1987 $response = new XMLElement( 'response', $elements, null, 'DAV:' );
1988
1989 return $response;
1990 }
1991
1992}
NeedPrivilege( $privilege, $any=null)
FromPath($inpath)
BuildSupportedMethods()
IsSchedulingCollection( $type='any')
static BuildDeadPropertyXML($property_name, $raw_string)
IsInSchedulingCollection( $type='any')
GetProperty( $name)
BuildSupportedReports(&$reply)
GetPropStat( $properties, &$reply, $props_only=false)
BuildTicketinfo(&$reply)
BuildPrivileges( $privilege_names=null, &$xmldoc=null)
FetchSupportedMethods()
ResourceProperty( $tag, $prop, &$reply, &$denied)
GetACL(&$xmldoc)
IsProxyCollection( $type='any')
sync_token( $cachedOK=true)
HavePrivilegeTo( $do_what, $any=null)
IsLocked( $depth=0)
__construct( $parameters=null, DAVResource $prefetched_collection=null)
FetchParentContainer()
FetchSupportedReports()
RenderAsXML( $properties, &$reply, $bound_parent_path=null)
set_bind_location( $new_dav_name)
BuildACE(&$xmldoc, $privs, $principal)