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]);
47 * IgnoreFloor: set to 0 or 1 _nothing else_
48 * Subfolders: direct child floors will be put here.
50 const folder *GetRESTFolder(int IgnoreFloor, HashList *Subfolders)
54 const folder *ThisFolder = NULL;
55 const folder *FoundFolder = NULL;
56 const folder *BestGuess = NULL;
63 int iRoom, jURL, urlp;
67 * Guess room: if the full URL matches a room, list thats it. We also need to remember direct sub rooms.
68 * 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.
70 itfl = GetNewHashPos(WCC->Floors, 0);
71 urlp = GetCount(WCC->Directory);
73 while (GetNextHashPos(WCC->Floors, itfl, &len, &Key, &vFolder) &&
77 if (!IgnoreFloor && /* so we can handle legacy URLS... */
78 (ThisFolder->Floor != WCC->CurrentFloor))
82 if (ThisFolder->nRoomNameParts > 1)
84 /*TODO: is that number all right? */
85 // if (urlp - ThisFolder->nRoomNameParts != 2) {
86 // if (BestGuess != NULL)
89 // itd = GetNewHashPos(WCC->Directory, 0);
90 // GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
92 itd = GetNewHashPos(WCC->Directory, 0);
93 GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir); //TODO: how many to fast forward?
95 for (iRoom = 0, /* Fast forward the floorname as we checked it above: */ jURL = IgnoreFloor;
97 (iRoom <= ThisFolder->nRoomNameParts) && (jURL <= urlp);
99 iRoom++, jURL++, GetNextHashPos(WCC->Directory, itd, &len, &Key, &vDir))
102 if (strcmp(ChrPtr(ThisFolder->RoomNameParts[iRoom]),
111 if ((iRoom == ThisFolder->nRoomNameParts) && (jURL == urlp))
113 FoundFolder = ThisFolder;
115 /* URL got more parts then this room, so we remember it for the best guess*/
116 else if ((jURL <= urlp) &&
117 (ThisFolder->nRoomNameParts <= nBestGuess))
119 BestGuess = ThisFolder;
120 nBestGuess = jURL - 1;
122 /* Room has more parts than the URL, it might be a sub-room? */
123 else if (iRoom <ThisFolder->nRoomNameParts)
124 {//// TODO: ThisFolder->nRoomNameParts == urlp - IgnoreFloor???
125 Put(Subfolders, SKEY(ThisFolder->name),
126 /* Cast away const, its a reference. */
127 (void*)ThisFolder, reference_free_handler);
131 delta = GetCount(WCC->Directory) - ThisFolder->nRoomNameParts;
132 if ((delta != 2) && (nBestGuess > 1))
135 itd = GetNewHashPos(WCC->Directory, 0);
137 if (!GetNextHashPos(WCC->Directory,
138 itd, &len, &Key, &vDir) ||
147 Dir = (StrBuf*) vDir;
148 if (strcmp(ChrPtr(ThisFolder->name),
157 DeleteHashPos(&itfl);
161 BestGuess = ThisFolder;
164 FoundFolder = ThisFolder;
168 /* TODO: Subfolders: remove patterns not matching the best guess or thisfolder */
169 DeleteHashPos(&itfl);
170 if (FoundFolder != NULL)
179 long GotoRestRoom(HashList *SubRooms)
181 int IgnoreFloor = 0; /* deprecated... */
185 const folder *ThisFolder;
187 State = REST_TOPLEVEL;
189 if (WCC->Hdr->HR.Handler != NULL)
190 State |= REST_IN_NAMESPACE;
192 Count = GetCount(WCC->Directory);
194 if (Count == 0) return State;
196 if (Count >= 1) State |=REST_IN_FLOOR;
197 if (Count == 1) return State;
200 * More than 3 params and no floor found?
201 * -> fall back to old non-floored notation
203 if ((Count >= 3) && (WCC->CurrentFloor == NULL))
208 State |= REST_IN_FLOOR;
210 ThisFolder = GetRESTFolder(IgnoreFloor, SubRooms);
211 if (ThisFolder != NULL)
213 if (WCC->ThisRoom != NULL)
214 if (CompareRooms(WCC->ThisRoom, ThisFolder) != 0)
215 gotoroom(ThisFolder->name);
216 State |= REST_IN_ROOM;
219 if (GetCount(SubRooms) > 0)
220 State |= REST_HAVE_SUB_ROOMS;
222 if ((WCC->ThisRoom != NULL) &&
223 (Count + IgnoreFloor > 3))
225 if (WCC->Hdr->HR.Handler->RID(ExistsID, IgnoreFloor))
227 State |= REST_GOT_LOCAL_PART;
230 /// WHOOPS, not there???
231 State |= REST_NONEXIST;
242 * List rooms (or "collections" in DAV terminology) which contain
243 * interesting groupware objects.
245 void groupdav_collection_list(void)
251 char datestring[256];
254 int is_groupware_collection = 0;
255 int starting_point = 1; /**< 0 for /, 1 for /groupdav/ */
257 if (WCC->Hdr->HR.Handler == NULL) {
260 else if (StrLength(WCC->Hdr->HR.ReqLine) == 0) {
268 http_datestring(datestring, sizeof datestring, now);
271 * Be rude. Completely ignore the XML request and simply send them
272 * everything we know about. Let the client sort it out.
274 hprintf("HTTP/1.0 207 Multi-Status\r\n");
275 groupdav_common_headers();
276 hprintf("Date: %s\r\n", datestring);
277 hprintf("Content-type: text/xml\r\n");
278 hprintf("Content-encoding: identity\r\n");
282 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
283 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
287 * If the client is requesting the root, show a root node.
289 if (starting_point == 0) {
290 wc_printf("<response>");
292 groupdav_identify_host();
294 wc_printf("</href>");
295 wc_printf("<propstat>");
296 wc_printf("<status>HTTP/1.1 200 OK</status>");
298 wc_printf("<displayname>/</displayname>");
299 wc_printf("<resourcetype><collection/></resourcetype>");
300 wc_printf("<getlastmodified>");
302 wc_printf("</getlastmodified>");
303 wc_printf("</prop>");
304 wc_printf("</propstat>");
305 wc_printf("</response>");
309 * If the client is requesting "/groupdav", show a /groupdav subdirectory.
311 if ((starting_point + WCC->Hdr->HR.dav_depth) >= 1) {
312 wc_printf("<response>");
314 groupdav_identify_host();
315 wc_printf("/groupdav");
316 wc_printf("</href>");
317 wc_printf("<propstat>");
318 wc_printf("<status>HTTP/1.1 200 OK</status>");
320 wc_printf("<displayname>GroupDAV</displayname>");
321 wc_printf("<resourcetype><collection/></resourcetype>");
322 wc_printf("<getlastmodified>");
324 wc_printf("</getlastmodified>");
325 wc_printf("</prop>");
326 wc_printf("</propstat>");
327 wc_printf("</response>");
331 * Now go through the list and make it look like a DAV collection
334 serv_getln(buf, sizeof buf);
335 if (buf[0] == '1') while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
337 extract_token(roomname, buf, 0, '|', sizeof roomname);
338 view = extract_int(buf, 7);
339 mtime = extract_long(buf, 8);
340 http_datestring(datestring, sizeof datestring, mtime);
343 * For now, only list rooms that we know a GroupDAV client
344 * might be interested in. In the future we may add
347 * We determine the type of objects which are stored in each
348 * room by looking at the *default* view for the room. This
349 * allows, for example, a Calendar room to appear as a
350 * GroupDAV calendar even if the user has switched it to a
351 * Calendar List view.
353 if ( (view == VIEW_CALENDAR) ||
354 (view == VIEW_TASKS) ||
355 (view == VIEW_ADDRESSBOOK) ||
356 (view == VIEW_NOTES) ||
357 (view == VIEW_JOURNAL) ||
360 is_groupware_collection = 1;
363 is_groupware_collection = 0;
366 if ( (is_groupware_collection) && ((starting_point + WCC->Hdr->HR.dav_depth) >= 2) ) {
367 wc_printf("<response>");
370 groupdav_identify_host();
371 wc_printf("/groupdav/");
372 urlescputs(roomname);
373 wc_printf("/</href>");
375 wc_printf("<propstat>");
376 wc_printf("<status>HTTP/1.1 200 OK</status>");
378 wc_printf("<displayname>");
380 wc_printf("</displayname>");
381 wc_printf("<resourcetype><collection/>");
385 wc_printf("<G:vevent-collection />");
388 wc_printf("<G:vtodo-collection />");
390 case VIEW_ADDRESSBOOK:
391 wc_printf("<G:vcard-collection />");
394 wc_printf("<G:vnotes-collection />");
397 wc_printf("<G:vjournal-collection />");
400 wc_printf("<G:wiki-collection />");
404 wc_printf("</resourcetype>");
405 wc_printf("<getlastmodified>");
407 wc_printf("</getlastmodified>");
408 wc_printf("</prop>");
409 wc_printf("</propstat>");
410 wc_printf("</response>");
413 wc_printf("</multistatus>\n");
421 * The pathname is always going to be /groupdav/room_name/msg_num
423 void groupdav_propfind(void)
425 HashList *SubRooms = NULL;
427 StrBuf *dav_roomname;
431 long dav_msgnum = (-1);
433 char encoded_uid[256];
437 char datestring[256];
442 http_datestring(datestring, sizeof datestring, now);
444 dav_roomname = NewStrBuf();
445 dav_uid = NewStrBuf();
446 StrBufExtract_token(dav_roomname, WCC->Hdr->HR.ReqLine, 0, '/');
447 StrBufExtract_token(dav_uid, WCC->Hdr->HR.ReqLine, 1, '/');
450 * If the room name is blank, the client is requesting a
453 SubRooms = NewHash(1, Flathash);
454 State = GotoRestRoom(SubRooms);
455 if (((State & REST_IN_ROOM) == 0) ||
456 (((State & (REST_GOT_LOCAL_PART)) == 0) &&
457 (WCC->Hdr->HR.dav_depth == 0)))
460 http_datestring(datestring, sizeof datestring, now);
463 * Be rude. Completely ignore the XML request and simply send them
464 * everything we know about. Let the client sort it out.
466 hprintf("HTTP/1.0 207 Multi-Status\r\n");
467 groupdav_common_headers();
468 hprintf("Date: %s\r\n", datestring);
469 hprintf("Content-type: text/xml\r\n");
470 hprintf("Content-encoding: identity\r\n");
476 * If the client is requesting the root, show a root node.
478 do_template("dav_propfind_top", NULL);
480 FreeStrBuf(&dav_roomname);
481 FreeStrBuf(&dav_uid);
482 FreeHashList(&SubRooms);
486 if ((State & (REST_GOT_LOCAL_PART)) == 0) {
487 readloop(headers, eReadEUIDS);
488 FreeHashList(&SubRooms);
495 FreeHashList(&SubRooms);
500 * If the room name is blank, the client is requesting a
503 if (StrLength(dav_roomname) == 0) {
504 groupdav_collection_list();
505 FreeStrBuf(&dav_roomname);
506 FreeStrBuf(&dav_uid);
510 /* Go to the correct room. */
511 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
512 gotoroom(dav_roomname);
514 if (strcasecmp(ChrPtr(WCC->CurRoom.name), ChrPtr(dav_roomname))) {
515 hprintf("HTTP/1.1 404 not found\r\n");
516 groupdav_common_headers();
517 hprintf("Date: %s\r\n", datestring);
518 hprintf("Content-Type: text/plain\r\n");
519 wc_printf("There is no folder called \"%s\" on this server.\r\n",
523 FreeStrBuf(&dav_roomname);
524 FreeStrBuf(&dav_uid);
528 /* If dav_uid is non-empty, client is requesting a PROPFIND on
529 * a specific item in the room. This is not valid GroupDAV, but
530 * it is valid WebDAV.
532 if (StrLength(dav_uid) != 0) {
534 dav_msgnum = locate_message_by_uid(ChrPtr(dav_uid));
535 if (dav_msgnum < 0) {
536 hprintf("HTTP/1.1 404 not found\r\n");
537 groupdav_common_headers();
538 hprintf("Content-Type: text/plain\r\n");
539 wc_printf("Object \"%s\" was not found in the \"%s\" folder.\r\n",
544 FreeStrBuf(&dav_roomname);
545 FreeStrBuf(&dav_uid);
549 /* Be rude. Completely ignore the XML request and simply send them
550 * everything we know about (which is going to simply be the ETag and
551 * nothing else). Let the client-side parser sort it out.
553 hprintf("HTTP/1.0 207 Multi-Status\r\n");
554 groupdav_common_headers();
555 hprintf("Date: %s\r\n", datestring);
556 hprintf("Content-type: text/xml\r\n");
557 hprintf("Content-encoding: identity\r\n");
561 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
562 "<multistatus xmlns=\"DAV:\">"
565 wc_printf("<response>");
568 groupdav_identify_host();
569 wc_printf("/groupdav/");
570 urlescputs(ChrPtr(WCC->CurRoom.name));
571 euid_escapize(encoded_uid, ChrPtr(dav_uid));
572 wc_printf("/%s", encoded_uid);
573 wc_printf("</href>");
574 wc_printf("<propstat>");
575 wc_printf("<status>HTTP/1.1 200 OK</status>");
577 wc_printf("<getetag>\"%ld\"</getetag>", dav_msgnum);
578 wc_printf("<getlastmodified>");
580 wc_printf("</getlastmodified>");
581 wc_printf("</prop>");
582 wc_printf("</propstat>");
584 wc_printf("</response>\n");
585 wc_printf("</multistatus>\n");
587 FreeStrBuf(&dav_roomname);
588 FreeStrBuf(&dav_uid);
591 FreeStrBuf(&dav_roomname);
592 FreeStrBuf(&dav_uid);
596 * We got to this point, which means that the client is requesting
597 * a 'collection' (i.e. a list of all items in the room).
599 * Be rude. Completely ignore the XML request and simply send them
600 * everything we know about (which is going to simply be the ETag and
601 * nothing else). Let the client-side parser sort it out.
603 hprintf("HTTP/1.0 207 Multi-Status\r\n");
604 groupdav_common_headers();
605 hprintf("Date: %s\r\n", datestring);
606 hprintf("Content-type: text/xml\r\n");
607 hprintf("Content-encoding: identity\r\n");
611 wc_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
612 "<multistatus xmlns=\"DAV:\" xmlns:G=\"http://groupdav.org/\">"
616 /* Transmit the collection resource (FIXME check depth and starting point) */
617 wc_printf("<response>");
620 groupdav_identify_host();
621 wc_printf("/groupdav/");
622 urlescputs(ChrPtr(WCC->CurRoom.name));
623 wc_printf("</href>");
625 wc_printf("<propstat>");
626 wc_printf("<status>HTTP/1.1 200 OK</status>");
628 wc_printf("<displayname>");
629 escputs(ChrPtr(WCC->CurRoom.name));
630 wc_printf("</displayname>");
631 wc_printf("<resourcetype><collection/>");
633 switch(WCC->CurRoom.defview) {
635 wc_printf("<G:vevent-collection />");
638 wc_printf("<G:vtodo-collection />");
640 case VIEW_ADDRESSBOOK:
641 wc_printf("<G:vcard-collection />");
645 wc_printf("</resourcetype>");
646 /* FIXME get the mtime
647 wc_printf("<getlastmodified>");
649 wc_printf("</getlastmodified>");
651 wc_printf("</prop>");
652 wc_printf("</propstat>");
653 wc_printf("</response>");
655 /* Transmit the collection listing (FIXME check depth and starting point) */
657 MsgNum = NewStrBuf();
658 serv_puts("MSGS ALL");
660 StrBuf_ServGetln(MsgNum);
661 if (GetServerStatus(MsgNum, NULL) == 1)
662 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000")) {
663 msgs = realloc(msgs, ++num_msgs * sizeof(long));
664 msgs[num_msgs-1] = StrTol(MsgNum);
667 if (num_msgs > 0) for (i=0; i<num_msgs; ++i) {
671 serv_printf("MSG0 %ld|3", msgs[i]);
672 StrBuf_ServGetln(MsgNum);
673 if (GetServerStatus(MsgNum, NULL) == 1)
674 while (BufLen = StrBuf_ServGetln(MsgNum), strcmp(ChrPtr(MsgNum), "000"))
676 if (!strncasecmp(ChrPtr(MsgNum), "exti=", 5)) {
677 strcpy(uid, &ChrPtr(MsgNum)[5]);
679 else if (!strncasecmp(ChrPtr(MsgNum), "time=", 5)) {
680 now = atol(&ChrPtr(MsgNum)[5]);
684 if (!IsEmptyStr(uid)) {
685 wc_printf("<response>");
687 groupdav_identify_host();
688 wc_printf("/groupdav/");
689 urlescputs(ChrPtr(WCC->CurRoom.name));
690 euid_escapize(encoded_uid, uid);
691 wc_printf("/%s", encoded_uid);
692 wc_printf("</href>");
693 switch(WCC->CurRoom.defview) {
695 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
698 wc_printf("<getcontenttype>text/x-ical</getcontenttype>");
700 case VIEW_ADDRESSBOOK:
701 wc_printf("<getcontenttype>text/x-vcard</getcontenttype>");
704 wc_printf("<propstat>");
705 wc_printf("<status>HTTP/1.1 200 OK</status>");
707 wc_printf("<getetag>\"%ld\"</getetag>", msgs[i]);
709 http_datestring(datestring, sizeof datestring, now);
710 wc_printf("<getlastmodified>");
712 wc_printf("</getlastmodified>");
714 wc_printf("</prop>");
715 wc_printf("</propstat>");
716 wc_printf("</response>");
721 wc_printf("</multistatus>\n");
731 int ParseMessageListHeaders_EUID(StrBuf *Line,
733 message_summary *Msg,
734 StrBuf *ConversionBuffer)
736 Msg->euid = NewStrBuf();
737 StrBufExtract_NextToken(Msg->euid, Line, pos, '|');
738 Msg->date = StrBufExtractNext_long(Line, pos, '|');
740 return StrLength(Msg->euid) > 0;
743 int DavUIDL_GetParamsGetServerCall(SharedMessageStatus *Stat,
749 Stat->defaultsortorder = 0;
752 Stat->maxmsgs = 9999999;
754 snprintf(cmd, len, "MSGS ALL|||2");
758 int DavUIDL_RenderView_or_Tail(SharedMessageStatus *Stat,
763 DoTemplate(HKEY("msg_listview"),NULL,&NoCtx);
768 int DavUIDL_Cleanup(void **ViewSpecific)
770 /* Note: wDumpContent() will output one additional </div> tag. */
771 /* We ought to move this out into template */
784 RegisterReadLoopHandlerset(
786 DavUIDL_GetParamsGetServerCall,
787 NULL, /// TODO: is this right?
788 ParseMessageListHeaders_EUID,
790 DavUIDL_RenderView_or_Tail,