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