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