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