Example #1
0
/**
* 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;
}
Example #2
0
    /**
     * Return general server-related properties for this URL
     */
    function ResourceProperty($tag, $prop, &$reply, &$denied)
    {
        global $c, $session, $request;
        //    dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
        if ($reply === null) {
            $reply = $GLOBALS['reply'];
        }
        switch ($tag) {
            case 'DAV::allprop':
                $property_list = $this->DAV_AllProperties();
                $discarded = array();
                foreach ($property_list as $k => $v) {
                    $this->ResourceProperty($v, $prop, $reply, $discarded);
                }
                break;
            case 'DAV::href':
                $prop->NewElement('href', ConstructURL($this->dav_name));
                break;
            case 'DAV::resource-id':
                if ($this->resource_id > 0) {
                    $reply->DAVElement($prop, 'resource-id', $reply->href(ConstructURL('/.resources/' . $this->resource_id)));
                } else {
                    return false;
                }
                break;
            case 'DAV::parent-set':
                $sql = <<<EOQRY
SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
 WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
EOQRY;
                $qry = new AwlQuery($sql, array(':bound_from' => $this->bound_from()));
                $parents = array();
                if ($qry->Exec('DAVResource', __LINE__, __FILE__) && $qry->rows() > 0) {
                    while ($row = $qry->Fetch()) {
                        $parents[$row->parent_container] = true;
                    }
                }
                $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->bound_from())] = true;
                $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->dav_name())] = true;
                $parent_set = $reply->DAVElement($prop, 'parent-set');
                foreach ($parents as $parent => $v) {
                    if (preg_match('{^(.*)?/([^/]+)/?$}', $parent, $matches)) {
                        $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', ConstructURL($matches[1])), new XMLElement('segment', $matches[2])));
                    } else {
                        if ($parent == '/') {
                            $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', '/'), new XMLElement('segment', ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : '')));
                        }
                    }
                }
                break;
            case 'DAV::getcontenttype':
                if (!isset($this->contenttype) && !$this->_is_collection && !isset($this->resource)) {
                    $this->FetchResource();
                }
                $prop->NewElement('getcontenttype', $this->contenttype);
                break;
            case 'DAV::resourcetype':
                $resourcetypes = $prop->NewElement('resourcetype');
                if ($this->_is_collection) {
                    $type_list = $this->GetProperty('resourcetype');
                    if (!is_array($type_list)) {
                        return true;
                    }
                    //        dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
                    foreach ($type_list as $k => $v) {
                        if ($v == '') {
                            continue;
                        }
                        $reply->NSElement($resourcetypes, $v);
                    }
                    if ($this->_is_binding) {
                        $reply->NSElement($resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding');
                    }
                }
                break;
            case 'DAV::getlastmodified':
                /** getlastmodified is HTTP Date format: i.e. the Last-Modified header in response to a GET */
                $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')));
                break;
            case 'DAV::creationdate':
                /** creationdate is ISO8601 format */
                $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true));
                break;
            case 'DAV::getcontentlength':
                if ($this->_is_collection) {
                    return false;
                }
                if (!isset($this->resource)) {
                    $this->FetchResource();
                }
                if (isset($this->resource)) {
                    $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data));
                }
                break;
            case 'DAV::getcontentlanguage':
                $locale = isset($c->current_locale) ? $c->current_locale : '';
                if (isset($this->locale) && $this->locale != '') {
                    $locale = $this->locale;
                }
                $reply->NSElement($prop, $tag, $locale);
                break;
            case 'DAV::acl-restrictions':
                $reply->NSElement($prop, $tag, array(new XMLElement('grant-only'), new XMLElement('no-invert')));
                break;
            case 'DAV::inherited-acl-set':
                $inherited_acls = array();
                if (!$this->_is_collection) {
                    $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
                }
                $reply->NSElement($prop, $tag, $inherited_acls);
                break;
            case 'DAV::owner':
                // The principal-URL of the owner
                if ($this->IsExternal()) {
                    $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL($this->collection->bound_from)));
                } else {
                    $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL(DeconstructURL($this->principal_url()))));
                }
                break;
            case 'DAV::add-member':
                if (!$this->_is_collection) {
                    return false;
                }
                if (isset($c->post_add_member) && $c->post_add_member === false) {
                    return false;
                }
                $reply->DAVElement($prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())) . '?add-member'));
                break;
                // Empty tag responses.
            // Empty tag responses.
            case 'DAV::group':
            case 'DAV::alternate-URI-set':
                $reply->NSElement($prop, $tag);
                break;
            case 'DAV::getetag':
                if ($this->_is_collection) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $this->unique_tag());
                break;
            case 'http://calendarserver.org/ns/:getctag':
                if (!$this->_is_collection) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $this->unique_tag());
                break;
            case 'DAV::sync-token':
                if (!$this->_is_collection) {
                    return false;
                }
                $sync_token = $this->sync_token();
                if (empty($sync_token)) {
                    return false;
                }
                $reply->NSElement($prop, $tag, $sync_token);
                break;
            case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
                $proxy_type = 'read';
            case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
                if (isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy) {
                    return false;
                }
                if (!isset($proxy_type)) {
                    $proxy_type = 'write';
                }
                // ProxyFor is an already constructed URL
                $reply->CalendarserverElement($prop, 'calendar-proxy-' . $proxy_type . '-for', $reply->href($this->principal->ProxyFor($proxy_type)));
                break;
            case 'DAV::current-user-privilege-set':
                if ($this->HavePrivilegeTo('DAV::read-current-user-privilege-set')) {
                    $reply->NSElement($prop, $tag, $this->BuildPrivileges());
                } else {
                    $denied[] = $tag;
                }
                break;
            case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
                if (!$this->IsCalendar() && !$this->IsSchedulingCollection()) {
                    return false;
                }
                $reply->NSElement($prop, $tag, 'text/calendar');
                break;
            case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
                if (!$this->_is_collection) {
                    return false;
                }
                if ($this->IsCalendar()) {
                    if (!isset($this->dead_properties)) {
                        $this->FetchDeadProperties();
                    }
                    if (isset($this->dead_properties[$tag])) {
                        $set_of_components = $this->dead_properties[$tag];
                        foreach ($set_of_components as $k => $v) {
                            if (preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches)) {
                                $set_of_components[$k] = $matches[1];
                            } else {
                                unset($set_of_components[$k]);
                            }
                        }
                    } else {
                        if (isset($c->default_calendar_components) && is_array($c->default_calendar_components)) {
                            $set_of_components = $c->default_calendar_components;
                        } else {
                            $set_of_components = array('VEVENT', 'VTODO', 'VJOURNAL');
                        }
                    }
                } else {
                    if ($this->IsSchedulingCollection()) {
                        $set_of_components = array('VEVENT', 'VTODO', 'VFREEBUSY');
                    } else {
                        return false;
                    }
                }
                $components = array();
                foreach ($set_of_components as $v) {
                    $components[] = $reply->NewXMLElement('comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
                }
                $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components);
                break;
            case 'DAV::supported-method-set':
                $prop->NewElement('supported-method-set', $this->BuildSupportedMethods());
                break;
            case 'DAV::supported-report-set':
                $prop->NewElement('supported-report-set', $this->BuildSupportedReports($reply));
                break;
            case 'DAV::supportedlock':
                $prop->NewElement('supportedlock', new XMLElement('lockentry', array(new XMLElement('lockscope', new XMLElement('exclusive')), new XMLElement('locktype', new XMLElement('write')))));
                break;
            case 'DAV::supported-privilege-set':
                $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply));
                break;
            case 'DAV::principal-collection-set':
                $prop->NewElement('principal-collection-set', $reply->href(ConstructURL('/')));
                break;
            case 'DAV::current-user-principal':
                $prop->NewElement('current-user-principal', $reply->href(ConstructURL(DeconstructURL($request->principal->url()))));
                break;
            case 'SOME-DENIED-PROPERTY':
                /** indicating the style for future expansion */
                $denied[] = $reply->Tag($tag);
                break;
            case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
                if (!$this->_is_collection) {
                    return false;
                }
                if (!isset($this->collection->vtimezone) || $this->collection->vtimezone == '') {
                    return false;
                }
                $cal = new iCalComponent();
                $cal->VCalendar();
                $cal->AddComponent(new iCalComponent($this->collection->vtimezone));
                $reply->NSElement($prop, $tag, $cal->Render());
                break;
            case 'urn:ietf:params:xml:ns:carddav:address-data':
            case 'urn:ietf:params:xml:ns:caldav:calendar-data':
                if ($this->_is_collection) {
                    return false;
                }
                if (!isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false) {
                    return false;
                }
                if (!isset($this->resource)) {
                    $this->FetchResource();
                }
                $reply->NSElement($prop, $tag, $this->resource->caldav_data);
                break;
            case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
                if (!$this->_is_collection || !$this->_is_addressbook) {
                    return false;
                }
                $reply->NSElement($prop, $tag, 65500);
                break;
            case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
                if (!$this->_is_collection || !$this->_is_addressbook) {
                    return false;
                }
                $address_data = $reply->NewXMLElement('address-data', false, array('content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
                $reply->NSElement($prop, $tag, $address_data);
                break;
            case 'DAV::acl':
                if ($this->HavePrivilegeTo('DAV::read-acl')) {
                    $reply->NSElement($prop, $tag, $this->GetACL($reply));
                } else {
                    $denied[] = $tag;
                }
                break;
            case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
            case 'DAV::ticketdiscovery':
                $reply->NSElement($prop, 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply));
                break;
            default:
                $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag));
                if (isset($property_value)) {
                    $reply->NSElement($prop, $tag, $property_value);
                } else {
                    if (!isset($this->dead_properties)) {
                        $this->FetchDeadProperties();
                    }
                    if (isset($this->dead_properties[$tag])) {
                        $reply->NSElement($prop, $tag, $this->dead_properties[$tag]);
                    } else {
                        //            dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
                        return false;
                    }
                }
        }
        return true;
    }
Example #3
0
 /**
  * Returns properties which are specific to this principal
  */
 function PrincipalProperty($tag, $prop, &$reply, &$denied)
 {
     dbg_error_log('principal', ': RenderAsXML: Principal Property "%s"', $tag);
     switch ($tag) {
         case 'DAV::getcontenttype':
             $prop->NewElement('getcontenttype', 'httpd/unix-directory');
             break;
         case 'DAV::resourcetype':
             $prop->NewElement('resourcetype', array(new XMLElement('principal'), new XMLElement('collection')));
             break;
         case 'DAV::displayname':
             $prop->NewElement('displayname', $this->fullname);
             break;
         case 'DAV::principal-URL':
             $prop->NewElement('principal-URL', $reply->href($this->principal_url));
             break;
         case 'DAV::getlastmodified':
             $prop->NewElement('getlastmodified', ISODateToHTTPDate($this->modified));
             break;
         case 'DAV::creationdate':
             $prop->NewElement('creationdate', DateToISODate($this->created));
             break;
         case 'DAV::getcontentlanguage':
             /** Use the principal's locale by preference, otherwise system default */
             $locale = isset($c->current_locale) ? $c->current_locale : '';
             if (isset($this->locale) && $this->locale != '') {
                 $locale = $this->locale;
             }
             $prop->NewElement('getcontentlanguage', $locale);
             break;
         case 'DAV::group-member-set':
             if (!$this->_is_group) {
                 return false;
             }
             $prop->NewElement('group-member-set', $reply->href($this->group_member_set));
             break;
         case 'DAV::group-membership':
             $prop->NewElement('group-membership', $reply->href($this->GroupMembership()));
             break;
         case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL':
             $reply->CalDAVElement($prop, 'schedule-inbox-URL', $reply->href($this->schedule_inbox_url));
             break;
         case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL':
             $reply->CalDAVElement($prop, 'schedule-outbox-URL', $reply->href($this->schedule_outbox_url));
             break;
         case 'http://calendarserver.org/ns/:dropbox-home-URL':
             $reply->CalendarserverElement($prop, 'dropbox-home-URL', $reply->href($this->dropbox_url));
             break;
         case 'http://calendarserver.org/ns/:xmpp-server':
             if (!isset($this->xmpp_uri)) {
                 return false;
             }
             $reply->CalendarserverElement($prop, 'xmpp-server', $this->xmpp_server);
             break;
         case 'http://calendarserver.org/ns/:xmpp-uri':
             if (!isset($this->xmpp_uri)) {
                 return false;
             }
             $reply->CalendarserverElement($prop, 'xmpp-uri', $this->xmpp_uri);
             break;
         case 'urn:ietf:params:xml:ns:carddav:addressbook-home-set':
             $reply->NSElement($prop, $tag, $reply->href($this->addressbook_home_set()));
             break;
         case 'urn:ietf:params:xml:ns:caldav:calendar-home-set':
             $reply->NSElement($prop, $tag, $reply->href($this->calendar_home_set()));
             break;
         case 'urn:ietf:params:xml:ns:caldav:calendar-free-busy-set':
             $reply->CalDAVElement($prop, 'calendar-free-busy-set', $reply->href($this->calendar_free_busy_set()));
             break;
         case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set':
             $reply->CalDAVElement($prop, 'calendar-user-address-set', $reply->href($this->user_address_set));
             break;
         case 'DAV::owner':
             // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
             $reply->DAVElement($prop, 'owner', $reply->href($this->principal_url));
             break;
             // Empty tag responses.
         // Empty tag responses.
         case 'DAV::alternate-URI-set':
             $prop->NewElement($reply->Tag($tag));
             break;
         case 'SOME-DENIED-PROPERTY':
             /** @todo indicating the style for future expansion */
             $denied[] = $reply->Tag($tag);
             break;
         default:
             return false;
             break;
     }
     return true;
 }
Example #4
0
/**
* 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;
}