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