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