X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fcalendar%2Fserv_calendar.c;h=d5df3a666195ea92aa759bd84cf7de55a3e53615;hb=48754bc0b287edfdfbcec352c79296f4bb80188e;hp=345aaa5a80bb16b4fcabe626e8ffc73c0b085086;hpb=3b7d03bc2ebeea5ab0bf8321d88ec8c6837b91f6;p=citadel.git diff --git a/citadel/modules/calendar/serv_calendar.c b/citadel/modules/calendar/serv_calendar.c index 345aaa5a8..d5df3a666 100644 --- a/citadel/modules/calendar/serv_calendar.c +++ b/citadel/modules/calendar/serv_calendar.c @@ -19,6 +19,7 @@ #ifdef HAVE_STRINGS_H #include #endif +#include #include #include "citadel.h" #include "server.h" @@ -32,10 +33,6 @@ #include "serv_calendar.h" #include "euidindex.h" #include "ctdl_module.h" - -#ifdef CITADEL_WITH_CALENDAR_SERVICE - -#include #include "ical_dezonify.h" @@ -55,8 +52,7 @@ icalcomponent *icalcomponent_new_citadel_vcalendar(void) { encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - lprintf(CTDL_CRIT, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + CtdlLogPrintf(CTDL_CRIT, "ERROR: could not allocate component!\n"); return NULL; } @@ -90,11 +86,6 @@ icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) { /* Encapsulate the subcomponent inside */ icalcomponent_add_component(encaps, subcomp); - /* Convert all timestamps to UTC so we don't have to deal with - * stupid VTIMEZONE crap. - */ - ical_dezonify(encaps); - /* Return the object we just created. */ return(encaps); } @@ -108,8 +99,6 @@ icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) { * to the currently selected room. */ void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) { - char temp[PATH_MAX]; - FILE *fp = NULL; char *ser = NULL; icalcomponent *encaps = NULL; struct CtdlMessage *msg = NULL; @@ -129,29 +118,21 @@ void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) { return; } - ser = icalcomponent_as_ical_string(cal); + ser = icalcomponent_as_ical_string_r(cal); if (ser == NULL) return; /* If the caller supplied a user, write to that user's default calendar room */ if (u) { - /* Make a temp file out of it */ - CtdlMakeTempFileName(temp, sizeof temp); - fp = fopen(temp, "w"); - if (fp != NULL) { - fwrite(ser, strlen(ser), 1, fp); - fclose(fp); - - /* This handy API function does all the work for us. */ - CtdlWriteObject(USERCALENDARROOM, /* which room */ - "text/calendar", /* MIME type */ - temp, /* temp file */ - u, /* which user */ - 0, /* not binary */ - 0, /* don't delete others of this type */ - 0 /* no flags */ - ); - unlink(temp); - } + /* This handy API function does all the work for us. */ + CtdlWriteObject(USERCALENDARROOM, /* which room */ + "text/calendar", /* MIME type */ + ser, /* data */ + strlen(ser)+1, /* length */ + u, /* which user */ + 0, /* not binary */ + 0, /* don't delete others of this type */ + 0 /* no flags */ + ); } /* If the caller did not supply a user, write to the currently selected room */ @@ -170,44 +151,15 @@ void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) { strcat(msg->cm_fields['M'], ser); /* Now write the data */ - CtdlSubmitMsg(msg, NULL, ""); + CtdlSubmitMsg(msg, NULL, "", QP_EADDR); CtdlFreeMessage(msg); } /* In either case, now we can free the serialized calendar object */ -// free(ser); -} - - -/* - * Add a calendar object to the user's calendar - * - * ok because it uses ical_write_to_cal() - */ -void ical_add(icalcomponent *cal, int recursion_level) { - icalcomponent *c; - - /* - * The VEVENT subcomponent is the one we're interested in saving. - */ - if (icalcomponent_isa(cal) == ICAL_VEVENT_COMPONENT) { - - ical_write_to_cal(&CC->user, cal); - - } - - /* If the component has subcomponents, recurse through them. */ - for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); - (c != 0); - c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { - /* Recursively process subcomponent */ - ical_add(c, recursion_level+1); - } - + free(ser); } - /* * Send a reply to a meeting invitation. * @@ -236,13 +188,13 @@ void ical_send_a_reply(icalcomponent *request, char *action) { strcpy(summary_string, "Calendar item"); if (request == NULL) { - lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); + CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); return; } the_reply = icalcomponent_new_clone(request); if (the_reply == NULL) { - lprintf(CTDL_ERR, "ERROR: cannot clone request\n"); + CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone request\n"); return; } @@ -264,7 +216,7 @@ void ical_send_a_reply(icalcomponent *request, char *action) { if (!strncasecmp(attendee_string, "MAILTO:", 7)) { strcpy(attendee_string, &attendee_string[7]); striplt(attendee_string); - recp = validate_recipients(attendee_string); + recp = validate_recipients(attendee_string, NULL, 0); if (recp != NULL) { if (!strcasecmp(recp->recp_local, CC->user.fullname)) { if (me_attend) icalproperty_free(me_attend); @@ -328,14 +280,14 @@ void ical_send_a_reply(icalcomponent *request, char *action) { } /* Now generate the reply message and send it out. */ - serialized_reply = strdup(icalcomponent_as_ical_string(the_reply)); + serialized_reply = icalcomponent_as_ical_string_r(the_reply); icalcomponent_free(the_reply); /* don't need this anymore */ if (serialized_reply == NULL) return; reply_message_text = malloc(strlen(serialized_reply) + SIZ); if (reply_message_text != NULL) { sprintf(reply_message_text, - "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n", + "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n", serialized_reply ); @@ -347,11 +299,12 @@ void ical_send_a_reply(icalcomponent *request, char *action) { "", summary_string, /* Use summary for subject */ NULL, - reply_message_text); + reply_message_text, + NULL); if (msg != NULL) { - valid = validate_recipients(organizer_string); - CtdlSubmitMsg(msg, valid, ""); + valid = validate_recipients(organizer_string, NULL, 0); + CtdlSubmitMsg(msg, valid, "", QP_EADDR); CtdlFreeMessage(msg); free_recipients(valid); } @@ -363,11 +316,12 @@ void ical_send_a_reply(icalcomponent *request, char *action) { /* * Callback function for mime parser that hunts for calendar content types - * and turns them into calendar objects + * and turns them into calendar objects. If something is found, it is placed + * in ird->cal, and the caller now owns that memory and is responsible for freeing it. */ void ical_locate_part(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, - void *cbuserdata) { + char *cbid, void *cbuserdata) { struct ical_respond_data *ird = NULL; @@ -383,7 +337,8 @@ void ical_locate_part(char *name, char *filename, char *partnum, char *disp, } } - if (strcasecmp(cbtype, "text/calendar")) { + if ( (strcasecmp(cbtype, "text/calendar")) + && (strcasecmp(cbtype, "application/ics")) ) { return; } @@ -393,9 +348,6 @@ void ical_locate_part(char *name, char *filename, char *partnum, char *disp, } ird->cal = icalcomponent_new_from_string(content); - if (ird->cal != NULL) { - ical_dezonify(ird->cal); - } } @@ -446,7 +398,7 @@ void ical_respond(long msgnum, char *partnum, char *action) { if (ird.cal != NULL) { /* Save this in the user's calendar if necessary */ if (!strcasecmp(action, "accept")) { - ical_add(ird.cal, 0); + ical_write_to_cal(&CC->user, ird.cal); } /* Send a reply if necessary */ @@ -454,9 +406,9 @@ void ical_respond(long msgnum, char *partnum, char *action) { ical_send_a_reply(ird.cal, action); } - /* Now that we've processed this message, we don't need it - * anymore. So delete it. (NOTE we don't do this anymore.) - CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); + /* We used to delete the invitation after handling it. + * We don't do that anymore, but here is the code that handled it: + * CtdlDeleteMessages(CC->room.QRname, &msgnum, 1, ""); */ /* Free the memory we allocated and return a response. */ @@ -525,11 +477,12 @@ struct original_event_container { */ void ical_locate_original_event(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, char *encoding, - void *cbuserdata) { + char *cbid, void *cbuserdata) { struct original_event_container *oec = NULL; - if (strcasecmp(cbtype, "text/calendar")) { + if ( (strcasecmp(cbtype, "text/calendar")) + && (strcasecmp(cbtype, "application/ics")) ) { return; } oec = (struct original_event_container *) cbuserdata; @@ -643,27 +596,27 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { /* Figure out just what event it is we're dealing with */ strcpy(uid, "--==<< InVaLiD uId >>==--"); ical_learn_uid_of_reply(uid, cal); - lprintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid); + CtdlLogPrintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid); strcpy(hold_rm, CC->room.QRname); /* save current room */ if (getroom(&CC->room, USERCALENDARROOM) != 0) { getroom(&CC->room, hold_rm); - lprintf(CTDL_CRIT, "cannot get user calendar room\n"); + CtdlLogPrintf(CTDL_CRIT, "cannot get user calendar room\n"); return(2); } /* * Look in the EUID index for a message with * the Citadel EUID set to the value we're looking for. Since - * Citadel always sets the message EUID to the vCalendar UID of + * Citadel always sets the message EUID to the iCalendar UID of * the event, this will work. */ msgnum_being_replaced = locate_message_by_euid(uid, &CC->room); getroom(&CC->room, hold_rm); /* return to saved room */ - lprintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced); + CtdlLogPrintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced); if (msgnum_being_replaced == 0) { return(1); /* no calendar event found */ } @@ -690,7 +643,7 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { original_event = oec.c; if (original_event == NULL) { - lprintf(CTDL_ERR, "ERROR: Original_component is NULL.\n"); + CtdlLogPrintf(CTDL_ERR, "ERROR: Original_component is NULL.\n"); return(2); } @@ -698,7 +651,7 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { ical_merge_attendee_reply(original_event, cal); /* Serialize it */ - serialized_event = strdup(icalcomponent_as_ical_string(original_event)); + serialized_event = icalcomponent_as_ical_string_r(original_event); icalcomponent_free(original_event); /* Don't need this anymore. */ if (serialized_event == NULL) return(2); @@ -707,7 +660,7 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { message_text = malloc(strlen(serialized_event) + SIZ); if (message_text != NULL) { sprintf(message_text, - "Content-type: text/calendar charset=\"utf-8\"\r\n\r\n%s\r\n", + "Content-type: text/calendar; charset=\"utf-8\"\r\n\r\n%s\r\n", serialized_event ); @@ -720,11 +673,12 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { "", "", /* no subject */ NULL, - message_text); + message_text, + NULL); if (msg != NULL) { CIT_ICAL->avoid_sending_invitations = 1; - CtdlSubmitMsg(msg, NULL, roomname); + CtdlSubmitMsg(msg, NULL, roomname, QP_EADDR); CtdlFreeMessage(msg); CIT_ICAL->avoid_sending_invitations = 0; } @@ -899,87 +853,296 @@ int ical_ctdl_is_overlap( +/* + * Phase 6 of "hunt for conflicts" + * called by ical_conflicts_phase5() + * + * Now both the proposed and existing events have been boiled down to start and end times. + * Check for overlap and output any conflicts. + * + * Returns nonzero if a conflict was reported. This allows the caller to stop iterating. + */ +int ical_conflicts_phase6(struct icaltimetype t1start, + struct icaltimetype t1end, + struct icaltimetype t2start, + struct icaltimetype t2end, + long existing_msgnum, + char *conflict_event_uid, + char *conflict_event_summary, + char *compare_uid) +{ + int conflict_reported = 0; + + /* debugging cruft * + time_t tt; + tt = icaltime_as_timet_with_zone(t1start, t1start.zone); + CtdlLogPrintf(CTDL_DEBUG, "PROPOSED START: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t1end, t1end.zone); + CtdlLogPrintf(CTDL_DEBUG, " PROPOSED END: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t2start, t2start.zone); + CtdlLogPrintf(CTDL_DEBUG, "EXISTING START: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t2end, t2end.zone); + CtdlLogPrintf(CTDL_DEBUG, " EXISTING END: %s", ctime(&tt)); + * debugging cruft */ + + /* compare and output */ + + if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) { + cprintf("%ld||%s|%s|%d|\n", + existing_msgnum, + conflict_event_uid, + conflict_event_summary, + ( ((strlen(compare_uid)>0) + &&(!strcasecmp(compare_uid, + conflict_event_uid))) ? 1 : 0 + ) + ); + conflict_reported = 1; + } + + return(conflict_reported); +} + + + /* - * Backend for ical_hunt_for_conflicts() + * Phase 5 of "hunt for conflicts" + * Called by ical_conflicts_phase4() + * + * We have the proposed event boiled down to start and end times. + * Now check it against an existing event. */ -void ical_hunt_for_conflicts_backend(long msgnum, void *data) { - icalcomponent *cal; - struct CtdlMessage *msg = NULL; - struct ical_respond_data ird; - struct icaltimetype t1start, t1end, t2start, t2end; - icalproperty *p; +void ical_conflicts_phase5(struct icaltimetype t1start, + struct icaltimetype t1end, + icalcomponent *existing_event, + long existing_msgnum, + char *compare_uid) +{ char conflict_event_uid[SIZ]; char conflict_event_summary[SIZ]; - char compare_uid[SIZ]; + struct icaltimetype t2start, t2end; + icalproperty *p; - cal = (icalcomponent *)data; - strcpy(compare_uid, ""); + /* recur variables */ + icalproperty *rrule = NULL; + struct icalrecurrencetype recur; + icalrecur_iterator *ritr = NULL; + struct icaldurationtype dur; + int num_recur = 0; + + /* initialization */ strcpy(conflict_event_uid, ""); strcpy(conflict_event_summary, ""); + t2start = icaltime_null_time(); + t2end = icaltime_null_time(); - msg = CtdlFetchMessage(msgnum, 1); - if (msg == NULL) return; - memset(&ird, 0, sizeof ird); - strcpy(ird.desired_partnum, "_HUNT_"); - mime_parser(msg->cm_fields['M'], - NULL, - *ical_locate_part, /* callback function */ - NULL, NULL, - (void *) &ird, /* user data */ - 0 - ); - CtdlFreeMessage(msg); + /* existing event stuff */ + p = ical_ctdl_get_subprop(existing_event, ICAL_DTSTART_PROPERTY); + if (p == NULL) return; + if (p != NULL) t2start = icalproperty_get_dtstart(p); + if (icaltime_is_utc(t2start)) { + t2start.zone = icaltimezone_get_utc_timezone(); + } + else { + t2start.zone = icalcomponent_get_timezone(existing_event, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (!t2start.zone) { + t2start.zone = get_default_icaltimezone(); + } + } - if (ird.cal == NULL) return; + p = ical_ctdl_get_subprop(existing_event, ICAL_DTEND_PROPERTY); + if (p != NULL) { + t2end = icalproperty_get_dtend(p); + if (icaltime_is_utc(t2end)) { + t2end.zone = icaltimezone_get_utc_timezone(); + } + else { + t2end.zone = icalcomponent_get_timezone(existing_event, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (!t2end.zone) { + t2end.zone = get_default_icaltimezone(); + } + } + dur = icaltime_subtract(t2end, t2start); + } + + rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY); + if (rrule) { + recur = icalproperty_get_rrule(rrule); + ritr = icalrecur_iterator_new(recur, t2start); + } + + do { + p = ical_ctdl_get_subprop(existing_event, ICAL_UID_PROPERTY); + if (p != NULL) { + strcpy(conflict_event_uid, icalproperty_get_comment(p)); + } + + p = ical_ctdl_get_subprop(existing_event, ICAL_SUMMARY_PROPERTY); + if (p != NULL) { + strcpy(conflict_event_summary, icalproperty_get_comment(p)); + } + + if (ical_conflicts_phase6(t1start, t1end, t2start, t2end, + existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid)) + { + num_recur = MAX_RECUR + 1; /* force it out of scope, no need to continue */ + } + + if (rrule) { + t2start = icalrecur_iterator_next(ritr); + if (!icaltime_is_null_time(t2end)) { + const icaltimezone *hold_zone = t2end.zone; + t2end = icaltime_add(t2start, dur); + t2end.zone = hold_zone; + } + ++num_recur; + } + + if (icaltime_compare(t2start, t1end) < 0) { + num_recur = MAX_RECUR + 1; /* force it out of scope */ + } + + } while ( (rrule) && (!icaltime_is_null_time(t2start)) && (num_recur < MAX_RECUR) ); + icalrecur_iterator_free(ritr); +} + + + + +/* + * Phase 4 of "hunt for conflicts" + * Called by ical_hunt_for_conflicts_backend() + * + * At this point we've got it boiled down to two icalcomponent events in memory. + * If they conflict, output something to the client. + */ +void ical_conflicts_phase4(icalcomponent *proposed_event, + icalcomponent *existing_event, + long existing_msgnum) +{ + struct icaltimetype t1start, t1end; t1start = icaltime_null_time(); t1end = icaltime_null_time(); - t2start = icaltime_null_time(); - t1end = icaltime_null_time(); + icalproperty *p; + char compare_uid[SIZ]; - /* Now compare cal to ird.cal */ - p = ical_ctdl_get_subprop(ird.cal, ICAL_DTSTART_PROPERTY); - if (p == NULL) return; - if (p != NULL) t2start = icalproperty_get_dtstart(p); - - p = ical_ctdl_get_subprop(ird.cal, ICAL_DTEND_PROPERTY); - if (p != NULL) t2end = icalproperty_get_dtend(p); + /* recur variables */ + icalproperty *rrule = NULL; + struct icalrecurrencetype recur; + icalrecur_iterator *ritr = NULL; + struct icaldurationtype dur; + int num_recur = 0; - p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY); + /* initialization */ + strcpy(compare_uid, ""); + + /* proposed event stuff */ + + p = ical_ctdl_get_subprop(proposed_event, ICAL_DTSTART_PROPERTY); if (p == NULL) return; if (p != NULL) t1start = icalproperty_get_dtstart(p); + if (icaltime_is_utc(t1start)) { + t1start.zone = icaltimezone_get_utc_timezone(); + } + else { + t1start.zone = icalcomponent_get_timezone(proposed_event, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (!t1start.zone) { + t1start.zone = get_default_icaltimezone(); + } + } - p = ical_ctdl_get_subprop(cal, ICAL_DTEND_PROPERTY); - if (p != NULL) t1end = icalproperty_get_dtend(p); - - p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY); + p = ical_ctdl_get_subprop(proposed_event, ICAL_DTEND_PROPERTY); if (p != NULL) { - strcpy(compare_uid, icalproperty_get_comment(p)); + t1end = icalproperty_get_dtend(p); + + if (icaltime_is_utc(t1end)) { + t1end.zone = icaltimezone_get_utc_timezone(); + } + else { + t1end.zone = icalcomponent_get_timezone(proposed_event, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (!t1end.zone) { + t1end.zone = get_default_icaltimezone(); + } + } + + dur = icaltime_subtract(t1end, t1start); } - p = ical_ctdl_get_subprop(ird.cal, ICAL_UID_PROPERTY); - if (p != NULL) { - strcpy(conflict_event_uid, icalproperty_get_comment(p)); + rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY); + if (rrule) { + recur = icalproperty_get_rrule(rrule); + ritr = icalrecur_iterator_new(recur, t1start); } - p = ical_ctdl_get_subprop(ird.cal, ICAL_SUMMARY_PROPERTY); + p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY); if (p != NULL) { - strcpy(conflict_event_summary, icalproperty_get_comment(p)); + strcpy(compare_uid, icalproperty_get_comment(p)); } - icalcomponent_free(ird.cal); + do { + ical_conflicts_phase5(t1start, t1end, existing_event, existing_msgnum, compare_uid); - if (ical_ctdl_is_overlap(t1start, t1end, t2start, t2end)) { - cprintf("%ld||%s|%s|%d|\n", - msgnum, - conflict_event_uid, - conflict_event_summary, - ( ((strlen(compare_uid)>0) - &&(!strcasecmp(compare_uid, - conflict_event_uid))) ? 1 : 0 - ) - ); - } + if (rrule) { + t1start = icalrecur_iterator_next(ritr); + if (!icaltime_is_null_time(t1end)) { + const icaltimezone *hold_zone = t1end.zone; + t1end = icaltime_add(t1start, dur); + t1end.zone = hold_zone; + } + ++num_recur; + } + + } while ( (rrule) && (!icaltime_is_null_time(t1start)) && (num_recur < MAX_RECUR) ); + icalrecur_iterator_free(ritr); +} + + + +/* + * Phase 3 of "hunt for conflicts" + * Called by ical_hunt_for_conflicts() + */ +void ical_hunt_for_conflicts_backend(long msgnum, void *data) { + icalcomponent *proposed_event; + struct CtdlMessage *msg = NULL; + struct ical_respond_data ird; + + proposed_event = (icalcomponent *)data; + + msg = CtdlFetchMessage(msgnum, 1); + if (msg == NULL) return; + memset(&ird, 0, sizeof ird); + strcpy(ird.desired_partnum, "_HUNT_"); + mime_parser(msg->cm_fields['M'], + NULL, + *ical_locate_part, /* callback function */ + NULL, NULL, + (void *) &ird, /* user data */ + 0 + ); + CtdlFreeMessage(msg); + + if (ird.cal == NULL) return; + + ical_conflicts_phase4(proposed_event, ird.cal, msgnum); + icalcomponent_free(ird.cal); } @@ -987,7 +1150,7 @@ void ical_hunt_for_conflicts_backend(long msgnum, void *data) { /* * Phase 2 of "hunt for conflicts" operation. * At this point we have a calendar object which represents the VEVENT that - * we're considering adding to the calendar. Now hunt through the user's + * is proposed for addition to the calendar. Now hunt through the user's * calendar room, and output zero or more existing VEVENTs which conflict * with this one. */ @@ -1027,7 +1190,7 @@ void ical_conflicts(long msgnum, char *partnum) { msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) { - cprintf("%d Message %ld not found.\n", + cprintf("%d Message %ld not found\n", ERROR + ILLEGAL_VALUE, (long)msgnum ); @@ -1051,29 +1214,37 @@ void ical_conflicts(long msgnum, char *partnum) { icalcomponent_free(ird.cal); return; } - else { - cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND); - return; - } - /* should never get here */ + cprintf("%d No calendar object found\n", ERROR + ROOM_NOT_FOUND); } /* * Look for busy time in a VEVENT and add it to the supplied VFREEBUSY. + * + * fb The VFREEBUSY component to which we are appending + * top_level_cal The top-level VCALENDAR component which contains VEVENT to be added + * cal Caller supplies NULL, but we then use this variable for recursion */ -void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) { +void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *top_level_cal, icalcomponent *cal) { icalproperty *p; icalvalue *v; - struct icalperiodtype my_period; + struct icalperiodtype this_event_period; - if (cal == NULL) return; - my_period = icalperiodtype_null_period(); + if (!top_level_cal) return; + if (!cal) cal = top_level_cal; + this_event_period = icalperiodtype_null_period(); + + /* Convert all time zones to UTC (FIXME this won't work with recurring events) */ + if (icalcomponent_isa(cal) != ICAL_VCALENDAR_COMPONENT) { + ical_dezonify(cal); + } + + /* Now boil it down to the VEVENT only (FIXME this won't work with recurring events) */ if (icalcomponent_isa(cal) != ICAL_VEVENT_COMPONENT) { - ical_add_to_freebusy(fb, + ical_add_to_freebusy(fb, top_level_cal, icalcomponent_get_first_component( cal, ICAL_VEVENT_COMPONENT ) @@ -1081,8 +1252,6 @@ void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) { return; } - ical_dezonify(cal); - /* If this event is not opaque, the user isn't publishing it as * busy time, so don't bother doing anything else. */ @@ -1099,17 +1268,17 @@ void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) { /* Convert the DTSTART and DTEND properties to an icalperiod. */ p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY); if (p != NULL) { - my_period.start = icalproperty_get_dtstart(p); + this_event_period.start = icalproperty_get_dtstart(p); } p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY); if (p != NULL) { - my_period.end = icalproperty_get_dtstart(p); + this_event_period.end = icalproperty_get_dtstart(p); } /* Now add it. */ icalcomponent_add_property(fb, - icalproperty_new_freebusy(my_period) + icalproperty_new_freebusy(this_event_period) ); /* Make sure the DTSTART property of the freebusy *list* is set to @@ -1161,11 +1330,11 @@ void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) { * */ void ical_freebusy_backend(long msgnum, void *data) { - icalcomponent *cal; + icalcomponent *fb; struct CtdlMessage *msg = NULL; struct ical_respond_data ird; - cal = (icalcomponent *)data; + fb = (icalcomponent *)data; /* User-supplied data will be the VFREEBUSY component */ msg = CtdlFetchMessage(msgnum, 1); if (msg == NULL) return; @@ -1180,12 +1349,10 @@ void ical_freebusy_backend(long msgnum, void *data) { ); CtdlFreeMessage(msg); - if (ird.cal == NULL) return; - - ical_add_to_freebusy(cal, ird.cal); - - /* Now free the memory. */ - icalcomponent_free(ird.cal); + if (ird.cal) { + ical_add_to_freebusy(fb, ird.cal, NULL); /* Add VEVENT times to VFREEBUSY */ + icalcomponent_free(ird.cal); + } } @@ -1214,8 +1381,8 @@ void ical_freebusy(char *who) { /* If not found, try it as an unqualified email address. */ if (found_user != 0) { strcpy(buf, who); - recp = validate_recipients(buf); - lprintf(CTDL_DEBUG, "Trying <%s>\n", buf); + recp = validate_recipients(buf, NULL, 0); + CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); if (recp != NULL) { if (recp->num_local == 1) { found_user = getuser(&usbuf, recp->recp_local); @@ -1229,8 +1396,8 @@ void ical_freebusy(char *who) { */ if (found_user != 0) { snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn); - lprintf(CTDL_DEBUG, "Trying <%s>\n", buf); - recp = validate_recipients(buf); + CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); + recp = validate_recipients(buf, NULL, 0); if (recp != NULL) { if (recp->num_local == 1) { found_user = getuser(&usbuf, recp->recp_local); @@ -1252,8 +1419,8 @@ void ical_freebusy(char *who) { if ( (!strcasecmp(type, "localhost")) || (!strcasecmp(type, "directory")) ) { snprintf(buf, sizeof buf, "%s@%s", who, host); - lprintf(CTDL_DEBUG, "Trying <%s>\n", buf); - recp = validate_recipients(buf); + CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); + recp = validate_recipients(buf, NULL, 0); if (recp != NULL) { if (recp->num_local == 1) { found_user = getuser(&usbuf, recp->recp_local); @@ -1281,7 +1448,7 @@ void ical_freebusy(char *who) { } /* Create a VFREEBUSY subcomponent */ - lprintf(CTDL_DEBUG, "Creating VFREEBUSY component\n"); + CtdlLogPrintf(CTDL_DEBUG, "Creating VFREEBUSY component\n"); fb = icalcomponent_new_vfreebusy(); if (fb == NULL) { cprintf("%d Internal error: cannot allocate memory.\n", @@ -1308,7 +1475,7 @@ void ical_freebusy(char *who) { icalcomponent_add_property(fb, icalproperty_new_organizer(buf)); /* Add busy time from events */ - lprintf(CTDL_DEBUG, "Adding busy time from events\n"); + CtdlLogPrintf(CTDL_DEBUG, "Adding busy time from events\n"); CtdlForEachMessage(MSGS_ALL, 0, NULL, NULL, NULL, ical_freebusy_backend, (void *)fb ); /* If values for DTSTART and DTEND are still not present, set them @@ -1322,7 +1489,7 @@ void ical_freebusy(char *who) { } /* Put the freebusy component into the calendar component */ - lprintf(CTDL_DEBUG, "Encapsulating\n"); + CtdlLogPrintf(CTDL_DEBUG, "Encapsulating\n"); encaps = ical_encapsulate_subcomponent(fb); if (encaps == NULL) { icalcomponent_free(fb); @@ -1333,12 +1500,12 @@ void ical_freebusy(char *who) { } /* Set the method to PUBLISH */ - lprintf(CTDL_DEBUG, "Setting method\n"); + CtdlLogPrintf(CTDL_DEBUG, "Setting method\n"); icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); /* Serialize it */ - lprintf(CTDL_DEBUG, "Serializing\n"); - serialized_request = strdup(icalcomponent_as_ical_string(encaps)); + CtdlLogPrintf(CTDL_DEBUG, "Serializing\n"); + serialized_request = icalcomponent_as_ical_string_r(encaps); icalcomponent_free(encaps); /* Don't need this anymore. */ cprintf("%d Here is the free/busy data:\n", LISTING_FOLLOWS); @@ -1404,7 +1571,24 @@ void ical_getics_backend(long msgnum, void *data) { for (c = icalcomponent_get_first_component(ird.cal, ICAL_ANY_COMPONENT); (c != NULL); c = icalcomponent_get_next_component(ird.cal, ICAL_ANY_COMPONENT)) { - icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); + + /* For VTIMEZONE components, suppress duplicates of the same tzid */ + + if (icalcomponent_isa(c) == ICAL_VTIMEZONE_COMPONENT) { + icalproperty *p = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY); + if (p) { + const char *tzid = icalproperty_get_tzid(p); + if (!icalcomponent_get_timezone(encaps, tzid)) { + icalcomponent_add_component(encaps, + icalcomponent_new_clone(c)); + } + } + } + + /* All other types of components can go in verbatim */ + else { + icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); + } } icalcomponent_free(ird.cal); } @@ -1424,13 +1608,12 @@ void ical_getics(void) if ( (CC->room.QRdefaultview != VIEW_CALENDAR) &&(CC->room.QRdefaultview != VIEW_TASKS) ) { cprintf("%d Not a calendar room\n", ERROR+NOT_HERE); - return; /* Not a vCalendar-centric room */ + return; /* Not an iCalendar-centric room */ } encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + CtdlLogPrintf(CTDL_DEBUG, "ERROR: could not allocate component!\n"); cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR); return; } @@ -1454,12 +1637,27 @@ void ical_getics(void) (void *) encaps ); - ser = strdup(icalcomponent_as_ical_string(encaps)); + ser = icalcomponent_as_ical_string_r(encaps); + icalcomponent_free(encaps); /* Don't need this anymore. */ client_write(ser, strlen(ser)); free(ser); cprintf("\n000\n"); - icalcomponent_free(encaps); /* Don't need this anymore. */ +} + +/* + * Helper callback function for ical_putics() to discover which TZID's we need. + * Simply put the tzid name string into a hash table. After the callbacks are + * done we'll go through them and attach the ones that we have. + */ +void ical_putics_grabtzids(icalparameter *param, void *data) +{ + const char *tzid = icalparameter_get_tzid(param); + HashList *keys = (HashList *) data; + + if ( (keys) && (tzid) && (!IsEmptyStr(tzid)) ) { + Put(keys, tzid, strlen(tzid), strdup(tzid), generic_free_handler); + } } @@ -1472,13 +1670,21 @@ void ical_putics(void) char *calstream = NULL; icalcomponent *cal; icalcomponent *c; + icalcomponent *encaps = NULL; + HashList *tzidlist = NULL; + HashPos *HashPos; + void *Value; + const char *Key; + long len; + /* Only allow this operation if we're in a room containing a calendar or tasks view */ if ( (CC->room.QRdefaultview != VIEW_CALENDAR) &&(CC->room.QRdefaultview != VIEW_TASKS) ) { cprintf("%d Not a calendar room\n", ERROR+NOT_HERE); - return; /* Not a vCalendar-centric room */ + return; } + /* Only allow this operation if we have permission to overwrite the existing calendar */ if (!CtdlDoIHavePermissionToDeleteMessagesFromThisRoom()) { cprintf("%d Permission denied.\n", ERROR+HIGHER_ACCESS_REQUIRED); return; @@ -1492,11 +1698,10 @@ void ical_putics(void) cal = icalcomponent_new_from_string(calstream); free(calstream); - ical_dezonify(cal); /* We got our data stream -- now do something with it. */ - /* Delete the existing messages in the room, because we are replacing + /* Delete the existing messages in the room, because we are overwriting * the entire calendar with an entire new (or updated) calendar. * (Careful: this opens an S_ROOMS critical section!) */ @@ -1516,7 +1721,55 @@ void ical_putics(void) for (c = icalcomponent_get_first_component(cal, ICAL_ANY_COMPONENT); (c != NULL); c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { - ical_write_to_cal(NULL, c); + + /* Non-VTIMEZONE components each get written as individual messages. + * But we also need to attach the relevant VTIMEZONE components to them. + */ + if ( (icalcomponent_isa(c) != ICAL_VTIMEZONE_COMPONENT) + && (encaps = icalcomponent_new_vcalendar()) ) { + icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID)); + icalcomponent_add_property(encaps, icalproperty_new_version("2.0")); + icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); + + /* Attach any needed timezones here */ + tzidlist = NewHash(1, NULL); + if (tzidlist) { + icalcomponent_foreach_tzid(c, ical_putics_grabtzids, tzidlist); + } + HashPos = GetNewHashPos(tzidlist, 0); + + while (GetNextHashPos(tzidlist, HashPos, &len, &Key, &Value)) { + CtdlLogPrintf(CTDL_DEBUG, "Attaching timezone '%s'\n", Value); + icaltimezone *t = NULL; + + /* First look for a timezone attached to the original calendar */ + t = icalcomponent_get_timezone(cal, Value); + + /* Try built-in tzdata if the right one wasn't attached */ + if (!t) { + t = icaltimezone_get_builtin_timezone(Value); + } + + /* I've got a valid timezone to attach. */ + if (t) { + icalcomponent_add_component(encaps, + icalcomponent_new_clone( + icaltimezone_get_component(t) + ) + ); + } + + } + DeleteHashPos(&HashPos); + DeleteHash(&tzidlist); + + /* Now attach the component itself (usually a VEVENT or VTODO) */ + icalcomponent_add_component(encaps, icalcomponent_new_clone(c)); + + /* Write it to the message store */ + ical_write_to_cal(NULL, encaps); + icalcomponent_free(encaps); + } } } @@ -1611,7 +1864,7 @@ void ical_create_room(void) /* Set expiration policy to manual; otherwise objects will be lost! */ if (lgetroom(&qr, USERCALENDARROOM)) { - lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; @@ -1628,7 +1881,7 @@ void ical_create_room(void) /* Set expiration policy to manual; otherwise objects will be lost! */ if (lgetroom(&qr, USERTASKSROOM)) { - lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; @@ -1645,7 +1898,7 @@ void ical_create_room(void) /* Set expiration policy to manual; otherwise objects will be lost! */ if (lgetroom(&qr, USERNOTESROOM)) { - lprintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; @@ -1662,10 +1915,14 @@ void ical_create_room(void) /* - * ical_send_out_invitations() is called by ical_saving_vevent() when it - * finds a VEVENT. + * ical_send_out_invitations() is called by ical_saving_vevent() when it finds a VEVENT. + * + * top_level_cal is the highest available level calendar object. + * cal is the subcomponent containing the VEVENT. + * + * Note: if you change the encapsulation code here, change it in WebCit's ical_encapsulate_subcomponent() */ -void ical_send_out_invitations(icalcomponent *cal) { +void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal) { icalcomponent *the_request = NULL; char *serialized_request = NULL; icalcomponent *encaps = NULL; @@ -1678,16 +1935,24 @@ void ical_send_out_invitations(icalcomponent *cal) { icalproperty *attendee = NULL; char summary_string[SIZ]; icalproperty *summary = NULL; + size_t reqsize; + icalproperty *p; + struct icaltimetype t; + icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL }; + int i; + icaltimezone *z; + int num_zones_attached = 0; + int zone_already_attached; if (cal == NULL) { - lprintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); + CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); return; } /* If this is a VCALENDAR component, look for a VEVENT subcomponent. */ if (icalcomponent_isa(cal) == ICAL_VCALENDAR_COMPONENT) { - ical_send_out_invitations( + ical_send_out_invitations(top_level_cal, icalcomponent_get_first_component( cal, ICAL_VEVENT_COMPONENT ) @@ -1698,7 +1963,7 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Clone the event */ the_request = icalcomponent_new_clone(cal); if (the_request == NULL) { - lprintf(CTDL_ERR, "ERROR: cannot clone calendar object\n"); + CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone calendar object\n"); return; } @@ -1734,7 +1999,7 @@ void ical_send_out_invitations(icalcomponent *cal) { } } - lprintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string); + CtdlLogPrintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string); /* If there are no attendees, there are no invitations to send, so... * don't bother putting one together! Punch out, Maverick! @@ -1747,8 +2012,7 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Encapsulate the VEVENT component into a complete VCALENDAR */ encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - lprintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + CtdlLogPrintf(CTDL_DEBUG, "ERROR: could not allocate component!\n"); icalcomponent_free(the_request); return; } @@ -1762,23 +2026,105 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Set the method to REQUEST */ icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST); - /* Now make sure all of the DTSTART and DTEND properties are UTC. */ - ical_dezonify(the_request); + /* Look for properties containing timezone parameters, to see if we need to attach VTIMEZONEs */ + for (p = icalcomponent_get_first_property(the_request, ICAL_ANY_PROPERTY); + p != NULL; + p = icalcomponent_get_next_property(the_request, ICAL_ANY_PROPERTY)) + { + if ( (icalproperty_isa(p) == ICAL_COMPLETED_PROPERTY) + || (icalproperty_isa(p) == ICAL_CREATED_PROPERTY) + || (icalproperty_isa(p) == ICAL_DATEMAX_PROPERTY) + || (icalproperty_isa(p) == ICAL_DATEMIN_PROPERTY) + || (icalproperty_isa(p) == ICAL_DTEND_PROPERTY) + || (icalproperty_isa(p) == ICAL_DTSTAMP_PROPERTY) + || (icalproperty_isa(p) == ICAL_DTSTART_PROPERTY) + || (icalproperty_isa(p) == ICAL_DUE_PROPERTY) + || (icalproperty_isa(p) == ICAL_EXDATE_PROPERTY) + || (icalproperty_isa(p) == ICAL_LASTMODIFIED_PROPERTY) + || (icalproperty_isa(p) == ICAL_MAXDATE_PROPERTY) + || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY) + || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY) + ) { + t = icalproperty_get_dtstart(p); // it's safe to use dtstart for all of them + CtdlLogPrintf(CTDL_DEBUG, "Found an icaltimetype: %s\n", + icaltime_as_ical_string(t) + ); - /* Here we go: put the VEVENT into the VCALENDAR. We now no longer + /* First see if there's a timezone attached to the data structure itself */ + if (icaltime_is_utc(t)) { + z = icaltimezone_get_utc_timezone(); + } + else { + z = icaltime_get_timezone(t); + } + if (z) CtdlLogPrintf(CTDL_DEBUG, "Timezone is present in data structure\n"); + + /* If not, try to determine the tzid from the parameter using attached zones */ + if (!z) { + z = icalcomponent_get_timezone(top_level_cal, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (z) CtdlLogPrintf(CTDL_DEBUG, "Timezone was found in attached zones\n"); + } + + /* Still no good? Try our internal database */ + if (!z) { + z = icaltimezone_get_builtin_timezone_from_tzid( + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (z) CtdlLogPrintf(CTDL_DEBUG, "Timezone was found in internal db\n"); + } + + if (z) { + CtdlLogPrintf(CTDL_DEBUG, "Have valid timezone, need to attach it.\n"); + + zone_already_attached = 0; + for (i=0; i<5; ++i) { + if (z == attached_zones[i]) { + ++zone_already_attached; + CtdlLogPrintf(CTDL_DEBUG, "zone already attached!!\n"); + } + } + if ((!zone_already_attached) && (num_zones_attached < 5)) { + CtdlLogPrintf(CTDL_DEBUG, "attach zone %d\n", num_zones_attached); + attached_zones[num_zones_attached++] = z; + } + + icalproperty_set_parameter(p, + icalparameter_new_tzid(icaltimezone_get_tzid(z)) + ); + } + } + } + + /* Encapsulate any timezones we need */ + if (num_zones_attached > 0) for (i=0; iserver_generated_invitations == 0) { @@ -1848,7 +2200,7 @@ void ical_saving_vevent(icalcomponent *cal) { * organizer, then send out invitations. */ if (CtdlIsMe(organizer_string, sizeof organizer_string)) { - ical_send_out_invitations(cal); + ical_send_out_invitations(top_level_cal, cal); } } } @@ -1858,7 +2210,7 @@ void ical_saving_vevent(icalcomponent *cal) { (c != NULL); c = icalcomponent_get_next_component(cal, ICAL_ANY_COMPONENT)) { /* Recursively process subcomponent */ - ical_saving_vevent(c); + ical_saving_vevent(top_level_cal, c); } } @@ -1871,19 +2223,21 @@ void ical_saving_vevent(icalcomponent *cal) { * the summary of the event (becomes message subject), * and the start time (becomes message date/time). */ -void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum, +void ical_obj_beforesave_backend(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, - char *encoding, void *cbuserdata) + char *encoding, char *cbid, void *cbuserdata) { icalcomponent *cal, *nested_event, *nested_todo, *whole_cal; icalproperty *p; - struct icalmessagemod *imm; - char new_uid[SIZ]; + char new_uid[256] = ""; + char buf[1024] = ""; + struct CtdlMessage *msg = (struct CtdlMessage *) cbuserdata; - imm = (struct icalmessagemod *)cbuserdata; + if (!msg) return; /* We're only interested in calendar data. */ - if (strcasecmp(cbtype, "text/calendar")) { + if ( (strcasecmp(cbtype, "text/calendar")) + && (strcasecmp(cbtype, "application/ics")) ) { return; } @@ -1911,6 +2265,9 @@ void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum, } if (cal != NULL) { + + /* Set the message EUID to the iCalendar UID */ + p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY); if (p == NULL) { /* If there's no uid we must generate one */ @@ -1919,16 +2276,44 @@ void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum, p = ical_ctdl_get_subprop(cal, ICAL_UID_PROPERTY); } if (p != NULL) { - strcpy(imm->uid, icalproperty_get_comment(p)); + safestrncpy(buf, icalproperty_get_comment(p), sizeof buf); + if (!IsEmptyStr(buf)) { + if (msg->cm_fields['E'] != NULL) { + free(msg->cm_fields['E']); + } + msg->cm_fields['E'] = strdup(buf); + CtdlLogPrintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", buf); + } } + + /* Set the message subject to the iCalendar summary */ + p = ical_ctdl_get_subprop(cal, ICAL_SUMMARY_PROPERTY); if (p != NULL) { - strcpy(imm->subject, icalproperty_get_comment(p)); + safestrncpy(buf, icalproperty_get_comment(p), sizeof buf); + if (!IsEmptyStr(buf)) { + if (msg->cm_fields['U'] != NULL) { + free(msg->cm_fields['U']); + } + msg->cm_fields['U'] = strdup(buf); + } } + + /* Set the message date/time to the iCalendar start time */ + p = ical_ctdl_get_subprop(cal, ICAL_DTSTART_PROPERTY); if (p != NULL) { - imm->dtstart = icaltime_as_timet(icalproperty_get_dtstart(p)); + time_t idtstart; + idtstart = icaltime_as_timet(icalproperty_get_dtstart(p)); + if (idtstart > 0) { + if (msg->cm_fields['T'] != NULL) { + free(msg->cm_fields['T']); + } + msg->cm_fields['T'] = strdup("000000000000000000"); + sprintf(msg->cm_fields['T'], "%ld", idtstart); + } } + } icalcomponent_free(cal); if (whole_cal != cal) { @@ -1942,29 +2327,23 @@ void ical_ctdl_set_exclusive_msgid(char *name, char *filename, char *partnum, /* * See if we need to prevent the object from being saved (we don't allow - * MIME types other than text/calendar in "calendar" or "tasks" rooms). Also, - * when saving an event to the calendar, set the message's Citadel exclusive - * message ID to the UID of the object. This causes our replication checker to - * automatically delete any existing instances of the same object. (Isn't - * that cool?) + * MIME types other than text/calendar in "calendar" or "tasks" rooms). * - * We also set the message's Subject to the event summary, and the Date/time to - * the event start time. + * If the message is being saved, we also set various message header fields + * using data found in the iCalendar object. */ int ical_obj_beforesave(struct CtdlMessage *msg) { - struct icalmessagemod imm; - /* First determine if this is a calendar or tasks room */ if ( (CC->room.QRdefaultview != VIEW_CALENDAR) && (CC->room.QRdefaultview != VIEW_TASKS) ) { - return(0); /* Not a vCalendar-centric room */ + return(0); /* Not an iCalendar-centric room */ } /* It must be an RFC822 message! */ if (msg->cm_format_type != 4) { - lprintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n"); + CtdlLogPrintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n"); return(1); /* You tried to save a non-RFC822 message! */ } @@ -1972,38 +2351,15 @@ int ical_obj_beforesave(struct CtdlMessage *msg) return(1); /* You tried to save a null message! */ } - memset(&imm, 0, sizeof(struct icalmessagemod)); - /* Do all of our lovely back-end parsing */ mime_parser(msg->cm_fields['M'], NULL, - *ical_ctdl_set_exclusive_msgid, + *ical_obj_beforesave_backend, NULL, NULL, - (void *)&imm, + (void *)msg, 0 ); - if (!IsEmptyStr(imm.uid)) { - if (msg->cm_fields['E'] != NULL) { - free(msg->cm_fields['E']); - } - msg->cm_fields['E'] = strdup(imm.uid); - lprintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", msg->cm_fields['E']); - } - if (!IsEmptyStr(imm.subject)) { - if (msg->cm_fields['U'] != NULL) { - free(msg->cm_fields['U']); - } - msg->cm_fields['U'] = strdup(imm.subject); - } - if (imm.dtstart > 0) { - if (msg->cm_fields['T'] != NULL) { - free(msg->cm_fields['T']); - } - msg->cm_fields['T'] = strdup("000000000000000000"); - sprintf(msg->cm_fields['T'], "%ld", imm.dtstart); - } - return(0); } @@ -2013,12 +2369,13 @@ int ical_obj_beforesave(struct CtdlMessage *msg) */ void ical_obj_aftersave_backend(char *name, char *filename, char *partnum, char *disp, void *content, char *cbtype, char *cbcharset, size_t length, - char *encoding, void *cbuserdata) + char *encoding, char *cbid, void *cbuserdata) { icalcomponent *cal; /* We're only interested in calendar items here. */ - if (strcasecmp(cbtype, "text/calendar")) { + if ( (strcasecmp(cbtype, "text/calendar")) + && (strcasecmp(cbtype, "application/ics")) ) { return; } @@ -2027,10 +2384,11 @@ void ical_obj_aftersave_backend(char *name, char *filename, char *partnum, * ical_obj_beforesave() sees it there, it'll set the Exclusive msgid * to that string. */ - if (!strcasecmp(cbtype, "text/calendar")) { + if ( (!strcasecmp(cbtype, "text/calendar")) + || (!strcasecmp(cbtype, "application/ics")) ) { cal = icalcomponent_new_from_string(content); if (cal != NULL) { - ical_saving_vevent(cal); + ical_saving_vevent(cal, cal); icalcomponent_free(cal); } } @@ -2135,7 +2493,7 @@ void ical_fixed_output_backend(icalcomponent *cal, /* - * Function to output vcalendar data as plain text. Nobody uses MSG0 + * Function to output iCalendar data as plain text. Nobody uses MSG0 * anymore, so really this is just so we expose the vCard data to the full * text indexer. */ @@ -2152,7 +2510,6 @@ void ical_fixed_output(char *ptr, int len) { return; } - ical_dezonify(cal); ical_fixed_output_backend(cal, 0); /* Free the memory we obtained from libical's constructor */ @@ -2166,8 +2523,6 @@ void serv_calendar_destroy(void) icaltimezone_free_builtin_timezones(); } -#endif /* CITADEL_WITH_CALENDAR_SERVICE */ - /* * Register this module with the Citadel server. */ @@ -2175,7 +2530,14 @@ CTDL_MODULE_INIT(calendar) { if (!threading) { -#ifdef CITADEL_WITH_CALENDAR_SERVICE + + /* Tell libical to return errors instead of aborting if it gets bad data */ + icalerror_errors_are_fatal = 0; + + /* Use our own application prefix in tzid's generated from system tzdata */ + icaltimezone_set_tzid_prefix("/citadel.org/"); + + /* Initialize our hook functions */ CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE); CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE); CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN); @@ -2183,11 +2545,10 @@ CTDL_MODULE_INIT(calendar) CtdlRegisterSessionHook(ical_session_startup, EVT_START); CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP); CtdlRegisterFixedOutputHook("text/calendar", ical_fixed_output); + CtdlRegisterFixedOutputHook("application/ics", ical_fixed_output); CtdlRegisterCleanupHook(serv_calendar_destroy); -#endif } /* return our Subversion id for the Log */ return "$Id$"; } -