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 * Whining about it will be summarily ignored.
11 * --> XML is deliberately output with no whitespace/newlines between tags.
12 * This makes it difficult to read, but we have discovered clients which
13 * crash when you try to pretty it up.
18 #include "webserver.h"
22 * Given an encoded UID, translate that to an unencoded Citadel EUID and
23 * then search for it in the current room. Return a message number or -1
27 long locate_message_by_uid(const char *uid) {
29 char decoded_uid[1024];
33 euid_unescapize(decoded_uid, uid);
35 /* ask Citadel if we have this one */
36 serv_printf("EUID %s", decoded_uid);
37 serv_getln(buf, sizeof buf);
39 retval = atol(&buf[4]);
48 * List rooms (or "collections" in DAV terminology) which contain
49 * interesting groupware objects.
51 void groupdav_collection_list(void)
60 int is_groupware_collection = 0;
61 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
63 if (WCC->Hdr->HR.Handler == NULL) {
66 else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
74 http_datestring(datestring, sizeof datestring, now);
77 * Be rude. Completely ignore the XML request and simply send them
78 * everything we know about. Let the client sort it out.
80 hprintf("HTTP/1.0 207 Multi-Status\r\n");
81 groupdav_common_headers();
82 hprintf("Date: %s\r\n", datestring);
83 hprintf("Content-type: text/xml\r\n");
84 hprintf("Content-encoding: identity\r\n");
88 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
89 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
93 * If the client is requesting the root, show a root node.
95 if (starting_point == 0) {
96 wc_printf("<response>");
98 groupdav_identify_host();
100 wc_printf("</href>");
101 wc_printf("<propstat>");
102 wc_printf("<status>HTTP/1.1 200 OK</status>");
104 wc_printf("<displayname>/</displayname>");
105 wc_printf("<resourcetype><collection/></resourcetype>");
106 wc_printf("<getlastmodified>");
108 wc_printf("</getlastmodified>");
109 wc_printf("</prop>");
110 wc_printf("</propstat>");
111 wc_printf("</response>");
115 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
117 if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
118 wc_printf("<response>");
120 groupdav_identify_host();
121 wc_printf("/groupdav");
122 wc_printf("</href>");
123 wc_printf("<propstat>");
124 wc_printf("<status>HTTP/1.1 200 OK</status>");
126 wc_printf("<displayname>GroupDAV</displayname>");
127 wc_printf("<resourcetype><collection/></resourcetype>");
128 wc_printf("<getlastmodified>");
130 wc_printf("</getlastmodified>");
131 wc_printf("</prop>");
132 wc_printf("</propstat>");
133 wc_printf("</response>");
137 * Now go through the list and make it look like a DAV collection
140 serv_getln(buf, sizeof buf);
141 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
143 extract_token(roomname, buf, 0, '|', sizeof roomname);
144 view = extract_int(buf, 7);
145 mtime = extract_long(buf, 8);
146 http_datestring(datestring, sizeof datestring, mtime);
149 * For now, only list rooms that we know a GroupDAV client
150 * might be interested in. In the future we may add
153 * We determine the type of objects which are stored in each
154 * room by looking at the *default* view for the room. This
155 * allows, for example, a Calendar room to appear as a
156 * GroupDAV calendar even if the user has switched it to a
157 * Calendar List view.
159 if ( (view == VIEW_CALENDAR) ||
160 (view == VIEW_TASKS) ||
161 (view == VIEW_ADDRESSBOOK) ||
162 (view == VIEW_NOTES) ||
163 (view == VIEW_JOURNAL) ||
166 is_groupware_collection = 1;
169 is_groupware_collection = 0;
172 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
173 wc_printf("<response>");
176 groupdav_identify_host();
177 wc_printf("/groupdav/");
178 urlescputs(roomname);
179 wc_printf("/</href>");
181 wc_printf("<propstat>");
182 wc_printf("<status>HTTP/1.1 200 OK</status>");
184 wc_printf("<displayname>");
186 wc_printf("</displayname>");
187 wc_printf("<resourcetype><collection/>");
191 wc_printf("<G:vevent-collection />");
194 wc_printf("<G:vtodo-collection />");
196 case VIEW_ADDRESSBOOK:
197 wc_printf("<G:vcard-collection />");
200 wc_printf("<G:vnotes-collection />");
203 wc_printf("<G:vjournal-collection />");
206 wc_printf("<G:wiki-collection />");
210 wc_printf("</resourcetype>");
211 wc_printf("<getlastmodified>");
213 wc_printf("</getlastmodified>");
214 wc_printf("</prop>");
215 wc_printf("</propstat>");
216 wc_printf("</response>");
219 wc_printf("</multistatus>\n");
227 * The pathname is always going to be /groupdav/room_name/msg_num
229 void groupdav_propfind(void)
232 StrBuf *dav_roomname;
236 long dav_msgnum = (-1);
238 char encoded_uid[256];
242 char datestring[256];
246 http_datestring(datestring, sizeof datestring, now);
248 dav_roomname = NewStrBuf();
249 dav_uid = NewStrBuf();
250 StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
251 StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
254 * If the room name is blank, the client is requesting a
257 if (StrLength(dav_roomname) == 0) {
258 groupdav_collection_list();
259 FreeStrBuf(&dav_roomname);
260 FreeStrBuf(&dav_uid);
264 /* Go to the correct room. */
265 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
266 gotoroom(dav_roomname);
268 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
269 hprintf("HTTP/1.1 404 not found\r\n");
270 groupdav_common_headers();
271 hprintf("Date: %s\r\n", datestring);
272 hprintf("Content-Type: text/plain\r\n");
273 wc_printf("There is no folder called \"%s\" on this server.\r\n",
277 FreeStrBuf(&dav_roomname);
278 FreeStrBuf(&dav_uid);
282 /* If dav_uid is non-empty, client is requesting a PROPFIND on
283 * a specific item in the room. This is not valid GroupDAV, but
284 * it is valid WebDAV.
286 if (StrLength(dav_uid) != 0) {
288 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
289 if (dav_msgnum < 0) {
290 hprintf("HTTP/1.1 404 not found\r\n");
291 groupdav_common_headers();
292 hprintf("Content-Type: text/plain\r\n");
293 wc_printf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
298 FreeStrBuf(&dav_roomname);
299 FreeStrBuf(&dav_uid);
303 /* Be rude. Completely ignore the XML request and simply send them
304 * everything we know about (which is going to simply be the ETag and
305 * nothing else). Let the client-side parser sort it out.
307 hprintf("HTTP/1.0 207 Multi-Status\r\n");
308 groupdav_common_headers();
309 hprintf("Date: %s\r\n", datestring);
310 hprintf("Content-type: text/xml\r\n");
311 hprintf("Content-encoding: identity\r\n");
315 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
316 "<multistatus xmlns=\"DAV:\">"
319 wc_printf("<response>");
322 groupdav_identify_host();
323 wc_printf("/groupdav/");
324 urlescputs(ChrPtr(WCC->CurRoom.name));
325 euid_escapize(encoded_uid, ChrPtr(dav_uid));
326 wc_printf("/%s", encoded_uid);
327 wc_printf("</href>");
328 wc_printf("<propstat>");
329 wc_printf("<status>HTTP/1.1 200 OK</status>");
331 wc_printf("<getetag>\"%ld\"</getetag>", dav_msgnum);
332 wc_printf("<getlastmodified>");
334 wc_printf("</getlastmodified>");
335 wc_printf("</prop>");
336 wc_printf("</propstat>");
338 wc_printf("</response>\n");
339 wc_printf("</multistatus>\n");
341 FreeStrBuf(&dav_roomname);
342 FreeStrBuf(&dav_uid);
345 FreeStrBuf(&dav_roomname);
346 FreeStrBuf(&dav_uid);
350 * We got to this point, which means that the client is requesting
351 * a 'collection' (i.e. a list of all items in the room).
353 * Be rude. Completely ignore the XML request and simply send them
354 * everything we know about (which is going to simply be the ETag and
355 * nothing else). Let the client-side parser sort it out.
357 hprintf("HTTP/1.0 207 Multi-Status\r\n");
358 groupdav_common_headers();
359 hprintf("Date: %s\r\n", datestring);
360 hprintf("Content-type: text/xml\r\n");
361 hprintf("Content-encoding: identity\r\n");
365 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
366 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
370 /* Transmit the collection resource (FIXME check depth and starting point) */
371 wc_printf("<response>");
374 groupdav_identify_host();
375 wc_printf("/groupdav/");
376 urlescputs(ChrPtr(WCC->CurRoom.name));
377 wc_printf("</href>");
379 wc_printf("<propstat>");
380 wc_printf("<status>HTTP/1.1 200 OK</status>");
382 wc_printf("<displayname>");
383 escputs(ChrPtr(WCC->CurRoom.name));
384 wc_printf("</displayname>");
385 wc_printf("<resourcetype><collection/>");
387 switch(WCC->CurRoom.defview) {
389 wc_printf("<G:vevent-collection />");
392 wc_printf("<G:vtodo-collection />");
394 case VIEW_ADDRESSBOOK:
395 wc_printf("<G:vcard-collection />");
399 wc_printf("</resourcetype>");
400 /* FIXME get the mtime
401 wc_printf("<getlastmodified>");
403 wc_printf("</getlastmodified>");
405 wc_printf("</prop>");
406 wc_printf("</propstat>");
407 wc_printf("</response>");
409 /* Transmit the collection listing (FIXME check depth and starting point) */
411 MsgNum = NewStrBuf();
412 serv_puts("MSGS ALL");
414 StrBuf_ServGetln(MsgNum);
415 if (GetServerStatus(MsgNum, NULL) == 1)
416 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) {
417 msgs = realloc(msgs, ++num_msgs * sizeof(long));
418 msgs[num_msgs-1] = StrTol(MsgNum);
421 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
425 serv_printf("MSG0 %ld|3", msgs[i]);
426 StrBuf_ServGetln(MsgNum);
427 if (GetServerStatus(MsgNum, NULL) == 1)
428 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))
430 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
431 strcpy(uid, &ChrPtr(MsgNum)[5]);
433 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
434 now = atol(&ChrPtr(MsgNum)[5]);
438 if (!IsEmptyStr(uid)) {
439 wc_printf("<response>");
441 groupdav_identify_host();
442 wc_printf("/groupdav/");
443 urlescputs(ChrPtr(WCC->CurRoom.name));
444 euid_escapize(encoded_uid, uid);
445 wc_printf("/%s", encoded_uid);
446 wc_printf("</href>");
447 switch(WCC->CurRoom.defview) {
449 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
452 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
454 case VIEW_ADDRESSBOOK:
455 wc_printf("<getcontenttype>text/x-vcard</getcontenttype>");
458 wc_printf("<propstat>");
459 wc_printf("<status>HTTP/1.1 200 OK</status>");
461 wc_printf("<getetag>\"%ld\"</getetag>", msgs[i]);
463 http_datestring(datestring, sizeof datestring, now);
464 wc_printf("<getlastmodified>");
466 wc_printf("</getlastmodified>");
468 wc_printf("</prop>");
469 wc_printf("</propstat>");
470 wc_printf("</response>");
475 wc_printf("</multistatus>\n");