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