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.
19 #include "webserver.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
28 long locate_message_by_uid(char *uid) {
30 char decoded_uid[1024];
34 euid_unescapize(decoded_uid, uid);
36 /************** THE NEW WAY ***********************/
37 serv_printf("EUID %s", decoded_uid);
38 serv_getln(buf, sizeof buf);
40 retval = atol(&buf[4]);
42 /***************************************************/
44 /************** THE OLD WAY ***********************
45 serv_puts("MSGS ALL|0|1");
46 serv_getln(buf, sizeof buf);
48 serv_printf("exti|%s", decoded_uid);
50 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
54 ***************************************************/
62 * List rooms (or "collections" in DAV terminology) which contain
63 * interesting groupware objects.
65 void groupdav_collection_list(char *dav_pathname, int dav_depth)
73 int is_groupware_collection = 0;
74 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
76 if (!strcmp(dav_pathname, "/")) {
79 else if (!strcasecmp(dav_pathname, "/groupdav")) {
82 else if (!strcasecmp(dav_pathname, "/groupdav/")) {
85 else if ( (!strncasecmp(dav_pathname, "/groupdav/", 10)) && (strlen(dav_pathname) > 10) ) {
90 http_datestring(datestring, sizeof datestring, now);
93 * Be rude. Completely ignore the XML request and simply send them
94 * everything we know about. Let the client sort it out.
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");
104 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
105 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
109 * If the client is requesting the root, show a root node.
111 if (starting_point == 0) {
112 wprintf("<response>");
114 groupdav_identify_host();
117 wprintf("<propstat>");
118 wprintf("<status>HTTP/1.1 200 OK</status>");
120 wprintf("<displayname>/</displayname>");
121 wprintf("<resourcetype><collection/></resourcetype>");
122 wprintf("<getlastmodified>");
124 wprintf("</getlastmodified>");
126 wprintf("</propstat>");
127 wprintf("</response>");
131 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
133 if ((starting_point + dav_depth) >= 1) {
134 wprintf("<response>");
136 groupdav_identify_host();
137 wprintf("/groupdav");
139 wprintf("<propstat>");
140 wprintf("<status>HTTP/1.1 200 OK</status>");
142 wprintf("<displayname>GroupDAV</displayname>");
143 wprintf("<resourcetype><collection/></resourcetype>");
144 wprintf("<getlastmodified>");
146 wprintf("</getlastmodified>");
148 wprintf("</propstat>");
149 wprintf("</response>");
153 * Now go through the list and make it look like a DAV collection
156 serv_getln(buf, sizeof buf);
157 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
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);
165 * For now, only list rooms that we know a GroupDAV client
166 * might be interested in. In the future we may add
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.
175 if ((view == VIEW_CALENDAR) || (view == VIEW_TASKS) || (view == VIEW_ADDRESSBOOK) ) {
176 is_groupware_collection = 1;
179 is_groupware_collection = 0;
182 if ( (is_groupware_collection) && ((starting_point + dav_depth) >= 2) ) {
183 wprintf("<response>");
186 groupdav_identify_host();
187 wprintf("/groupdav/");
188 urlescputs(roomname);
191 wprintf("<propstat>");
192 wprintf("<status>HTTP/1.1 200 OK</status>");
194 wprintf("<displayname>");
196 wprintf("</displayname>");
197 wprintf("<resourcetype><collection/>");
201 wprintf("<G:vevent-collection />");
204 wprintf("<G:vtodo-collection />");
206 case VIEW_ADDRESSBOOK:
207 wprintf("<G:vcard-collection />");
211 wprintf("</resourcetype>");
212 wprintf("<getlastmodified>");
214 wprintf("</getlastmodified>");
216 wprintf("</propstat>");
217 wprintf("</response>");
220 wprintf("</multistatus>\n");
228 * The pathname is always going to be /groupdav/room_name/msg_num
230 void groupdav_propfind(char *dav_pathname, int dav_depth, char *dav_content_type, char *dav_content) {
231 char dav_roomname[256];
234 long dav_msgnum = (-1);
237 char encoded_uid[256];
241 char datestring[256];
245 http_datestring(datestring, sizeof datestring, now);
247 extract_token(dav_roomname, dav_pathname, 2, '/', sizeof dav_roomname);
248 extract_token(dav_uid, dav_pathname, 3, '/', sizeof dav_uid);
251 * If the room name is blank, the client is requesting a
254 if (strlen(dav_roomname) == 0) {
255 groupdav_collection_list(dav_pathname, dav_depth);
259 /* Go to the correct room. */
260 if (strcasecmp(WC->wc_roomname, dav_roomname)) {
261 gotoroom(dav_roomname);
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);
268 "Content-Type: text/plain\r\n"
270 "There is no folder called \"%s\" on this server.\r\n",
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.
280 if (strlen(dav_uid) > 0) {
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();
287 "Content-Type: text/plain\r\n"
289 "Object \"%s\" was not found in the \"%s\" folder.\r\n",
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.
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");
308 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
309 "<multistatus xmlns=\"DAV:\">"
312 wprintf("<response>");
315 groupdav_identify_host();
316 wprintf("/groupdav/");
317 urlescputs(WC->wc_roomname);
318 euid_escapize(encoded_uid, dav_uid);
319 wprintf("/%s", encoded_uid);
321 wprintf("<propstat>");
322 wprintf("<status>HTTP/1.1 200 OK</status>");
323 wprintf("<prop><getetag>\"%ld\"</getetag></prop>", dav_msgnum);
324 wprintf("</propstat>");
326 wprintf("</response>\n");
327 wprintf("</multistatus>\n");
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).
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.
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");
349 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
350 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
354 /** Transmit the collection resource (FIXME check depth and starting point) */
355 wprintf("<response>");
358 groupdav_identify_host();
359 wprintf("/groupdav/");
360 urlescputs(WC->wc_roomname);
363 wprintf("<propstat>");
364 wprintf("<status>HTTP/1.1 200 OK</status>");
366 wprintf("<displayname>");
367 escputs(WC->wc_roomname);
368 wprintf("</displayname>");
369 wprintf("<resourcetype><collection/>");
371 switch(WC->wc_default_view) {
373 wprintf("<G:vevent-collection />");
376 wprintf("<G:vtodo-collection />");
378 case VIEW_ADDRESSBOOK:
379 wprintf("<G:vcard-collection />");
383 wprintf("</resourcetype>");
384 /* FIXME get the mtime
385 wprintf("<getlastmodified>");
387 wprintf("</getlastmodified>");
390 wprintf("</propstat>");
391 wprintf("</response>");
393 /** Transmit the collection listing (FIXME check depth and starting point) */
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);
402 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
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]);
413 if (strlen(uid) > 0) {
414 wprintf("<response>");
416 groupdav_identify_host();
417 wprintf("/groupdav/");
418 urlescputs(WC->wc_roomname);
419 euid_escapize(encoded_uid, uid);
420 wprintf("/%s", encoded_uid);
422 switch(WC->wc_default_view) {
424 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
427 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
429 case VIEW_ADDRESSBOOK:
430 wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
433 wprintf("<propstat>");
434 wprintf("<status>HTTP/1.1 200 OK</status>");
436 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
438 wprintf("</propstat>");
439 wprintf("</response>");
443 wprintf("</multistatus>\n");