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(const char *uid) {
30 char decoded_uid[1024];
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);
40 retval = atol(&buf[4]);
49 * List rooms (or "collections" in DAV terminology) which contain
50 * interesting groupware objects.
52 void groupdav_collection_list(void)
61 int is_groupware_collection = 0;
62 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
64 if (WCC->Hdr->HR.Handler == NULL) {
67 else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
75 http_datestring(datestring, sizeof datestring, now);
78 * Be rude. Completely ignore the XML request and simply send them
79 * everything we know about. Let the client sort it out.
81 hprintf("HTTP/1.0 207 Multi-Status\r\n");
82 groupdav_common_headers();
83 hprintf("Date: %s\r\n", datestring);
84 hprintf("Content-type: text/xml\r\n");
85 hprintf("Content-encoding: identity\r\n");
89 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
90 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
94 * If the client is requesting the root, show a root node.
96 if (starting_point == 0) {
97 wprintf("<response>");
99 groupdav_identify_host();
102 wprintf("<propstat>");
103 wprintf("<status>HTTP/1.1 200 OK</status>");
105 wprintf("<displayname>/</displayname>");
106 wprintf("<resourcetype><collection/></resourcetype>");
107 wprintf("<getlastmodified>");
109 wprintf("</getlastmodified>");
111 wprintf("</propstat>");
112 wprintf("</response>");
116 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
118 if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
119 wprintf("<response>");
121 groupdav_identify_host();
122 wprintf("/groupdav");
124 wprintf("<propstat>");
125 wprintf("<status>HTTP/1.1 200 OK</status>");
127 wprintf("<displayname>GroupDAV</displayname>");
128 wprintf("<resourcetype><collection/></resourcetype>");
129 wprintf("<getlastmodified>");
131 wprintf("</getlastmodified>");
133 wprintf("</propstat>");
134 wprintf("</response>");
138 * Now go through the list and make it look like a DAV collection
141 serv_getln(buf, sizeof buf);
142 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
144 extract_token(roomname, buf, 0, '|', sizeof roomname);
145 view = extract_int(buf, 7);
146 mtime = extract_long(buf, 8);
147 http_datestring(datestring, sizeof datestring, mtime);
150 * For now, only list rooms that we know a GroupDAV client
151 * might be interested in. In the future we may add
154 * We determine the type of objects which are stored in each
155 * room by looking at the *default* view for the room. This
156 * allows, for example, a Calendar room to appear as a
157 * GroupDAV calendar even if the user has switched it to a
158 * Calendar List view.
160 if ((view == VIEW_CALENDAR) ||
161 (view == VIEW_TASKS) ||
162 (view == VIEW_ADDRESSBOOK) ||
163 (view == VIEW_NOTES) ||
164 (view == VIEW_JOURNAL) ) {
165 is_groupware_collection = 1;
168 is_groupware_collection = 0;
171 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
172 wprintf("<response>");
175 groupdav_identify_host();
176 wprintf("/groupdav/");
177 urlescputs(roomname);
180 wprintf("<propstat>");
181 wprintf("<status>HTTP/1.1 200 OK</status>");
183 wprintf("<displayname>");
185 wprintf("</displayname>");
186 wprintf("<resourcetype><collection/>");
190 wprintf("<G:vevent-collection />");
193 wprintf("<G:vtodo-collection />");
195 case VIEW_ADDRESSBOOK:
196 wprintf("<G:vcard-collection />");
199 wprintf("<G:vnotes-collection />");
202 wprintf("<G:vjournal-collection />");
206 wprintf("</resourcetype>");
207 wprintf("<getlastmodified>");
209 wprintf("</getlastmodified>");
211 wprintf("</propstat>");
212 wprintf("</response>");
215 wprintf("</multistatus>\n");
223 * The pathname is always going to be /groupdav/room_name/msg_num
225 void groupdav_propfind(void)
228 StrBuf *dav_roomname;
232 long dav_msgnum = (-1);
234 char encoded_uid[256];
238 char datestring[256];
242 http_datestring(datestring, sizeof datestring, now);
244 dav_roomname = NewStrBuf();
245 dav_uid = NewStrBuf();
246 StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
247 StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
250 * If the room name is blank, the client is requesting a
253 if (StrLength(dav_roomname) == 0) {
254 groupdav_collection_list();
255 FreeStrBuf(&dav_roomname);
256 FreeStrBuf(&dav_uid);
260 /* Go to the correct room. */
261 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
262 gotoroom(dav_roomname);
264 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
265 hprintf("HTTP/1.1 404 not found\r\n");
266 groupdav_common_headers();
267 hprintf("Date: %s\r\n", datestring);
268 hprintf("Content-Type: text/plain\r\n");
269 wprintf("There is no folder called \"%s\" on this server.\r\n",
273 FreeStrBuf(&dav_roomname);
274 FreeStrBuf(&dav_uid);
278 /* If dav_uid is non-empty, client is requesting a PROPFIND on
279 * a specific item in the room. This is not valid GroupDAV, but
280 * it is valid WebDAV.
282 if (StrLength(dav_uid) != 0) {
284 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
285 if (dav_msgnum < 0) {
286 hprintf("HTTP/1.1 404 not found\r\n");
287 groupdav_common_headers();
288 hprintf("Content-Type: text/plain\r\n");
289 wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
294 FreeStrBuf(&dav_roomname);
295 FreeStrBuf(&dav_uid);
299 /* Be rude. Completely ignore the XML request and simply send them
300 * everything we know about (which is going to simply be the ETag and
301 * nothing else). Let the client-side parser sort it out.
303 hprintf("HTTP/1.0 207 Multi-Status\r\n");
304 groupdav_common_headers();
305 hprintf("Date: %s\r\n", datestring);
306 hprintf("Content-type: text/xml\r\n");
307 hprintf("Content-encoding: identity\r\n");
311 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
312 "<multistatus xmlns=\"DAV:\">"
315 wprintf("<response>");
318 groupdav_identify_host();
319 wprintf("/groupdav/");
320 urlescputs(ChrPtr(WC->wc_roomname));
321 euid_escapize(encoded_uid, ChrPtr(dav_uid));
322 wprintf("/%s", encoded_uid);
324 wprintf("<propstat>");
325 wprintf("<status>HTTP/1.1 200 OK</status>");
327 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
328 wprintf("<getlastmodified>");
330 wprintf("</getlastmodified>");
332 wprintf("</propstat>");
334 wprintf("</response>\n");
335 wprintf("</multistatus>\n");
337 FreeStrBuf(&dav_roomname);
338 FreeStrBuf(&dav_uid);
341 FreeStrBuf(&dav_roomname);
342 FreeStrBuf(&dav_uid);
346 * We got to this point, which means that the client is requesting
347 * a 'collection' (i.e. a list of all items in the room).
349 * Be rude. Completely ignore the XML request and simply send them
350 * everything we know about (which is going to simply be the ETag and
351 * nothing else). Let the client-side parser sort it out.
353 hprintf("HTTP/1.0 207 Multi-Status\r\n");
354 groupdav_common_headers();
355 hprintf("Date: %s\r\n", datestring);
356 hprintf("Content-type: text/xml\r\n");
357 hprintf("Content-encoding: identity\r\n");
361 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
362 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
366 /** Transmit the collection resource (FIXME check depth and starting point) */
367 wprintf("<response>");
370 groupdav_identify_host();
371 wprintf("/groupdav/");
372 urlescputs(ChrPtr(WC->wc_roomname));
375 wprintf("<propstat>");
376 wprintf("<status>HTTP/1.1 200 OK</status>");
378 wprintf("<displayname>");
379 escputs(ChrPtr(WC->wc_roomname));
380 wprintf("</displayname>");
381 wprintf("<resourcetype><collection/>");
383 switch(WC->wc_default_view) {
385 wprintf("<G:vevent-collection />");
388 wprintf("<G:vtodo-collection />");
390 case VIEW_ADDRESSBOOK:
391 wprintf("<G:vcard-collection />");
395 wprintf("</resourcetype>");
396 /* FIXME get the mtime
397 wprintf("<getlastmodified>");
399 wprintf("</getlastmodified>");
402 wprintf("</propstat>");
403 wprintf("</response>");
405 /** Transmit the collection listing (FIXME check depth and starting point) */
407 MsgNum = NewStrBuf ();
408 serv_puts("MSGS ALL");
410 StrBuf_ServGetln(MsgNum);
411 if (GetServerStatus(MsgNum, NULL) == 1)
412 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) {
413 msgs = realloc(msgs, ++num_msgs * sizeof(long));
414 msgs[num_msgs-1] = StrTol(MsgNum);
417 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
421 serv_printf("MSG0 %ld|3", msgs[i]);
422 StrBuf_ServGetln(MsgNum);
423 if (GetServerStatus(MsgNum, NULL) == 1)
424 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))
426 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
427 strcpy(uid, &ChrPtr(MsgNum)[5]);
429 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
430 now = atol(&ChrPtr(MsgNum)[5]);
434 if (!IsEmptyStr(uid)) {
435 wprintf("<response>");
437 groupdav_identify_host();
438 wprintf("/groupdav/");
439 urlescputs(ChrPtr(WC->wc_roomname));
440 euid_escapize(encoded_uid, uid);
441 wprintf("/%s", encoded_uid);
443 switch(WC->wc_default_view) {
445 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
448 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
450 case VIEW_ADDRESSBOOK:
451 wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
454 wprintf("<propstat>");
455 wprintf("<status>HTTP/1.1 200 OK</status>");
457 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
459 http_datestring(datestring, sizeof datestring, now);
460 wprintf("<getlastmodified>");
462 wprintf("</getlastmodified>");
465 wprintf("</propstat>");
466 wprintf("</response>");
471 wprintf("</multistatus>\n");