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