Began making changes to do better handling of character sets.
[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         wprintf("HTTP/1.0 207 Multi-Status\r\n");
97         groupdav_common_headers();
98         wprintf("Date: %s\r\n", datestring);
99         wprintf("Content-type: text/xml\r\n");
100         wprintf("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 (strlen(dav_roomname) == 0) {
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                 wprintf("HTTP/1.1 404 not found\r\n");
265                 groupdav_common_headers();
266                 wprintf("Date: %s\r\n", datestring);
267                 wprintf(
268                         "Content-Type: text/plain\r\n"
269                         "\r\n"
270                         "There is no folder called \"%s\" on this server.\r\n",
271                         dav_roomname
272                 );
273                 return;
274         }
275
276         /* If dav_uid is non-empty, client is requesting a PROPFIND on
277          * a specific item in the room.  This is not valid GroupDAV, but
278          * it is valid WebDAV.
279          */
280         if (strlen(dav_uid) > 0) {
281
282                 dav_msgnum = locate_message_by_uid(dav_uid);
283                 if (dav_msgnum < 0) {
284                         wprintf("HTTP/1.1 404 not found\r\n");
285                         groupdav_common_headers();
286                         wprintf(
287                                 "Content-Type: text/plain\r\n"
288                                 "\r\n"
289                                 "Object \"%s\" was not found in the \"%s\" folder.\r\n",
290                                 dav_uid,
291                                 dav_roomname
292                         );
293                         return;
294                 }
295
296                 /* Be rude.  Completely ignore the XML request and simply send them
297                  * everything we know about (which is going to simply be the ETag and
298                  * nothing else).  Let the client-side parser sort it out.
299                  */
300                 wprintf("HTTP/1.0 207 Multi-Status\r\n");
301                 groupdav_common_headers();
302                 wprintf("Date: %s\r\n", datestring);
303                 wprintf("Content-type: text/xml\r\n");
304                 wprintf("Content-encoding: identity\r\n");
305         
306                 begin_burst();
307         
308                 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
309                         "<multistatus xmlns=\"DAV:\">"
310                 );
311
312                 wprintf("<response>");
313
314                 wprintf("<href>");
315                 groupdav_identify_host();
316                 wprintf("/groupdav/");
317                 urlescputs(WC->wc_roomname);
318                 euid_escapize(encoded_uid, dav_uid);
319                 wprintf("/%s", encoded_uid);
320                 wprintf("</href>");
321                 wprintf("<propstat>");
322                 wprintf("<status>HTTP/1.1 200 OK</status>");
323                 wprintf("<prop><getetag>\"%ld\"</getetag></prop>", dav_msgnum);
324                 wprintf("</propstat>");
325
326                 wprintf("</response>\n");
327                 wprintf("</multistatus>\n");
328                 end_burst();
329                 return;
330         }
331
332
333         /*
334          * We got to this point, which means that the client is requesting
335          * a 'collection' (i.e. a list of all items in the room).
336          *
337          * Be rude.  Completely ignore the XML request and simply send them
338          * everything we know about (which is going to simply be the ETag and
339          * nothing else).  Let the client-side parser sort it out.
340          */
341         wprintf("HTTP/1.0 207 Multi-Status\r\n");
342         groupdav_common_headers();
343         wprintf("Date: %s\r\n", datestring);
344         wprintf("Content-type: text/xml\r\n");
345         wprintf("Content-encoding: identity\r\n");
346
347         begin_burst();
348
349         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
350                 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
351         );
352
353
354         /** Transmit the collection resource (FIXME check depth and starting point) */
355         wprintf("<response>");
356
357         wprintf("<href>");
358                 groupdav_identify_host();
359                 wprintf("/groupdav/");
360                 urlescputs(WC->wc_roomname);
361         wprintf("</href>");
362
363         wprintf("<propstat>");
364         wprintf("<status>HTTP/1.1 200 OK</status>");
365         wprintf("<prop>");
366         wprintf("<displayname>");
367         escputs(WC->wc_roomname);
368         wprintf("</displayname>");
369         wprintf("<resourcetype><collection/>");
370
371         switch(WC->wc_default_view) {
372                 case VIEW_CALENDAR:
373                         wprintf("<G:vevent-collection />");
374                         break;
375                 case VIEW_TASKS:
376                         wprintf("<G:vtodo-collection />");
377                         break;
378                 case VIEW_ADDRESSBOOK:
379                         wprintf("<G:vcard-collection />");
380                         break;
381         }
382
383         wprintf("</resourcetype>");
384         /* FIXME get the mtime
385         wprintf("<getlastmodified>");
386                 escputs(datestring);
387         wprintf("</getlastmodified>");
388         */
389         wprintf("</prop>");
390         wprintf("</propstat>");
391         wprintf("</response>");
392
393         /** Transmit the collection listing (FIXME check depth and starting point) */
394
395         serv_puts("MSGS ALL");
396         serv_getln(buf, sizeof buf);
397         if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
398                 msgs = realloc(msgs, ++num_msgs * sizeof(long));
399                 msgs[num_msgs-1] = atol(msgnum);
400         }
401
402         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
403
404                 strcpy(uid, "");
405                 serv_printf("MSG0 %ld|3", msgs[i]);
406                 serv_getln(buf, sizeof buf);
407                 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
408                         if (!strncasecmp(buf, "exti=", 5)) {
409                                 strcpy(uid, &buf[5]);
410                         }
411                 }
412
413                 if (strlen(uid) > 0) {
414                         wprintf("<response>");
415                                 wprintf("<href>");
416                                         groupdav_identify_host();
417                                         wprintf("/groupdav/");
418                                         urlescputs(WC->wc_roomname);
419                                         euid_escapize(encoded_uid, uid);
420                                         wprintf("/%s", encoded_uid);
421                                 wprintf("</href>");
422                                 wprintf("<propstat>");
423                                         wprintf("<status>HTTP/1.1 200 OK</status>");
424                                         wprintf("<prop>");
425                                                 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
426                                         wprintf("</prop>");
427                                 wprintf("</propstat>");
428                         wprintf("</response>");
429                 }
430         }
431
432         wprintf("</multistatus>\n");
433         end_burst();
434
435         if (msgs != NULL) {
436                 free(msgs);
437         }
438 }