* Remove leading '|' from references if present
[citadel.git] / webcit / groupdav_propfind.c
blobf225eb8f1732a648804c81d43db82d4fa68c2d3d
1 /*
2 * $Id$
4 * Handles GroupDAV PROPFIND requests.
6 * A few notes about our XML output:
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.
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.
18 #include "webcit.h"
19 #include "webserver.h"
20 #include "groupdav.h"
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.
28 long locate_message_by_uid(const char *uid) {
29 char buf[256];
30 char decoded_uid[1024];
31 long retval = (-1L);
33 /* decode the UID */
34 euid_unescapize(decoded_uid, uid);
36 /* ask Citadel if we have this one */
37 serv_printf("EUID %s", decoded_uid);
38 serv_getln(buf, sizeof buf);
39 if (buf[0] == '2') {
40 retval = atol(&buf[4]);
43 return(retval);
49 * List rooms (or "collections" in DAV terminology) which contain
50 * interesting groupware objects.
52 void groupdav_collection_list(const char *dav_pathname, int dav_depth)
54 char buf[256];
55 char roomname[256];
56 int view;
57 char datestring[256];
58 time_t now;
59 time_t mtime;
60 int is_groupware_collection = 0;
61 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
63 if (!strcmp(dav_pathname, "/")) {
64 starting_point = 0;
66 else if (!strcasecmp(dav_pathname, "/groupdav")) {
67 starting_point = 1;
69 else if (!strcasecmp(dav_pathname, "/groupdav/")) {
70 starting_point = 1;
72 else if ( (!strncasecmp(dav_pathname, "/groupdav/", 10)) && (strlen(dav_pathname) > 10) ) {
73 starting_point = 2;
76 now = time(NULL);
77 http_datestring(datestring, sizeof datestring, now);
79 /**
80 * Be rude. Completely ignore the XML request and simply send them
81 * everything we know about. Let the client sort it out.
83 hprintf("HTTP/1.0 207 Multi-Status\r\n");
84 groupdav_common_headers();
85 hprintf("Date: %s\r\n", datestring);
86 hprintf("Content-type: text/xml\r\n");
87 hprintf("Content-encoding: identity\r\n");
89 begin_burst();
91 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
92 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
95 /**
96 * If the client is requesting the root, show a root node.
98 if (starting_point == 0) {
99 wprintf("<response>");
100 wprintf("<href>");
101 groupdav_identify_host();
102 wprintf("/");
103 wprintf("</href>");
104 wprintf("<propstat>");
105 wprintf("<status>HTTP/1.1 200 OK</status>");
106 wprintf("<prop>");
107 wprintf("<displayname>/</displayname>");
108 wprintf("<resourcetype><collection/></resourcetype>");
109 wprintf("<getlastmodified>");
110 escputs(datestring);
111 wprintf("</getlastmodified>");
112 wprintf("</prop>");
113 wprintf("</propstat>");
114 wprintf("</response>");
118 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
120 if ((starting_point + dav_depth) >= 1) {
121 wprintf("<response>");
122 wprintf("<href>");
123 groupdav_identify_host();
124 wprintf("/groupdav");
125 wprintf("</href>");
126 wprintf("<propstat>");
127 wprintf("<status>HTTP/1.1 200 OK</status>");
128 wprintf("<prop>");
129 wprintf("<displayname>GroupDAV</displayname>");
130 wprintf("<resourcetype><collection/></resourcetype>");
131 wprintf("<getlastmodified>");
132 escputs(datestring);
133 wprintf("</getlastmodified>");
134 wprintf("</prop>");
135 wprintf("</propstat>");
136 wprintf("</response>");
140 * Now go through the list and make it look like a DAV collection
142 serv_puts("LKRA");
143 serv_getln(buf, sizeof buf);
144 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
146 extract_token(roomname, buf, 0, '|', sizeof roomname);
147 view = extract_int(buf, 7);
148 mtime = extract_long(buf, 8);
149 http_datestring(datestring, sizeof datestring, mtime);
152 * For now, only list rooms that we know a GroupDAV client
153 * might be interested in. In the future we may add
154 * the rest.
156 * We determine the type of objects which are stored in each
157 * room by looking at the *default* view for the room. This
158 * allows, for example, a Calendar room to appear as a
159 * GroupDAV calendar even if the user has switched it to a
160 * Calendar List view.
162 if ((view == VIEW_CALENDAR) || (view == VIEW_TASKS) || (view == VIEW_ADDRESSBOOK) ) {
163 is_groupware_collection = 1;
165 else {
166 is_groupware_collection = 0;
169 if ( (is_groupware_collection) && ((starting_point + dav_depth) >= 2) ) {
170 wprintf("<response>");
172 wprintf("<href>");
173 groupdav_identify_host();
174 wprintf("/groupdav/");
175 urlescputs(roomname);
176 wprintf("/</href>");
178 wprintf("<propstat>");
179 wprintf("<status>HTTP/1.1 200 OK</status>");
180 wprintf("<prop>");
181 wprintf("<displayname>");
182 escputs(roomname);
183 wprintf("</displayname>");
184 wprintf("<resourcetype><collection/>");
186 switch(view) {
187 case VIEW_CALENDAR:
188 wprintf("<G:vevent-collection />");
189 break;
190 case VIEW_TASKS:
191 wprintf("<G:vtodo-collection />");
192 break;
193 case VIEW_ADDRESSBOOK:
194 wprintf("<G:vcard-collection />");
195 break;
198 wprintf("</resourcetype>");
199 wprintf("<getlastmodified>");
200 escputs(datestring);
201 wprintf("</getlastmodified>");
202 wprintf("</prop>");
203 wprintf("</propstat>");
204 wprintf("</response>");
207 wprintf("</multistatus>\n");
209 end_burst();
215 * The pathname is always going to be /groupdav/room_name/msg_num
217 void groupdav_propfind(StrBuf *dav_pathname, int dav_depth, StrBuf *dav_content_type, StrBuf *dav_content, int offset) {
218 StrBuf *dav_roomname;
219 StrBuf *dav_uid;
220 char msgnum[256];
221 long dav_msgnum = (-1);
222 char buf[256];
223 char uid[256];
224 char encoded_uid[256];
225 long *msgs = NULL;
226 int num_msgs = 0;
227 int i;
228 char datestring[256];
229 time_t now;
231 now = time(NULL);
232 http_datestring(datestring, sizeof datestring, now);
234 dav_roomname = NewStrBuf();
235 dav_uid = NewStrBuf();
236 StrBufExtract_token(dav_roomname, dav_pathname, 2, '/');
237 StrBufExtract_token(dav_uid, dav_pathname, 3, '/');
240 * If the room name is blank, the client is requesting a
241 * folder list.
243 if (StrLength(dav_roomname) == 0) {
244 groupdav_collection_list(ChrPtr(dav_pathname), dav_depth);
245 FreeStrBuf(&dav_roomname);
246 FreeStrBuf(&dav_uid);
247 return;
250 /* Go to the correct room. */
251 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
252 gotoroom(dav_roomname);
254 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
255 hprintf("HTTP/1.1 404 not found\r\n");
256 groupdav_common_headers();
257 hprintf("Date: %s\r\n", datestring);
258 hprintf("Content-Type: text/plain\r\n");
259 wprintf("There is no folder called \"%s\" on this server.\r\n",
260 ChrPtr(dav_roomname)
262 end_burst();
263 FreeStrBuf(&dav_roomname);
264 FreeStrBuf(&dav_uid);
265 return;
268 /* If dav_uid is non-empty, client is requesting a PROPFIND on
269 * a specific item in the room. This is not valid GroupDAV, but
270 * it is valid WebDAV.
272 if (StrLength(dav_uid) != 0) {
274 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
275 if (dav_msgnum < 0) {
276 hprintf("HTTP/1.1 404 not found\r\n");
277 groupdav_common_headers();
278 hprintf("Content-Type: text/plain\r\n");
279 wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
280 ChrPtr(dav_uid),
281 ChrPtr(dav_roomname)
283 end_burst();
284 FreeStrBuf(&dav_roomname);
285 FreeStrBuf(&dav_uid);
286 return;
289 /* Be rude. Completely ignore the XML request and simply send them
290 * everything we know about (which is going to simply be the ETag and
291 * nothing else). Let the client-side parser sort it out.
293 hprintf("HTTP/1.0 207 Multi-Status\r\n");
294 groupdav_common_headers();
295 hprintf("Date: %s\r\n", datestring);
296 hprintf("Content-type: text/xml\r\n");
297 hprintf("Content-encoding: identity\r\n");
299 begin_burst();
301 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
302 "<multistatus xmlns=\"DAV:\">"
305 wprintf("<response>");
307 wprintf("<href>");
308 groupdav_identify_host();
309 wprintf("/groupdav/");
310 urlescputs(ChrPtr(WC->wc_roomname));
311 euid_escapize(encoded_uid, ChrPtr(dav_uid));
312 wprintf("/%s", encoded_uid);
313 wprintf("</href>");
314 wprintf("<propstat>");
315 wprintf("<status>HTTP/1.1 200 OK</status>");
316 wprintf("<prop>");
317 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
318 wprintf("<getlastmodified>");
319 escputs(datestring);
320 wprintf("</getlastmodified>");
321 wprintf("</prop>");
322 wprintf("</propstat>");
324 wprintf("</response>\n");
325 wprintf("</multistatus>\n");
326 end_burst();
327 FreeStrBuf(&dav_roomname);
328 FreeStrBuf(&dav_uid);
329 return;
331 FreeStrBuf(&dav_roomname);
332 FreeStrBuf(&dav_uid);
336 * We got to this point, which means that the client is requesting
337 * a 'collection' (i.e. a list of all items in the room).
339 * Be rude. Completely ignore the XML request and simply send them
340 * everything we know about (which is going to simply be the ETag and
341 * nothing else). Let the client-side parser sort it out.
343 hprintf("HTTP/1.0 207 Multi-Status\r\n");
344 groupdav_common_headers();
345 hprintf("Date: %s\r\n", datestring);
346 hprintf("Content-type: text/xml\r\n");
347 hprintf("Content-encoding: identity\r\n");
349 begin_burst();
351 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
352 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
356 /** Transmit the collection resource (FIXME check depth and starting point) */
357 wprintf("<response>");
359 wprintf("<href>");
360 groupdav_identify_host();
361 wprintf("/groupdav/");
362 urlescputs(ChrPtr(WC->wc_roomname));
363 wprintf("</href>");
365 wprintf("<propstat>");
366 wprintf("<status>HTTP/1.1 200 OK</status>");
367 wprintf("<prop>");
368 wprintf("<displayname>");
369 escputs(ChrPtr(WC->wc_roomname));
370 wprintf("</displayname>");
371 wprintf("<resourcetype><collection/>");
373 switch(WC->wc_default_view) {
374 case VIEW_CALENDAR:
375 wprintf("<G:vevent-collection />");
376 break;
377 case VIEW_TASKS:
378 wprintf("<G:vtodo-collection />");
379 break;
380 case VIEW_ADDRESSBOOK:
381 wprintf("<G:vcard-collection />");
382 break;
385 wprintf("</resourcetype>");
386 /* FIXME get the mtime
387 wprintf("<getlastmodified>");
388 escputs(datestring);
389 wprintf("</getlastmodified>");
391 wprintf("</prop>");
392 wprintf("</propstat>");
393 wprintf("</response>");
395 /** Transmit the collection listing (FIXME check depth and starting point) */
397 serv_puts("MSGS ALL");
398 serv_getln(buf, sizeof buf);
399 if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
400 msgs = realloc(msgs, ++num_msgs * sizeof(long));
401 msgs[num_msgs-1] = atol(msgnum);
404 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
406 strcpy(uid, "");
407 now = (-1);
408 serv_printf("MSG0 %ld|3", msgs[i]);
409 serv_getln(buf, sizeof buf);
410 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
411 if (!strncasecmp(buf, "exti=", 5)) {
412 strcpy(uid, &buf[5]);
414 else if (!strncasecmp(buf, "time=", 5)) {
415 now = atol(&buf[5]);
419 if (!IsEmptyStr(uid)) {
420 wprintf("<response>");
421 wprintf("<href>");
422 groupdav_identify_host();
423 wprintf("/groupdav/");
424 urlescputs(ChrPtr(WC->wc_roomname));
425 euid_escapize(encoded_uid, uid);
426 wprintf("/%s", encoded_uid);
427 wprintf("</href>");
428 switch(WC->wc_default_view) {
429 case VIEW_CALENDAR:
430 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
431 break;
432 case VIEW_TASKS:
433 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
434 break;
435 case VIEW_ADDRESSBOOK:
436 wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
437 break;
439 wprintf("<propstat>");
440 wprintf("<status>HTTP/1.1 200 OK</status>");
441 wprintf("<prop>");
442 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
443 if (now > 0L) {
444 http_datestring(datestring, sizeof datestring, now);
445 wprintf("<getlastmodified>");
446 escputs(datestring);
447 wprintf("</getlastmodified>");
449 wprintf("</prop>");
450 wprintf("</propstat>");
451 wprintf("</response>");
455 wprintf("</multistatus>\n");
456 end_burst();
458 if (msgs != NULL) {
459 free(msgs);