/** * Send a need-privileges error response. This function will only return * if the $href is not supplied and the current user has the specified * permission for the request path. * * @param string $privilege The name of the needed privilege. * @param string $href The unconstructed URI where we needed the privilege. */ function NeedPrivilege($privileges, $href = null) { if (is_string($privileges)) { $privileges = array($privileges); } if (!isset($href)) { if ($this->HavePrivilegeTo($privileges)) { return; } $href = $this->path; } $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($href)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], $privileges[0]); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $this->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); exit(0); // Unecessary, but might clarify things }
foreach ($dav as $v) { header('DAV: ' . trim($v, ', '), false); } } require_once 'CalDAVRequest.php'; $request = new CalDAVRequest(); //if ( $request->method == 'OPTIONS' || $c->always_send_dav_header ) send_dav_header(); // Avoid polluting global namespace $allowed = implode(', ', array_keys($request->supported_methods)); // header( 'Allow: '.$allowed); if (!($request->IsPrincipal() || isset($request->collection) || $request->method == 'PUT' || $request->method == 'MKCALENDAR' || $request->method == 'MKCOL')) { if (preg_match('#^/principals/users(/.*/)$#', $request->path, $matches)) { // Although this doesn't work with the iPhone, perhaps it will with iCal... /** @todo integrate handling this URL into CalDAVRequest.php */ $redirect_url = ConstructURL('/caldav.php' . $matches[1]); dbg_error_log('LOG WARNING', 'Redirecting %s for "%s" to "%s"', $request->method, $request->path, $redirect_url); header('Location: ' . $redirect_url); @ob_flush(); exit(0); } } param_to_global('add_member', '.*'); $add_member = isset($add_member); switch ($request->method) { case 'OPTIONS': include_once 'caldav-OPTIONS.php'; break; case 'REPORT': include_once 'caldav-REPORT.php'; break;
} } /** Else: * the object existed at start and we have multiple modifications, * or, * the object didn't exist at start and we have subsequent modifications, * but: * in either case we simply stick with our existing report. */ } else { /** The simple case: this is the first one for this dav_id */ if ($object->sync_status == 404) { $resultset = array(new XMLElement('href', ConstructURL($object->dav_name)), new XMLElement('status', display_status($object->sync_status))); $first_status = 404; } else { $dav_resource = new DAVResource($object); $resultset = $dav_resource->GetPropStat($proplist, $reply); array_unshift($resultset, new XMLElement('href', ConstructURL($object->dav_name))); $first_status = $object->sync_status; } $responses[] = new XMLElement('response', $resultset); $last_dav_name = $object->dav_name; } } $responses[] = new XMLElement('sync-token', 'data:,' . $new_token); } else { $request->DoResponse(500, translate("Database error")); } } $multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray()); $request->XMLResponse(207, $multistatus);
switch ($request->path) { case '/.well-known/caldav': case '/.well-known/carddav': header('Location: ' . $c->protocol_server_port . ConstructURL('/', true)); $request->DoResponse(301); // Moved permanently // does not return. // Moved permanently // does not return. case '/.well-known/timezone': $parameters = ''; foreach ($_GET as $k => $v) { $parameters .= $parameters == '' ? '?' : '&'; $parameters .= $k . '=' . rawurlencode($v); } header('Location: ' . $c->protocol_server_port . str_replace('/caldav.php', '', ConstructURL('/tz.php', true)) . $parameters); $request->DoResponse(301); // Moved permanently // does not return. } if ($c->enable_scheduling != true) { $request->DoResponse(404, translate('The application program does not understand that request.')); // Does not return } dbg_log_array('well-known', 'method:' . $request->method); switch ($request->method) { case 'GET': ischedule_get(); break; case 'POST': include 'iSchedule-POST.php';
/** * Return the privileges bits for the current session user to this resource */ function Privileges() { global $session; if (!isset($this->privileges)) { $this->privileges = 0; } if (is_string($this->privileges)) { $this->privileges = bindec($this->privileges); } if ($this->_is_group && in_array(ConstructURL('/' . $session->username . '/'), $this->GroupMemberSet())) { $this->privileges |= privilege_to_bits(array('DAV::read', 'DAV::read-current-user-privilege-set')); } return $this->privileges; }
/** * Deconstruct a dav_name from the supplied URL. The dav_name will be urldecoded. * * @param string $partial_path The part of the path after the script name */ function DeconstructURL($url, $force_script = false) { global $c; $dav_name = rawurldecode($url); /** Allow a path like .../username/calendar.ics to translate into the calendar URL */ if (preg_match('#^(/[^/]+/[^/]+).ics$#', $dav_name, $matches)) { $dav_name = $matches[1] . '/'; } /** remove any leading protocol/server/port/prefix... */ if (!isset($c->deconstruction_base_path)) { $c->deconstruction_base_path = ConstructURL('/'); } if (preg_match('%^(.*?)' . str_replace('%', '\\%', $c->deconstruction_base_path) . '(.*)$%', $dav_name, $matches)) { if ($matches[1] == '' || $matches[1] == $c->protocol_server_port) { $dav_name = $matches[2]; } } /** strip doubled slashes */ if (strstr($dav_name, '//')) { $dav_name = preg_replace('#//+#', '/', $dav_name); } if (substr($dav_name, 0, 1) != '/') { $dav_name = '/' . $dav_name; } return $dav_name; }
} /** * If we have encountered any instances of failure, the whole damn thing fails. */ if (count($failure) > 0) { $props = array(); $status = array(); foreach ($success as $tag => $v) { // Unfortunately although these succeeded, we failed overall, so they didn't happen... $props[] = new XMLElement($reply->Tag($tag)); } $status[] = new XMLElement('propstat', array(new XMLElement('prop', $props), new XMLElement('status', 'HTTP/1.1 424 Failed Dependency'))); if ($request_type == 'extended-mkcol') { $request->DoResponse($failure_code, $reply->Render('mkcol-response', array_merge($status, $failure), 'text/xml; charset="utf-8"')); } else { array_unshift($failure, $reply->href(ConstructURL($request->path))); $failure[] = new XMLElement('responsedescription', translate('Some properties were not able to be set.')); $request->DoResponse(207, $reply->Render('multistatus', new XMLElement('response', $failure)), 'text/xml; charset="utf-8"'); } } } $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name'; $qry = new AwlQuery($sql, array(':dav_name' => $request->path)); if (!$qry->Exec('MKCOL', __LINE__, __FILE__)) { $request->DoResponse(500, translate('Error querying database.')); } if ($qry->rows() != 0) { $request->DoResponse(405, translate('A collection already exists at that location.')); } $qry = new AwlQuery(); $qry->Begin();
/** * Return the URL for this principal * @param string $type The type of URL we want (the principal, by default) * @param boolean $internal Whether an internal reference is requested * @return string The principal-URL */ public function url($type = 'principal', $internal = false) { global $c; if ($internal) { $result = $this->dav_name(); } else { if (isset($this->original_request_url) && $type == 'principal') { $result = $this->original_request_url; } else { $result = $this->url; } } switch ($type) { case 'principal': break; case 'schedule-default-calendar': $result = $this->default_calendar(); break; case 'schedule-inbox': $result .= '.in/'; break; case 'schedule-outbox': $result .= '.out/'; break; case 'dropbox': $result .= '.drop/'; break; case 'notifications': $result .= '.notify/'; break; default: fatal('Unknown internal URL type "' . $type . '"'); } return ConstructURL(DeconstructURL($result)); }
function caldav_get_feed($request, $collection) { global $c, $session; dbg_error_log("feed", "GET method handler"); $collection->NeedPrivilege(array('DAV::read')); if (!$collection->Exists()) { $request->DoResponse(404, translate("Resource Not Found.")); } if (!$collection->IsCollection() || !$collection->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections)) { $request->DoResponse(405, translate("Feeds are only supported for calendars at present.")); } // Try and pull the answer out of a hat $cache = getCacheInstance(); $cache_ns = 'collection-' . $collection->dav_name(); $cache_key = 'feed' . $session->user_no; $response = $cache->get($cache_ns, $cache_key); if ($response !== false) { return $response; } $principal = $collection->GetProperty('principal'); /** * 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, caldav_type, caldav_data.user_no, caldav_data.dav_name,'; $sql .= ' caldav_data.modified, caldav_data.created, '; $sql .= ' summary, dtstart, dtend, calendar_item.description '; $sql .= ' FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING ( dav_id ) WHERE '; if (isset($c->get_includes_subcollections) && $c->get_includes_subcollections) { $sql .= ' (collection.dav_name ~ :path_match '; $sql .= ' OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; $params = array(':path_match' => '^' . $request->path); } else { $sql .= ' caldav_data.collection_id = :collection_id '; $params = array(':collection_id' => $collection->resource_id()); } $sql .= ' ORDER BY caldav_data.created DESC'; $sql .= ' LIMIT ' . (isset($c->feed_item_limit) ? $c->feed_item_limit : 15); $qry = new AwlQuery($sql, $params); if (!$qry->Exec("GET", __LINE__, __FILE__)) { $request->DoResponse(500, translate("Database Error")); } /** * Here we are constructing the feed response for this collection, including * the timezones that are referred to by the events we have selected. * Library used: http://framework.zend.com/manual/en/zend.feed.writer.html */ require_once 'AtomFeed.php'; $feed = new AtomFeed(); $feed->setTitle('DAViCal Atom Feed: ' . $collection->GetProperty('displayname')); $url = $c->protocol_server_port . $collection->url(); $url = preg_replace('{/$}', '.ics', $url); $feed->setLink($url); $feed->setFeedLink($c->protocol_server_port_script . $request->path, 'atom'); $feed->addAuthor(array('name' => $principal->GetProperty('displayname'), 'email' => $principal->GetProperty('email'), 'uri' => $c->protocol_server_port . $principal->url())); $feed_description = $collection->GetProperty('description'); if (isset($feed_description) && $feed_description != '') { $feed->setDescription($feed_description); } require_once 'RRule-v2.php'; $need_zones = array(); $timezones = array(); while ($event = $qry->Fetch()) { if ($event->caldav_type != 'VEVENT' && $event->caldav_type != 'VTODO' && $event->caldav_type != 'VJOURNAL') { dbg_error_log('feed', 'Skipping peculiar "%s" component in VCALENDAR', $event->caldav_type); continue; } $is_todo = $event->caldav_type == 'VTODO'; $ical = new vComponent($event->caldav_data); $event_data = $ical->GetComponents('VTIMEZONE', false); $item = $feed->createEntry(); $item->setId($c->protocol_server_port_script . ConstructURL($event->dav_name)); $dt_created = new RepeatRuleDateTime($event->created); $item->setDateCreated($dt_created->epoch()); $dt_modified = new RepeatRuleDateTime($event->modified); $item->setDateModified($dt_modified->epoch()); $summary = $event->summary; $p_title = $summary != '' ? $summary : translate('No summary'); if ($is_todo) { $p_title = "TODO: " . $p_title; } $item->setTitle($p_title); $content = ""; $dt_start = new RepeatRuleDateTime($event->dtstart); if ($dt_start != null) { $p_time = '<strong>' . translate('Time') . ':</strong> ' . strftime(translate('%F %T'), $dt_start->epoch()); $dt_end = new RepeatRuleDateTime($event->dtend); if ($dt_end != null) { $p_time .= ' - ' . ($dt_end->AsDate() == $dt_start->AsDate() ? strftime(translate('%T'), $dt_end->epoch()) : strftime(translate('%F %T'), $dt_end->epoch())); } $content .= $p_time; } $p_location = $event_data[0]->GetProperty('LOCATION'); if ($p_location != null) { $content .= '<br />' . '<strong>' . translate('Location') . '</strong>: ' . hyperlink($p_location->Value()); } $p_attach = $event_data[0]->GetProperty('ATTACH'); if ($p_attach != null) { $content .= '<br />' . '<strong>' . translate('Attachment') . '</strong>: ' . hyperlink($p_attach->Value()); } $p_url = $event_data[0]->GetProperty('URL'); if ($p_url != null) { $content .= '<br />' . '<strong>' . translate('URL') . '</strong>: ' . hyperlink($p_url->Value()); } $p_cat = $event_data[0]->GetProperty('CATEGORIES'); if ($p_cat != null) { $content .= '<br />' . '<strong>' . translate('Categories') . '</strong>: ' . $p_cat->Value(); $categories = explode(',', $p_cat->Value()); foreach ($categories as $category) { $item->addCategory(array('term' => trim($category))); } } $p_description = $event->description; if ($p_description != '') { $content .= '<br />' . '<br />' . '<strong>' . translate('Description') . '</strong>:<br />' . nl2br(hyperlink($p_description)); $item->setDescription($p_description); } $item->setContent($content); $feed->addEntry($item); //break; } $last_modified = new RepeatRuleDateTime($collection->GetProperty('modified')); $feed->setDateModified($last_modified->epoch()); $response = $feed->export('atom'); $cache->set($cache_ns, $cache_key, $response); return $response; }
} /** * If we have encountered any instances of failure, the whole damn thing fails. */ if (count($failure) > 0) { foreach ($success as $tag => $v) { // Unfortunately although these succeeded, we failed overall, so they didn't happen... $failure[] = new XMLElement('propstat', array(new XMLElement('prop', new XMLElement($tag)), new XMLElement('status', 'HTTP/1.1 424 Failed Dependency'))); } $url = ConstructURL($request->path); array_unshift($failure, new XMLElement('href', $url)); $failure[] = new XMLElement('responsedescription', translate("Some properties were not able to be changed.")); $qry->Rollback(); $multistatus = new XMLElement("multistatus", new XMLElement('response', $failure), array('xmlns' => 'DAV:')); $request->DoResponse(207, $multistatus->Render(0, '<?xml version="1.0" encoding="utf-8" ?>'), 'text/xml; charset="utf-8"'); } /** * Otherwise we will try and do the SQL. This is inside a transaction, so PostgreSQL guarantees the atomicity */ if ($qry->Commit()) { $url = ConstructURL($request->path); $href = new XMLElement('href', $url); $desc = new XMLElement('responsedescription', translate("All requested changes were made.")); $multistatus = new XMLElement("multistatus", new XMLElement('response', array($href, $desc)), array('xmlns' => 'DAV:')); $request->DoResponse(200, $multistatus->Render(0, '<?xml version="1.0" encoding="utf-8" ?>'), 'text/xml; charset="utf-8"'); } /** * Or it was all crap. */ $request->DoResponse(500); exit(0);
/** * Get the calendar_free_busy_set, as lazily as possible */ function calendar_free_busy_set() { if (!isset($this->calendar_free_busy_set)) { /** * calendar-free-busy-set has been dropped from draft 5 of the scheduling extensions for CalDAV * in favour of ??? */ $this->calendar_free_busy_set = array(); $qry = new AwlQuery('SELECT dav_name FROM collection WHERE is_calendar AND (schedule_transp = \'opaque\' OR schedule_transp IS NULL) AND dav_name ~ :dav_name_start ORDER BY user_no, collection_id', array(':dav_name_start' => '^' . $this->dav_name)); if ($qry->Exec('principal', __LINE__, __FILE__)) { while ($calendar = $qry->Fetch()) { $this->calendar_free_busy_set[] = ConstructURL($calendar->dav_name, true); } } } return $this->calendar_free_busy_set; }
/** * Render XML for this resource * * @param array $properties The requested properties for this principal * @param reference $reply A reference to the XMLDocument being used for the reply * * @return string An XML fragment with the requested properties for this principal */ function RenderAsXML($properties, &$reply, $bound_parent_path = null) { global $session, $c, $request; dbg_error_log('DAVResource', ':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists()); if (!$this->Exists()) { return null; } $elements = $this->GetPropStat($properties, $reply); if (isset($bound_parent_path)) { $dav_name = str_replace($this->parent_path(), $bound_parent_path, $this->dav_name); } else { $dav_name = $this->dav_name; } array_unshift($elements, $reply->href(ConstructURL($dav_name))); $response = new XMLElement('response', $elements, null, 'DAV:'); return $response; }
/** * Deliver scheduling replies to organizer and other attendees * @param iCalComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply($ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ic->GetProperties('ORGANIZER'); // for now we treat events with out organizers as an error if (count($organizer) < 1) { return false; } $attendees = array_merge($organizer, $ic->GetProperties('ATTENDEE')); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); if (count($wr_attendees) > 0) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Delivering to %s", $attendee_email); $attendee_principal = new CalDAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = preg_replace('/^.*caldav.php/', '', $attendee_principal->schedule_inbox_url); $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("POST", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new iCalComponent(); $ncal->VCalendar(); $ncal->AddProperty('METHOD', 'REPLY'); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource($attendee_principal->user_no, $deliver_path . $etag . '.ics', $content, $ar->GetProperty('collection_id'), $request->user_no, md5($content), $ncal, $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }
/** * Return XML for a single component from the DB * * @param array $properties The properties for this component * @param string $item The DB row data for this component * * @return string An XML document which is the response for the component */ function component_to_xml($properties, $item) { global $session, $c, $request, $reply; dbg_error_log("REPORT", "Building XML Response for item '%s'", $item->dav_name); $denied = array(); $unsupported = array(); $caldav_data = $item->caldav_data; $displayname = preg_replace('{^.*/}', '', $item->dav_name); $type = 'unknown'; $contenttype = 'text/plain'; switch ($item->caldav_type) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } if (isset($properties['calendar-data']) || isset($properties['displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal /** @todo We should examine the ORGANIZER and ATTENDEE fields in the event. If this person is there then they should see this */ if ($type == 'calendar' && $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { $ical = new iCalComponent($caldav_data); $resources = $ical->GetComponents('VTIMEZONE', false); $first = $resources[0]; // if the event is confidential we fake one that just says "Busy" $confidential = new iCalComponent(); $confidential->SetType($first->GetType()); $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'); $ical->SetComponents(array($confidential), $confidential->GetType()); $caldav_data = $ical->Render(); $displayname = translate('Busy'); } } } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); foreach ($properties as $k => $v) { switch ($k) { case 'getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($k, $contentlength); break; case 'getlastmodified': $prop->NewElement($k, ISODateToHTTPDate($item->modified)); break; case 'calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'getcontenttype': $prop->NewElement($k, $contenttype); break; case 'current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'displayname': $prop->NewElement($k, $displayname); break; case 'resourcetype': $prop->NewElement($k); // Just an empty resourcetype for a non-collection. break; case 'getetag': $prop->NewElement($k, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($k, privileges($request->permissions)); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $k; break; default: dbg_error_log('REPORT', "Request for unsupported property '%s' of calendar item.", $v); $unsupported[] = $k; } } $status = new XMLElement("status", "HTTP/1.1 200 OK"); $propstat = new XMLElement("propstat", array($prop, $status)); $href = new XMLElement("href", $url); $elements = array($href, $propstat); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } $response = new XMLElement("response", $elements); return $response; }
if ($source->IsPrincipal() || !$source->IsCollection()) { $request->PreconditionFailed(403, 'DAV::binding-allowed', translate('DAViCal only allows BIND requests for collections at present.')); } if ($source->IsBinding()) { $source = new DAVResource($source->bound_from()); } /* bind_id INT8 DEFAULT nextval('dav_id_seq') PRIMARY KEY, bound_source_id INT8 REFERENCES collection(collection_id) ON UPDATE CASCADE ON DELETE CASCADE, access_ticket_id TEXT REFERENCES access_ticket(ticket_id) ON UPDATE CASCADE ON DELETE SET NULL, parent_container TEXT NOT NULL, dav_name TEXT UNIQUE NOT NULL, dav_displayname TEXT, external_url TEXT, type TEXT */ $sql = 'INSERT INTO dav_binding ( bound_source_id, access_ticket_id, dav_owner_id, parent_container, dav_name, dav_displayname ) VALUES( :target_id, :ticket_id, :session_principal, :parent_container, :dav_name, :displayname )'; $params = array(':target_id' => $source->GetProperty('collection_id'), ':ticket_id' => isset($request->ticket) ? $request->ticket->id() : null, ':parent_container' => $parent->dav_name(), ':session_principal' => $session->principal_id, ':dav_name' => $destination_path, ':displayname' => $source->GetProperty('displayname')); $qry = new AwlQuery($sql, $params); if ($qry->Exec('BIND', __LINE__, __FILE__)) { header('Location: ' . ConstructURL($destination_path)); // Uncache anything to do with the target $cache = getCacheInstance(); $cache_ns = 'collection-' . $destination_path; $cache->delete($cache_ns, null); $request->DoResponse(201); } else { $request->DoResponse(500, translate('Database Error')); } }
} break; } } if ($ticket_timeout == 'infinity') { $sql_timeout = null; } else { if (preg_match('{^([a-z]+)-(\\d+)$}i', $ticket_timeout, $matches)) { /** It isn't specified, but timeout seems to be 'unit-number' like 'Seconds-3600', so we make it '3600 Seconds' which PostgreSQL understands */ $sql_timeout = $matches[2] . ' ' . $matches[1]; } else { $sql_timeout = $ticket_timeout; } } $collection_id = $target->GetProperty('collection_id'); $resource_id = $target->GetProperty('dav_id'); $i = 0; do { $ticket_id = substr(str_replace('/', '', str_replace('+', '', base64_encode(sha1(date('r') . rand(0, 2100000000) . microtime(true), true)))), 7, 8); $qry = new AwlQuery('INSERT INTO access_ticket ( ticket_id, dav_owner_id, privileges, target_collection_id, target_resource_id, expires ) VALUES( :ticket_id, :owner, :privs::INT::BIT(24), :collection, :resource, (current_timestamp + :expires::interval) )', array(':ticket_id' => $ticket_id, ':owner' => $session->principal_id, ':privs' => $ticket_privileges, ':collection' => $collection_id, ':resource' => $resource_id, ':expires' => $sql_timeout)); $result = $qry->Exec('MKTICKET', __LINE__, __FILE__); } while (!$result && $i++ < 2); $privs = new XMLElement('privilege'); foreach (bits_to_privilege($ticket_privileges) as $k => $v) { $reply->NSElement($privs, $v); } $ticketinfo = new XMLElement('T:ticketinfo', array(new XMLElement('T:id', $ticket_id), new XMLElement('owner', $reply->href(ConstructURL('/' . $session->username . '/'))), $privs, new XMLElement('T:timeout', $ticket_timeout), new XMLElement('T:visits', 'infinity'))); $prop = new XMLElement("prop", new XMLElement('T:ticketdiscovery', $ticketinfo), $reply->GetXmlNsArray()); header('Ticket: ' . $ticket_id); $request->XMLResponse(200, $prop);
/** * Return XML for a single component from the DB * * @param array $properties The properties for this component * @param string $item The DB row data for this component * * @return string An XML document which is the response for the component */ function component_to_xml($properties, $item) { global $session, $c, $request, $reply; dbg_error_log("REPORT", "Building XML Response for item '%s'", $item->dav_name); $denied = array(); $unsupported = array(); $caldav_data = $item->caldav_data; $displayname = preg_replace('{^.*/}', '', $item->dav_name); $type = 'unknown'; $contenttype = 'text/plain'; switch (strtoupper($item->caldav_type)) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; if (isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) || isset($properties['DAV::displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendar looking at his calendar and can not admin the other cal if ($item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { dbg_error_log("REPORT", "Anonymising confidential event for: %s", $item->dav_name); $vcal = new vCalendar($caldav_data); $caldav_data = $vcal->Confidential()->Render(); $displayname = translate('Busy'); } } } if (isset($c->hide_alarm) && $c->hide_alarm) { $dav_resource = new DAVResource($item->dav_name); if (isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) && !$dav_resource->HavePrivilegeTo('write')) { dbg_error_log("REPORT", "Stripping event alarms for: %s", $item->dav_name); $vcal = new vCalendar($caldav_data); $vcal->ClearComponents('VALARM'); $caldav_data = $vcal->Render(); } } break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); $need_resource = false; foreach ($properties as $full_tag => $v) { $base_tag = preg_replace('{^.*:}', '', $full_tag); switch ($full_tag) { case 'DAV::getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($base_tag, $contentlength); break; case 'DAV::getlastmodified': $prop->NewElement($base_tag, ISODateToHTTPDate($item->modified)); break; case 'urn:ietf:params:xml:ns:caldav:calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $base_tag, $caldav_data); } else { $unsupported[] = $base_tag; } break; case 'urn:ietf:params:xml:ns:carddav:address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $base_tag, $caldav_data); } else { $unsupported[] = $base_tag; } break; case 'DAV::getcontenttype': $prop->NewElement($base_tag, $contenttype); break; case 'DAV::current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'DAV::displayname': $prop->NewElement($base_tag, $displayname); break; case 'DAV::resourcetype': $prop->NewElement($base_tag); // Just an empty resourcetype for a non-collection. break; case 'DAV::getetag': $prop->NewElement($base_tag, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($base_tag, privileges($request->permissions)); break; default: // It's harder. We need the DAVResource() to get this one. $need_resource = true; } if ($need_resource) { break; } } $href = new XMLElement("href", $url); if ($need_resource) { if (!isset($dav_resource)) { $dav_resource = new DAVResource($item->dav_name); } $elements = $dav_resource->GetPropStat(array_keys($properties), $reply); array_unshift($elements, $href); } else { $elements = array($href); $status = new XMLElement("status", "HTTP/1.1 200 OK"); $elements[] = new XMLElement("propstat", array($prop, $status)); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $reply->NSElement($noprop, $v); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (!$request->PreferMinimal() && count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $reply->NSElement($noprop, $v); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } } $response = new XMLElement("response", $elements); return $response; }