* fix another off by one while retrieving the iterator counter params
[citadel.git] / webcit / groupdav_propfind.c
blobc0f702f4a560361a9a3e235b57cafe8911c2a14e
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 /************** THE NEW WAY ***********************/
37 serv_printf("EUID %s", decoded_uid);
38 serv_getln(buf, sizeof buf);
39 if (buf[0] == '2') {
40 retval = atol(&buf[4]);
42 /***************************************************/
44 /************** THE OLD WAY ***********************
45 serv_puts("MSGS ALL|0|1");
46 serv_getln(buf, sizeof buf);
47 if (buf[0] == '8') {
48 serv_printf("exti|%s", decoded_uid);
49 serv_puts("000");
50 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
51 retval = atol(buf);
54 ***************************************************/
56 return(retval);
62 * List rooms (or "collections" in DAV terminology) which contain
63 * interesting groupware objects.
65 void groupdav_collection_list(const char *dav_pathname, int dav_depth)
67 char buf[256];
68 char roomname[256];
69 int view;
70 char datestring[256];
71 time_t now;
72 time_t mtime;
73 int is_groupware_collection = 0;
74 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
76 if (!strcmp(dav_pathname, "/")) {
77 starting_point = 0;
79 else if (!strcasecmp(dav_pathname, "/groupdav")) {
80 starting_point = 1;
82 else if (!strcasecmp(dav_pathname, "/groupdav/")) {
83 starting_point = 1;
85 else if ( (!strncasecmp(dav_pathname, "/groupdav/", 10)) && (strlen(dav_pathname) > 10) ) {
86 starting_point = 2;
89 now = time(NULL);
90 http_datestring(datestring, sizeof datestring, now);
92 /**
93 * Be rude. Completely ignore the XML request and simply send them
94 * everything we know about. Let the client sort it out.
96 hprintf("HTTP/1.0 207 Multi-Status\r\n");
97 groupdav_common_headers();
98 hprintf("Date: %s\r\n", datestring);
99 hprintf("Content-type: text/xml\r\n");
100 hprintf("Content-encoding: identity\r\n");
102 begin_burst();
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>");
113 wprintf("<href>");
114 groupdav_identify_host();
115 wprintf("/");
116 wprintf("</href>");
117 wprintf("<propstat>");
118 wprintf("<status>HTTP/1.1 200 OK</status>");
119 wprintf("<prop>");
120 wprintf("<displayname>/</displayname>");
121 wprintf("<resourcetype><collection/></resourcetype>");
122 wprintf("<getlastmodified>");
123 escputs(datestring);
124 wprintf("</getlastmodified>");
125 wprintf("</prop>");
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>");
135 wprintf("<href>");
136 groupdav_identify_host();
137 wprintf("/groupdav");
138 wprintf("</href>");
139 wprintf("<propstat>");
140 wprintf("<status>HTTP/1.1 200 OK</status>");
141 wprintf("<prop>");
142 wprintf("<displayname>GroupDAV</displayname>");
143 wprintf("<resourcetype><collection/></resourcetype>");
144 wprintf("<getlastmodified>");
145 escputs(datestring);
146 wprintf("</getlastmodified>");
147 wprintf("</prop>");
148 wprintf("</propstat>");
149 wprintf("</response>");
153 * Now go through the list and make it look like a DAV collection
155 serv_puts("LKRA");
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
167 * the rest.
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;
178 else {
179 is_groupware_collection = 0;
182 if ( (is_groupware_collection) && ((starting_point + dav_depth) >= 2) ) {
183 wprintf("<response>");
185 wprintf("<href>");
186 groupdav_identify_host();
187 wprintf("/groupdav/");
188 urlescputs(roomname);
189 wprintf("/</href>");
191 wprintf("<propstat>");
192 wprintf("<status>HTTP/1.1 200 OK</status>");
193 wprintf("<prop>");
194 wprintf("<displayname>");
195 escputs(roomname);
196 wprintf("</displayname>");
197 wprintf("<resourcetype><collection/>");
199 switch(view) {
200 case VIEW_CALENDAR:
201 wprintf("<G:vevent-collection />");
202 break;
203 case VIEW_TASKS:
204 wprintf("<G:vtodo-collection />");
205 break;
206 case VIEW_ADDRESSBOOK:
207 wprintf("<G:vcard-collection />");
208 break;
211 wprintf("</resourcetype>");
212 wprintf("<getlastmodified>");
213 escputs(datestring);
214 wprintf("</getlastmodified>");
215 wprintf("</prop>");
216 wprintf("</propstat>");
217 wprintf("</response>");
220 wprintf("</multistatus>\n");
222 end_burst();
228 * The pathname is always going to be /groupdav/room_name/msg_num
230 void groupdav_propfind(StrBuf *dav_pathname, int dav_depth, StrBuf *dav_content_type, StrBuf *dav_content, int offset) {
231 StrBuf *dav_roomname;
232 StrBuf *dav_uid;
233 char msgnum[256];
234 long dav_msgnum = (-1);
235 char buf[256];
236 char uid[256];
237 char encoded_uid[256];
238 long *msgs = NULL;
239 int num_msgs = 0;
240 int i;
241 char datestring[256];
242 time_t now;
244 now = time(NULL);
245 http_datestring(datestring, sizeof datestring, now);
247 dav_roomname = NewStrBuf();
248 dav_uid = NewStrBuf();
249 StrBufExtract_token(dav_roomname, dav_pathname, 2, '/');
250 StrBufExtract_token(dav_uid, dav_pathname, 3, '/');
253 * If the room name is blank, the client is requesting a
254 * folder list.
256 if (StrLength(dav_roomname) == 0) {
257 groupdav_collection_list(ChrPtr(dav_pathname), dav_depth);
258 FreeStrBuf(&dav_roomname);
259 FreeStrBuf(&dav_uid);
260 return;
263 /* Go to the correct room. */
264 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
265 gotoroom(dav_roomname);
267 if (strcasecmp(ChrPtr(WC->wc_roomname), ChrPtr(dav_roomname))) {
268 hprintf("HTTP/1.1 404 not found\r\n");
269 groupdav_common_headers();
270 hprintf("Date: %s\r\n", datestring);
271 hprintf("Content-Type: text/plain\r\n");
272 wprintf("There is no folder called \"%s\" on this server.\r\n",
273 ChrPtr(dav_roomname)
275 end_burst();
276 FreeStrBuf(&dav_roomname);
277 FreeStrBuf(&dav_uid);
278 return;
281 /* If dav_uid is non-empty, client is requesting a PROPFIND on
282 * a specific item in the room. This is not valid GroupDAV, but
283 * it is valid WebDAV.
285 if (StrLength(dav_uid) != 0) {
287 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
288 if (dav_msgnum < 0) {
289 hprintf("HTTP/1.1 404 not found\r\n");
290 groupdav_common_headers();
291 hprintf("Content-Type: text/plain\r\n");
292 wprintf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
293 ChrPtr(dav_uid),
294 ChrPtr(dav_roomname)
296 end_burst();
297 FreeStrBuf(&dav_roomname);
298 FreeStrBuf(&dav_uid);
299 return;
302 /* Be rude. Completely ignore the XML request and simply send them
303 * everything we know about (which is going to simply be the ETag and
304 * nothing else). Let the client-side parser sort it out.
306 hprintf("HTTP/1.0 207 Multi-Status\r\n");
307 groupdav_common_headers();
308 hprintf("Date: %s\r\n", datestring);
309 hprintf("Content-type: text/xml\r\n");
310 hprintf("Content-encoding: identity\r\n");
312 begin_burst();
314 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
315 "<multistatus xmlns=\"DAV:\">"
318 wprintf("<response>");
320 wprintf("<href>");
321 groupdav_identify_host();
322 wprintf("/groupdav/");
323 urlescputs(ChrPtr(WC->wc_roomname));
324 euid_escapize(encoded_uid, ChrPtr(dav_uid));
325 wprintf("/%s", encoded_uid);
326 wprintf("</href>");
327 wprintf("<propstat>");
328 wprintf("<status>HTTP/1.1 200 OK</status>");
329 wprintf("<prop>");
330 wprintf("<getetag>\"%ld\"</getetag>", dav_msgnum);
331 wprintf("<getlastmodified>");
332 escputs(datestring);
333 wprintf("</getlastmodified>");
334 wprintf("</prop>");
335 wprintf("</propstat>");
337 wprintf("</response>\n");
338 wprintf("</multistatus>\n");
339 end_burst();
340 FreeStrBuf(&dav_roomname);
341 FreeStrBuf(&dav_uid);
342 return;
344 FreeStrBuf(&dav_roomname);
345 FreeStrBuf(&dav_uid);
349 * We got to this point, which means that the client is requesting
350 * a 'collection' (i.e. a list of all items in the room).
352 * Be rude. Completely ignore the XML request and simply send them
353 * everything we know about (which is going to simply be the ETag and
354 * nothing else). Let the client-side parser sort it out.
356 hprintf("HTTP/1.0 207 Multi-Status\r\n");
357 groupdav_common_headers();
358 hprintf("Date: %s\r\n", datestring);
359 hprintf("Content-type: text/xml\r\n");
360 hprintf("Content-encoding: identity\r\n");
362 begin_burst();
364 wprintf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
365 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
369 /** Transmit the collection resource (FIXME check depth and starting point) */
370 wprintf("<response>");
372 wprintf("<href>");
373 groupdav_identify_host();
374 wprintf("/groupdav/");
375 urlescputs(ChrPtr(WC->wc_roomname));
376 wprintf("</href>");
378 wprintf("<propstat>");
379 wprintf("<status>HTTP/1.1 200 OK</status>");
380 wprintf("<prop>");
381 wprintf("<displayname>");
382 escputs(ChrPtr(WC->wc_roomname));
383 wprintf("</displayname>");
384 wprintf("<resourcetype><collection/>");
386 switch(WC->wc_default_view) {
387 case VIEW_CALENDAR:
388 wprintf("<G:vevent-collection />");
389 break;
390 case VIEW_TASKS:
391 wprintf("<G:vtodo-collection />");
392 break;
393 case VIEW_ADDRESSBOOK:
394 wprintf("<G:vcard-collection />");
395 break;
398 wprintf("</resourcetype>");
399 /* FIXME get the mtime
400 wprintf("<getlastmodified>");
401 escputs(datestring);
402 wprintf("</getlastmodified>");
404 wprintf("</prop>");
405 wprintf("</propstat>");
406 wprintf("</response>");
408 /** Transmit the collection listing (FIXME check depth and starting point) */
410 serv_puts("MSGS ALL");
411 serv_getln(buf, sizeof buf);
412 if (buf[0] == '1') while (serv_getln(msgnum, sizeof msgnum), strcmp(msgnum, "000")) {
413 msgs = realloc(msgs, ++num_msgs * sizeof(long));
414 msgs[num_msgs-1] = atol(msgnum);
417 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
419 strcpy(uid, "");
420 now = (-1);
421 serv_printf("MSG0 %ld|3", msgs[i]);
422 serv_getln(buf, sizeof buf);
423 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
424 if (!strncasecmp(buf, "exti=", 5)) {
425 strcpy(uid, &buf[5]);
427 else if (!strncasecmp(buf, "time=", 5)) {
428 now = atol(&buf[5]);
432 if (!IsEmptyStr(uid)) {
433 wprintf("<response>");
434 wprintf("<href>");
435 groupdav_identify_host();
436 wprintf("/groupdav/");
437 urlescputs(ChrPtr(WC->wc_roomname));
438 euid_escapize(encoded_uid, uid);
439 wprintf("/%s", encoded_uid);
440 wprintf("</href>");
441 switch(WC->wc_default_view) {
442 case VIEW_CALENDAR:
443 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
444 break;
445 case VIEW_TASKS:
446 wprintf("<getcontenttype>text/x-ical</getcontenttype>");
447 break;
448 case VIEW_ADDRESSBOOK:
449 wprintf("<getcontenttype>text/x-vcard</getcontenttype>");
450 break;
452 wprintf("<propstat>");
453 wprintf("<status>HTTP/1.1 200 OK</status>");
454 wprintf("<prop>");
455 wprintf("<getetag>\"%ld\"</getetag>", msgs[i]);
456 if (now > 0L) {
457 http_datestring(datestring, sizeof datestring, now);
458 wprintf("<getlastmodified>");
459 escputs(datestring);
460 wprintf("</getlastmodified>");
462 wprintf("</prop>");
463 wprintf("</propstat>");
464 wprintf("</response>");
468 wprintf("</multistatus>\n");
469 end_burst();
471 if (msgs != NULL) {
472 free(msgs);