From 65e6eb2effc692b82bb04dbf89ea98a5a4b65ac6 Mon Sep 17 00:00:00 2001 From: Andrew McMillan Date: Mon, 15 Mar 2010 14:55:06 +1300 Subject: [PATCH] GET now working with bound resources. --- inc/DAVResource.php | 42 +++- inc/caldav-GET.php | 190 ++++++++---------- .../regression-suite/968-GET-bound.result | 41 ++++ .../tests/regression-suite/968-GET-bound.test | 16 ++ 4 files changed, 180 insertions(+), 109 deletions(-) create mode 100644 testing/tests/regression-suite/968-GET-bound.result create mode 100644 testing/tests/regression-suite/968-GET-bound.test diff --git a/inc/DAVResource.php b/inc/DAVResource.php index 949a3839..4dee75c8 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -384,13 +384,11 @@ EOSQL; $this->collection->default_privileges = (1 | 16 | 32); } else { - $bind_path = $this->dav_name; - if ( !preg_match( '{/$}', $bind_path ) ) $bind_path .= '/'; $sql = << $bind_path, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); + $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth ); + if ( !preg_match( '#/$#', $this->dav_name ) ) { + $sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash '; + $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name); + $params[':plus_slash'] = $this->dav_name.'/'; + } + $sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1'; $qry = new AwlQuery( $sql, $params ); if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) { $this->collection = $row; @@ -406,9 +410,9 @@ EOSQL; $this->_is_binding = true; $this->collection->parent_set = $row->parent_container; $this->collection->parent_container = $row->bind_parent_container; - $this->bound_from = $row->dav_name; + $this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name); $this->collection->bound_from = $row->dav_name; - $this->collection->dav_name = $this->dav_name; + $this->collection->dav_name = $row->bound_to; if ( isset($row->access_ticket_id) ) { if ( !isset($this->tickets) ) $this->tickets = array(); $this->tickets[] = new DAVTicket($row->access_ticket_id); @@ -489,7 +493,7 @@ EOSQL; SELECT * FROM caldav_data LEFT JOIN calendar_item USING (collection_id,dav_id) WHERE caldav_data.dav_name = :dav_name EOQRY; - $params = array( ':dav_name' => $this->dav_name ); + $params = array( ':dav_name' => $this->bound_from() ); $qry = new AwlQuery( $sql, $params ); if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) { @@ -574,6 +578,7 @@ EOQRY; } if ( isset($this->tickets) ) { + if ( !isset($this->resource_id) ) $this->FetchResource(); foreach( $this->tickets AS $k => $ticket ) { if ( $ticket->MatchesResource($this->resource_id) || $ticket->MatchesPath($this->dav_name) ) { $this->privileges |= $ticket->privileges(); @@ -983,6 +988,15 @@ EOQRY; } + /** + * Returns the database row for this resource + */ + function event() { + if ( !isset($this->resource) ) $this->FetchResource(); + return $this->resource; + } + + /** * Returns the principal-URL for this resource */ @@ -997,6 +1011,20 @@ EOQRY; } + /** + * Returns the definitive resource_id for this resource - usually a dav_id + */ + function resource_id() { + if ( isset($this->resource_id) ) return $this->resource_id; + if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal(); + else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource(); + + if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null; + + return $this->resource_id; + } + + /** * Checks whether the target collection is publicly_readable */ diff --git a/inc/caldav-GET.php b/inc/caldav-GET.php index 4d46e782..a23c4e4d 100644 --- a/inc/caldav-GET.php +++ b/inc/caldav-GET.php @@ -11,98 +11,64 @@ dbg_error_log("get", "GET method handler"); require_once("iCalendar.php"); +require_once("DAVResource.php"); + +$dav_resource = new DAVResource($request->path); +$dav_resource->NeedPrivilege( array('urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read') ); + +if ( ! $dav_resource->Exists() ) { + $request->DoResponse( 404, translate("Resource Not Found.") ); +} + +function obfuscate_event( $resource ) { + // The user is not admin / owner of this calendar looking at his calendar and can not admin the other cal, + // or maybe they don't have *read* access but they got here, so they must at least have free/busy access + // so we will present an obfuscated version of the event that just says "Busy" (translated :-) + $confidential = new iCalComponent(); + $confidential->SetType($resource->GetType()); + $confidential->AddProperty( 'SUMMARY', translate('Busy') ); + $confidential->AddProperty( 'CLASS', 'CONFIDENTIAL' ); + $confidential->SetProperties( $resource->GetProperties('DTSTART'), 'DTSTART' ); + $confidential->SetProperties( $resource->GetProperties('RRULE'), 'RRULE' ); + $confidential->SetProperties( $resource->GetProperties('DURATION'), 'DURATION' ); + $confidential->SetProperties( $resource->GetProperties('DTEND'), 'DTEND' ); + $confidential->SetProperties( $resource->GetProperties('UID'), 'UID' ); + $confidential->SetProperties( $resource->GetProperties('CREATED'), 'CREATED' ); + + return $confidential; +} -$request->NeedPrivilege( array('urn:ietf:params:xml:ns:caldav:read-free-busy','DAV::read') ); - -if ( $request->IsCollection() ) { - if ( $request->IsCalendar() ) { - /** - * The CalDAV specification does not define GET on a collection, but typically this is - * used as a .ics download for the whole collection, which is what we do also. - * - * @todo Change this to reference the collection_id of the collection at this location. - */ - $sql = 'SELECT caldav_data, class, caldav_type, calendar_item.user_no, logged_user FROM caldav_data INNER JOIN calendar_item USING ( dav_id ) WHERE caldav_data.dav_name ~ :dav_name '; - if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= ' ORDER BY dav_id'; - $params = array( ':dav_name' => $request->path.'[^/]+$'); - } - else { +if ( $dav_resource->IsCollection() ) { + if ( ! $dav_resource->IsCalendar() ) { /** RFC2616 says we must send an Allow header if we send a 405 */ header("Allow: PROPFIND,PROPPATCH,OPTIONS,MKCOL,REPORT,DELETE"); - $request->DoResponse( 405, translate("GET requests are only handled on calendar collections.") ); - } -} -else { - $sql = 'SELECT caldav_data, caldav_data.dav_etag, class, caldav_type, calendar_item.user_no, logged_user FROM caldav_data INNER JOIN calendar_item USING ( dav_id ) WHERE caldav_data.dav_name = :dav_name '; - $params = array( ':dav_name' => $request->path); -} - -$qry = new AwlQuery( $sql, $params ); -if ( !$qry->Exec("GET",__LINE__,__FILE__) ) { - $request->DoResponse( 500, translate("Database Error") ); -} -else if ( $qry->rows() == 1 && ! $request->IsCollection() ) { - $event = $qry->Fetch(); - $resource = new iCalComponent( $event->caldav_data ); - - /** Default deny... */ - $allowed = false; - if ( $request->AllowedTo('all') || $session->user_no == $event->user_no || $session->user_no == $event->logged_user - || ( $c->allow_get_email_visibility && $resource->IsAttendee($session->email) ) ) { - /** - * These people get to see all of the event, and they should always - * get any alarms as well. - */ - $allowed = true; - } - else if ( $event->class != 'PRIVATE' ) { - $allowed = true; // but we may well obfuscate it below - if ( ! $request->HavePrivilegeTo('DAV::read') || ( $event->class == 'CONFIDENTIAL' && ! $request->HavePrivilegeTo('DAV::write-content') ) ) { - // The user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal, - // or maybe they don't have *read* access but they got here, so they must at least have free/busy access - // so we will present an obfuscated version of the event that just says "Busy" (translated :-) - $confidential = new iCalComponent(); - - $ical = new iCalComponent( $event->caldav_data ); - $resources = $ical->GetComponents('VTIMEZONE',false); - $first = $resources[0]; - - $confidential->SetType($event->caldav_type); - $confidential->AddProperty( 'SUMMARY', translate('Busy') ); - $confidential->AddProperty( 'CLASS', 'CONFIDENTIAL' ); - $confidential->SetProperties( $first->GetProperties('DTSTART'), 'DTSTART' ); - $confidential->SetProperties( $first->GetProperties('RRULE'), 'RRULE' ); - $confidential->SetProperties( $first->GetProperties('DURATION'), 'DURATION' ); - $confidential->SetProperties( $first->GetProperties('DTEND'), 'DTEND' ); - $confidential->SetProperties( $first->GetProperties('UID'), 'UID' ); - $confidential->SetProperties( $first->GetProperties('CREATED'), 'CREATED' ); - - $ical->SetComponents( array($confidential), $confidential->GetType() ); - $event->caldav_data = $ical->Render(); - } - } - // else $event->class == 'PRIVATE' and this person may not see it. - - if ( ! $allowed ) { - $request->DoResponse( 403, translate("Forbidden") ); + $request->DoResponse( 405, translate("GET requests on collections are only supported for calendars.") ); + } + + /** + * The CalDAV specification does not define GET on a collection, but typically this is + * used as a .ics download for the whole collection, which is what we do also. + */ + $sql = 'SELECT caldav_data, class, caldav_type, calendar_item.user_no, logged_user '; + $sql .= 'FROM caldav_data INNER JOIN calendar_item USING ( dav_id ) WHERE caldav_data.collection_id = :collection_id '; + if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= ' ORDER BY dav_id'; + $params = array( ':collection_id' => $dav_resource->resource_id() ); + + $qry = new AwlQuery( $sql, $params ); + if ( !$qry->Exec("GET",__LINE__,__FILE__) ) { + $request->DoResponse( 500, translate("Database Error") ); } - header( "Etag: \"$event->dav_etag\"" ); - header( "Content-Length: ".strlen($event->caldav_data) ); - $request->DoResponse( 200, ($request->method == "HEAD" ? "" : $event->caldav_data), "text/calendar; charset=\"utf-8\"" ); -} -else if ( $qry->rows() < 1 && ! $request->IsCollection() ) { - $request->DoResponse( 404, translate("Calendar Resource Not Found.") ); -} -else { /** * Here we are constructing a whole calendar response for this collection, including * the timezones that are referred to by the events we have selected. */ $vcal = new iCalComponent(); - if ( isset($request->collection->dav_displayname) ) { - $vcal->VCalendar( array("X-WR-CALNAME" => $request->collection->dav_displayname) ); + $vcal->VCalendar(); + $displayname = $dav_resource->GetProperty('displayname'); + if ( isset($displayname) ) { + $vcal->AddProperty("X-WR-CALNAME", $displayname); } $need_zones = array(); @@ -127,7 +93,7 @@ else { $tzid = $resource->GetPParamValue('DUE', 'TZID'); if ( isset($tzid) && !isset($need_zones[$tzid]) ) $need_zones[$tzid] = 1; $tzid = $resource->GetPParamValue('DTEND', 'TZID'); if ( isset($tzid) && !isset($need_zones[$tzid]) ) $need_zones[$tzid] = 1; - if ( $request->AllowedTo('all') || $session->user_no == $event->user_no || $session->user_no == $event->logged_user + if ( $dav_resource->HavePrivilegeTo('all') || $session->user_no == $event->user_no || $session->user_no == $event->logged_user || ( $c->allow_get_email_visibility && $resource->IsAttendee($session->email) ) ) { /** * These people get to see all of the event, and they should always @@ -139,23 +105,8 @@ else { /** No visibility even of the existence of these events if they aren't admin/owner/attendee */ if ( $event->class == 'PRIVATE' ) continue; - if ( ! $request->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL' ) { - // The user is not admin / owner of this calendar looking at his calendar and can not admin the other cal, - // or maybe they don't have *read* access but they got here, so they must at least have free/busy access - // so we will present an obfuscated version of the event that just says "Busy" (translated :-) - $confidential = new iCalComponent(); - $confidential->SetType($resource->GetType()); - $confidential->AddProperty( 'SUMMARY', translate('Busy') ); - $confidential->AddProperty( 'CLASS', 'CONFIDENTIAL' ); - $confidential->SetProperties( $resource->GetProperties('DTSTART'), 'DTSTART' ); - $confidential->SetProperties( $resource->GetProperties('RRULE'), 'RRULE' ); - $confidential->SetProperties( $resource->GetProperties('DURATION'), 'DURATION' ); - $confidential->SetProperties( $resource->GetProperties('DTEND'), 'DTEND' ); - $confidential->SetProperties( $resource->GetProperties('UID'), 'UID' ); - $confidential->SetProperties( $resource->GetProperties('CREATED'), 'CREATED' ); - - - $vcal->AddComponent($confidential); + if ( ! $dav_resource->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL' ) { + $vcal->AddComponent(obfuscated_event($resource)); } elseif ( isset($c->hide_alarm) && $c->hide_alarm ) { // Otherwise we hide the alarms (if configured to) @@ -174,8 +125,43 @@ else { } $response = $vcal->Render(); - header( "Content-Length: ".strlen($response) ); - header( 'Etag: "'.$request->collection->dav_etag.'"' ); - $request->DoResponse( 200, ($request->method == "HEAD" ? "" : $response), "text/calendar; charset=\"utf-8\"" ); + header( 'Content-Length: '.strlen($response) ); + header( 'Etag: '.$dav_resource->unique_tag() ); + $request->DoResponse( 200, ($request->method == 'HEAD' ? '' : $response), 'text/calendar; charset="utf-8"' ); } + +// Just a single event then + +$event = $dav_resource->event(); +$resource = new iCalComponent( $event->caldav_data ); + +/** Default deny... */ +$allowed = false; +if ( $dav_resource->HavePrivilegeTo('all') || $session->user_no == $event->user_no || $session->user_no == $event->logged_user + || ( $c->allow_get_email_visibility && $resource->IsAttendee($session->email) ) ) { + /** + * These people get to see all of the event, and they should always + * get any alarms as well. + */ + $allowed = true; +} +else if ( $event->class != 'PRIVATE' ) { + $allowed = true; // but we may well obfuscate it below + if ( ! $dav_resource->HavePrivilegeTo('DAV::read') || ( $event->class == 'CONFIDENTIAL' && ! $request->HavePrivilegeTo('DAV::write-content') ) ) { + $ical = new iCalComponent( $event->caldav_data ); + $resources = $ical->GetComponents('VTIMEZONE',false); + $confidential = obfuscated_event($resources[0]); + $ical->SetComponents( array($confidential), $event->caldav_type ); + $event->caldav_data = $ical->Render(); + } +} +// else $event->class == 'PRIVATE' and this person may not see it. + +if ( ! $allowed ) { + $request->DoResponse( 403, translate("Forbidden") ); +} + +header( 'Etag: "'.$event->dav_etag.'"' ); +header( 'Content-Length: '.strlen($event->caldav_data) ); +$request->DoResponse( 200, ($request->method == 'HEAD' ? '' : $event->caldav_data), 'text/calendar; charset="utf-8"' ); diff --git a/testing/tests/regression-suite/968-GET-bound.result b/testing/tests/regression-suite/968-GET-bound.result new file mode 100644 index 00000000..bea1f9ed --- /dev/null +++ b/testing/tests/regression-suite/968-GET-bound.result @@ -0,0 +1,41 @@ +HTTP/1.1 200 OK +Date: Dow, 01 Jan 2000 00:00:00 GMT +DAV: 1, 2, access-control, calendar-access, calendar-schedule, extended-mkcol, calendar-proxy +Etag: "64d28f4f57515d6c63cec02b5d882eaa" +Content-Length: 859 +Content-Type: text/calendar; charset="utf-8" + +BEGIN:VCALENDAR +PRODID:-//davical.org//NONSGML AWL Calendar//EN +VERSION:2.0 +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20081023T054958Z +LAST-MODIFIED:20081023T055044Z +DTSTAMP:20081023T054958Z +UID:33169d69-2969-4a96-a3e1-2e312b7614e6 +SUMMARY:Daily Action Meeting +RRULE:FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR +DTSTART;TZID=Pacific/Auckland:20081020T110000 +DTEND;TZID=Pacific/Auckland:20081020T113000 +X-MOZ-GENERATION:2 +END:VEVENT +BEGIN:VTIMEZONE +TZID:Pacific/Auckland +X-LIC-LOCATION:Pacific/Auckland +BEGIN:DAYLIGHT +TZOFFSETFROM:+1200 +TZOFFSETTO:+1300 +TZNAME:NZDT +DTSTART:19700927T020000 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=9 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+1300 +TZOFFSETTO:+1200 +TZNAME:NZST +DTSTART:19700405T030000 +RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=1SU;BYMONTH=4 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR diff --git a/testing/tests/regression-suite/968-GET-bound.test b/testing/tests/regression-suite/968-GET-bound.test new file mode 100644 index 00000000..e8e636b7 --- /dev/null +++ b/testing/tests/regression-suite/968-GET-bound.test @@ -0,0 +1,16 @@ +# +# Test GET access to a bound calendar +# +TYPE=GET +URL=http://regression.host/caldav.php/user4/user2/33169d69-2969-4a96-a3e1-2e312b7614e6.ics +AUTH=user4:user4 + +HEADER=User-Agent: DAViCalTester/public +HEAD + +BEGINDATA +ENDDATA + + + +