Removed logging subsystem from webcit. It's all syslog now.
[citadel.git] / webcit / groupdav_propfind.c
index 2385d9900fdc7cc3186fe0f9eb80726f93e63e2e..01dbe4f6177300562c5df06edf80a793525937d3 100644 (file)
 /*
- * $Id$
- *
  * Handles GroupDAV PROPFIND requests.
  *
  * A few notes about our XML output:
  *
  * --> Yes, we are spewing tags directly instead of using an XML library.
- *     If you would like to rewrite this using libxml2, code it up and submit
- *     a patch.  Whining will be summarily ignored.
+ *     Whining about it will be summarily ignored.
  *
  * --> XML is deliberately output with no whitespace/newlines between tags.
  *     This makes it difficult to read, but we have discovered clients which
  *     crash when you try to pretty it up.
  *
+ * Copyright (c) 2005-2010 by the citadel.org team
+ *
+ * This program is open source software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
 #include "webcit.h"
 #include "webserver.h"
 #include "groupdav.h"
 
+extern int DisableGzip;
 
 /*
  * Given an encoded UID, translate that to an unencoded Citadel EUID and
  * then search for it in the current room.  Return a message number or -1
  * if not found.
  *
- * NOTE: this function relies on the Citadel server's brute-force search.
- * There's got to be a way to optimize this better.
  */
-long locate_message_by_uid(char *uid) {
-       char buf[SIZ];
-       char decoded_uid[SIZ];
+long locate_message_by_uid(const char *uid) {
+       char buf[256];
+       char decoded_uid[1024];
        long retval = (-1L);
 
-       /* Decode the uid */
+       /* decode the UID */
        euid_unescapize(decoded_uid, uid);
 
+       /* ask Citadel if we have this one */
        serv_printf("EUID %s", decoded_uid);
        serv_getln(buf, sizeof buf);
        if (buf[0] == '2') {
-               retval = extract_long(&buf[4], 0);
+               retval = atol(&buf[4]);
        }
+
        return(retval);
 }
 
 
 /*
- * List folders containing interesting groupware objects
+ * IgnoreFloor: set to 0 or 1 _nothing else_
+ * Subfolders: direct child floors will be put here.
+ */
+const folder *GetRESTFolder(int IgnoreFloor, HashList *Subfolders)
+{
+       wcsession  *WCC = WC;
+       void *vFolder;
+       const folder *ThisFolder = NULL;
+       const folder *FoundFolder = NULL;
+       const folder *BestGuess = NULL;
+       int nBestGuess = 0;
+       HashPos    *itd, *itfl;
+       StrBuf     * Dir;
+       void       *vDir;
+       long        len;
+        const char *Key;
+       int iRoom, jURL, urlp;
+       int delta;
+
+/*
+ * Guess room: if the full URL matches a room, list thats it. We also need to remember direct sub rooms.
+ * if the URL is longer, we need to find the "best guess" so we can find the room we're in, and the rest of the URL will be uids and so on.
  */
-void groupdav_folder_list(void) {
-       char buf[SIZ];
-       char roomname[SIZ];
+       itfl = GetNewHashPos(WCC->Floors, 0);
+       urlp = GetCount(WCC->Directory);
+
+       while (GetNextHashPos(WCC->Floors, itfl, &len, &Key, &vFolder) && 
+              (ThisFolder == NULL))
+       {
+               ThisFolder = vFolder;
+               if (!IgnoreFloor && /* so we can handle legacy URLS... */
+                   (ThisFolder->Floor != WCC->CurrentFloor))
+                       continue;
+
+
+               if (ThisFolder->nRoomNameParts > 1) 
+               {
+                       /*TODO: is that number all right? */
+//                     if (urlp - ThisFolder->nRoomNameParts != 2) {
+//                             if (BestGuess != NULL)
+//                                     continue;
+//ThisFolder->name
+//                             itd  = GetNewHashPos(WCC->Directory, 0);
+//                             GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
+//                     }
+                       itd  = GetNewHashPos(WCC->Directory, 0);
+                       GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
+       
+                       for (iRoom = 0, /* Fast forward the floorname as we checked it above: */ jURL = IgnoreFloor; 
+
+                            (iRoom <= ThisFolder->nRoomNameParts) && (jURL <= urlp); 
+
+                            iRoom++, jURL++, GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir))
+                       {
+                               Dir = (StrBuf*)vDir;
+                               if (strcmp(ChrPtr(ThisFolder->RoomNameParts[iRoom]), 
+                                          ChrPtr(Dir)) != 0)
+                               {
+                                       DeleteHashPos(&itd);
+                                       continue;
+                               }
+                       }
+                       DeleteHashPos(&itd);
+                       /* Gotcha? */
+                       if ((iRoom == ThisFolder->nRoomNameParts) && (jURL == urlp))
+                       {
+                               FoundFolder = ThisFolder;
+                       }
+                       /* URL got more parts then this room, so we remember it for the best guess*/
+                       else if ((jURL <= urlp) &&
+                                (ThisFolder->nRoomNameParts <= nBestGuess))
+                       {
+                               BestGuess = ThisFolder;
+                               nBestGuess = jURL - 1;
+                       }
+                       /* Room has more parts than the URL, it might be a sub-room? */
+                       else if (iRoom <ThisFolder->nRoomNameParts) 
+                       {//// TODO: ThisFolder->nRoomNameParts == urlp - IgnoreFloor???
+                               Put(Subfolders, SKEY(ThisFolder->name), 
+                                   /* Cast away const, its a reference. */
+                                   (void*)ThisFolder, reference_free_handler);
+                       }
+               }
+               else {
+                       delta = GetCount(WCC->Directory) - ThisFolder->nRoomNameParts;
+                       if ((delta != 2) && (nBestGuess > 1))
+                           continue;
+                       
+                       itd  = GetNewHashPos(WCC->Directory, 0);
+                                               
+                       if (!GetNextHashPos(WCC->Directory, 
+                                           itd, &len, &Key, &vDir) ||
+                           (vDir == NULL))
+                       {
+                               DeleteHashPos(&itd);
+                               
+                               syslog(0, "5\n");
+                               continue;
+                       }
+                       DeleteHashPos(&itd);
+                       Dir = (StrBuf*) vDir;
+                       if (strcmp(ChrPtr(ThisFolder->name), 
+                                              ChrPtr(Dir))
+                           != 0)
+                       {
+                               DeleteHashPos(&itd);
+                               
+                               syslog(0, "5\n");
+                               continue;
+                       }
+                       DeleteHashPos(&itfl);
+                       DeleteHashPos(&itd);
+                       if (delta != 2) {
+                               nBestGuess = 1;
+                               BestGuess = ThisFolder;
+                       }
+                       else 
+                               FoundFolder = ThisFolder;
+               }
+       }
+
+/* TODO: Subfolders: remove patterns not matching the best guess or thisfolder */
+       DeleteHashPos(&itfl);
+       if (FoundFolder != NULL)
+               return FoundFolder;
+       else
+               return BestGuess;
+}
+
+
+
+
+long GotoRestRoom(HashList *SubRooms)
+{
+       int IgnoreFloor = 0; /* deprecated... */
+       wcsession *WCC = WC;
+       long Count;
+       long State;
+       const folder *ThisFolder;
+
+       State = REST_TOPLEVEL;
+
+       if (WCC->Hdr->HR.Handler != NULL) 
+               State |= REST_IN_NAMESPACE;
+
+       Count = GetCount(WCC->Directory);
+       
+       if (Count == 0) return State;
+
+       if (Count >= 1) State |=REST_IN_FLOOR;
+       if (Count == 1) return State;
+       
+       /* 
+        * More than 3 params and no floor found? 
+        * -> fall back to old non-floored notation
+        */
+       if ((Count >= 3) && (WCC->CurrentFloor == NULL))
+               IgnoreFloor = 1;
+       if (Count >= 3)
+       {
+               IgnoreFloor = 0;
+               State |= REST_IN_FLOOR;
+
+               ThisFolder = GetRESTFolder(IgnoreFloor, SubRooms);
+               if (ThisFolder != NULL)
+               {
+                       if (WCC->ThisRoom != NULL)
+                               if (CompareRooms(WCC->ThisRoom, ThisFolder) != 0)
+                                       gotoroom(ThisFolder->name);
+                       State |= REST_IN_ROOM;
+                       
+               }
+               if (GetCount(SubRooms) > 0)
+                       State |= REST_HAVE_SUB_ROOMS;
+       }
+       if ((WCC->ThisRoom != NULL) && 
+           (Count + IgnoreFloor > 3))
+       {
+               if (WCC->Hdr->HR.Handler->RID(ExistsID, IgnoreFloor))
+               {
+                       State |= REST_GOT_LOCAL_PART;
+               }
+               else {
+                       /// WHOOPS, not there???
+                       State |= REST_NONEXIST;
+               }
+
+
+       }
+       return State;
+}
+
+
+
+/*
+ * List rooms (or "collections" in DAV terminology) which contain
+ * interesting groupware objects.
+ */
+void groupdav_collection_list(void)
+{
+       wcsession *WCC = WC;
+       char buf[256];
+       char roomname[256];
        int view;
-       char datestring[SIZ];
+       char datestring[256];
        time_t now;
+       time_t mtime;
+       int is_groupware_collection = 0;
+       int starting_point = 1;         /**< 0 for /, 1 for /groupdav/ */
+
+       if (WCC->Hdr->HR.Handler == NULL) {
+               starting_point = 0;
+       }
+       else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
+               starting_point = 1;
+       }
+       else {
+               starting_point = 2;
+       }
 
        now = time(NULL);
        http_datestring(datestring, sizeof datestring, now);
@@ -62,73 +286,147 @@ void groupdav_folder_list(void) {
         * Be rude.  Completely ignore the XML request and simply send them
         * everything we know about.  Let the client sort it out.
         */
-       wprintf("HTTP/1.1 207 Multi-Status\r\n");
+       hprintf("HTTP/1.0 207 Multi-Status\r\n");
        groupdav_common_headers();
-       wprintf("Date: %s\r\n", datestring);
-       wprintf("Content-type: text/xml\r\n");
-       wprintf("Content-encoding: identity\r\n");
+       hprintf("Date: %s\r\n", datestring);
+       hprintf("Content-type: text/xml\r\n");
+       if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
+               hprintf("Content-encoding: identity\r\n");
 
        begin_burst();
 
-       wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-               "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
+       wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+               "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
        );
 
+       /*
+        * If the client is requesting the root, show a root node.
+        */
+       if (starting_point == 0) {
+               wc_printf("<response>");
+                       wc_printf("<href>");
+                               groupdav_identify_host();
+                               wc_printf("/");
+                       wc_printf("</href>");
+                       wc_printf("<propstat>");
+                               wc_printf("<status>HTTP/1.1 200 OK</status>");
+                               wc_printf("<prop>");
+                                       wc_printf("<displayname>/</displayname>");
+                                       wc_printf("<resourcetype><collection/></resourcetype>");
+                                       wc_printf("<getlastmodified>");
+                                               escputs(datestring);
+                                       wc_printf("</getlastmodified>");
+                               wc_printf("</prop>");
+                       wc_printf("</propstat>");
+               wc_printf("</response>");
+       }
+
+       /*
+        * If the client is requesting "/groupdav", show a /groupdav subdirectory.
+        */
+       if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
+               wc_printf("<response>");
+                       wc_printf("<href>");
+                               groupdav_identify_host();
+                               wc_printf("/groupdav");
+                       wc_printf("</href>");
+                       wc_printf("<propstat>");
+                               wc_printf("<status>HTTP/1.1 200 OK</status>");
+                               wc_printf("<prop>");
+                                       wc_printf("<displayname>GroupDAV</displayname>");
+                                       wc_printf("<resourcetype><collection/></resourcetype>");
+                                       wc_printf("<getlastmodified>");
+                                               escputs(datestring);
+                                       wc_printf("</getlastmodified>");
+                               wc_printf("</prop>");
+                       wc_printf("</propstat>");
+               wc_printf("</response>");
+       }
+
+       /*
+        * Now go through the list and make it look like a DAV collection
+        */
        serv_puts("LKRA");
        serv_getln(buf, sizeof buf);
        if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
 
                extract_token(roomname, buf, 0, '|', sizeof roomname);
-               view = extract_int(buf, 6);
+               view = extract_int(buf, 7);
+               mtime = extract_long(buf, 8);
+               http_datestring(datestring, sizeof datestring, mtime);
 
                /*
                 * For now, only list rooms that we know a GroupDAV client
                 * might be interested in.  In the future we may add
                 * the rest.
+                *
+                * We determine the type of objects which are stored in each
+                * room by looking at the *default* view for the room.  This
+                * allows, for example, a Calendar room to appear as a
+                * GroupDAV calendar even if the user has switched it to a
+                * Calendar List view.
                 */
-               if ((view == VIEW_CALENDAR)
-                  || (view == VIEW_TASKS)
-                  || (view == VIEW_ADDRESSBOOK) ) {
+               if (    (view == VIEW_CALENDAR) || 
+                       (view == VIEW_TASKS) || 
+                       (view == VIEW_ADDRESSBOOK) ||
+                       (view == VIEW_NOTES) ||
+                       (view == VIEW_JOURNAL) ||
+                       (view == VIEW_WIKI)
+               ) {
+                       is_groupware_collection = 1;
+               }
+               else {
+                       is_groupware_collection = 0;
+               }
 
-                       wprintf("<D:response>");
+               if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
+                       wc_printf("<response>");
 
-                       wprintf("<D:href>");
-                       if (strlen(WC->http_host) > 0) {
-                               wprintf("%s://%s",
-                                       (is_https ? "https" : "http"),
-                                       WC->http_host);
-                       }
-                       wprintf("/groupdav/");
+                       wc_printf("<href>");
+                       groupdav_identify_host();
+                       wc_printf("/groupdav/");
                        urlescputs(roomname);
-                       wprintf("/</D:href>");
+                       wc_printf("/</href>");
 
-                       wprintf("<D:propstat>");
-                       wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
-                       wprintf("<D:prop>");
-                       wprintf("<D:fullname>");
+                       wc_printf("<propstat>");
+                       wc_printf("<status>HTTP/1.1 200 OK</status>");
+                       wc_printf("<prop>");
+                       wc_printf("<displayname>");
                        escputs(roomname);
-                       wprintf("</D:fullname>");
-                       wprintf("<D:resourcetype><D:collection/>");
+                       wc_printf("</displayname>");
+                       wc_printf("<resourcetype><collection/>");
 
                        switch(view) {
-                               case VIEW_CALENDAR:
-                                       wprintf("<G:vevent-collection />");
-                                       break;
-                               case VIEW_TASKS:
-                                       wprintf("<G:vtodo-collection />");
-                                       break;
-                               case VIEW_ADDRESSBOOK:
-                                       wprintf("<G:vcard-collection />");
-                                       break;
+                       case VIEW_CALENDAR:
+                               wc_printf("<G:vevent-collection />");
+                               break;
+                       case VIEW_TASKS:
+                               wc_printf("<G:vtodo-collection />");
+                               break;
+                       case VIEW_ADDRESSBOOK:
+                               wc_printf("<G:vcard-collection />");
+                               break;
+                       case VIEW_NOTES:
+                               wc_printf("<G:vnotes-collection />");
+                               break;
+                       case VIEW_JOURNAL:
+                               wc_printf("<G:vjournal-collection />");
+                               break;
+                       case VIEW_WIKI:
+                               wc_printf("<G:wiki-collection />");
+                               break;
                        }
 
-                       wprintf("</D:resourcetype>");
-                       wprintf("</D:prop>");
-                       wprintf("</D:propstat>");
-                       wprintf("</D:response>");
+                       wc_printf("</resourcetype>");
+                       wc_printf("<getlastmodified>");
+                               escputs(datestring);
+                       wc_printf("</getlastmodified>");
+                       wc_printf("</prop>");
+                       wc_printf("</propstat>");
+                       wc_printf("</response>");
                }
        }
-       wprintf("</D:multistatus>\n");
+       wc_printf("</multistatus>\n");
 
        end_burst();
 }
@@ -138,12 +436,18 @@ void groupdav_folder_list(void) {
 /*
  * The pathname is always going to be /groupdav/room_name/msg_num
  */
-void groupdav_propfind(char *dav_pathname) {
-       char dav_roomname[256];
-       char dav_uid[256];
-       char msgnum[256];
+void groupdav_propfind(void) 
+{
+#ifdef DEV_RESTDAV
+       HashList *SubRooms = NULL;
+       long State;
+#endif
+       wcsession *WCC = WC;
+       StrBuf *dav_roomname;
+       StrBuf *dav_uid;
+       StrBuf *MsgNum;
+       long BufLen;
        long dav_msgnum = (-1);
-       char buf[256];
        char uid[256];
        char encoded_uid[256];
        long *msgs = NULL;
@@ -155,59 +459,109 @@ void groupdav_propfind(char *dav_pathname) {
        now = time(NULL);
        http_datestring(datestring, sizeof datestring, now);
 
-       extract_token(dav_roomname, dav_pathname, 2, '/', sizeof dav_roomname);
-       extract_token(dav_uid, dav_pathname, 3, '/', sizeof dav_uid);
-
+       dav_roomname = NewStrBuf();
+       dav_uid = NewStrBuf();
+       StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
+       StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
+#ifdef DEV_RESTDAV
        /*
-       lprintf(9, "dav_pathname: %s\n", dav_pathname);
-       lprintf(9, "dav_roomname: %s\n", dav_roomname);
-       lprintf(9, "     dav_uid: %s\n", dav_uid);
-       */
+        * If the room name is blank, the client is requesting a
+        * folder list.
+        */
+       SubRooms = NewHash(1, Flathash);
+       State = GotoRestRoom(SubRooms);
+       if (((State & REST_IN_ROOM) == 0) ||
+           (((State & (REST_GOT_LOCAL_PART)) == 0) &&
+            (WCC->Hdr->HR.dav_depth == 0)))
+       {
+               now = time(NULL);
+               http_datestring(datestring, sizeof datestring, now);
+
+               /*
+                * Be rude.  Completely ignore the XML request and simply send them
+                * everything we know about.  Let the client sort it out.
+                */
+               hprintf("HTTP/1.0 207 Multi-Status\r\n");
+               groupdav_common_headers();
+               hprintf("Date: %s\r\n", datestring);
+               hprintf("Content-type: text/xml\r\n");
+               if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
+                       hprintf("Content-encoding: identity\r\n");
+
+               begin_burst();
+
+
+               /*
+                * If the client is requesting the root, show a root node.
+                */
+               do_template("dav_propfind_top", NULL);
+               end_burst();
+               FreeStrBuf(&dav_roomname);
+               FreeStrBuf(&dav_uid);
+               FreeHashList(&SubRooms);
+               return;
+       }
+
+       if ((State & (REST_GOT_LOCAL_PART)) == 0) {
+               readloop(headers, eReadEUIDS);
+               FreeHashList(&SubRooms);
+               return;
+
+       }
+
+
+       
+       FreeHashList(&SubRooms);
+
+#endif
 
        /*
         * If the room name is blank, the client is requesting a
         * folder list.
         */
-       if (strlen(dav_roomname) == 0) {
-               groupdav_folder_list();
+       if (StrLength(dav_roomname) == 0) {
+               groupdav_collection_list();
+               FreeStrBuf(&dav_roomname);
+               FreeStrBuf(&dav_uid);
                return;
        }
 
        /* Go to the correct room. */
-       if (strcasecmp(WC->wc_roomname, dav_roomname)) {
+       if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
                gotoroom(dav_roomname);
        }
-       if (strcasecmp(WC->wc_roomname, dav_roomname)) {
-               wprintf("HTTP/1.1 404 not found\r\n");
+       if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
+               hprintf("HTTP/1.1 404 not found\r\n");
                groupdav_common_headers();
-               wprintf("Date: %s\r\n", datestring);
-               wprintf(
-                       "Content-Type: text/plain\r\n"
-                       "\r\n"
-                       "There is no folder called \"%s\" on this server.\r\n",
-                       dav_roomname
+               hprintf("Date: %s\r\n", datestring);
+               hprintf("Content-Type: text/plain\r\n");
+               wc_printf("There is no folder called \"%s\" on this server.\r\n",
+                       ChrPtr(dav_roomname)
                );
+               end_burst();
+               FreeStrBuf(&dav_roomname);
+               FreeStrBuf(&dav_uid);
                return;
        }
 
        /* If dav_uid is non-empty, client is requesting a PROPFIND on
         * a specific item in the room.  This is not valid GroupDAV, but
-        * we try to honor it anyway because some clients are expecting
-        * it to work...
+        * it is valid WebDAV.
         */
-       if (strlen(dav_uid) > 0) {
+       if (StrLength(dav_uid) != 0) {
 
-               dav_msgnum = locate_message_by_uid(dav_uid);
+               dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
                if (dav_msgnum < 0) {
-                       wprintf("HTTP/1.1 404 not found\r\n");
+                       hprintf("HTTP/1.1 404 not found\r\n");
                        groupdav_common_headers();
-                       wprintf(
-                               "Content-Type: text/plain\r\n"
-                               "\r\n"
-                               "Object \"%s\" was not found in the \"%s\" folder.\r\n",
-                               dav_uid,
-                               dav_roomname
+                       hprintf("Content-Type: text/plain\r\n");
+                       wc_printf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
+                               ChrPtr(dav_uid),
+                               ChrPtr(dav_roomname)
                        );
+                       end_burst();
+                       FreeStrBuf(&dav_roomname);
+                       FreeStrBuf(&dav_uid);
                        return;
                }
 
@@ -215,41 +569,47 @@ void groupdav_propfind(char *dav_pathname) {
                 * everything we know about (which is going to simply be the ETag and
                 * nothing else).  Let the client-side parser sort it out.
                 */
-               wprintf("HTTP/1.1 207 Multi-Status\r\n");
+               hprintf("HTTP/1.0 207 Multi-Status\r\n");
                groupdav_common_headers();
-               wprintf("Date: %s\r\n", datestring);
-               wprintf("Content-type: text/xml\r\n");
-               wprintf("Content-encoding: identity\r\n");
+               hprintf("Date: %s\r\n", datestring);
+               hprintf("Content-type: text/xml\r\n");
+               if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
+                       hprintf("Content-encoding: identity\r\n");
        
                begin_burst();
        
-               wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-                       "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
+               wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                       "<multistatus xmlns=\"DAV:\">"
                );
 
-               wprintf("<D:response>");
-
-               wprintf("<D:href>");
-               if (strlen(WC->http_host) > 0) {
-                       wprintf("%s://%s",
-                               (is_https ? "https" : "http"),
-                               WC->http_host);
-               }
-               wprintf("/groupdav/");
-               urlescputs(WC->wc_roomname);
-               euid_escapize(encoded_uid, dav_uid);
-               wprintf("/%s", encoded_uid);
-               wprintf("</D:href>");
-               wprintf("<D:propstat>");
-               wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
-               wprintf("<D:prop><D:getetag>\"%ld\"</D:getetag></D:prop>", dav_msgnum);
-               wprintf("</D:propstat>");
-
-               wprintf("</D:response>\n");
-               wprintf("</D:multistatus>\n");
+               wc_printf("<response>");
+               
+               wc_printf("<href>");
+               groupdav_identify_host();
+               wc_printf("/groupdav/");
+               urlescputs(ChrPtr(WCC->CurRoom.name));
+               euid_escapize(encoded_uid, ChrPtr(dav_uid));
+               wc_printf("/%s", encoded_uid);
+               wc_printf("</href>");
+               wc_printf("<propstat>");
+               wc_printf("<status>HTTP/1.1 200 OK</status>");
+               wc_printf("<prop>");
+               wc_printf("<getetag>\"%ld\"</getetag>", dav_msgnum);
+               wc_printf("<getlastmodified>");
+               escputs(datestring);
+               wc_printf("</getlastmodified>");
+               wc_printf("</prop>");
+               wc_printf("</propstat>");
+
+               wc_printf("</response>\n");
+               wc_printf("</multistatus>\n");
                end_burst();
+               FreeStrBuf(&dav_roomname);
+               FreeStrBuf(&dav_uid);
                return;
        }
+       FreeStrBuf(&dav_roomname);
+       FreeStrBuf(&dav_uid);
 
 
        /*
@@ -260,61 +620,195 @@ void groupdav_propfind(char *dav_pathname) {
         * everything we know about (which is going to simply be the ETag and
         * nothing else).  Let the client-side parser sort it out.
         */
-       wprintf("HTTP/1.1 207 Multi-Status\r\n");
+       hprintf("HTTP/1.0 207 Multi-Status\r\n");
        groupdav_common_headers();
-       wprintf("Date: %s\r\n", datestring);
-       wprintf("Content-type: text/xml\r\n");
-       wprintf("Content-encoding: identity\r\n");
+       hprintf("Date: %s\r\n", datestring);
+       hprintf("Content-type: text/xml\r\n");
+       if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
+               hprintf("Content-encoding: identity\r\n");
 
        begin_burst();
 
-       wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
-               "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
+       wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+               "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
        );
 
-       serv_puts("MSGS ALL");
-       serv_getln(buf, sizeof buf);
-       if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
-               msgs = realloc(msgs, ++num_msgs * sizeof(long));
-               msgs[num_msgs-1] = atol(msgnum);
+
+       /* Transmit the collection resource (FIXME check depth and starting point) */
+       wc_printf("<response>");
+
+       wc_printf("<href>");
+       groupdav_identify_host();
+       wc_printf("/groupdav/");
+       urlescputs(ChrPtr(WCC->CurRoom.name));
+       wc_printf("</href>");
+
+       wc_printf("<propstat>");
+       wc_printf("<status>HTTP/1.1 200 OK</status>");
+       wc_printf("<prop>");
+       wc_printf("<displayname>");
+       escputs(ChrPtr(WCC->CurRoom.name));
+       wc_printf("</displayname>");
+       wc_printf("<resourcetype><collection/>");
+
+       switch(WCC->CurRoom.defview) {
+               case VIEW_CALENDAR:
+                       wc_printf("<G:vevent-collection />");
+                       break;
+               case VIEW_TASKS:
+                       wc_printf("<G:vtodo-collection />");
+                       break;
+               case VIEW_ADDRESSBOOK:
+                       wc_printf("<G:vcard-collection />");
+                       break;
        }
 
+       wc_printf("</resourcetype>");
+       /* FIXME get the mtime
+       wc_printf("<getlastmodified>");
+               escputs(datestring);
+       wc_printf("</getlastmodified>");
+       */
+       wc_printf("</prop>");
+       wc_printf("</propstat>");
+       wc_printf("</response>");
+
+       /* Transmit the collection listing (FIXME check depth and starting point) */
+
+       MsgNum = NewStrBuf();
+       serv_puts("MSGS ALL");
+
+       StrBuf_ServGetln(MsgNum);
+       if (GetServerStatus(MsgNum, NULL) == 1)
+               while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))  {
+                       msgs = realloc(msgs, ++num_msgs * sizeof(long));
+                       msgs[num_msgs-1] = StrTol(MsgNum);
+               }
+
        if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
 
                strcpy(uid, "");
+               now = (-1);
                serv_printf("MSG0 %ld|3", msgs[i]);
-               serv_getln(buf, sizeof buf);
-               if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
-                       if (!strncasecmp(buf, "exti=", 5)) {
-                               strcpy(uid, &buf[5]);
+               StrBuf_ServGetln(MsgNum);
+               if (GetServerStatus(MsgNum, NULL) == 1)
+                       while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) 
+                       {
+                               if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
+                                       strcpy(uid, &ChrPtr(MsgNum)[5]);
+                               }
+                               else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
+                                       now = atol(&ChrPtr(MsgNum)[5]);
                        }
                }
 
-               if (strlen(uid) > 0) {
-                       wprintf("<D:response>");
-                       wprintf("<D:href>");
-                       if (strlen(WC->http_host) > 0) {
-                               wprintf("%s://%s",
-                                       (is_https ? "https" : "http"),
-                                       WC->http_host);
-                       }
-                       wprintf("/groupdav/");
-                       urlescputs(WC->wc_roomname);
-                       euid_escapize(encoded_uid, uid);
-                       wprintf("/%s", encoded_uid);
-                       wprintf("</D:href>");
-                       wprintf("<D:propstat>");
-                       wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
-                       wprintf("<D:prop><D:getetag>\"%ld\"</D:getetag></D:prop>", msgs[i]);
-                       wprintf("</D:propstat>");
-                       wprintf("</D:response>");
+               if (!IsEmptyStr(uid)) {
+                       wc_printf("<response>");
+                               wc_printf("<href>");
+                                       groupdav_identify_host();
+                                       wc_printf("/groupdav/");
+                                       urlescputs(ChrPtr(WCC->CurRoom.name));
+                                       euid_escapize(encoded_uid, uid);
+                                       wc_printf("/%s", encoded_uid);
+                               wc_printf("</href>");
+                               switch(WCC->CurRoom.defview) {
+                               case VIEW_CALENDAR:
+                                       wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
+                                       break;
+                               case VIEW_TASKS:
+                                       wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
+                                       break;
+                               case VIEW_ADDRESSBOOK:
+                                       wc_printf("<getcontenttype>text/x-vcard</getcontenttype>");
+                                       break;
+                               }
+                               wc_printf("<propstat>");
+                                       wc_printf("<status>HTTP/1.1 200 OK</status>");
+                                       wc_printf("<prop>");
+                                               wc_printf("<getetag>\"%ld\"</getetag>", msgs[i]);
+                                       if (now > 0L) {
+                                               http_datestring(datestring, sizeof datestring, now);
+                                               wc_printf("<getlastmodified>");
+                                               escputs(datestring);
+                                               wc_printf("</getlastmodified>");
+                                       }
+                                       wc_printf("</prop>");
+                               wc_printf("</propstat>");
+                       wc_printf("</response>");
                }
        }
+       FreeStrBuf(&MsgNum);
 
-       wprintf("</D:multistatus>\n");
+       wc_printf("</multistatus>\n");
        end_burst();
 
        if (msgs != NULL) {
                free(msgs);
        }
 }
+
+
+
+int ParseMessageListHeaders_EUID(StrBuf *Line, 
+                                const char **pos, 
+                                message_summary *Msg, 
+                                StrBuf *ConversionBuffer)
+{
+       Msg->euid = NewStrBuf();
+       StrBufExtract_NextToken(Msg->euid,  Line, pos, '|');
+       Msg->date = StrBufExtractNext_long(Line, pos, '|');
+       
+       return StrLength(Msg->euid) > 0;
+}
+
+int DavUIDL_GetParamsGetServerCall(SharedMessageStatus *Stat, 
+                                   void **ViewSpecific, 
+                                   long oper, 
+                                   char *cmd, 
+                                   long len)
+{
+       Stat->defaultsortorder = 0;
+       Stat->sortit = 0;
+       Stat->load_seen = 0;
+       Stat->maxmsgs  = 9999999;
+
+       snprintf(cmd, len, "MSGS ALL|||2");
+       return 200;
+}
+
+int DavUIDL_RenderView_or_Tail(SharedMessageStatus *Stat, 
+                               void **ViewSpecific, 
+                               long oper)
+{
+       
+       DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
+       
+       return 0;
+}
+
+int DavUIDL_Cleanup(void **ViewSpecific)
+{
+       /* Note: wDumpContent() will output one additional </div> tag. */
+       /* We ought to move this out into template */
+       wDumpContent(1);
+
+       return 0;
+}
+
+
+
+
+void 
+InitModule_PROPFIND
+(void)
+{
+       RegisterReadLoopHandlerset(
+               eReadEUIDS,
+               DavUIDL_GetParamsGetServerCall,
+               NULL, /// TODO: is this right?
+               ParseMessageListHeaders_EUID,
+               NULL, //// ""
+               DavUIDL_RenderView_or_Tail,
+               DavUIDL_Cleanup);
+
+}