* move some more vars from the session context to strbuf (the use of StrBufAppendTemp...
[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 /**************  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(const 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(StrBuf *dav_pathname, int dav_depth, StrBuf *dav_content_type, StrBuf *dav_content, int offset) {
231         StrBuf *dav_roomname;
232         StrBuf *dav_uid;
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         dav_roomname = NewStrBuf();
248         dav_uid = NewStrBuf();
249         StrBufExtract_token(dav_roomname, dav_pathname, 2, '/');
250         StrBufExtract_token(dav_uid, dav_pathname, 3, '/');
251
252         /*
253          * If the room name is blank, the client is requesting a
254          * folder list.
255          */
256         if (StrLength(dav_roomname) == 0) {
257                 groupdav_collection_list(ChrPtr(dav_pathname), dav_depth);
258                 FreeStrBuf(&dav_roomname);
259                 FreeStrBuf(&dav_uid);
260                 return;
261         }
262
263         /* Go to the correct room. */
264         if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
265                 gotoroom(dav_roomname);
266         }
267         if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
268                 hprintf("HTTP/1.1 404 not found\r\n");
269                 groupdav_common_headers();
270                 hprintf("Date: %s\r\n", datestring);
271                 hprintf("Content-Type: text/plain\r\n");
272                 wprintf("There is no folder called \"%s\" on this server.\r\n",
273                         ChrPtr(dav_roomname)
274                 );
275                 end_burst();
276                 FreeStrBuf(&dav_roomname);
277                 FreeStrBuf(&dav_uid);
278                 return;
279         }
280
281         /* If dav_uid is non-empty, client is requesting a PROPFIND on
282          * a specific item in the room.  This is not valid GroupDAV, but
283          * it is valid WebDAV.
284          */
285         if (StrLength(dav_uid) != 0) {
286
287                 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
288                 if (dav_msgnum < 0) {
289                         hprintf("HTTP/1.1 404 not found\r\n");
290                         groupdav_common_headers();
291                         hprintf("Content-Type: text/plain\r\n");
292                         wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
293                                 ChrPtr(dav_uid),
294                                 ChrPtr(dav_roomname)
295                         );
296                         end_burst();
297                         FreeStrBuf(&dav_roomname);
298                         FreeStrBuf(&dav_uid);
299                         return;
300                 }
301
302                 /* Be rude.  Completely ignore the XML request and simply send them
303                  * everything we know about (which is going to simply be the ETag and
304                  * nothing else).  Let the client-side parser sort it out.
305                  */
306                 hprintf("HTTP/1.0 207 Multi-Status\r\n");
307                 groupdav_common_headers();
308                 hprintf("Date: %s\r\n", datestring);
309                 hprintf("Content-type: text/xml\r\n");
310                 hprintf("Content-encoding: identity\r\n");
311         
312                 begin_burst();
313         
314                 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
315                         "<multistatus xmlns=\"DAV:\">"
316                 );
317
318                 wprintf("<response>");
319                 
320                 wprintf("<href>");
321                 groupdav_identify_host();
322                 wprintf("/groupdav/");
323                 urlescputs(ChrPtr(WC->wc_roomname));
324                 euid_escapize(encoded_uid, ChrPtr(dav_uid));
325                 wprintf("/%s", encoded_uid);
326                 wprintf("</href>");
327                 wprintf("<propstat>");
328                 wprintf("<status>HTTP/1.1 200 OK</status>");
329                 wprintf("<prop>");
330                 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
331                 wprintf("<getlastmodified>");
332                 escputs(datestring);
333                 wprintf("</getlastmodified>");
334                 wprintf("</prop>");
335                 wprintf("</propstat>");
336
337                 wprintf("</response>\n");
338                 wprintf("</multistatus>\n");
339                 end_burst();
340                 FreeStrBuf(&dav_roomname);
341                 FreeStrBuf(&dav_uid);
342                 return;
343         }
344         FreeStrBuf(&dav_roomname);
345         FreeStrBuf(&dav_uid);
346
347
348         /*
349          * We got to this point, which means that the client is requesting
350          * a 'collection' (i.e. a list of all items in the room).
351          *
352          * Be rude.  Completely ignore the XML request and simply send them
353          * everything we know about (which is going to simply be the ETag and
354          * nothing else).  Let the client-side parser sort it out.
355          */
356         hprintf("HTTP/1.0 207 Multi-Status\r\n");
357         groupdav_common_headers();
358         hprintf("Date: %s\r\n", datestring);
359         hprintf("Content-type: text/xml\r\n");
360         hprintf("Content-encoding: identity\r\n");
361
362         begin_burst();
363
364         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
365                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
366         );
367
368
369         /** Transmit the collection resource (FIXME check depth and starting point) */
370         wprintf("<response>");
371
372         wprintf("<href>");
373                 groupdav_identify_host();
374                 wprintf("/groupdav/");
375                 urlescputs(ChrPtr(WC->wc_roomname));
376         wprintf("</href>");
377
378         wprintf("<propstat>");
379         wprintf("<status>HTTP/1.1 200 OK</status>");
380         wprintf("<prop>");
381         wprintf("<displayname>");
382         escputs(ChrPtr(WC->wc_roomname));
383         wprintf("</displayname>");
384         wprintf("<resourcetype><collection/>");
385
386         switch(WC->wc_default_view) {
387                 case VIEW_CALENDAR:
388                         wprintf("<G:vevent-collection />");
389                         break;
390                 case VIEW_TASKS:
391                         wprintf("<G:vtodo-collection />");
392                         break;
393                 case VIEW_ADDRESSBOOK:
394                         wprintf("<G:vcard-collection />");
395                         break;
396         }
397
398         wprintf("</resourcetype>");
399         /* FIXME get the mtime
400         wprintf("<getlastmodified>");
401                 escputs(datestring);
402         wprintf("</getlastmodified>");
403         */
404         wprintf("</prop>");
405         wprintf("</propstat>");
406         wprintf("</response>");
407
408         /** Transmit the collection listing (FIXME check depth and starting point) */
409
410         serv_puts("MSGS ALL");
411         serv_getln(buf, sizeof buf);
412         if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
413                 msgs = realloc(msgs, ++num_msgs * sizeof(long));
414                 msgs[num_msgs-1] = atol(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                 serv_getln(buf, sizeof buf);
423                 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
424                         if (!strncasecmp(buf, "exti=", 5)) {
425                                 strcpy(uid, &buf[5]);
426                         }
427                         else if (!strncasecmp(buf, "time=", 5)) {
428                                 now = atol(&buf[5]);
429                         }
430                 }
431
432                 if (!IsEmptyStr(uid)) {
433                         wprintf("<response>");
434                                 wprintf("<href>");
435                                         groupdav_identify_host();
436                                         wprintf("/groupdav/");
437                                         urlescputs(ChrPtr(WC->wc_roomname));
438                                         euid_escapize(encoded_uid, uid);
439                                         wprintf("/%s", encoded_uid);
440                                 wprintf("</href>");
441                                 switch(WC->wc_default_view) {
442                                 case VIEW_CALENDAR:
443                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
444                                         break;
445                                 case VIEW_TASKS:
446                                         wprintf("<getcontenttype>text/x-ical</getcontenttype>");
447                                         break;
448                                 case VIEW_ADDRESSBOOK:
449                                         wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
450                                         break;
451                                 }
452                                 wprintf("<propstat>");
453                                         wprintf("<status>HTTP/1.1 200 OK</status>");
454                                         wprintf("<prop>");
455                                                 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
456                                         if (now > 0L) {
457                                                 http_datestring(datestring, sizeof datestring, now);
458                                                 wprintf("<getlastmodified>");
459                                                 escputs(datestring);
460                                                 wprintf("</getlastmodified>");
461                                         }
462                                         wprintf("</prop>");
463                                 wprintf("</propstat>");
464                         wprintf("</response>");
465                 }
466         }
467
468         wprintf("</multistatus>\n");
469         end_burst();
470
471         if (msgs != NULL) {
472                 free(msgs);
473         }
474 }