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