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