/** * Expand the properties, recursing only once */ function expand_properties($urls, $ptree, &$reply, $recurse_again = true) { if (!is_array($urls)) { $urls = array($urls); } if (!is_array($ptree)) { $ptree = array($ptree); } $responses = array(); foreach ($urls as $m => $url) { $resource = new DAVResource($url); $props = array(); $subtrees = array(); foreach ($ptree as $n => $property) { if (!is_object($property)) { continue; } $pname = $property->GetAttribute('name'); $pns = $property->GetAttribute('namespace'); if (!isset($pns) || $pns == '') { $pns = 'DAV:'; } // Not sure if this is the correct way to default this. $pname = $pns . ':' . $pname; $props[] = $pname; $subtrees[$pname] = $property->GetElements(); } $part_response = $resource->RenderAsXML($props, $reply); if (isset($part_response)) { if ($recurse_again) { $href_containers = get_href_containers($part_response); if (isset($href_containers)) { foreach ($href_containers as $h => $property) { $hrefs = $property->GetElements(); $pname = $property->GetTag(); $pns = $property->GetAttribute('xmlns'); if (!isset($pns) || $pns == '') { $pns = 'DAV:'; } // Not sure if this is the correct way to default this. $pname = $pns . ':' . $pname; $paths = array(); foreach ($hrefs as $k => $v) { $content = $v->GetContent(); $paths[] = $content; } // dbg_error_log('REPORT',' Found property "%s" contains hrefs "%s"', $pname, implode(', ',$paths) ); $property->SetContent(expand_properties($paths, $subtrees[$pname], $reply, false)); } } // else { // dbg_error_log('REPORT',' No href containers in response to "%s"', implode(', ', $props ) ); // } } $responses[] = $part_response; } } return $responses; }
/** * Entry point for scheduling on DELETE, for which there are thee outcomes: * - We don't do scheduling (disabled, no organizer, ...) * - We are an ATTENDEE declining the meeting. * - We are the ORGANIZER canceling the meeting. * * @param DAVResource $deleted_resource The resource which has already been deleted */ function do_scheduling_for_delete(DAVResource $deleted_resource) { // By the time we arrive here the resource *has* actually been deleted from disk // we can only fail to (de-)schedule the activity... global $request, $c; if (!isset($request) || isset($c->enable_auto_schedule) && !$c->enable_auto_schedule) { return true; } if ($deleted_resource->IsInSchedulingCollection()) { return true; } $caldav_data = $deleted_resource->GetProperty('dav-data'); if (empty($caldav_data)) { return true; } $vcal = new vCalendar($caldav_data); $organizer = $vcal->GetOrganizer(); if ($organizer === false || empty($organizer)) { dbg_error_log('schedule', 'Event has no organizer - no scheduling required.'); return true; } if ($vcal->GetScheduleAgent() != 'SERVER') { dbg_error_log('schedule', 'SCHEDULE-AGENT=%s - no scheduling required.', $vcal->GetScheduleAgent()); return true; } $organizer_email = preg_replace('/^mailto:/i', '', $organizer->Value()); if ($request->principal->email() == $organizer_email) { return doItipOrganizerCancel($vcal); } else { if (isset($_SERVER['HTTP_SCHEDULE_REPLY']) && $_SERVER['HTTP_SCHEDULE_REPLY'] == 'F') { dbg_error_log('schedule', 'Schedule-Reply header set to "F" - no scheduling required.'); return true; } return doItipAttendeeReply($vcal, 'DECLINED', $request->principal->email()); } }
break; default: /** * @todo We should handle a lot more properties here. principal-URL seems a likely one to be used. * @todo We should catch the unsupported properties in the query and fire back an error indicating so. */ dbg_error_log("principal", "Unhandled tag '%s' to match '%s'\n", $v1->GetNSTag(), $match); } } if ($subwhere != "") { $where .= sprintf("%s(%s)", $where == "" ? "" : $clause_joiner, $subwhere); } } if ($where != "") { $where = "WHERE {$where}"; } $sql = "SELECT * FROM dav_principal {$where} ORDER BY principal_id LIMIT 100"; $qry = new AwlQuery($sql, $params); $get_props = $xmltree->GetPath('/DAV::principal-property-search/DAV::prop/*'); $properties = array(); foreach ($get_props as $k1 => $v1) { $properties[] = $v1->GetNSTag(); } if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { $principal = new DAVResource($row); $responses[] = $principal->RenderAsXML($properties, $reply); } } $multistatus = new XMLElement("multistatus", $responses, $reply->GetXmlNsArray()); $request->XMLResponse(207, $multistatus);
/** * Get XML response for items in the collection * If '/' is requested, a list of visible users is given, otherwise * a list of calendars for the user which are parented by this path. */ function get_collection_contents($depth, $collection, $parent_path = null) { global $c, $session, $request, $reply, $property_list; $bound_from = $collection->bound_from(); $bound_to = $collection->dav_name(); if (!isset($parent_path)) { $parent_path = $collection->dav_name(); } dbg_error_log('PROPFIND', 'Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', $depth, $collection->dav_name(), $bound_from, $bound_to); $date_format = AwlDatabase::HttpDateFormat; $responses = array(); if (!$collection->IsCalendar() && !$collection->IsAddressbook()) { /** * Calendar/Addressbook collections may not contain collections, so we are only looking in the other ones */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth); if ($bound_from == '/') { $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, "; $sql .= "to_char(joined at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(updated at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, '; $sql .= 'principal_id AS collection_id, '; $sql .= 'principal.* '; $sql .= 'FROM usr JOIN principal USING (user_no) '; $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; $sql .= 'ORDER BY usr.user_no'; } else { $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', array(':this_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($binding = $qry->Fetch()) { $resource = new DAVResource($binding->dav_name); if ($resource->IsExternal()) { require_once "external-fetch.php"; update_external($resource); } if ($resource->HavePrivilegeTo('DAV::read', false)) { $resource->set_bind_location(str_replace($bound_from, $bound_to, $binding->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, $binding->dav_name)); } } } } $sql = 'SELECT principal.*, collection.*, \'collection\' AS type '; $sql .= 'FROM collection LEFT JOIN principal USING (user_no) '; $sql .= 'WHERE parent_container = :this_dav_name '; $sql .= ' ORDER BY collection_id'; $params[':this_dav_name'] = $bound_from; unset($params[':session_principal']); unset($params[':scan_depth']); } $qry = new AwlQuery($sql, $params); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($subcollection = $qry->Fetch()) { $resource = new DAVResource($subcollection); if (!$resource->HavePrivilegeTo('DAV::read')) { continue; } $resource->set_bind_location(str_replace($bound_from, $bound_to, $subcollection->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, str_replace($resource->parent_path(), $parent_path, $resource->dav_name()))); } } } if ((!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy == false) && $collection->IsPrincipal()) { // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write) dbg_error_log('PROPFIND', 'Adding calendar-proxy-read and write. Path: %s', $bound_from); $response = add_proxy_response('read', $bound_from); if (isset($response)) { $responses[] = $response; } $response = add_proxy_response('write', $bound_from); if (isset($response)) { $responses[] = $response; } } } /** * freebusy permission is not allowed to see the items in a collection. Must have at least read permission. */ if ($collection->HavePrivilegeTo('DAV::read', false)) { dbg_error_log('PROPFIND', 'Getting collection items: Depth %d, Path: %s', $depth, $bound_from); $privacy_clause = ' '; $todo_clause = ' '; $time_limit_clause = ' '; if ($collection->IsCalendar()) { if (!$collection->HavePrivilegeTo('all', false)) { $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) { $todo_clause = " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) { $time_limit_clause = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END) "; } } $sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, '; $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(coalesce(calendar_item.last_modified, caldav_data.modified) at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'summary AS dav_displayname '; $sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) '; $sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) '; $sql .= 'WHERE collection.dav_name = :collection_dav_name ' . $time_limit_clause . ' ' . $todo_clause . ' ' . $privacy_clause; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql, array(':collection_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($item = $qry->Fetch()) { if ($bound_from != $bound_to) { $item->bound_from = $item->dav_name; $item->dav_name = str_replace($bound_from, $bound_to, $item->dav_name); } $resource = new DAVResource($item); $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path); } } } return $responses; }
<?php /** * CalDAV Server - handle GET method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/> * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ 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 obfuscated_event($icalendar) { // 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($icalendar->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($icalendar->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($icalendar->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($icalendar->GetProperties('DURATION'), 'DURATION');
* we use the xmlns="http://www.xythos.com/namespaces/StorageServer" rather * than the DAV: namespace. * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log('DELTICKET', 'method handler'); require_once 'DAVResource.php'; if (!$request->HavePrivilegeTo('DAV::unbind') && $request->ticket->owner() != $session->principal_id) { $request->NeedPrivilege('DAV::unbind'); } if (!isset($request->ticket)) { if (isset($_GET['ticket']) || isset($_SERVER['HTTP_TICKET'])) { $r = new DAVResource($request->path); if (!$r->Exists()) { $request->PreconditionFailed(404, 'not-found'); } else { $request->PreconditionFailed(412, 'ticket-does-not-exist', 'The specified ticket does not exist'); } } else { $request->MalformedRequest('No ticket specified'); } } $qry = new AwlQuery('DELETE FROM access_ticket WHERE ticket_id=:ticket_id', array(':ticket_id' => $request->ticket->id())); if ($qry->Exec('DELTICKET', __LINE__, __FILE__)) { $request->DoResponse(204); } $request->DoResponse(500);
} } /** 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);
<?php /** * CalDAV Server - handle OPTIONS method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/> * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("OPTIONS", "method handler"); include_once 'DAVResource.php'; $resource = new DAVResource($request->path); $resource->NeedPrivilege('DAV::read', true); if (!$resource->Exists()) { $request->DoResponse(404, translate("No collection found at that location.")); } $allowed = implode(', ', array_keys($resource->FetchSupportedMethods())); header('Allow: ' . $allowed); $request->DoResponse(200, "");
<?php /** * CalDAV Server - handle PROPPATCH method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ dbg_error_log("PROPPATCH", "method handler"); require_once 'iCalendar.php'; require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!($dav_resource->HavePrivilegeTo('DAV::write-properties') || $dav_resource->IsBinding())) { $request->DoResponse(403); } $position = 0; $xmltree = BuildXMLTree($request->xml_tags, $position); // echo $xmltree->Render(); if ($xmltree->GetTag() != "DAV::propertyupdate") { $request->DoResponse(403); } /** * Find the properties being set, and the properties being removed */ $setprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::set/DAV::prop/*"); $rmprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::remove/DAV::prop/*"); /** * We build full status responses for failures. For success we just record
<?php /** * CalDAV Server - handle DELETE method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/> * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("delete", "DELETE method handler"); require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); $container = $dav_resource->GetParentContainer(); $container->NeedPrivilege('DAV::unbind'); $lock_opener = $request->FailIfLocked(); require_once 'schedule-functions.php'; function delete_collection($id) { $params = array(':collection_id' => $id); $qry = new AwlQuery('SELECT child.collection_id AS child_id FROM collection child JOIN collection parent ON (parent.dav_name = child.parent_container) WHERE parent.collection_id = :collection_id', $params); if ($qry->Exec('DELETE', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { delete_collection($row->child_id); } } if ($qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM property WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM locks WHERE dav_name LIKE (SELECT dav_name FROM collection WHERE collection_id = :collection_id) || '%'", $params) && $qry->QDo("DELETE FROM caldav_data WHERE collection_id = :collection_id", $params) && $qry->QDo("DELETE FROM collection WHERE collection_id = :collection_id", $params)) { @dbg_error_log("DELETE", "DELETE (collection): User: %d, ETag: %s, Path: %s", $session->user_no, $request->etag_if_match, $request->path); return true; }
$this->{$k} = $v; } $this->username = $principal->username(); $this->principal_id = $principal->principal_id(); $this->email = $principal->email(); $this->dav_name = $principal->dav_name(); $this->principal = $principal; $this->logged_in = true; } function AllowedTo($do_something) { return $this->logged_in; } } $session = new FakeSession(); $dest = new DAVResource($target); $session = new FakeSession($dest->user_no()); if ($mode == 'append' && !$dest->Exists()) { printf("The target '%s' does not exist.\n", $target); exit(1); } if (!$dest->IsCollection()) { printf("The target '%s' is not a collection.\n", $target); exit(1); } $user_no = $dest->user_no(); $username = $session->username; param_to_global('mode'); include_once 'caldav-PUT-functions.php'; controlRequestContainer($session->username, $dest->user_no(), $target, false, $dest->IsPublic() ? true : false); import_collection($ics, $dest->user_no(), $target, $session->user_no, $mode == 'append');
$vcalendar->SetUID($uid); } if ($add_member) { $request->path = $request->dav_name() . $uid . '.ics'; $dav_resource = new DAVResource($request->path); if ($dav_resource->Exists()) { $uid = uuid(); $vcalendar->SetUID($uid); $request->path = $request->dav_name() . $uid . '.ics'; $dav_resource = new DAVResource($request->path); if ($dav_resource->Exists()) { throw new Exception("Failed to generate unique segment name for add-member!"); } } } else { $dav_resource = new DAVResource($request->path); } if (!$dav_resource->HavePrivilegeTo('DAV::write-content')) { $request->DoResponse(403, 'No write permission'); } if (!$dav_resource->Exists() && !$dav_resource->HavePrivilegeTo('DAV::bind')) { $request->DoResponse(403, 'No bind permission.'); } if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/var/log/davical/PUT.debug', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } controlRequestContainer($dav_resource->GetProperty('username'), $dav_resource->GetProperty('user_no'), $dav_resource->bound_from(), true);
/** * 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'); }
function process_ace($grantor, $by_principal, $by_collection, $ace) { global $cache_delete_list, $request; $elements = $ace->GetContent(); $principal_node = $elements[0]; $grant = $elements[1]; if ($principal_node->GetNSTag() != 'DAV::principal') { $request->MalformedRequest('ACL request must contain a principal, not ' . $principal->GetNSTag()); } $grant_tag = $grant->GetNSTag(); if ($grant_tag == 'DAV::deny') { $request->PreconditionFailed(403, 'grant-only'); } if ($grant_tag == 'DAV::invert') { $request->PreconditionFailed(403, 'no-invert'); } if ($grant->GetNSTag() != 'DAV::grant') { $request->MalformedRequest('ACL request must contain a principal for each ACE'); } $privilege_names = array(); $xml_privs = $grant->GetPath("/DAV::grant/DAV::privilege/*"); foreach ($xml_privs as $k => $priv) { $privilege_names[] = $priv->GetNSTag(); } $privileges = privilege_to_bits($privilege_names); $principal_content = $principal_node->GetContent(); if (count($principal_content) != 1) { $request->MalformedRequest('ACL request must contain exactly one principal per ACE'); } $principal_content = $principal_content[0]; switch ($principal_content->GetNSTag()) { case 'DAV::property': $principal_property = $principal_content->GetContent(); if ($principal_property[0]->GetNSTag() != 'DAV::owner') { $request->PreconditionFailed(403, 'recognized-principal'); } if (privilege_to_bits('all') != $privileges) { $request->PreconditionFailed(403, 'no-protected-ace-conflict', 'Owner must always have all permissions'); } continue; // and then we ignore it, since it's protected break; case 'DAV::unauthenticated': $request->PreconditionFailed(403, 'allowed-principal', 'May not set privileges for unauthenticated users'); break; case 'DAV::href': $principal_type = 'href'; $grantee = new DAVResource(DeconstructURL($principal_content->GetContent())); $grantee_id = $grantee->getProperty('principal_id'); if (!$grantee->Exists() || !$grantee->IsPrincipal()) { $request->PreconditionFailed(403, 'recognized-principal', 'Principal "' + $principal_content->GetContent() + '" not found.'); } $sqlparms = array(':to_principal' => $grantee_id); $where = 'WHERE to_principal=:to_principal AND '; if (isset($by_principal)) { $sqlparms[':by_principal'] = $by_principal; $where .= 'by_principal = :by_principal'; } else { $sqlparms[':by_collection'] = $by_collection; $where .= 'by_collection = :by_collection'; } $qry = new AwlQuery('SELECT privileges FROM grants ' . $where, $sqlparms); if ($qry->Exec('ACL', __LINE__, __FILE__) && $qry->rows() == 1 && ($current = $qry->Fetch())) { $sql = 'UPDATE grants SET privileges=:privileges::INT::BIT(24) ' . $where; } else { $sqlparms[':by_principal'] = $by_principal; $sqlparms[':by_collection'] = $by_collection; $sql = 'INSERT INTO grants (by_principal, by_collection, to_principal, privileges) VALUES(:by_principal, :by_collection, :to_principal, :privileges::INT::BIT(24))'; } $sqlparms[':privileges'] = $privileges; $qry = new AwlQuery($sql, $sqlparms); if ($qry->Exec('ACL', __LINE__, __FILE__)) { Principal::cacheDelete('dav_name', $grantee->dav_name()); Principal::cacheFlush('principal_id IN (SELECT member_id FROM group_member WHERE group_id = ?)', array($grantee_id)); } break; case 'DAV::authenticated': $principal_type = 'authenticated'; if (bindec($grantor->GetProperty('default_privileges')) == $privileges) { continue; } // There is no change, so skip it $sqlparms = array(':privileges' => $privileges); if (isset($by_collection)) { $sql = 'UPDATE collection SET default_privileges=:privileges::INT::BIT(24) WHERE collection_id=:by_collection'; $sqlparms[':by_collection'] = $by_collection; } else { $sql = 'UPDATE principal SET default_privileges=:privileges::INT::BIT(24) WHERE principal_id=:by_principal'; $sqlparms[':by_principal'] = $by_principal; } $qry = new AwlQuery($sql, $sqlparms); if ($qry->Exec('ACL', __LINE__, __FILE__)) { /** * Basically this has changed everyone's permissions now, so... */ Principal::cacheFlush('TRUE'); } break; case 'DAV::all': // $principal_type = 'all'; $request->PreconditionFailed(403, 'allowed-principal', 'May not set privileges for unauthenticated users'); break; default: $request->PreconditionFailed(403, 'recognized-principal'); break; } }
<?php /** * CalDAV Server - handle GET method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd <http://www.morphoss.com/> * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("get", "GET method handler"); require "caldav-GET-functions.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->IsExternal()) { require_once "external-fetch.php"; update_external($dav_resource); } if (!$dav_resource->Exists()) { $request->DoResponse(404, translate("Resource Not Found.")); } if ($dav_resource->IsCollection()) { $response = export_iCalendar($dav_resource); header('Etag: ' . $dav_resource->unique_tag()); $request->DoResponse(200, $request->method == 'HEAD' ? '' : $response, 'text/calendar; charset="utf-8"'); } // Just a single event then $resource = $dav_resource->resource(); $ic = new iCalComponent($resource->caldav_data); $resource->caldav_data = preg_replace('{(?<!\\r)\\n}', "\r\n", $resource->caldav_data);
*/ function BuildSqlFilter($filter) { $components = array(); if ($filter->GetNSTag() == "urn:ietf:params:xml:ns:caldav:comp-filter" && $filter->GetAttribute("name") == "VCALENDAR") { $filter = $filter->GetContent(); } else { dbg_error_log("calquery", "Got bizarre CALDAV:FILTER[%s=%s]] which does not contain comp-filter = VCALENDAR!!", $filter->GetNSTag(), $filter->GetAttribute("name")); } return SqlFilterFragment($filter, $components); } /** * Something that we can handle, at least roughly correctly. */ $responses = array(); $target_collection = new DAVResource($request->path); $bound_from = $target_collection->bound_from(); if (!$target_collection->Exists()) { $request->DoResponse(404); } $params = array(); if (!($target_collection->IsCalendar() || $target_collection->IsSchedulingCollection())) { if (!(isset($c->allow_recursive_report) && $c->allow_recursive_report)) { $request->DoResponse(403, translate('The calendar-query report must be run against a calendar or a scheduling collection')); } else { if ($request->path == '/' || $target_collection->IsPrincipal() || $target_collection->IsAddressbook()) { $request->DoResponse(403, translate('The calendar-query report may not be run against that URL.')); } } /** * We're here because they allow recursive reports, and this appears to be such a location.
* @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("PUT", "method handler"); require_once 'DAVResource.php'; if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/tmp/PUT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } $lock_opener = $request->FailIfLocked(); $dest = new DAVResource($request->path); $container = $dest->FetchParentContainer(); if ($container->IsCalendar()) { $request->PreconditionFailed(412, 'urn:ietf:params:xml:ns:caldav:supported-calendar-data', translate('Incorrect content type for calendar: ') . $request->content_type); } else { if ($container->IsAddressbook()) { $request->PreconditionFailed(412, 'urn:ietf:params:xml:ns:carddav:supported-address-data', translate('Incorrect content type for addressbook: ') . $request->content_type); } } if (!$dest->Exists()) { if ($container->IsPrincipal()) { $request->DoResponse(403, translate('A DAViCal principal collection may only contain collections')); } if (!$container->Exists()) { $request->DoResponse(409, translate('Destination collection does not exist')); }
dbg_error_log('MKCOL', 'method handler'); require_once 'AwlQuery.php'; $request->NeedPrivilege('DAV::bind'); $displayname = $request->path; // Enforce trailling '/' on collection name if (!preg_match('#/$#', $request->path)) { dbg_error_log('MKCOL', 'Add trailling "/" to "%s"', $request->path); $request->path .= '/'; } $parent_container = '/'; if (preg_match('#^(.*/)([^/]+)(/)?$#', $request->path, $matches)) { $parent_container = $matches[1]; $displayname = $matches[2]; } require_once 'DAVResource.php'; $parent = new DAVResource($parent_container); if ($parent->IsSchedulingCollection('inbox')) { $request->PreconditionFailed(403, 'urn:ietf:params:xml:ns:caldav:no-mkcol-in-inbox'); } $request_type = $request->method; $is_calendar = $request_type == 'MKCALENDAR'; $is_addressbook = false; $resourcetypes = '<DAV::collection/>'; if ($is_calendar) { $resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>'; } require_once 'XMLDocument.php'; $reply = new XMLDocument(array('DAV:' => '', 'urn:ietf:params:xml:ns:caldav' => 'C')); $failure_code = null; $failure = array(); $dav_properties = array();
$lock_opener = $request->FailIfLocked(); $dest = new DAVResource($request->destination); if ($dest->dav_name() == '/' || $dest->IsPrincipal()) { $dest->NeedPrivilege('DAV::bind'); } if (!$dest->ContainerExists()) { $request->DoResponse(409, translate('Destination collection does not exist')); } if (!$request->overwrite && $dest->Exists()) { $request->DoResponse(412, translate('Not overwriting existing destination resource')); } if (isset($request->etag_none_match) && $request->etag_none_match != '*') { $request->DoResponse(412); /** request to move, but only if there is no source? WTF! */ } $src = new DAVResource($request->path); if (!$src->Exists()) { $request->DoResponse(412, translate('Source resource does not exist.')); } if ($src->IsCollection()) { switch ($dest->ContainerType()) { case 'calendar': case 'addressbook': case 'schedule-inbox': case 'schedule-outbox': $request->DoResponse(412, translate('Special collections may not contain a calendar or other special collection.')); } } else { if (isset($request->etag_if_match) && $request->etag_if_match != '' || isset($request->etag_none_match) && $request->etag_none_match != '') { /** * RFC2068, 14.25:
} fetch_external($row->bind_id, ''); $request->DoResponse(201); } else { $request->DoResponse(500, translate('Database Error')); } } else { $source = new DAVResource($href); if (!$source->Exists()) { $request->PreconditionFailed(403, 'DAV::bind-source-exists', translate('The BIND Request MUST identify an existing resource.')); } 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);
} break; */ default: dbg_error_log("cardquery", "Could not handle unknown tag '%s' in calendar query report", $tag); break; } } dbg_error_log("cardquery", "Generated SQL was '%s'", $sql); return array('sql' => $sql, 'params' => $params); } /** * Something that we can handle, at least roughly correctly. */ $responses = array(); $target_collection = new DAVResource($request->path); $bound_from = $target_collection->bound_from(); if (!$target_collection->Exists()) { $request->DoResponse(404); } if (!($target_collection->IsAddressbook() || $target_collection->IsSchedulingCollection())) { $request->DoResponse(403, translate('The addressbook-query report must be run against an addressbook collection')); } /** * @todo Once we are past DB version 1.2.1 we can change this query more radically. The best performance to * date seems to be: * SELECT caldav_data.*,address_item.* FROM collection JOIN address_item USING (collection_id,user_no) * JOIN caldav_data USING (dav_id) WHERE collection.dav_name = '/user1/home/' * AND caldav_data.caldav_type = 'VEVENT' ORDER BY caldav_data.user_no, caldav_data.dav_name; */ $params = array();
* elements of the filter which are implemented in the SQL will be removed. * * @param arrayref &$filter A reference to an array of XMLElement defining the filter * * @return string A string suitable for use as an SQL 'WHERE' clause selecting the desired records. */ function BuildSqlFilter($filter) { $components = array(); return SqlFilterFragment($filter, $components); } /** * Something that we can handle, at least roughly correctly. */ $responses = array(); $target_collection = new DAVResource($request->path); $bound_from = $target_collection->bound_from(); if (!$target_collection->Exists()) { $request->DoResponse(404); } $params = array(); $where = ' WHERE caldav_data.collection_id = ' . $target_collection->resource_id(); if (!($target_collection->IsCalendar() || $target_collection->IsSchedulingCollection())) { if (!(isset($c->allow_recursive_report) && $c->allow_recursive_report) || $target_collection->IsSchedulingCollection()) { $request->DoResponse(403, translate('The calendar-query report must be run against a calendar or a scheduling collection')); } /** * We're here because they allow recursive reports, and this appears to be such a location. */ $where = 'WHERE (collection.dav_name ~ :path_match '; $where .= 'OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) ';
* @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("PUT", "method handler"); require_once 'DAVResource.php'; if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/tmp/PUT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } $lock_opener = $request->FailIfLocked(); $dest = new DAVResource($request->path); $container = $dest->FetchParentContainer(); if (!$dest->Exists()) { if ($container->IsPrincipal()) { $request->PreconditionFailed(405, 'method-not-allowed', translate('A DAViCal principal collection may only contain collections')); } if (!$container->Exists()) { $request->PreconditionFailed(409, 'collection-must-exist', translate('The destination collection does not exist')); } $container->NeedPrivilege('DAV::bind'); } else { if ($dest->IsCollection()) { if (!isset($c->readonly_webdav_collections) || $c->readonly_webdav_collections) { $request->PreconditionFailed(405, 'method-not-allowed', translate('You may not PUT to a collection URL')); } $request->DoResponse(403, translate('PUT on a collection is only allowed for text/calendar content against a calendar collection'));
function export_iCalendar(DAVResource $dav_resource) { global $session, $c, $request; if (!$dav_resource->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections)) { /** 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 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. */ if (isset($c->get_includes_subcollections) && $c->get_includes_subcollections) { $where = 'caldav_data.collection_id IN '; $where .= '(SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match '; $where .= 'UNION '; $where .= 'SELECT collection_id FROM collection WHERE collection.dav_name ~ :path_match) '; $params = array(':path_match' => '^' . $dav_resource->dav_name()); $distinct = 'DISTINCT ON (calendar_item.uid) '; } else { $where = 'caldav_data.collection_id = :collection_id '; $params = array(':collection_id' => $dav_resource->resource_id()); $distinct = ''; } $sql = 'SELECT ' . $distinct . ' caldav_data, class, caldav_type, calendar_item.user_no, logged_user '; $sql .= 'FROM collection INNER JOIN caldav_data USING(collection_id) '; $sql .= 'INNER JOIN calendar_item USING ( dav_id ) WHERE ' . $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= ' ORDER BY calendar_item.uid, calendar_item.dav_id'; } $qry = new AwlQuery($sql, $params); if (!$qry->Exec("GET", __LINE__, __FILE__)) { $request->DoResponse(500, translate("Database Error")); } /** * 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(); $vcal->VCalendar(); $displayname = $dav_resource->GetProperty('displayname'); if (isset($displayname)) { $vcal->AddProperty("X-WR-CALNAME", $displayname); } if (!empty($c->auto_refresh_duration)) { $vcal->AddProperty("X-APPLE-AUTO-REFRESH-INTERVAL", $c->auto_refresh_duration); $vcal->AddProperty("AUTO-REFRESH", $c->auto_refresh_duration); $vcal->AddProperty("X-PUBLISHED-TTL", $c->auto_refresh_duration); } $need_zones = array(); $timezones = array(); while ($event = $qry->Fetch()) { $ical = new iCalComponent($event->caldav_data); /** Save the timezone component(s) into a minimal set for inclusion later */ $event_zones = $ical->GetComponents('VTIMEZONE', true); foreach ($event_zones as $k => $tz) { $tzid = $tz->GetPValue('TZID'); if (!isset($tzid)) { continue; } if ($tzid != '' && !isset($timezones[$tzid])) { $timezones[$tzid] = $tz; } } /** Work out which ones are actually used here */ $comps = $ical->GetComponents('VTIMEZONE', false); foreach ($comps as $k => $comp) { $tzid = $comp->GetPParamValue('DTSTART', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DUE', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DTEND', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $event->user_no || $session->user_no == $event->logged_user || isset($session->email) && $c->allow_get_email_visibility && $comp->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $vcal->AddComponent($comp); continue; } /** No visibility even of the existence of these events if they aren't admin/owner/attendee */ if ($event->class == 'PRIVATE') { continue; } if (!$dav_resource->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL') { $vcal->AddComponent(obfuscated_event($comp)); } elseif (isset($c->hide_alarm) && $c->hide_alarm) { // Otherwise we hide the alarms (if configured to) $comp->ClearComponents('VALARM'); $vcal->AddComponent($comp); } else { $vcal->AddComponent($comp); } } } /** Put the timezones on there that we need */ foreach ($need_zones as $tzid => $v) { if (isset($timezones[$tzid])) { $vcal->AddComponent($timezones[$tzid]); } } return $vcal->Render(); }
<?php /** * CalDAV Server - handle PUT method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("PUT", "method handler"); require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!$dav_resource->HavePrivilegeTo('DAV::write-content')) { $request->DoResponse(403); } if (!$dav_resource->Exists() && !$dav_resource->HavePrivilegeTo('DAV::bind')) { $request->DoResponse(403); } if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/tmp/PUT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } include_once 'caldav-PUT-functions.php'; controlRequestContainer($dav_resource->GetProperty('username'), $dav_resource->GetProperty('user_no'), $dav_resource->bound_from(), true); $lock_opener = $request->FailIfLocked(); if ($dav_resource->IsCollection()) {
foreach ($qry_content[1]->GetElements() as $k => $v) { $include_properties[] = $v->GetNSTag(); /** $include_properties is referenced in DAVResource where allprop is expanded */ if ($v->GetNSTag() == 'urn:ietf:params:xml:ns:caldav:calendar-data') { check_for_expansion($v); } } } break; default: $properties[$proptype] = 1; } if (empty($properties)) { $properties['DAV::allprop'] = 1; } $collection = new DAVResource($request->path); $bound_from = $collection->bound_from(); /** * Build the href list for the IN ( href, href, href, ... ) clause. */ $mg_hrefs = $xmltree->GetPath('/*/DAV::href'); $href_in = ''; $params = array(); foreach ($mg_hrefs as $k => $v) { /** * We need this to work if they specified a relative *or* a full path, so we strip off * anything up to the matching request->path (which will include any http...) and then * put the $bound_from prefix back on. */ $rawurl = rawurldecode($v->GetContent()); $path_pos = strpos($rawurl, $request->path);
if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['report']) && $c->dbg['report'])) { $fh = fopen('/tmp/REPORT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } if (!isset($request->xml_tags)) { $request->DoResponse(406, translate("REPORT body contains no XML data!")); } $position = 0; $xmltree = BuildXMLTree($request->xml_tags, $position); if (!is_object($xmltree)) { $request->DoResponse(406, translate("REPORT body is not valid XML data!")); } $target = new DAVResource($request->path); if ($xmltree->GetTag() != 'DAV::principal-property-search' && $xmltree->GetTag() != 'DAV::principal-property-search-set') { $target->NeedPrivilege(array('DAV::read', 'urn:ietf:params:xml:ns:caldav:read-free-busy'), true); // They may have either } require_once "iCalendar.php"; $reportnum = -1; $report = array(); $denied = array(); $unsupported = array(); if (isset($prop_filter)) { unset($prop_filter); } if ($xmltree->GetTag() == 'urn:ietf:params:xml:ns:caldav:free-busy-query') { include "caldav-REPORT-freebusy.php"; exit;
/** * A slightly simpler version of write_resource which will make more sense for calling from * an external program. This makes assumptions that the collection and user do exist * and bypasses all checks for whether it is reasonable to write this here. * @param string $path The path to the resource being written * @param string $caldav_data The actual resource to be written * @param string $put_action_type INSERT or UPDATE depending on what we are to do * @return boolean True for success, false for failure. */ function simple_write_resource($path, $caldav_data, $put_action_type, $write_action_log = false) { global $session; /** * We pull the user_no & collection_id out of the collection table, based on the resource path */ $dav_resource = new DAVResource($path); $etag = md5($caldav_data); $collection_path = preg_replace('#/[^/]*$#', '/', $path); $collection = new DAVResource($collection_path); if ($collection->IsCollection() || $collection->IsSchedulingCollection()) { return write_resource($dav_resource, $caldav_data, $collection, $session->user_no, $etag, $put_action_type, false, $write_action_log); } return false; }
* with some documented variations, which we will also follow. In particular * we use the xmlns="http://www.xythos.com/namespaces/StorageServer" rather * than the DAV: namespace. * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log('MKTICKET', 'method handler'); require_once 'DAVResource.php'; $request->NeedPrivilege('DAV::bind'); require_once 'XMLDocument.php'; $reply = new XMLDocument(array('DAV:' => '', 'http://www.xythos.com/namespaces/StorageServer' => 'T')); $target = new DAVResource($request->path); if (!$target->Exists()) { $request->XMLResponse(404, new XMLElement('error', new XMLElement('resource-must-not-be-null'), $reply->GetXmlNsArray())); } if (!isset($request->xml_tags)) { $request->XMLResponse(400, new XMLElement('error', new XMLElement('missing-xml-for-request'), $reply->GetXmlNsArray())); } $xmltree = BuildXMLTree($request->xml_tags, $position); if ($xmltree->GetTag() != 'http://www.xythos.com/namespaces/StorageServer:ticketinfo' && $xmltree->GetTag() != 'DAV::ticketinfo') { $request->XMLResponse(400, new XMLElement('error', new XMLElement('invalid-xml-for-request'), $reply->GetXmlNsArray())); } $ticket_timeout = 'Seconds-3600'; $ticket_privs_array = array('read-free-busy'); foreach ($xmltree->GetContent() as $k => $v) { // <!ELEMENT ticketinfo (id?, owner?, timeout, visits, privilege)> switch ($v->GetTag()) {