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]);
45 const folder *GetRESTFolder(int IgnoreFloor, HashList *Subfolders)
49 const folder *ThisFolder = NULL;
50 const folder *FoundFolder = NULL;
51 const folder *BestGuess = NULL;
58 int iRoom, jURL, urlp;
62 * Guess room: if the full URL matches a room, list thats it. We also need to remember direct sub rooms.
63 * if the URL is longer, we need to find the "best guess" so we can find the room we're in, and the rest of the URL will be uids and so on.
65 itfl = GetNewHashPos(WCC->Floors, 0);
66 urlp = GetCount(WCC->Directory);
68 while (GetNextHashPos(WCC->Floors, itfl, &len, &Key, &vFolder) &&
72 if (!IgnoreFloor && /* so we can handle legacy URLS... */
73 (ThisFolder->Floor != WCC->CurrentFloor))
77 if (ThisFolder->nRoomNameParts > 1)
79 /*TODO: is that number all right? */
80 if (urlp - ThisFolder->nRoomNameParts != 2) {
81 // if (BestGuess != NULL)
84 // itd = GetNewHashPos(WCC->Directory, 0);
85 // GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
87 itd = GetNewHashPos(WCC->Directory, 0);
88 GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
90 for (iRoom = 0, /* Fast forward the floorname as we checked it above: */ jURL = IgnoreFloor;
92 (iRoom <= ThisFolder->nRoomNameParts) && (jURL <= urlp);
94 iRoom++, jURL++, GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir))
97 if (strcmp(ChrPtr(ThisFolder->RoomNameParts[iRoom]),
106 if ((iRoom == ThisFolder->nRoomNameParts) && (jURL == urlp))
108 FoundFolder = ThisFolder;
110 /* URL got more parts then this room, so we remember it for the best guess*/
111 else if ((jURL <= urlp) &&
112 (ThisFolder->nRoomNameParts <= nBestGuess))
114 BestGuess = ThisFolder;
115 nBestGuess = jURL - 1;
117 /* Room has more parts than the URL, it might be a sub-room? */
118 else if (iRoom <ThisFolder->nRoomNameParts)
119 {//// TODO: ThisFolder->nRoomNameParts == urlp - IgnoreFloor???
120 Put(Subfolders, SKEY(ThisFolder->name), ThisFolder, reference_free_handler);
124 delta = GetCount(WCC->Directory) - ThisFolder->nRoomNameParts;
125 if ((delta != 2) && (nBestGuess > 1))
128 itd = GetNewHashPos(WCC->Directory, 0);
130 if (!GetNextHashPos(WCC->Directory,
131 itd, &len, &Key, &vDir) ||
140 Dir = (StrBuf*) vDir;
141 if (strcmp(ChrPtr(ThisFolder->name),
150 DeleteHashPos(&itfl);
154 BestGuess = ThisFolder;
157 FoundFolder = ThisFolder;;
160 DeleteHashPos(&itfl);
161 if (FoundFolder != NULL)
175 const folder *ThisFolder;
176 HashList *SubRooms = NULL;
178 State = REST_TOPLEVEL;
180 if (WCC->Hdr->HR.Handler != NULL)
181 State |= REST_IN_NAMESPACE;
183 Count = GetCount(WCC->Directory);
185 if (Count == 0) return State;
187 if (Count >= 1) State |=REST_IN_FLOOR;
188 if (Count == 1) return State;
191 State |= REST_IN_FLOOR;
192 SubRooms = NewHash(1, Flathash);
193 ThisFolder = GetRESTFolder(0, SubRooms);
194 WCC->ThisRoom = ThisFolder;
195 if (ThisFolder != NULL)
197 gotoroom(ThisFolder->name);
198 State |= REST_IN_ROOM;
206 * More than 3 params and no floor found?
207 * -> fall back to old non-floored notation
210 if ((Count >= 3) && (WCC->CurrentFloor == NULL))
212 SubRooms = NewHash(1, Flathash);
213 ThisFolder = GetRESTFolder(1, SubRooms);
214 WCC->ThisRoom = ThisFolder;
215 if (ThisFolder != NULL)
217 gotoroom(ThisFolder->name);
218 State |= REST_IN_ROOM;
226 if (Count == 3) return State;
228 /// TODO: ID detection
229 /// TODO: File detection
238 * List rooms (or "collections" in DAV terminology) which contain
239 * interesting groupware objects.
241 void groupdav_collection_list(void)
247 char datestring[256];
250 int is_groupware_collection = 0;
251 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
253 if (WCC->Hdr->HR.Handler == NULL) {
256 else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
264 http_datestring(datestring, sizeof datestring, now);
267 * Be rude. Completely ignore the XML request and simply send them
268 * everything we know about. Let the client sort it out.
270 hprintf("HTTP/1.0 207 Multi-Status\r\n");
271 groupdav_common_headers();
272 hprintf("Date: %s\r\n", datestring);
273 hprintf("Content-type: text/xml\r\n");
274 hprintf("Content-encoding: identity\r\n");
278 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
279 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
283 * If the client is requesting the root, show a root node.
285 if (starting_point == 0) {
286 wc_printf("<response>");
288 groupdav_identify_host();
290 wc_printf("</href>");
291 wc_printf("<propstat>");
292 wc_printf("<status>HTTP/1.1 200 OK</status>");
294 wc_printf("<displayname>/</displayname>");
295 wc_printf("<resourcetype><collection/></resourcetype>");
296 wc_printf("<getlastmodified>");
298 wc_printf("</getlastmodified>");
299 wc_printf("</prop>");
300 wc_printf("</propstat>");
301 wc_printf("</response>");
305 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
307 if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
308 wc_printf("<response>");
310 groupdav_identify_host();
311 wc_printf("/groupdav");
312 wc_printf("</href>");
313 wc_printf("<propstat>");
314 wc_printf("<status>HTTP/1.1 200 OK</status>");
316 wc_printf("<displayname>GroupDAV</displayname>");
317 wc_printf("<resourcetype><collection/></resourcetype>");
318 wc_printf("<getlastmodified>");
320 wc_printf("</getlastmodified>");
321 wc_printf("</prop>");
322 wc_printf("</propstat>");
323 wc_printf("</response>");
327 * Now go through the list and make it look like a DAV collection
330 serv_getln(buf, sizeof buf);
331 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
333 extract_token(roomname, buf, 0, '|', sizeof roomname);
334 view = extract_int(buf, 7);
335 mtime = extract_long(buf, 8);
336 http_datestring(datestring, sizeof datestring, mtime);
339 * For now, only list rooms that we know a GroupDAV client
340 * might be interested in. In the future we may add
343 * We determine the type of objects which are stored in each
344 * room by looking at the *default* view for the room. This
345 * allows, for example, a Calendar room to appear as a
346 * GroupDAV calendar even if the user has switched it to a
347 * Calendar List view.
349 if ( (view == VIEW_CALENDAR) ||
350 (view == VIEW_TASKS) ||
351 (view == VIEW_ADDRESSBOOK) ||
352 (view == VIEW_NOTES) ||
353 (view == VIEW_JOURNAL) ||
356 is_groupware_collection = 1;
359 is_groupware_collection = 0;
362 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
363 wc_printf("<response>");
366 groupdav_identify_host();
367 wc_printf("/groupdav/");
368 urlescputs(roomname);
369 wc_printf("/</href>");
371 wc_printf("<propstat>");
372 wc_printf("<status>HTTP/1.1 200 OK</status>");
374 wc_printf("<displayname>");
376 wc_printf("</displayname>");
377 wc_printf("<resourcetype><collection/>");
381 wc_printf("<G:vevent-collection />");
384 wc_printf("<G:vtodo-collection />");
386 case VIEW_ADDRESSBOOK:
387 wc_printf("<G:vcard-collection />");
390 wc_printf("<G:vnotes-collection />");
393 wc_printf("<G:vjournal-collection />");
396 wc_printf("<G:wiki-collection />");
400 wc_printf("</resourcetype>");
401 wc_printf("<getlastmodified>");
403 wc_printf("</getlastmodified>");
404 wc_printf("</prop>");
405 wc_printf("</propstat>");
406 wc_printf("</response>");
409 wc_printf("</multistatus>\n");
417 * The pathname is always going to be /groupdav/room_name/msg_num
419 void groupdav_propfind(void)
422 StrBuf *dav_roomname;
426 long dav_msgnum = (-1);
428 char encoded_uid[256];
432 char datestring[256];
437 http_datestring(datestring, sizeof datestring, now);
439 dav_roomname = NewStrBuf();
440 dav_uid = NewStrBuf();
441 StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
442 StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
445 * If the room name is blank, the client is requesting a
448 State = GotoRestRoom();
449 if (((State & REST_IN_ROOM) == 0) ||
450 (((State & (REST_GOT_EUID|REST_GOT_ID|REST_GOT_FILENAME)) == 0) &&
451 (WCC->Hdr->HR.dav_depth == 0)))
454 http_datestring(datestring, sizeof datestring, now);
457 * Be rude. Completely ignore the XML request and simply send them
458 * everything we know about. Let the client sort it out.
460 hprintf("HTTP/1.0 207 Multi-Status\r\n");
461 groupdav_common_headers();
462 hprintf("Date: %s\r\n", datestring);
463 hprintf("Content-type: text/xml\r\n");
464 hprintf("Content-encoding: identity\r\n");
470 * If the client is requesting the root, show a root node.
472 do_template("dav_propfind_top", NULL);
474 FreeStrBuf(&dav_roomname);
475 FreeStrBuf(&dav_uid);
479 if ((State & (REST_GOT_EUID|REST_GOT_ID|REST_GOT_FILENAME)) == 0) {
480 readloop(headers, eReadEUIDS);
487 * If the room name is blank, the client is requesting a
490 if (StrLength(dav_roomname) == 0) {
491 groupdav_collection_list();
492 FreeStrBuf(&dav_roomname);
493 FreeStrBuf(&dav_uid);
497 /* Go to the correct room. */
498 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
499 gotoroom(dav_roomname);
501 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
502 hprintf("HTTP/1.1 404 not found\r\n");
503 groupdav_common_headers();
504 hprintf("Date: %s\r\n", datestring);
505 hprintf("Content-Type: text/plain\r\n");
506 wc_printf("There is no folder called \"%s\" on this server.\r\n",
510 FreeStrBuf(&dav_roomname);
511 FreeStrBuf(&dav_uid);
515 /* If dav_uid is non-empty, client is requesting a PROPFIND on
516 * a specific item in the room. This is not valid GroupDAV, but
517 * it is valid WebDAV.
519 if (StrLength(dav_uid) != 0) {
521 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
522 if (dav_msgnum < 0) {
523 hprintf("HTTP/1.1 404 not found\r\n");
524 groupdav_common_headers();
525 hprintf("Content-Type: text/plain\r\n");
526 wc_printf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
531 FreeStrBuf(&dav_roomname);
532 FreeStrBuf(&dav_uid);
536 /* Be rude. Completely ignore the XML request and simply send them
537 * everything we know about (which is going to simply be the ETag and
538 * nothing else). Let the client-side parser sort it out.
540 hprintf("HTTP/1.0 207 Multi-Status\r\n");
541 groupdav_common_headers();
542 hprintf("Date: %s\r\n", datestring);
543 hprintf("Content-type: text/xml\r\n");
544 hprintf("Content-encoding: identity\r\n");
548 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
549 "<multistatus xmlns=\"DAV:\">"
552 wc_printf("<response>");
555 groupdav_identify_host();
556 wc_printf("/groupdav/");
557 urlescputs(ChrPtr(WCC->CurRoom.name));
558 euid_escapize(encoded_uid, ChrPtr(dav_uid));
559 wc_printf("/%s", encoded_uid);
560 wc_printf("</href>");
561 wc_printf("<propstat>");
562 wc_printf("<status>HTTP/1.1 200 OK</status>");
564 wc_printf("<getetag>\"%ld\"</getetag>", dav_msgnum);
565 wc_printf("<getlastmodified>");
567 wc_printf("</getlastmodified>");
568 wc_printf("</prop>");
569 wc_printf("</propstat>");
571 wc_printf("</response>\n");
572 wc_printf("</multistatus>\n");
574 FreeStrBuf(&dav_roomname);
575 FreeStrBuf(&dav_uid);
578 FreeStrBuf(&dav_roomname);
579 FreeStrBuf(&dav_uid);
583 * We got to this point, which means that the client is requesting
584 * a 'collection' (i.e. a list of all items in the room).
586 * Be rude. Completely ignore the XML request and simply send them
587 * everything we know about (which is going to simply be the ETag and
588 * nothing else). Let the client-side parser sort it out.
590 hprintf("HTTP/1.0 207 Multi-Status\r\n");
591 groupdav_common_headers();
592 hprintf("Date: %s\r\n", datestring);
593 hprintf("Content-type: text/xml\r\n");
594 hprintf("Content-encoding: identity\r\n");
598 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
599 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
603 /* Transmit the collection resource (FIXME check depth and starting point) */
604 wc_printf("<response>");
607 groupdav_identify_host();
608 wc_printf("/groupdav/");
609 urlescputs(ChrPtr(WCC->CurRoom.name));
610 wc_printf("</href>");
612 wc_printf("<propstat>");
613 wc_printf("<status>HTTP/1.1 200 OK</status>");
615 wc_printf("<displayname>");
616 escputs(ChrPtr(WCC->CurRoom.name));
617 wc_printf("</displayname>");
618 wc_printf("<resourcetype><collection/>");
620 switch(WCC->CurRoom.defview) {
622 wc_printf("<G:vevent-collection />");
625 wc_printf("<G:vtodo-collection />");
627 case VIEW_ADDRESSBOOK:
628 wc_printf("<G:vcard-collection />");
632 wc_printf("</resourcetype>");
633 /* FIXME get the mtime
634 wc_printf("<getlastmodified>");
636 wc_printf("</getlastmodified>");
638 wc_printf("</prop>");
639 wc_printf("</propstat>");
640 wc_printf("</response>");
642 /* Transmit the collection listing (FIXME check depth and starting point) */
644 MsgNum = NewStrBuf();
645 serv_puts("MSGS ALL");
647 StrBuf_ServGetln(MsgNum);
648 if (GetServerStatus(MsgNum, NULL) == 1)
649 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) {
650 msgs = realloc(msgs, ++num_msgs * sizeof(long));
651 msgs[num_msgs-1] = StrTol(MsgNum);
654 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
658 serv_printf("MSG0 %ld|3", msgs[i]);
659 StrBuf_ServGetln(MsgNum);
660 if (GetServerStatus(MsgNum, NULL) == 1)
661 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))
663 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
664 strcpy(uid, &ChrPtr(MsgNum)[5]);
666 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
667 now = atol(&ChrPtr(MsgNum)[5]);
671 if (!IsEmptyStr(uid)) {
672 wc_printf("<response>");
674 groupdav_identify_host();
675 wc_printf("/groupdav/");
676 urlescputs(ChrPtr(WCC->CurRoom.name));
677 euid_escapize(encoded_uid, uid);
678 wc_printf("/%s", encoded_uid);
679 wc_printf("</href>");
680 switch(WCC->CurRoom.defview) {
682 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
685 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
687 case VIEW_ADDRESSBOOK:
688 wc_printf("<getcontenttype>text/x-vcard</getcontenttype>");
691 wc_printf("<propstat>");
692 wc_printf("<status>HTTP/1.1 200 OK</status>");
694 wc_printf("<getetag>\"%ld\"</getetag>", msgs[i]);
696 http_datestring(datestring, sizeof datestring, now);
697 wc_printf("<getlastmodified>");
699 wc_printf("</getlastmodified>");
701 wc_printf("</prop>");
702 wc_printf("</propstat>");
703 wc_printf("</response>");
708 wc_printf("</multistatus>\n");
718 int ParseMessageListHeaders_EUID(StrBuf *Line,
720 message_summary *Msg,
721 StrBuf *ConversionBuffer)
723 Msg->euid = NewStrBuf();
724 StrBufExtract_NextToken(Msg->euid, Line, pos, '|');
725 return StrLength(Msg->euid) > 0;
728 int DavUIDL_GetParamsGetServerCall(SharedMessageStatus *Stat,
734 Stat->defaultsortorder = 0;
737 Stat->maxmsgs = 9999999;
739 snprintf(cmd, len, "MSGS ALL|||2");
743 int DavUIDL_RenderView_or_Tail(SharedMessageStatus *Stat,
748 DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
753 int DavUIDL_Cleanup(void **ViewSpecific)
755 /* Note: wDumpContent() will output one additional </div> tag. */
756 /* We ought to move this out into template */
769 RegisterReadLoopHandlerset(
771 DavUIDL_GetParamsGetServerCall,
772 NULL, /// TODO: is this right?
773 ParseMessageListHeaders_EUID,
775 DavUIDL_RenderView_or_Tail,