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