3e069609b4a8839a1fef316fe43585b61184dcda
[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 <ctype.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <stdio.h>
22 #include <fcntl.h>
23 #include <signal.h>
24 #include <sys/types.h>
25 #include <sys/wait.h>
26 #include <sys/socket.h>
27 #include <limits.h>
28 #include <string.h>
29 #include <pwd.h>
30 #include <errno.h>
31 #include <stdarg.h>
32 #include <time.h>
33 #include <pthread.h>
34 #include "webcit.h"
35 #include "webserver.h"
36 #include "groupdav.h"
37
38
39 /*
40  * Given an encoded UID, translate that to an unencoded Citadel EUID and
41  * then search for it in the current room.  Return a message number or -1
42  * if not found.
43  *
44  * NOTE: this function relies on the Citadel server's brute-force search.
45  * There's got to be a way to optimize this better.
46  */
47 long locate_message_by_uid(char *uid) {
48         char buf[SIZ];
49         char decoded_uid[SIZ];
50         long retval = (-1L);
51
52         /* Decode the uid */
53         euid_unescapize(decoded_uid, uid);
54
55         serv_puts("MSGS ALL|0|1");
56         serv_getln(buf, sizeof buf);
57         if (buf[0] == '8') {
58                 serv_printf("exti|%s", decoded_uid);
59                 serv_puts("000");
60                 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
61                         retval = atol(buf);
62                 }
63         }
64         return(retval);
65 }
66
67
68 /*
69  * List folders containing interesting groupware objects
70  */
71 void groupdav_folder_list(void) {
72         char buf[SIZ];
73         char roomname[SIZ];
74         int view;
75         char datestring[SIZ];
76         time_t now;
77
78         now = time(NULL);
79         http_datestring(datestring, sizeof datestring, now);
80
81         /*
82          * Be rude.  Completely ignore the XML request and simply send them
83          * everything we know about.  Let the client sort it out.
84          */
85         wprintf("HTTP/1.0 207 Multi-Status\r\n");
86         groupdav_common_headers();
87         wprintf("Date: %s\r\n", datestring);
88         wprintf("Content-type: text/xml\r\n");
89         wprintf("Content-encoding: identity\r\n");
90
91         begin_burst();
92
93         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
94                 "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
95         );
96
97         serv_puts("LKRA");
98         serv_getln(buf, sizeof buf);
99         if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
100
101                 extract_token(roomname, buf, 0, '|', sizeof roomname);
102                 view = extract_int(buf, 6);
103
104                 /*
105                  * For now, only list rooms that we know a GroupDAV client
106                  * might be interested in.  In the future we may add
107                  * the rest.
108                  */
109                 if ((view == VIEW_CALENDAR)
110                    || (view == VIEW_TASKS)
111                    || (view == VIEW_ADDRESSBOOK) ) {
112
113                         wprintf("<D:response>");
114
115                         wprintf("<D:href>");
116                         if (strlen(WC->http_host) > 0) {
117                                 wprintf("%s://%s",
118                                         (is_https ? "https" : "http"),
119                                         WC->http_host);
120                         }
121                         wprintf("/groupdav/");
122                         urlescputs(roomname);
123                         wprintf("/</D:href>");
124
125                         wprintf("<D:propstat>");
126                         wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
127                         wprintf("<D:prop>");
128                         wprintf("<D:displayname>");
129                         escputs(roomname);
130                         wprintf("</D:displayname>");
131                         wprintf("<D:resourcetype><D:collection/>");
132
133                         switch(view) {
134                                 case VIEW_CALENDAR:
135                                         wprintf("<G:vevent-collection />");
136                                         break;
137                                 case VIEW_TASKS:
138                                         wprintf("<G:vtodo-collection />");
139                                         break;
140                                 case VIEW_ADDRESSBOOK:
141                                         wprintf("<G:vcard-collection />");
142                                         break;
143                         }
144
145                         wprintf("</D:resourcetype>");
146                         wprintf("</D:prop>");
147                         wprintf("</D:propstat>");
148                         wprintf("</D:response>");
149                 }
150         }
151         wprintf("</D:multistatus>\n");
152
153         end_burst();
154 }
155
156
157
158 /*
159  * The pathname is always going to be /groupdav/room_name/msg_num
160  */
161 void groupdav_propfind(char *dav_pathname) {
162         char dav_roomname[256];
163         char dav_uid[256];
164         char msgnum[256];
165         long dav_msgnum = (-1);
166         char buf[256];
167         char uid[256];
168         char encoded_uid[256];
169         long *msgs = NULL;
170         int num_msgs = 0;
171         int i;
172         char datestring[256];
173         time_t now;
174
175         now = time(NULL);
176         http_datestring(datestring, sizeof datestring, now);
177
178         extract_token(dav_roomname, dav_pathname, 2, '/', sizeof dav_roomname);
179         extract_token(dav_uid, dav_pathname, 3, '/', sizeof dav_uid);
180
181         lprintf(9, "dav_pathname: %s\n", dav_pathname);
182         lprintf(9, "dav_roomname: %s\n", dav_roomname);
183         lprintf(9, "     dav_uid: %s\n", dav_uid);
184
185         /*
186          * If the room name is blank, the client is requesting a
187          * folder list.
188          */
189         if (strlen(dav_roomname) == 0) {
190                 groupdav_folder_list();
191                 return;
192         }
193
194         /* Go to the correct room. */
195         if (strcasecmp(WC->wc_roomname, dav_roomname)) {
196                 gotoroom(dav_roomname);
197         }
198         if (strcasecmp(WC->wc_roomname, dav_roomname)) {
199                 wprintf("HTTP/1.1 404 not found\r\n");
200                 groupdav_common_headers();
201                 wprintf("Date: %s\r\n", datestring);
202                 wprintf(
203                         "Content-Type: text/plain\r\n"
204                         "\r\n"
205                         "There is no folder called \"%s\" on this server.\r\n",
206                         dav_roomname
207                 );
208                 return;
209         }
210
211         /* If dav_uid is non-empty, client is requesting a PROPFIND on
212          * a specific item in the room.  This is not valid GroupDAV, but
213          * we try to honor it anyway because some clients are expecting
214          * it to work...
215          */
216         if (strlen(dav_uid) > 0) {
217
218                 dav_msgnum = locate_message_by_uid(dav_uid);
219                 if (dav_msgnum < 0) {
220                         wprintf("HTTP/1.1 404 not found\r\n");
221                         groupdav_common_headers();
222                         wprintf(
223                                 "Content-Type: text/plain\r\n"
224                                 "\r\n"
225                                 "Object \"%s\" was not found in the \"%s\" folder.\r\n",
226                                 dav_uid,
227                                 dav_roomname
228                         );
229                         return;
230                 }
231
232                 /* Be rude.  Completely ignore the XML request and simply send them
233                  * everything we know about (which is going to simply be the ETag and
234                  * nothing else).  Let the client-side parser sort it out.
235                  */
236                 wprintf("HTTP/1.0 207 Multi-Status\r\n");
237                 groupdav_common_headers();
238                 wprintf("Date: %s\r\n", datestring);
239                 wprintf("Content-type: text/xml\r\n");
240                 wprintf("Content-encoding: identity\r\n");
241         
242                 begin_burst();
243         
244                 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
245                         "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
246                 );
247
248                 wprintf("<D:response>");
249
250                 wprintf("<D:href>");
251                 if (strlen(WC->http_host) > 0) {
252                         wprintf("%s://%s",
253                                 (is_https ? "https" : "http"),
254                                 WC->http_host);
255                 }
256                 wprintf("/groupdav/");
257                 urlescputs(WC->wc_roomname);
258                 euid_escapize(encoded_uid, dav_uid);
259                 wprintf("/%s", encoded_uid);
260                 wprintf("</D:href>");
261                 wprintf("<D:propstat>");
262                 wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
263                 wprintf("<D:prop><D:getetag>\"%ld\"</D:getetag></D:prop>", dav_msgnum);
264                 wprintf("</D:propstat>");
265
266                 wprintf("</D:response>\n");
267                 wprintf("</D:multistatus>\n");
268                 end_burst();
269                 return;
270         }
271
272
273         /*
274          * We got to this point, which means that the client is requesting
275          * a 'collection' (i.e. a list of all items in the room).
276          *
277          * Be rude.  Completely ignore the XML request and simply send them
278          * everything we know about (which is going to simply be the ETag and
279          * nothing else).  Let the client-side parser sort it out.
280          */
281         wprintf("HTTP/1.0 207 Multi-Status\r\n");
282         groupdav_common_headers();
283         wprintf("Date: %s\r\n", datestring);
284         wprintf("Content-type: text/xml\r\n");
285         wprintf("Content-encoding: identity\r\n");
286
287         begin_burst();
288
289         wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
290                 "<D:multistatus xmlns:D=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
291         );
292
293         serv_puts("MSGS ALL");
294         serv_getln(buf, sizeof buf);
295         if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
296                 msgs = realloc(msgs, ++num_msgs * sizeof(long));
297                 msgs[num_msgs-1] = atol(msgnum);
298         }
299
300         if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
301
302                 strcpy(uid, "");
303                 serv_printf("MSG0 %ld|3", msgs[i]);
304                 serv_getln(buf, sizeof buf);
305                 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
306                         if (!strncasecmp(buf, "exti=", 5)) {
307                                 strcpy(uid, &buf[5]);
308                         }
309                 }
310
311                 if (strlen(uid) > 0) {
312                         wprintf("<D:response>");
313                         wprintf("<D:href>");
314                         if (strlen(WC->http_host) > 0) {
315                                 wprintf("%s://%s",
316                                         (is_https ? "https" : "http"),
317                                         WC->http_host);
318                         }
319                         wprintf("/groupdav/");
320                         urlescputs(WC->wc_roomname);
321                         euid_escapize(encoded_uid, uid);
322                         wprintf("/%s", encoded_uid);
323                         wprintf("</D:href>");
324                         wprintf("<D:propstat>");
325                         wprintf("<D:status>HTTP/1.1 200 OK</D:status>");
326                         wprintf("<D:prop><D:getetag>\"%ld\"</D:getetag></D:prop>", msgs[i]);
327                         wprintf("</D:propstat>");
328                         wprintf("</D:response>");
329                 }
330         }
331
332         wprintf("</D:multistatus>\n");
333         end_burst();
334
335         if (msgs != NULL) {
336                 free(msgs);
337         }
338 }