Fix default landing mechanism
[citadel.git] / webcit / dav_propfind.c
1 /*
2  * Handles GroupDAV and CalDAV PROPFIND requests.
3  *
4  * A few notes about our XML output:
5  *
6  * --> Yes, we are spewing tags directly instead of using an XML library.
7  *     Whining about it will be summarily ignored.
8  *
9  * --> XML is deliberately output with no whitespace/newlines between tags.
10  *     This makes it difficult to read, but we have discovered clients which
11  *     crash when you try to pretty it up.
12  *
13  * References:
14  * http://www.ietf.org/rfc/rfc4791.txt
15  * http://blogs.nologin.es/rickyepoderi/index.php?/archives/14-Introducing-CalDAV-Part-I.html
16
17 Sample query:
18
19 PROPFIND /groupdav/calendar/ HTTP/1.1
20 Content-type: text/xml; charset=utf-8
21 Content-length: 166
22
23 <?xml version="1.0" encoding="UTF-8"?>
24 <D:propfind xmlns:D="DAV:">
25   <D:prop>
26     <D:getcontenttype/>
27     <D:resourcetype/>
28     <D:getetag/>
29   </D:prop>
30 </D:propfind>
31
32  *
33  * Copyright (c) 2005-2012 by the citadel.org team
34  *
35  * This program is open source software; you can redistribute it and/or
36  * modify it under the terms of the GNU General Public License version 3.
37  *
38  * This program is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  * GNU General Public License for more details.
42  */
43
44 #include "webcit.h"
45 #include "webserver.h"
46 #include "dav.h"
47
48 /*
49  * Given an encoded UID, translate that to an unencoded Citadel EUID and
50  * then search for it in the current room.  Return a message number or -1
51  * if not found.
52  *
53  */
54 long locate_message_by_uid(const char *uid) {
55         char buf[256];
56         char decoded_uid[1024];
57         long retval = (-1L);
58
59         /* decode the UID */
60         euid_unescapize(decoded_uid, uid);
61
62         /* ask Citadel if we have this one */
63         serv_printf("EUID %s", decoded_uid);
64         serv_getln(buf, sizeof buf);
65         if (buf[0] == '2') {
66                 retval = atol(&buf[4]);
67         }
68
69         return(retval);
70 }
71
72
73 /*
74  * IgnoreFloor: set to 0 or 1 _nothing else_
75  * Subfolders: direct child floors will be put here.
76  */
77 const folder *GetRESTFolder(int IgnoreFloor, HashList *Subfolders)
78 {
79         wcsession  *WCC = WC;
80         void *vFolder;
81         const folder *ThisFolder = NULL;
82         const folder *FoundFolder = NULL;
83         const folder *BestGuess = NULL;
84         int nBestGuess = 0;
85         HashPos    *itd, *itfl;
86         StrBuf     * Dir;
87         void       *vDir;
88         long        len;
89         const char *Key;
90         int iRoom, jURL, urlp;
91         int delta;
92
93 /*
94  * Guess room: if the full URL matches a room, list thats it. We also need to remember direct sub rooms.
95  * if the URL is longer, we need to find the "best guess" so we can find the room we're in, and the rest
96  * of the URL will be uids and so on.
97  */
98         itfl = GetNewHashPos(WCC->Floors, 0);
99         urlp = GetCount(WCC->Directory);
100
101         while (GetNextHashPos(WCC->Floors, itfl, &len, &Key, &vFolder) && 
102                (ThisFolder == NULL))
103         {
104                 ThisFolder = vFolder;
105                 if (!IgnoreFloor && /* so we can handle legacy URLS... */
106                     (ThisFolder->Floor != WCC->CurrentFloor))
107                         continue;
108
109                 if (ThisFolder->nRoomNameParts > 1) 
110                 {
111                         /*TODO: is that number all right? */
112 //                      if (urlp - ThisFolder->nRoomNameParts != 2) {
113 //                              if (BestGuess != NULL)
114 //                                      continue;
115 //ThisFolder->name
116 //                              itd  = GetNewHashPos(WCC->Directory, 0);
117 //                              GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
118 //                      }
119                         itd  = GetNewHashPos(WCC->Directory, 0);
120                         GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
121         
122                         for (iRoom = 0, /* Fast forward the floorname as we checked it above: */ jURL = IgnoreFloor; 
123
124                              (iRoom <= ThisFolder->nRoomNameParts) && (jURL <= urlp); 
125
126                              iRoom++, jURL++, GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir))
127                         {
128                                 Dir = (StrBuf*)vDir;
129                                 if (strcmp(ChrPtr(ThisFolder->RoomNameParts[iRoom]), 
130                                            ChrPtr(Dir)) != 0)
131                                 {
132                                         DeleteHashPos(&itd);
133                                         continue;
134                                 }
135                         }
136                         DeleteHashPos(&itd);
137                         /* Gotcha? */
138                         if ((iRoom == ThisFolder->nRoomNameParts) && (jURL == urlp))
139                         {
140                                 FoundFolder = ThisFolder;
141                         }
142                         /* URL got more parts then this room, so we remember it for the best guess*/
143                         else if ((jURL <= urlp) &&
144                                  (ThisFolder->nRoomNameParts <= nBestGuess))
145                         {
146                                 BestGuess = ThisFolder;
147                                 nBestGuess = jURL - 1;
148                         }
149                         /* Room has more parts than the URL, it might be a sub-room? */
150                         else if (iRoom <ThisFolder->nRoomNameParts) 
151                         {//// TODO: ThisFolder->nRoomNameParts == urlp - IgnoreFloor???
152                                 Put(Subfolders, SKEY(ThisFolder->name), 
153                                     /* Cast away const, its a reference. */
154                                     (void*)ThisFolder, reference_free_handler);
155                         }
156                 }
157                 else {
158                         delta = GetCount(WCC->Directory) - ThisFolder->nRoomNameParts;
159                         if ((delta != 2) && (nBestGuess > 1))
160                             continue;
161                         
162                         itd  = GetNewHashPos(WCC->Directory, 0);
163                                                 
164                         if (!GetNextHashPos(WCC->Directory, 
165                                             itd, &len, &Key, &vDir) ||
166                             (vDir == NULL))
167                         {
168                                 DeleteHashPos(&itd);
169                                 
170                                 syslog(LOG_DEBUG, "5\n");
171                                 continue;
172                         }
173                         DeleteHashPos(&itd);
174                         Dir = (StrBuf*) vDir;
175                         if (strcmp(ChrPtr(ThisFolder->name), 
176                                                ChrPtr(Dir))
177                             != 0)
178                         {
179                                 DeleteHashPos(&itd);
180                                 
181                                 syslog(LOG_DEBUG, "5\n");
182                                 continue;
183                         }
184                         DeleteHashPos(&itfl);
185                         DeleteHashPos(&itd);
186                         if (delta != 2) {
187                                 nBestGuess = 1;
188                                 BestGuess = ThisFolder;
189                         }
190                         else 
191                                 FoundFolder = ThisFolder;
192                 }
193         }
194
195 /* TODO: Subfolders: remove patterns not matching the best guess or thisfolder */
196         DeleteHashPos(&itfl);
197         if (FoundFolder != NULL)
198                 return FoundFolder;
199         else
200                 return BestGuess;
201 }
202
203
204
205
206 long GotoRestRoom(HashList *SubRooms)
207 {
208         int IgnoreFloor = 0; /* deprecated... */
209         wcsession *WCC = WC;
210         long Count;
211         long State;
212         const folder *ThisFolder;
213
214         State = REST_TOPLEVEL;
215
216         if (WCC->Hdr->HR.Handler != NULL) 
217                 State |= REST_IN_NAMESPACE;
218
219         Count = GetCount(WCC->Directory);
220         
221         if (Count == 0) return State;
222
223         if (Count >= 1) State |=REST_IN_FLOOR;
224         if (Count == 1) return State;
225         
226         /* 
227          * More than 3 params and no floor found? 
228          * -> fall back to old non-floored notation
229          */
230         if ((Count >= 3) && (WCC->CurrentFloor == NULL))
231                 IgnoreFloor = 1;
232         if (Count >= 3)
233         {
234                 IgnoreFloor = 0;
235                 State |= REST_IN_FLOOR;
236
237                 ThisFolder = GetRESTFolder(IgnoreFloor, SubRooms);
238                 if (ThisFolder != NULL)
239                 {
240                         if (WCC->ThisRoom != NULL)
241                                 if (CompareRooms(WCC->ThisRoom, ThisFolder) != 0)
242                                         gotoroom(ThisFolder->name);
243                         State |= REST_IN_ROOM;
244                         
245                 }
246                 if (GetCount(SubRooms) > 0)
247                         State |= REST_HAVE_SUB_ROOMS;
248         }
249         if ((WCC->ThisRoom != NULL) && 
250             (Count + IgnoreFloor > 3))
251         {
252                 if (WCC->Hdr->HR.Handler->RID(ExistsID, IgnoreFloor))
253                 {
254                         State |= REST_GOT_LOCAL_PART;
255                 }
256                 else {
257                         /// WHOOPS, not there???
258                         State |= REST_NONEXIST;
259                 }
260
261
262         }
263         return State;
264 }
265
266
267
268 /*
269  * List rooms (or "collections" in DAV terminology) which contain
270  * interesting groupware objects.
271  */
272 void dav_collection_list(void)
273 {
274         wcsession *WCC = WC;
275         char buf[256];
276         char roomname[256];
277         int view;
278         char datestring[256];
279         time_t now;
280         time_t mtime;
281         int is_groupware_collection = 0;
282         int starting_point = 1;         /**< 0 for /, 1 for /groupdav/ */
283
284         if (WCC->Hdr->HR.Handler == NULL) {
285                 starting_point = 0;
286         }
287         else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
288                 starting_point = 1;
289         }
290         else {
291                 starting_point = 2;
292         }
293
294         now = time(NULL);
295         http_datestring(datestring, sizeof datestring, now);
296
297         /*
298          * Be rude.  Completely ignore the XML request and simply send them
299          * everything we know about.  Let the client sort it out.
300          */
301         hprintf("HTTP/1.0 207 Multi-Status\r\n");
302         dav_common_headers();
303         hprintf("Date: %s\r\n", datestring);
304         hprintf("Content-type: text/xml\r\n");
305         if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
306                 hprintf("Content-encoding: identity\r\n");
307
308         begin_burst();
309
310         wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
311                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
312         );
313
314         /*
315          * If the client is requesting the root, show a root node.
316          */
317         if (starting_point == 0) {
318                 wc_printf("<response>");
319                         wc_printf("<href>");
320                                 dav_identify_host();
321                                 wc_printf("/");
322                         wc_printf("</href>");
323                         wc_printf("<propstat>");
324                                 wc_printf("<status>HTTP/1.1 200 OK</status>");
325                                 wc_printf("<prop>");
326                                         wc_printf("<displayname>/</displayname>");
327                                         wc_printf("<resourcetype><collection/></resourcetype>");
328                                         wc_printf("<getlastmodified>");
329                                                 escputs(datestring);
330                                         wc_printf("</getlastmodified>");
331                                 wc_printf("</prop>");
332                         wc_printf("</propstat>");
333                 wc_printf("</response>");
334         }
335
336         /*
337          * If the client is requesting "/groupdav", show a /groupdav subdirectory.
338          */
339         if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
340                 wc_printf("<response>");
341                         wc_printf("<href>");
342                                 dav_identify_host();
343                                 wc_printf("/groupdav");
344                         wc_printf("</href>");
345                         wc_printf("<propstat>");
346                                 wc_printf("<status>HTTP/1.1 200 OK</status>");
347                                 wc_printf("<prop>");
348                                         wc_printf("<displayname>GroupDAV</displayname>");
349                                         wc_printf("<resourcetype><collection/></resourcetype>");
350                                         wc_printf("<getlastmodified>");
351                                                 escputs(datestring);
352                                         wc_printf("</getlastmodified>");
353                                 wc_printf("</prop>");
354                         wc_printf("</propstat>");
355                 wc_printf("</response>");
356         }
357
358         /*
359          * Now go through the list and make it look like a DAV collection
360          */
361         serv_puts("LKRA");
362         serv_getln(buf, sizeof buf);
363         if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
364
365                 extract_token(roomname, buf, 0, '|', sizeof roomname);
366                 view = extract_int(buf, 7);
367                 mtime = extract_long(buf, 8);
368                 http_datestring(datestring, sizeof datestring, mtime);
369
370                 /*
371                  * For now, only list rooms that we know a GroupDAV client
372                  * might be interested in.  In the future we may add
373                  * the rest.
374                  *
375                  * We determine the type of objects which are stored in each
376                  * room by looking at the *default* view for the room.  This
377                  * allows, for example, a Calendar room to appear as a
378                  * GroupDAV calendar even if the user has switched it to a
379                  * Calendar List view.
380                  */
381                 if (    (view == VIEW_CALENDAR) || 
382                         (view == VIEW_TASKS) || 
383                         (view == VIEW_ADDRESSBOOK) ||
384                         (view == VIEW_NOTES) ||
385                         (view == VIEW_JOURNAL) ||
386                         (view == VIEW_WIKI) ||
387                         (view == VIEW_WIKIMD)
388                 ) {
389                         is_groupware_collection = 1;
390                 }
391                 else {
392                         is_groupware_collection = 0;
393                 }
394
395                 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
396                         wc_printf("<response>");
397
398                         wc_printf("<href>");
399                         dav_identify_host();
400                         wc_printf("/groupdav/");
401                         urlescputs(roomname);
402                         wc_printf("/</href>");
403
404                         wc_printf("<propstat>");
405                         wc_printf("<status>HTTP/1.1 200 OK</status>");
406                         wc_printf("<prop>");
407                         wc_printf("<displayname>");
408                         escputs(roomname);
409                         wc_printf("</displayname>");
410                         wc_printf("<resourcetype><collection/>");
411
412                         switch(view) {
413                         case VIEW_CALENDAR:
414                                 wc_printf("<G:vevent-collection />");
415                                 break;
416                         case VIEW_TASKS:
417                                 wc_printf("<G:vtodo-collection />");
418                                 break;
419                         case VIEW_ADDRESSBOOK:
420                                 wc_printf("<G:vcard-collection />");
421                                 break;
422                         case VIEW_NOTES:
423                                 wc_printf("<G:vnotes-collection />");
424                                 break;
425                         case VIEW_JOURNAL:
426                                 wc_printf("<G:vjournal-collection />");
427                                 break;
428                         case VIEW_WIKI:
429                         case VIEW_WIKIMD:
430                                 wc_printf("<G:wiki-collection />");
431                                 break;
432                         }
433
434                         wc_printf("</resourcetype>");
435                         wc_printf("<getlastmodified>");
436                                 escputs(datestring);
437                         wc_printf("</getlastmodified>");
438                         wc_printf("</prop>");
439                         wc_printf("</propstat>");
440                         wc_printf("</response>");
441                 }
442         }
443         wc_printf("</multistatus>\n");
444
445         end_burst();
446 }
447
448
449 void propfind_xml_start(void *data, const char *supplied_el, const char **attr) {
450         // syslog(LOG_DEBUG, "<%s>", supplied_el);
451 }
452
453 void propfind_xml_end(void *data, const char *supplied_el) {
454         // syslog(LOG_DEBUG, "</%s>", supplied_el);
455 }
456
457
458
459 /*
460  * The pathname is always going to be /groupdav/room_name/msg_num
461  */
462 void dav_propfind(void) 
463 {
464         wcsession *WCC = WC;
465         StrBuf *dav_roomname;
466         StrBuf *dav_uid;
467         StrBuf *MsgNum;
468         long BufLen;
469         long dav_msgnum = (-1);
470         char uid[256];
471         char encoded_uid[256];
472         long *msgs = NULL;
473         int num_msgs = 0;
474         int i;
475         char datestring[256];
476         time_t now;
477
478         now = time(NULL);
479         http_datestring(datestring, sizeof datestring, now);
480
481         int parse_success = 0;
482         XML_Parser xp = XML_ParserCreateNS(NULL, '|');
483         if (xp) {
484                 // XML_SetUserData(xp, XXX);
485                 XML_SetElementHandler(xp, propfind_xml_start, propfind_xml_end);
486                 // XML_SetCharacterDataHandler(xp, xrds_xml_chardata);
487
488                 const char *req = ChrPtr(WCC->upload);
489                 if (req) {
490                         req = strchr(req, '<');                 /* hunt for the first tag */
491                 }
492                 if (!req) {
493                         req = "ERROR";                          /* force it to barf */
494                 }
495
496                 i = XML_Parse(xp, req, strlen(req), 1);
497                 if (!i) {
498                         syslog(LOG_DEBUG, "XML_Parse() failed: %s", XML_ErrorString(XML_GetErrorCode(xp)));
499                         XML_ParserFree(xp);
500                         parse_success = 0;
501                 }
502                 else {
503                         parse_success = 1;
504                 }
505         }
506
507         if (!parse_success) {
508                 hprintf("HTTP/1.1 500 Internal Server Error\r\n");
509                 dav_common_headers();
510                 hprintf("Date: %s\r\n", datestring);
511                 hprintf("Content-Type: text/plain\r\n");
512                 wc_printf("An internal error has occurred at %s:%d.\r\n", __FILE__ , __LINE__ );
513                 end_burst();
514                 return;
515         }
516
517         dav_roomname = NewStrBuf();
518         dav_uid = NewStrBuf();
519         StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
520         StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
521
522         syslog(LOG_DEBUG, "PROPFIND requested for '%s' at depth %d",
523                 ChrPtr(dav_roomname), WCC->Hdr->HR.dav_depth
524         );
525
526         /*
527          * If the room name is blank, the client is requesting a folder list.
528          */
529         if (StrLength(dav_roomname) == 0) {
530                 dav_collection_list();
531                 FreeStrBuf(&dav_roomname);
532                 FreeStrBuf(&dav_uid);
533                 return;
534         }
535
536         /* Go to the correct room. */
537         if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
538                 gotoroom(dav_roomname);
539         }
540         if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
541                 hprintf("HTTP/1.1 404 not found\r\n");
542                 dav_common_headers();
543                 hprintf("Date: %s\r\n", datestring);
544                 hprintf("Content-Type: text/plain\r\n");
545                 wc_printf("There is no folder called \"%s\" on this server.\r\n", ChrPtr(dav_roomname));
546                 end_burst();
547                 FreeStrBuf(&dav_roomname);
548                 FreeStrBuf(&dav_uid);
549                 return;
550         }
551
552         /* If dav_uid is non-empty, client is requesting a PROPFIND on
553          * a specific item in the room.  This is not valid GroupDAV, but
554          * it is valid WebDAV (and probably CalDAV too).
555          */
556         if (StrLength(dav_uid) != 0) {
557
558                 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
559                 if (dav_msgnum < 0) {
560                         hprintf("HTTP/1.1 404 not found\r\n");
561                         dav_common_headers();
562                         hprintf("Content-Type: text/plain\r\n");
563                         wc_printf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
564                                 ChrPtr(dav_uid),
565                                 ChrPtr(dav_roomname)
566                         );
567                         end_burst();
568                         FreeStrBuf(&dav_roomname);
569                         FreeStrBuf(&dav_uid);
570                         return;
571                 }
572
573                 /* Be rude.  Completely ignore the XML request and simply send them
574                  * everything we know about (which is going to simply be the ETag and
575                  * nothing else).  Let the client-side parser sort it out.
576                  */
577                 hprintf("HTTP/1.0 207 Multi-Status\r\n");
578                 dav_common_headers();
579                 hprintf("Date: %s\r\n", datestring);
580                 hprintf("Content-type: text/xml\r\n");
581                 if (DisableGzip || (!WCC->Hdr->HR.gzip_ok))     
582                         hprintf("Content-encoding: identity\r\n");
583         
584                 begin_burst();
585         
586                 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
587                         "<multistatus xmlns=\"DAV:\">"
588                 );
589
590                 wc_printf("<response>");
591                 
592                 wc_printf("<href>");
593                 dav_identify_host();
594                 wc_printf("/groupdav/");
595                 urlescputs(ChrPtr(WCC->CurRoom.name));
596                 euid_escapize(encoded_uid, ChrPtr(dav_uid));
597                 wc_printf("/%s", encoded_uid);
598                 wc_printf("</href>");
599                 wc_printf("<propstat>");
600                 wc_printf("<status>HTTP/1.1 200 OK</status>");
601                 wc_printf("<prop>");
602                 wc_printf("<getetag>\"%ld\"</getetag>", dav_msgnum);
603                 wc_printf("<getlastmodified>");
604                 escputs(datestring);
605                 wc_printf("</getlastmodified>");
606                 wc_printf("</prop>");
607                 wc_printf("</propstat>");
608
609                 wc_printf("</response>\n");
610                 wc_printf("</multistatus>\n");
611                 end_burst();
612                 FreeStrBuf(&dav_roomname);
613                 FreeStrBuf(&dav_uid);
614                 return;
615         }
616         FreeStrBuf(&dav_roomname);
617         FreeStrBuf(&dav_uid);
618
619
620         /*
621          * If we get to this point the client is performing a PROPFIND on the room itself.
622          *
623          * We call it a room; DAV calls it a "collection."  We have to give it some properties
624          * of the room itself and then offer a list of all items contained therein.
625          *
626          * Be rude.  Completely ignore the XML request and simply send them
627          * everything we know about (which is going to simply be the ETag and
628          * nothing else).  Let the client-side parser sort it out.
629          */
630         //syslog(LOG_DEBUG, "BE RUDE AND IGNORE: \033[31m%s\033[0m", ChrPtr(WC->upload) );
631         hprintf("HTTP/1.0 207 Multi-Status\r\n");
632         dav_common_headers();
633         hprintf("Date: %s\r\n", datestring);
634         hprintf("Content-type: text/xml\r\n");
635         if (DisableGzip || (!WCC->Hdr->HR.gzip_ok)) {
636                 hprintf("Content-encoding: identity\r\n");
637         }
638         begin_burst();
639
640         wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
641                 "<D:multistatus "
642                         "xmlns:D=\"DAV:\" "
643                         "xmlns:G=\"http://groupdav.org/\" "
644                         "xmlns:C=\"urn:ietf:params:xml:ns:caldav\""
645                 ">"
646         );
647
648         /* Transmit the collection resource */
649         wc_printf("<D:response>");
650
651         wc_printf("<D:href>");
652         dav_identify_host();
653         wc_printf("/groupdav/");
654         urlescputs(ChrPtr(WCC->CurRoom.name));
655         wc_printf("</D:href>");
656
657         wc_printf("<D:propstat>");
658         wc_printf("<D:status>HTTP/1.1 200 OK</D:status>");
659         wc_printf("<D:prop>");
660         wc_printf("<D:displayname>");
661         escputs(ChrPtr(WCC->CurRoom.name));
662         wc_printf("</D:displayname>");
663
664         wc_printf("<D:owner/>");                /* empty owner ought to be legal; see rfc3744 section 5.1 */
665
666         wc_printf("<D:resourcetype><D:collection/>");
667         switch(WCC->CurRoom.defview) {
668                 case VIEW_CALENDAR:
669                         wc_printf("<G:vevent-collection />");
670                         wc_printf("<C:calendar />");
671                         break;
672                 case VIEW_TASKS:
673                         wc_printf("<G:vtodo-collection />");
674                         break;
675                 case VIEW_ADDRESSBOOK:
676                         wc_printf("<G:vcard-collection />");
677                         break;
678         }
679         wc_printf("</D:resourcetype>");
680
681         /* FIXME get the mtime
682         wc_printf("<D:getlastmodified>");
683                 escputs(datestring);
684         wc_printf("</D:getlastmodified>");
685         */
686         wc_printf("</D:prop>");
687         wc_printf("</D:propstat>");
688         wc_printf("</D:response>");
689
690         /* If a depth greater than zero was specified, transmit the collection listing */
691
692         if (WCC->Hdr->HR.dav_depth > 0) {
693                 MsgNum = NewStrBuf();
694                 serv_puts("MSGS ALL");
695         
696                 StrBuf_ServGetln(MsgNum);
697                 if (GetServerStatus(MsgNum, NULL) == 1)
698                         while (BufLen = StrBuf_ServGetln(MsgNum), 
699                         ((BufLen >= 0) && 
700                                 ((BufLen != 3) || strcmp(ChrPtr(MsgNum), "000"))  ))
701                         {
702                                 msgs = realloc(msgs, ++num_msgs * sizeof(long));
703                                 msgs[num_msgs-1] = StrTol(MsgNum);
704                         }
705         
706                 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
707         
708                         syslog(LOG_DEBUG, "PROPFIND enumerating message # %ld", msgs[i]);
709                         strcpy(uid, "");
710                         now = (-1);
711                         serv_printf("MSG0 %ld|3", msgs[i]);
712                         StrBuf_ServGetln(MsgNum);
713                         if (GetServerStatus(MsgNum, NULL) == 1)
714                                 while (BufLen = StrBuf_ServGetln(MsgNum), 
715                                 ((BufLen >= 0) && 
716                                         ((BufLen != 3) || strcmp(ChrPtr(MsgNum), "000")) ))
717                                 {
718                                         if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
719                                                 strcpy(uid, &ChrPtr(MsgNum)[5]);
720                                         }
721                                         else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
722                                                 now = atol(&ChrPtr(MsgNum)[5]);
723                                 }
724                         }
725         
726                         if (!IsEmptyStr(uid)) {
727                                 wc_printf("<D:response>");
728                                         wc_printf("<D:href>");
729                                                 dav_identify_host();
730                                                 wc_printf("/groupdav/");
731                                                 urlescputs(ChrPtr(WCC->CurRoom.name));
732                                                 euid_escapize(encoded_uid, uid);
733                                                 wc_printf("/%s", encoded_uid);
734                                         wc_printf("</D:href>");
735                                         switch(WCC->CurRoom.defview) {
736                                         case VIEW_CALENDAR:
737                                                 wc_printf("<D:getcontenttype>text/x-ical</D:getcontenttype>");
738                                                 break;
739                                         case VIEW_TASKS:
740                                                 wc_printf("<D:getcontenttype>text/x-ical</D:getcontenttype>");
741                                                 break;
742                                         case VIEW_ADDRESSBOOK:
743                                                 wc_printf("<D:getcontenttype>text/x-vcard</D:getcontenttype>");
744                                                 break;
745                                         }
746                                         wc_printf("<D:propstat>");
747                                                 wc_printf("<D:status>HTTP/1.1 200 OK</D:status>");
748                                                 wc_printf("<D:prop>");
749                                                         wc_printf("<D:getetag>\"%ld\"</D:getetag>", msgs[i]);
750                                                 if (now > 0L) {
751                                                         http_datestring(datestring, sizeof datestring, now);
752                                                         wc_printf("<D:getlastmodified>");
753                                                         escputs(datestring);
754                                                         wc_printf("</D:getlastmodified>");
755                                                 }
756                                                 wc_printf("</D:prop>");
757                                         wc_printf("</D:propstat>");
758                                 wc_printf("</D:response>");
759                         }
760                 }
761                 FreeStrBuf(&MsgNum);
762         }
763
764         wc_printf("</D:multistatus>\n");
765         end_burst();
766
767         if (msgs != NULL) {
768                 free(msgs);
769         }
770 }
771
772
773
774 int ParseMessageListHeaders_EUID(StrBuf *Line, 
775                                  const char **pos, 
776                                  message_summary *Msg, 
777                                  StrBuf *ConversionBuffer)
778 {
779         Msg->euid = NewStrBuf();
780         StrBufExtract_NextToken(Msg->euid,  Line, pos, '|');
781         Msg->date = StrBufExtractNext_long(Line, pos, '|');
782         
783         return StrLength(Msg->euid) > 0;
784 }
785
786 int DavUIDL_GetParamsGetServerCall(SharedMessageStatus *Stat, 
787                                    void **ViewSpecific, 
788                                    long oper, 
789                                    char *cmd, 
790                                    long len,
791                                    char *filter,
792                                    long flen)
793 {
794         Stat->defaultsortorder = 0;
795         Stat->sortit = 0;
796         Stat->load_seen = 0;
797         Stat->maxmsgs  = 9999999;
798
799         snprintf(cmd, len, "MSGS ALL|||2");
800         return 200;
801 }
802
803 int DavUIDL_RenderView_or_Tail(SharedMessageStatus *Stat, 
804                                 void **ViewSpecific, 
805                                 long oper)
806 {
807         
808         DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
809         
810         return 0;
811 }
812
813 int DavUIDL_Cleanup(void **ViewSpecific)
814 {
815         /* Note: wDumpContent() will output one additional </div> tag. */
816         /* We ought to move this out into template */
817         wDumpContent(1);
818
819         return 0;
820 }
821
822
823
824
825 void 
826 InitModule_PROPFIND
827 (void)
828 {
829         RegisterReadLoopHandlerset(
830                 eReadEUIDS,
831                 DavUIDL_GetParamsGetServerCall,
832                 NULL,
833                 NULL, /// TODO: is this right?
834                 ParseMessageListHeaders_EUID,
835                 NULL, //// ""
836                 DavUIDL_RenderView_or_Tail,
837                 DavUIDL_Cleanup);
838
839 }