* fix groupdav handling
[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  *     If you would like to rewrite this using libxml2, code it up and submit
10  *     a patch.  Whining will be summarily ignored.
11  *
12  * --> XML is deliberately output with no whitespace/newlines between tags.
13  *     This makes it difficult to read, but we have discovered clients which
14  *     crash when you try to pretty it up.
15  *
16  */
17
18 #include "webcit.h"
19 #include "webserver.h"
20 #include "groupdav.h"
21
22 /*
23  * Given an encoded UID, translate that to an unencoded Citadel EUID and
24  * then search for it in the current room.  Return a message number or -1
25  * if not found.
26  *
27  */
28 long locate_message_by_uid(const char *uid) {
29         char buf[256];
30         char decoded_uid[1024];
31         long retval = (-1L);
32
33         /* decode the UID */
34         euid_unescapize(decoded_uid, uid);
35
36         /* ask Citadel if we have this one */
37         serv_printf("EUID %s", decoded_uid);
38         serv_getln(buf, sizeof buf);
39         if (buf[0] == '2') {
40                 retval = atol(&buf[4]);
41         }
42
43         return(retval);
44 }
45
46
47
48 /*
49  * List rooms (or "collections" in DAV terminology) which contain
50  * interesting groupware objects.
51  */
52 void groupdav_collection_list(void)
53 {
54         wcsession *WCC = WC;
55         char buf[256];
56         char roomname[256];
57         int view;
58         char datestring[256];
59         time_t now;
60         time_t mtime;
61         int is_groupware_collection = 0;
62         int starting_point = 1;         /**< 0 for /, 1 for /groupdav/ */
63
64         if (WCC->Hdr->HR.Handler == NULL) {
65                 starting_point = 0;
66         }
67         else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
68                 starting_point = 1;
69         }
70         else {
71                 starting_point = 2;
72         }
73
74         now = time(NULL);
75         http_datestring(datestring, sizeof datestring, now);
76
77         /**
78          * Be rude.  Completely ignore the XML request and simply send them
79          * everything we know about.  Let the client sort it out.
80          */
81         hprintf("HTTP/1.0 207 Multi-Status\r\n");
82         groupdav_common_headers();
83         hprintf("Date: %s\r\n", datestring);
84         hprintf("Content-type: text/xml\r\n");
85         hprintf("Content-encoding: identity\r\n");
86
87         begin_burst();
88
89         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
90                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
91         );
92
93         /**
94          *      If the client is requesting the root, show a root node.
95          */
96         if (starting_point == 0) {
97                 wprintf("<response>");
98                         wprintf("<href>");
99                                 groupdav_identify_host();
100                                 wprintf("/");
101                         wprintf("</href>");
102                         wprintf("<propstat>");
103                                 wprintf("<status>HTTP/1.1 200 OK</status>");
104                                 wprintf("<prop>");
105                                         wprintf("<displayname>/</displayname>");
106                                         wprintf("<resourcetype><collection/></resourcetype>");
107                                         wprintf("<getlastmodified>");
108                                                 escputs(datestring);
109                                         wprintf("</getlastmodified>");
110                                 wprintf("</prop>");
111                         wprintf("</propstat>");
112                 wprintf("</response>");
113         }
114
115         /**
116          *      If the client is requesting "/groupdav", show a /groupdav subdirectory.
117          */
118         if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
119                 wprintf("<response>");
120                         wprintf("<href>");
121                                 groupdav_identify_host();
122                                 wprintf("/groupdav");
123                         wprintf("</href>");
124                         wprintf("<propstat>");
125                                 wprintf("<status>HTTP/1.1 200 OK</status>");
126                                 wprintf("<prop>");
127                                         wprintf("<displayname>GroupDAV</displayname>");
128                                         wprintf("<resourcetype><collection/></resourcetype>");
129                                         wprintf("<getlastmodified>");
130                                                 escputs(datestring);
131                                         wprintf("</getlastmodified>");
132                                 wprintf("</prop>");
133                         wprintf("</propstat>");
134                 wprintf("</response>");
135         }
136
137         /**
138          *      Now go through the list and make it look like a DAV collection
139          */
140         serv_puts("LKRA");
141         serv_getln(buf, sizeof buf);
142         if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
143
144                 extract_token(roomname, buf, 0, '|', sizeof roomname);
145                 view = extract_int(buf, 7);
146                 mtime = extract_long(buf, 8);
147                 http_datestring(datestring, sizeof datestring, mtime);
148
149                 /*
150                  * For now, only list rooms that we know a GroupDAV client
151                  * might be interested in.  In the future we may add
152                  * the rest.
153                  *
154                  * We determine the type of objects which are stored in each
155                  * room by looking at the *default* view for the room.  This
156                  * allows, for example, a Calendar room to appear as a
157                  * GroupDAV calendar even if the user has switched it to a
158                  * Calendar List view.
159                  */
160                 if ((view == VIEW_CALENDAR) || 
161                     (view == VIEW_TASKS) || 
162                     (view == VIEW_ADDRESSBOOK) ||
163                     (view == VIEW_NOTES) ||
164                     (view == VIEW_JOURNAL) ) {
165                         is_groupware_collection = 1;
166                 }
167                 else {
168                         is_groupware_collection = 0;
169                 }
170
171                 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
172                         wprintf("<response>");
173
174                         wprintf("<href>");
175                         groupdav_identify_host();
176                         wprintf("/groupdav/");
177                         urlescputs(roomname);
178                         wprintf("/</href>");
179
180                         wprintf("<propstat>");
181                         wprintf("<status>HTTP/1.1 200 OK</status>");
182                         wprintf("<prop>");
183                         wprintf("<displayname>");
184                         escputs(roomname);
185                         wprintf("</displayname>");
186                         wprintf("<resourcetype><collection/>");
187
188                         switch(view) {
189                         case VIEW_CALENDAR:
190                                 wprintf("<G:vevent-collection />");
191                                 break;
192                         case VIEW_TASKS:
193                                 wprintf("<G:vtodo-collection />");
194                                 break;
195                         case VIEW_ADDRESSBOOK:
196                                 wprintf("<G:vcard-collection />");
197                                 break;
198                         case VIEW_NOTES:
199                                 wprintf("<G:vnotes-collection />");
200                                 break;
201                         case VIEW_JOURNAL:
202                                 wprintf("<G:vjournal-collection />");
203                                 break;
204                         }
205
206                         wprintf("</resourcetype>");
207                         wprintf("<getlastmodified>");
208                                 escputs(datestring);
209                         wprintf("</getlastmodified>");
210                         wprintf("</prop>");
211                         wprintf("</propstat>");
212                         wprintf("</response>");
213                 }
214         }
215         wprintf("</multistatus>\n");
216
217         end_burst();
218 }
219
220
221
222 /*
223  * The pathname is always going to be /groupdav/room_name/msg_num
224  */
225 void groupdav_propfind(void) 
226 {
227         wcsession *WCC = WC;
228         StrBuf *dav_roomname;
229         StrBuf *dav_uid;
230         StrBuf *MsgNum;
231         long BufLen;
232         long dav_msgnum = (-1);
233         char uid[256];
234         char encoded_uid[256];
235         long *msgs = NULL;
236         int num_msgs = 0;
237         int i;
238         char datestring[256];
239         time_t now;
240
241         now = time(NULL);
242         http_datestring(datestring, sizeof datestring, now);
243
244         dav_roomname = NewStrBuf();
245         dav_uid = NewStrBuf();
246         StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
247         StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
248
249         /*
250          * If the room name is blank, the client is requesting a
251          * folder list.
252          */
253         if (StrLength(dav_roomname) == 0) {
254                 groupdav_collection_list();
255                 FreeStrBuf(&dav_roomname);
256                 FreeStrBuf(&dav_uid);
257                 return;
258         }
259
260         /* Go to the correct room. */
261         if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
262                 gotoroom(dav_roomname);
263         }
264         if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
265                 hprintf("HTTP/1.1 404 not found\r\n");
266                 groupdav_common_headers();
267                 hprintf("Date: %s\r\n", datestring);
268                 hprintf("Content-Type: text/plain\r\n");
269                 wprintf("There is no folder called \"%s\" on this server.\r\n",
270                         ChrPtr(dav_roomname)
271                 );
272                 end_burst();
273                 FreeStrBuf(&dav_roomname);
274                 FreeStrBuf(&dav_uid);
275                 return;
276         }
277
278         /* If dav_uid is non-empty, client is requesting a PROPFIND on
279          * a specific item in the room.  This is not valid GroupDAV, but
280          * it is valid WebDAV.
281          */
282         if (StrLength(dav_uid) != 0) {
283
284                 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
285                 if (dav_msgnum < 0) {
286                         hprintf("HTTP/1.1 404 not found\r\n");
287                         groupdav_common_headers();
288                         hprintf("Content-Type: text/plain\r\n");
289                         wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
290                                 ChrPtr(dav_uid),
291                                 ChrPtr(dav_roomname)
292                         );
293                         end_burst();
294                         FreeStrBuf(&dav_roomname);
295                         FreeStrBuf(&dav_uid);
296                         return;
297                 }
298
299                 /* Be rude.  Completely ignore the XML request and simply send them
300                  * everything we know about (which is going to simply be the ETag and
301                  * nothing else).  Let the client-side parser sort it out.
302                  */
303                 hprintf("HTTP/1.0 207 Multi-Status\r\n");
304                 groupdav_common_headers();
305                 hprintf("Date: %s\r\n", datestring);
306                 hprintf("Content-type: text/xml\r\n");
307                 hprintf("Content-encoding: identity\r\n");
308         
309                 begin_burst();
310         
311                 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
312                         "<multistatus xmlns=\"DAV:\">"
313                 );
314
315                 wprintf("<response>");
316                 
317                 wprintf("<href>");
318                 groupdav_identify_host();
319                 wprintf("/groupdav/");
320                 urlescputs(ChrPtr(WC->wc_roomname));
321                 euid_escapize(encoded_uid, ChrPtr(dav_uid));
322                 wprintf("/%s", encoded_uid);
323                 wprintf("</href>");
324                 wprintf("<propstat>");
325                 wprintf("<status>HTTP/1.1 200 OK</status>");
326                 wprintf("<prop>");
327                 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
328                 wprintf("<getlastmodified>");
329                 escputs(datestring);
330                 wprintf("</getlastmodified>");
331                 wprintf("</prop>");
332                 wprintf("</propstat>");
333
334                 wprintf("</response>\n");
335                 wprintf("</multistatus>\n");
336                 end_burst();
337                 FreeStrBuf(&dav_roomname);
338                 FreeStrBuf(&dav_uid);
339                 return;
340         }
341         FreeStrBuf(&dav_roomname);
342         FreeStrBuf(&dav_uid);
343
344
345         /*
346          * We got to this point, which means that the client is requesting
347          * a 'collection' (i.e. a list of all items in the room).
348          *
349          * Be rude.  Completely ignore the XML request and simply send them
350          * everything we know about (which is going to simply be the ETag and
351          * nothing else).  Let the client-side parser sort it out.
352          */
353         hprintf("HTTP/1.0 207 Multi-Status\r\n");
354         groupdav_common_headers();
355         hprintf("Date: %s\r\n", datestring);
356         hprintf("Content-type: text/xml\r\n");
357         hprintf("Content-encoding: identity\r\n");
358
359         begin_burst();
360
361         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
362                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
363         );
364
365
366         /** Transmit the collection resource (FIXME check depth and starting point) */
367         wprintf("<response>");
368
369         wprintf("<href>");
370                 groupdav_identify_host();
371                 wprintf("/groupdav/");
372                 urlescputs(ChrPtr(WC->wc_roomname));
373         wprintf("</href>");
374
375         wprintf("<propstat>");
376         wprintf("<status>HTTP/1.1 200 OK</status>");
377         wprintf("<prop>");
378         wprintf("<displayname>");
379         escputs(ChrPtr(WC->wc_roomname));
380         wprintf("</displayname>");
381         wprintf("<resourcetype><collection/>");
382
383         switch(WC->wc_default_view) {
384                 case VIEW_CALENDAR:
385                         wprintf("<G:vevent-collection />");
386                         break;
387                 case VIEW_TASKS:
388                         wprintf("<G:vtodo-collection />");
389                         break;
390                 case VIEW_ADDRESSBOOK:
391                         wprintf("<G:vcard-collection />");
392                         break;
393         }
394
395         wprintf("</resourcetype>");
396         /* FIXME get the mtime
397         wprintf("<getlastmodified>");
398                 escputs(datestring);
399         wprintf("</getlastmodified>");
400         */
401         wprintf("</prop>");
402         wprintf("</propstat>");
403         wprintf("</response>");
404
405         /** Transmit the collection listing (FIXME check depth and starting point) */
406
407         MsgNum = NewStrBuf ();
408         serv_puts("MSGS ALL");
409
410         StrBuf_ServGetln(MsgNum);
411         if (GetServerStatus(MsgNum, NULL) == 1)
412                 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))  {
413                         msgs = realloc(msgs, ++num_msgs * sizeof(long));
414                         msgs[num_msgs-1] = StrTol(MsgNum);
415                 }
416
417         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
418
419                 strcpy(uid, "");
420                 now = (-1);
421                 serv_printf("MSG0 %ld|3", msgs[i]);
422                 StrBuf_ServGetln(MsgNum);
423                 if (GetServerStatus(MsgNum, NULL) == 1)
424                         while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) 
425                         {
426                                 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
427                                         strcpy(uid, &ChrPtr(MsgNum)[5]);
428                                 }
429                                 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
430                                         now = atol(&ChrPtr(MsgNum)[5]);
431                         }
432                 }
433
434                 if (!IsEmptyStr(uid)) {
435                         wprintf("<response>");
436                                 wprintf("<href>");
437                                         groupdav_identify_host();
438                                         wprintf("/groupdav/");
439                                         urlescputs(ChrPtr(WC->wc_roomname));
440                                         euid_escapize(encoded_uid, uid);
441                                         wprintf("/%s", encoded_uid);
442                                 wprintf("</href>");
443                                 switch(WC->wc_default_view) {
444                                 case VIEW_CALENDAR:
445                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
446                                         break;
447                                 case VIEW_TASKS:
448                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
449                                         break;
450                                 case VIEW_ADDRESSBOOK:
451                                         wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
452                                         break;
453                                 }
454                                 wprintf("<propstat>");
455                                         wprintf("<status>HTTP/1.1 200 OK</status>");
456                                         wprintf("<prop>");
457                                                 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
458                                         if (now > 0L) {
459                                                 http_datestring(datestring, sizeof datestring, now);
460                                                 wprintf("<getlastmodified>");
461                                                 escputs(datestring);
462                                                 wprintf("</getlastmodified>");
463                                         }
464                                         wprintf("</prop>");
465                                 wprintf("</propstat>");
466                         wprintf("</response>");
467                 }
468         }
469         FreeStrBuf(&MsgNum);
470
471         wprintf("</multistatus>\n");
472         end_burst();
473
474         if (msgs != NULL) {
475                 free(msgs);
476         }
477 }