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