4 * Functions which handle calendar objects and their processing/display.
11 #ifndef WEBCIT_WITH_CALENDAR_SERVICE
14 * Handler stubs for builds with no calendar library available
16 void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) {
18 wprintf("<I>This message contains calendaring/scheduling information,"
19 " but support for calendars is not available on this "
20 "particular system. Please ask your system administrator to "
21 "install a new version of the Citadel web service with "
22 "calendaring enabled.</I><br />\n"
27 void display_calendar(long msgnum) {
29 "Cannot display calendar item. You are seeing this error "
30 "because your WebCit service has not been installed with "
31 "calendar support. Please contact your system administrator."
35 void display_task(long msgnum) {
37 "Cannot display to-do item. You are seeing this error "
38 "because your WebCit service has not been installed with "
39 "calendar support. Please contact your system administrator."
43 #else /* WEBCIT_WITH_CALENDAR_SERVICE */
46 /****** End of handler stubs. Everything below this line is real. ******/
52 * Process a calendar object
53 * ...at this point it's already been deserialized by cal_process_attachment()
56 void cal_process_object(icalcomponent *cal,
62 icalproperty *method = NULL;
63 icalproperty_method the_method = ICAL_METHOD_NONE;
65 struct icaltimetype t;
68 char conflict_name[SIZ];
71 /* Leading HTML for the display of this object */
72 if (recursion_level == 0) {
73 wprintf("<CENTER><TABLE border=0>\n");
76 /* Look for a method */
77 method = icalcomponent_get_first_property(cal, ICAL_METHOD_PROPERTY);
79 /* See what we need to do with this */
81 the_method = icalproperty_get_method(method);
83 case ICAL_METHOD_REQUEST:
84 wprintf("<TR><TD COLSPAN=2>\n"
86 "SRC=\"/static/calarea_48x.gif\">"
88 "<B>Meeting invitation</B>"
92 case ICAL_METHOD_REPLY:
93 wprintf("<TR><TD COLSPAN=2>\n"
95 "SRC=\"/static/calarea_48x.gif\">"
97 "<B>Attendee's reply to your invitation</B>"
101 case ICAL_METHOD_PUBLISH:
102 wprintf("<TR><TD COLSPAN=2>\n"
104 "SRC=\"/static/calarea_48x.gif\">"
106 "<B>Published event</B>"
111 wprintf("<TR><TD COLSPAN=2>"
112 "I don't know what to do with this.</TD></TR>"
118 p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY);
120 wprintf("<TR><TD><B>Summary:</B></TD><TD>");
121 escputs((char *)icalproperty_get_comment(p));
122 wprintf("</TD></TR>\n");
125 p = icalcomponent_get_first_property(cal, ICAL_LOCATION_PROPERTY);
127 wprintf("<TR><TD><B>Location:</B></TD><TD>");
128 escputs((char *)icalproperty_get_comment(p));
129 wprintf("</TD></TR>\n");
133 * Only show start/end times if we're actually looking at the VEVENT
134 * component. Otherwise it shows bogus dates for things like timezone.
136 if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) {
138 p = icalcomponent_get_first_property(cal,
139 ICAL_DTSTART_PROPERTY);
141 t = icalproperty_get_dtstart(p);
144 wprintf("<TR><TD><B>Date:"
146 "%s %d, %d</TD></TR>",
152 tt = icaltime_as_timet(t);
153 fmt_date(buf, tt, 0);
154 wprintf("<TR><TD><B>Starting date/time:"
161 p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
163 t = icalproperty_get_dtend(p);
164 tt = icaltime_as_timet(t);
165 fmt_date(buf, tt, 0);
166 wprintf("<TR><TD><B>Ending date/time:</B></TD><TD>"
173 p = icalcomponent_get_first_property(cal, ICAL_DESCRIPTION_PROPERTY);
175 wprintf("<TR><TD><B>Description:</B></TD><TD>");
176 escputs((char *)icalproperty_get_comment(p));
177 wprintf("</TD></TR>\n");
180 /* If the component has attendees, iterate through them. */
181 for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) {
182 wprintf("<TR><TD><B>Attendee:</B></TD><TD>");
183 strcpy(buf, icalproperty_get_attendee(p));
184 if (!strncasecmp(buf, "MAILTO:", 7)) {
186 /* screen name or email address */
187 strcpy(buf, &buf[7]);
192 /* participant status */
193 partstat_as_string(buf, p);
196 wprintf("</TD></TR>\n");
199 /* If the component has subcomponents, recurse through them. */
200 for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT);
202 c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) {
203 /* Recursively process subcomponent */
204 cal_process_object(c, recursion_level+1, msgnum, cal_partnum);
207 /* If this is a REQUEST, display conflicts and buttons */
208 if (the_method == ICAL_METHOD_REQUEST) {
210 /* Check for conflicts */
211 lprintf(9, "Checking server calendar for conflicts...\n");
212 serv_printf("ICAL conflicts|%ld|%s|", msgnum, cal_partnum);
213 serv_getln(buf, sizeof buf);
215 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
216 extract_token(conflict_name, buf, 3, '|', sizeof conflict_name);
217 is_update = extract_int(buf, 4);
218 wprintf("<TR><TD><B><I>%s</I></B></TD>"
229 "This is an update of" :
230 "This event would conflict with"
234 escputs(conflict_name);
235 wprintf(""</I> "
236 "which is already in your calendar."
240 lprintf(9, "...done.\n");
242 /* Display the Accept/Decline buttons */
243 wprintf("<TR><TD>How would you like to respond to this invitation?</td>"
245 "<A HREF=\"/respond_to_request?msgnum=%ld&cal_partnum=%s&sc=Accept\">Accept</a>"
247 "<A HREF=\"/respond_to_request?msgnum=%ld&cal_partnum=%s&sc=Tentative\">Tentative</a>"
249 "<A HREF=\"/respond_to_request?msgnum=%ld&cal_partnum=%s&sc=Decline\">Decline</a>"
250 "</FONT></TD></TR>\n",
258 /* If this is a REPLY, display update button */
259 if (the_method == ICAL_METHOD_REPLY) {
262 * In the future, if we want to validate this object before
263 * continuing, we can do it this way:
264 serv_printf("ICAL whatever|%ld|%s|", msgnum, cal_partnum);
265 serv_getln(buf, sizeof buf);
269 /* Display the update buttons */
271 "Click <i>Update</i> to accept this reply and "
272 "update your calendar."
273 "</td><td><font size=+1>"
274 "<a href=\"/handle_rsvp?msgnum=%ld&cal_partnum=%s&sc=Update\">Update</a>"
276 "<a href=\"/handle_rsvp?msgnum=%ld&cal_partnum=%s&sc=Ignore\">Ignore</a>"
285 /* Trailing HTML for the display of this object */
286 if (recursion_level == 0) {
288 wprintf("</TR></TABLE></CENTER>\n");
294 * Deserialize a calendar object in a message so it can be processed.
295 * (This is the main entry point for these things)
297 void cal_process_attachment(char *part_source, long msgnum, char *cal_partnum) {
300 cal = icalcomponent_new_from_string(part_source);
303 wprintf("Error parsing calendar object<br />\n");
308 cal_process_object(cal, 0, msgnum, cal_partnum);
310 /* Free the memory we obtained from libical's constructor */
311 icalcomponent_free(cal);
318 * Respond to a meeting request
320 void respond_to_request(void) {
323 output_headers(1, 1, 2, 0, 0, 0, 0);
325 wprintf("<div id=\"banner\">\n");
326 wprintf("<TABLE WIDTH=100%% BORDER=0 BGCOLOR=\"#444455\"><TR><TD>"
327 "<SPAN CLASS=\"titlebar\">Respond to meeting request</SPAN>"
328 "</TD></TR></TABLE>\n"
330 wprintf("</div>\n<div id=\"content\">\n");
332 serv_printf("ICAL respond|%s|%s|%s|",
337 serv_getln(buf, sizeof buf);
340 wprintf("<TABLE BORDER=0><TR><TD>"
341 "<IMG SRC=\"static/calarea_48x.gif\" ALIGN=CENTER>"
344 if (!strcasecmp(bstr("sc"), "accept")) {
345 wprintf("You have accepted this meeting invitation. "
346 "It has been entered into your calendar, "
348 } else if (!strcasecmp(bstr("sc"), "tentative")) {
349 wprintf("You have tentatively accepted this meeting invitation. "
350 "It has been 'pencilled in' to your calendar, "
352 } else if (!strcasecmp(bstr("sc"), "decline")) {
353 wprintf("You have declined this meeting invitation. "
354 "It has <b>not</b> been entered into your calendar, "
357 wprintf("and a reply has been sent to the meeting organizer."
358 "</TD></TR></TABLE>\n"
361 wprintf("<IMG SRC=\"static/error.gif\" ALIGN=CENTER>"
365 wprintf("<A HREF=\"/dotskip?room=");
366 urlescputs(WC->wc_roomname);
367 wprintf("\"><br />Return to messages</A><br />\n");
375 * Handle an incoming RSVP
377 void handle_rsvp(void) {
380 output_headers(1, 1, 2, 0, 0, 0, 0);
382 wprintf("<div id=\"banner\">\n");
383 wprintf("<TABLE WIDTH=100%% BORDER=0 BGCOLOR=\"#444455\"><TR><TD>"
384 "<SPAN CLASS=\"titlebar\">"
385 "Update your calendar with this RSVP</SPAN>"
386 "</TD></TR></TABLE>\n"
387 "</div>\n<div id=\"content\">\n"
390 serv_printf("ICAL handle_rsvp|%s|%s|%s|",
395 serv_getln(buf, sizeof buf);
398 wprintf("<TABLE BORDER=0><TR><TD>"
399 "<IMG SRC=\"static/calarea_48x.gif\" ALIGN=CENTER>"
402 if (!strcasecmp(bstr("sc"), "update")) {
403 wprintf("Your calendar has been updated "
404 "to reflect this RSVP."
406 } else if (!strcasecmp(bstr("sc"), "ignore")) {
407 wprintf("You have chosen to ignore this RSVP. "
408 "Your calendar has <b>not</b> been updated."
411 wprintf("</TD></TR></TABLE>\n"
414 wprintf("<IMG SRC=\"static/error.gif\" ALIGN=CENTER>"
418 wprintf("<A HREF=\"/dotskip?room=");
419 urlescputs(WC->wc_roomname);
420 wprintf("\"><br />Return to messages</A><br />\n");
428 /*****************************************************************************/
433 * Display handlers for message reading
439 * If we're reading calendar items, just store them for now. We have to
440 * sort and re-output them later when we draw the calendar.
442 void display_individual_cal(icalcomponent *cal, long msgnum) {
446 WC->disp_cal = realloc(WC->disp_cal,
447 (sizeof(struct disp_cal) * WC->num_cal) );
448 WC->disp_cal[WC->num_cal - 1].cal = icalcomponent_new_clone(cal);
450 WC->disp_cal[WC->num_cal - 1].cal_msgnum = msgnum;
456 * Display a task by itself (for editing)
459 void display_edit_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
460 icalcomponent *vtodo;
462 struct icaltimetype t;
464 int created_new_vtodo = 0;
468 if (supplied_vtodo != NULL) {
469 vtodo = supplied_vtodo;
471 /* If we're looking at a fully encapsulated VCALENDAR
472 * rather than a VTODO component, attempt to use the first
473 * relevant VTODO subcomponent. If there is none, the
474 * NULL returned by icalcomponent_get_first_component() will
475 * tell the next iteration of this function to create a
478 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
479 display_edit_individual_task(
480 icalcomponent_get_first_component(
481 vtodo, ICAL_VTODO_COMPONENT
488 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
489 created_new_vtodo = 1;
492 output_headers(1, 1, 2, 0, 0, 0, 0);
493 wprintf("<div id=\"banner\">\n"
494 "<TABLE WIDTH=100%% BORDER=0 BGCOLOR=\"#444455\"><TR>"
495 "<TD><IMG SRC=\"/static/taskmanag_48x.gif\"></TD>"
496 "<td><SPAN CLASS=\"titlebar\">Edit task</SPAN>"
497 "</TD></TR></TABLE>\n"
498 "</div>\n<div id=\"content\">\n"
501 wprintf("<div id=\"fix_scrollbar_bug\">"
502 "<table border=0 width=100%% bgcolor=\"#ffffff\"><tr><td>");
504 wprintf("<FORM METHOD=\"POST\" ACTION=\"/save_task\">\n");
505 wprintf("<INPUT TYPE=\"hidden\" NAME=\"msgnum\" VALUE=\"%ld\">\n",
508 wprintf("<TABLE border=0>\n");
510 wprintf("<TR><TD>Summary:</TD><TD>"
511 "<INPUT TYPE=\"text\" NAME=\"summary\" "
512 "MAXLENGTH=\"64\" SIZE=\"64\" VALUE=\"");
513 p = icalcomponent_get_first_property(vtodo, ICAL_SUMMARY_PROPERTY);
515 escputs((char *)icalproperty_get_comment(p));
517 wprintf("\"></TD></TR>\n");
519 wprintf("<TR><TD>Start date:</TD><TD>");
520 p = icalcomponent_get_first_property(vtodo, ICAL_DTSTART_PROPERTY);
522 t = icalproperty_get_dtstart(p);
525 t = icaltime_from_timet(now, 0);
527 display_icaltimetype_as_webform(&t, "dtstart");
528 wprintf("</TD></TR>\n");
530 wprintf("<TR><TD>Due date:</TD><TD>");
531 p = icalcomponent_get_first_property(vtodo, ICAL_DUE_PROPERTY);
533 t = icalproperty_get_due(p);
536 t = icaltime_from_timet(now, 0);
538 display_icaltimetype_as_webform(&t, "due");
539 wprintf("</TD></TR>\n");
540 wprintf("<TR><TD>Description:</TD><TD>");
541 wprintf("<TEXTAREA NAME=\"description\" wrap=soft "
542 "ROWS=10 COLS=80 WIDTH=80>\n"
544 p = icalcomponent_get_first_property(vtodo, ICAL_DESCRIPTION_PROPERTY);
546 escputs((char *)icalproperty_get_comment(p));
548 wprintf("</TEXTAREA></TD></TR></TABLE>\n");
551 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Save\">"
553 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Delete\">\n"
555 "<INPUT TYPE=\"submit\" NAME=\"sc\" VALUE=\"Cancel\">\n"
559 wprintf("</FORM>\n");
561 wprintf("</td></tr></table></div>\n");
564 if (created_new_vtodo) {
565 icalcomponent_free(vtodo);
570 * Save an edited task
573 void save_individual_task(icalcomponent *supplied_vtodo, long msgnum) {
575 int delete_existing = 0;
577 icalcomponent *vtodo, *encaps;
578 int created_new_vtodo = 0;
581 struct icaltimetype t;
583 if (supplied_vtodo != NULL) {
584 vtodo = supplied_vtodo;
585 /* If we're looking at a fully encapsulated VCALENDAR
586 * rather than a VTODO component, attempt to use the first
587 * relevant VTODO subcomponent. If there is none, the
588 * NULL returned by icalcomponent_get_first_component() will
589 * tell the next iteration of this function to create a
592 if (icalcomponent_isa(vtodo) == ICAL_VCALENDAR_COMPONENT) {
593 save_individual_task(
594 icalcomponent_get_first_component(
595 vtodo, ICAL_VTODO_COMPONENT
602 vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT);
603 created_new_vtodo = 1;
606 if (!strcasecmp(bstr("sc"), "Save")) {
608 /* Replace values in the component with ones from the form */
610 while (prop = icalcomponent_get_first_property(vtodo,
611 ICAL_SUMMARY_PROPERTY), prop != NULL) {
612 icalcomponent_remove_property(vtodo, prop);
613 icalproperty_free(prop);
615 icalcomponent_add_property(vtodo,
616 icalproperty_new_summary(bstr("summary")));
618 while (prop = icalcomponent_get_first_property(vtodo,
619 ICAL_DESCRIPTION_PROPERTY), prop != NULL) {
620 icalcomponent_remove_property(vtodo, prop);
621 icalproperty_free(prop);
623 icalcomponent_add_property(vtodo,
624 icalproperty_new_description(bstr("description")));
626 while (prop = icalcomponent_get_first_property(vtodo,
627 ICAL_DTSTART_PROPERTY), prop != NULL) {
628 icalcomponent_remove_property(vtodo, prop);
629 icalproperty_free(prop);
631 icaltime_from_webform(&t, "dtstart");
632 icalcomponent_add_property(vtodo,
633 icalproperty_new_dtstart(t)
636 while (prop = icalcomponent_get_first_property(vtodo,
637 ICAL_DUE_PROPERTY), prop != NULL) {
638 icalcomponent_remove_property(vtodo, prop);
639 icalproperty_free(prop);
641 icaltime_from_webform(&t, "due");
642 icalcomponent_add_property(vtodo,
643 icalproperty_new_due(t)
646 /* Give this task a UID if it doesn't have one. */
647 lprintf(9, "Give this task a UID if it doesn't have one.\n");
648 if (icalcomponent_get_first_property(vtodo,
649 ICAL_UID_PROPERTY) == NULL) {
651 icalcomponent_add_property(vtodo,
652 icalproperty_new_uid(buf)
656 /* Increment the sequence ID */
657 lprintf(9, "Increment the sequence ID\n");
658 while (prop = icalcomponent_get_first_property(vtodo,
659 ICAL_SEQUENCE_PROPERTY), (prop != NULL) ) {
660 i = icalproperty_get_sequence(prop);
661 lprintf(9, "Sequence was %d\n", i);
662 if (i > sequence) sequence = i;
663 icalcomponent_remove_property(vtodo, prop);
664 icalproperty_free(prop);
667 lprintf(9, "New sequence is %d. Adding...\n", sequence);
668 icalcomponent_add_property(vtodo,
669 icalproperty_new_sequence(sequence)
673 * Encapsulate event into full VCALENDAR component. Clone it first,
674 * for two reasons: one, it's easier to just free the whole thing
675 * when we're done instead of unbundling, but more importantly, we
676 * can't encapsulate something that may already be encapsulated
679 lprintf(9, "Encapsulating into full VCALENDAR component\n");
680 encaps = ical_encapsulate_subcomponent(icalcomponent_new_clone(vtodo));
682 /* Serialize it and save it to the message base */
683 serv_puts("ENT0 1|||4");
684 serv_getln(buf, sizeof buf);
686 serv_puts("Content-type: text/calendar");
688 serv_puts(icalcomponent_as_ical_string(encaps));
691 /* Probably not necessary; the server will see the UID
692 * of the object and delete the old one anyway, but
697 icalcomponent_free(encaps);
701 * If the user clicked 'Delete' then explicitly delete the message.
703 if (!strcasecmp(bstr("sc"), "Delete")) {
707 if ( (delete_existing) && (msgnum > 0L) ) {
708 serv_printf("DELE %ld", atol(bstr("msgnum")));
709 serv_getln(buf, sizeof buf);
712 if (created_new_vtodo) {
713 icalcomponent_free(vtodo);
716 /* Go back to the task list */
723 * Code common to all display handlers. Given a message number and a MIME
724 * type, we load the message and hunt for that MIME type. If found, we load
725 * the relevant part, deserialize it into a libical component, filter it for
726 * the requested object type, and feed it to the specified handler.
729 void display_using_handler(long msgnum,
731 icalcomponent_kind which_kind,
732 void (*callback)(icalcomponent *, long)
735 char mime_partnum[SIZ];
736 char mime_filename[SIZ];
737 char mime_content_type[SIZ];
738 char mime_disposition[SIZ];
740 char relevant_partnum[SIZ];
741 char *relevant_source = NULL;
742 icalcomponent *cal, *c;
744 sprintf(buf, "MSG0 %ld|1", msgnum); /* ask for headers only */
746 serv_getln(buf, sizeof buf);
747 if (buf[0] != '1') return;
749 while (serv_getln(buf, sizeof buf), strcmp(buf, "000")) {
750 if (!strncasecmp(buf, "part=", 5)) {
751 extract_token(mime_filename, &buf[5], 1, '|', sizeof mime_filename);
752 extract_token(mime_partnum, &buf[5], 2, '|', sizeof mime_partnum);
753 extract_token(mime_disposition, &buf[5], 3, '|', sizeof mime_disposition);
754 extract_token(mime_content_type, &buf[5], 4, '|', sizeof mime_content_type);
755 mime_length = extract_int(&buf[5], 5);
757 if (!strcasecmp(mime_content_type, "text/calendar")) {
758 strcpy(relevant_partnum, mime_partnum);
764 if (strlen(relevant_partnum) > 0) {
765 relevant_source = load_mimepart(msgnum, relevant_partnum);
766 if (relevant_source != NULL) {
768 cal = icalcomponent_new_from_string(relevant_source);
773 /* Simple components of desired type */
774 if (icalcomponent_isa(cal) == which_kind) {
775 callback(cal, msgnum);
778 /* Subcomponents of desired type */
779 for (c = icalcomponent_get_first_component(cal,
782 c = icalcomponent_get_next_component(cal,
786 icalcomponent_free(cal);
788 free(relevant_source);
794 void display_calendar(long msgnum) {
795 display_using_handler(msgnum, "text/calendar",
796 ICAL_VEVENT_COMPONENT,
797 display_individual_cal);
800 void display_task(long msgnum) {
801 display_using_handler(msgnum, "text/calendar",
802 ICAL_VTODO_COMPONENT,
803 display_individual_cal);
806 void display_edit_task(void) {
809 /* Force change the room if we have to */
810 if (strlen(bstr("taskrm")) > 0) {
811 gotoroom(bstr("taskrm"));
814 msgnum = atol(bstr("msgnum"));
817 display_using_handler(msgnum, "text/calendar",
818 ICAL_VTODO_COMPONENT,
819 display_edit_individual_task);
823 display_edit_individual_task(NULL, 0L);
827 void save_task(void) {
830 msgnum = atol(bstr("msgnum"));
832 display_using_handler(msgnum, "text/calendar",
833 ICAL_VTODO_COMPONENT,
834 save_individual_task);
837 save_individual_task(NULL, 0L);
841 void display_edit_event(void) {
844 msgnum = atol(bstr("msgnum"));
847 display_using_handler(msgnum, "text/calendar",
848 ICAL_VEVENT_COMPONENT,
849 display_edit_individual_event);
853 display_edit_individual_event(NULL, 0L);
857 void save_event(void) {
860 msgnum = atol(bstr("msgnum"));
863 display_using_handler(msgnum, "text/calendar",
864 ICAL_VEVENT_COMPONENT,
865 save_individual_event);
868 save_individual_event(NULL, 0L);
877 * freebusy display (for client software)
879 void do_freebusy(char *req) {
884 extract_token(who, req, 1, ' ', sizeof who);
885 if (!strncasecmp(who, "/freebusy/", 10)) {
886 strcpy(who, &who[10]);
890 if ( (!strcasecmp(&who[strlen(who)-4], ".vcf"))
891 || (!strcasecmp(&who[strlen(who)-4], ".ifb"))
892 || (!strcasecmp(&who[strlen(who)-4], ".vfb")) ) {
893 who[strlen(who)-4] = 0;
896 lprintf(9, "freebusy requested for <%s>\n", who);
897 serv_printf("ICAL freebusy|%s", who);
898 serv_getln(buf, sizeof buf);
901 wprintf("HTTP/1.0 404 %s\n", &buf[4]);
902 output_headers(0, 0, 0, 0, 0, 0, 0);
903 wprintf("Content-Type: text/plain\r\n");
905 wprintf("%s\n", &buf[4]);
909 fb = read_server_text();
910 http_transmit_thing(fb, strlen(fb), "text/calendar", 0);
916 #endif /* WEBCIT_WITH_CALENDAR_SERVICE */