X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fmodules%2Fcalendar%2Fserv_calendar.c;h=2c9419692e3e0154b625bb9290e01a17f57b74a8;hb=7273f3d88e0e7be7cc06313835c5f50827fc4f91;hp=be7e47b68008b2b8b4627fdaf901b4f21974ed98;hpb=e20db6b41132b52fdc82bcbcd5a5ec7ef8bdff79;p=citadel.git diff --git a/citadel/modules/calendar/serv_calendar.c b/citadel/modules/calendar/serv_calendar.c index be7e47b68..2c9419692 100644 --- a/citadel/modules/calendar/serv_calendar.c +++ b/citadel/modules/calendar/serv_calendar.c @@ -1,38 +1,36 @@ /* - * $Id$ - * * This module implements iCalendar object processing and the Calendar> * room on a Citadel server. It handles iCalendar objects using the * iTIP protocol. See RFCs 2445 and 2446. * + * + * Copyright (c) 1987-2011 by the citadel.org team + * + * This program is open source software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define PRODID "-//Citadel//NONSGML Citadel Calendar//EN" -#include "sysdep.h" -#include -#include -#include -#include -#include -#include -#ifdef HAVE_STRINGS_H -#include -#endif +#include "ctdl_module.h" + #include -#include -#include "citadel.h" -#include "server.h" -#include "citserver.h" -#include "support.h" -#include "config.h" -#include "user_ops.h" -#include "room_ops.h" + #include "msgbase.h" #include "internet_addressing.h" #include "serv_calendar.h" #include "euidindex.h" -#include "ctdl_module.h" #include "ical_dezonify.h" @@ -52,8 +50,7 @@ icalcomponent *icalcomponent_new_citadel_vcalendar(void) { encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - CtdlLogPrintf(CTDL_CRIT, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + syslog(LOG_CRIT, "ERROR: could not allocate component!\n"); return NULL; } @@ -161,45 +158,6 @@ void ical_write_to_cal(struct ctdluser *u, icalcomponent *cal) { } -/* - * 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; - -#if 1 - /* Write the whole thing because it may need to save timezones etc. - * FIXME - if this works, we can probably eliminate this entire function - */ - - ical_write_to_cal(&CC->user, cal); - -#else /* this was the old code to kill everything but the VEVENT component ... probably ng now */ - - /* - * 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); - } -#endif - -} - - - /* * Send a reply to a meeting invitation. * @@ -221,6 +179,7 @@ void ical_send_a_reply(icalcomponent *request, char *action) { icalparameter *partstat = NULL; char *serialized_reply = NULL; char *reply_message_text = NULL; + const char *ch; struct CtdlMessage *msg = NULL; struct recptypes *valid = NULL; @@ -228,13 +187,13 @@ void ical_send_a_reply(icalcomponent *request, char *action) { strcpy(summary_string, "Calendar item"); if (request == NULL) { - CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); + syslog(LOG_ERR, "ERROR: trying to reply to NULL event?\n"); return; } the_reply = icalcomponent_new_clone(request); if (the_reply == NULL) { - CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone request\n"); + syslog(LOG_ERR, "ERROR: cannot clone request\n"); return; } @@ -250,22 +209,20 @@ void ical_send_a_reply(icalcomponent *request, char *action) { while (attendee = icalcomponent_get_first_property(vevent, ICAL_ATTENDEE_PROPERTY), (attendee != NULL) ) { - if (icalproperty_get_attendee(attendee)) { - strcpy(attendee_string, - icalproperty_get_attendee(attendee) ); - if (!strncasecmp(attendee_string, "MAILTO:", 7)) { - strcpy(attendee_string, &attendee_string[7]); - striplt(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); - me_attend = icalproperty_new_clone(attendee); - } - free_recipients(recp); + ch = icalproperty_get_attendee(attendee); + if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) { + safestrncpy(attendee_string, ch + 7, sizeof (attendee_string)); + striplt(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); + me_attend = icalproperty_new_clone(attendee); } + free_recipients(recp); } } + /* Remove it... */ icalcomponent_remove_property(vevent, attendee); icalproperty_free(attendee); @@ -356,7 +313,8 @@ 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, @@ -437,7 +395,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 */ @@ -445,9 +403,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. */ @@ -579,10 +537,13 @@ STARTOVER: /* Check to see if these two attendees match... */ - if (!strcasecmp( - icalproperty_get_attendee(e_attendee), - icalproperty_get_attendee(r_attendee) - )) { + const char *e, *r; + e = icalproperty_get_attendee(e_attendee); + r = icalproperty_get_attendee(r_attendee); + + if ((e != NULL) && + (r != NULL) && + !strcasecmp(e, r)) { /* ...and if they do, remove the attendee from the event * and replace it with the attendee from the reply. (The * reply's copy will have the same address, but an updated @@ -635,13 +596,13 @@ 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); - CtdlLogPrintf(CTDL_DEBUG, "UID of event being replied to is <%s>\n", uid); + syslog(LOG_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); - CtdlLogPrintf(CTDL_CRIT, "cannot get user calendar room\n"); + if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) { + CtdlGetRoom(&CC->room, hold_rm); + syslog(LOG_CRIT, "cannot get user calendar room\n"); return(2); } @@ -651,11 +612,11 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { * 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); + msgnum_being_replaced = CtdlLocateMessageByEuid(uid, &CC->room); - getroom(&CC->room, hold_rm); /* return to saved room */ + CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ - CtdlLogPrintf(CTDL_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced); + syslog(LOG_DEBUG, "msgnum_being_replaced == %ld\n", msgnum_being_replaced); if (msgnum_being_replaced == 0) { return(1); /* no calendar event found */ } @@ -682,7 +643,7 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { original_event = oec.c; if (original_event == NULL) { - CtdlLogPrintf(CTDL_ERR, "ERROR: Original_component is NULL.\n"); + syslog(LOG_ERR, "ERROR: Original_component is NULL.\n"); return(2); } @@ -694,7 +655,7 @@ int ical_update_my_calendar_with_reply(icalcomponent *cal) { icalcomponent_free(original_event); /* Don't need this anymore. */ if (serialized_event == NULL) return(2); - MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); + CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); message_text = malloc(strlen(serialized_event) + SIZ); if (message_text != NULL) { @@ -847,59 +808,74 @@ int ical_ctdl_is_overlap( struct icaltimetype t2start, struct icaltimetype t2end ) { - if (icaltime_is_null_time(t1start)) return(0); if (icaltime_is_null_time(t2start)) return(0); - /* First, check for all-day events */ - if (t1start.is_date) { - if (!icaltime_compare_date_only(t1start, t2start)) { - return(1); - } - if (!icaltime_is_null_time(t2end)) { - if (!icaltime_compare_date_only(t1start, t2end)) { - return(1); - } + /* if either event lacks end time, assume end = start */ + if (icaltime_is_null_time(t1end)) + memcpy(&t1end, &t1start, sizeof(struct icaltimetype)); + else { + if (t1end.is_date && icaltime_compare(t1start, t1end)) { + /* + * the end date is non-inclusive so adjust it by one + * day because our test is inclusive, note that a day is + * not too much because we are talking about all day + * events + * if start = end we assume that nevertheless the whole + * day is meant + */ + icaltime_adjust(&t1end, -1, 0, 0, 0); } } - if (t2start.is_date) { - if (!icaltime_compare_date_only(t2start, t1start)) { - return(1); - } - if (!icaltime_is_null_time(t1end)) { - if (!icaltime_compare_date_only(t2start, t1end)) { - return(1); - } + if (icaltime_is_null_time(t2end)) + memcpy(&t2end, &t2start, sizeof(struct icaltimetype)); + else { + if (t2end.is_date && icaltime_compare(t2start, t2end)) { + icaltime_adjust(&t2end, -1, 0, 0, 0); } } - /* Now check for overlaps using date *and* time. */ + /* First, check for all-day events */ + if (t1start.is_date || t2start.is_date) { + /* If event 1 ends before event 2 starts, we're in the clear. */ + if (icaltime_compare_date_only(t1end, t2start) < 0) return(0); + + /* If event 2 ends before event 1 starts, we're also ok. */ + if (icaltime_compare_date_only(t2end, t1start) < 0) return(0); + + return(1); + } - /* First, bail out if either event 1 or event 2 is missing end time. */ - if (icaltime_is_null_time(t1end)) return(0); - if (icaltime_is_null_time(t2end)) return(0); + /* syslog(LOG_DEBUG, "Comparing t1start %d:%d t1end %d:%d t2start %d:%d t2end %d:%d \n", + t1start.hour, t1start.minute, t1end.hour, t1end.minute, + t2start.hour, t2start.minute, t2end.hour, t2end.minute); + */ + + /* Now check for overlaps using date *and* time. */ /* If event 1 ends before event 2 starts, we're in the clear. */ if (icaltime_compare(t1end, t2start) <= 0) return(0); + /* syslog(LOG_DEBUG, "first passed\n"); */ /* If event 2 ends before event 1 starts, we're also ok. */ if (icaltime_compare(t2end, t1start) <= 0) return(0); + /* syslog(LOG_DEBUG, "second passed\n"); */ /* Otherwise, they overlap. */ return(1); } - - /* * 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. */ -void ical_conflicts_phase6(struct icaltimetype t1start, +int ical_conflicts_phase6(struct icaltimetype t1start, struct icaltimetype t1end, struct icaltimetype t2start, struct icaltimetype t2end, @@ -908,17 +884,19 @@ void ical_conflicts_phase6(struct icaltimetype t1start, char *conflict_event_summary, char *compare_uid) { - - /* debugging cruft */ - // time_t tt; - // tt = icaltime_as_timet(t1start); - // CtdlLogPrintf(CTDL_DEBUG, "PROPOSED START: %s", ctime(&tt)); - // tt = icaltime_as_timet(t1end); - // CtdlLogPrintf(CTDL_DEBUG, " PROPOSED END: %s", ctime(&tt)); - // tt = icaltime_as_timet(t2start); - // CtdlLogPrintf(CTDL_DEBUG, "EXISTING START: %s", ctime(&tt)); - // tt = icaltime_as_timet(t2end); - // CtdlLogPrintf(CTDL_DEBUG, " EXISTING END: %s", ctime(&tt)); + int conflict_reported = 0; + + /* debugging cruft * + time_t tt; + tt = icaltime_as_timet_with_zone(t1start, t1start.zone); + syslog(LOG_DEBUG, "PROPOSED START: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t1end, t1end.zone); + syslog(LOG_DEBUG, " PROPOSED END: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t2start, t2start.zone); + syslog(LOG_DEBUG, "EXISTING START: %s", ctime(&tt)); + tt = icaltime_as_timet_with_zone(t2end, t2end.zone); + syslog(LOG_DEBUG, " EXISTING END: %s", ctime(&tt)); + * debugging cruft */ /* compare and output */ @@ -932,8 +910,10 @@ void ical_conflicts_phase6(struct icaltimetype t1start, conflict_event_uid))) ? 1 : 0 ) ); + conflict_reported = 1; } + return(conflict_reported); } @@ -973,18 +953,47 @@ void ical_conflicts_phase5(struct icaltimetype t1start, 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(); + } + } 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); } + else { + memset (&dur, 0, sizeof(struct icaldurationtype)); + } rrule = ical_ctdl_get_subprop(existing_event, ICAL_RRULE_PROPERTY); if (rrule) { recur = icalproperty_get_rrule(rrule); ritr = icalrecur_iterator_new(recur, t2start); - CtdlLogPrintf(CTDL_DEBUG, "Recurrence found: %s\n", icalrecurrencetype_as_string(&recur)); } do { @@ -998,21 +1007,28 @@ void ical_conflicts_phase5(struct icaltimetype t1start, strcpy(conflict_event_summary, icalproperty_get_comment(p)); } - ical_conflicts_phase6(t1start, t1end, t2start, t2end, - existing_msgnum, conflict_event_uid, conflict_event_summary, compare_uid - ); + 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); - if (num_recur > 0) CtdlLogPrintf(CTDL_DEBUG, "Iterated over existing event %d times.\n", num_recur); } @@ -1043,25 +1059,58 @@ void ical_conflicts_phase4(icalcomponent *proposed_event, int num_recur = 0; /* initialization */ - strcpy(compare_uid, ""); + *compare_uid = '\0'; /* 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 (p == NULL) + return; + else + 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(proposed_event, ICAL_DTEND_PROPERTY); if (p != NULL) { 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); } + else { + memset (&dur, 0, sizeof(struct icaldurationtype)); + } rrule = ical_ctdl_get_subprop(proposed_event, ICAL_RRULE_PROPERTY); if (rrule) { recur = icalproperty_get_rrule(rrule); ritr = icalrecur_iterator_new(recur, t1start); - CtdlLogPrintf(CTDL_DEBUG, "Recurrence found: %s\n", icalrecurrencetype_as_string(&recur)); } p = ical_ctdl_get_subprop(proposed_event, ICAL_UID_PROPERTY); @@ -1075,14 +1124,15 @@ void ical_conflicts_phase4(icalcomponent *proposed_event, 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); - if (num_recur > 0) CtdlLogPrintf(CTDL_DEBUG, "Iterated over proposed event %d times.\n", num_recur); } @@ -1131,8 +1181,8 @@ void ical_hunt_for_conflicts(icalcomponent *cal) { strcpy(hold_rm, CC->room.QRname); /* save current room */ - if (getroom(&CC->room, USERCALENDARROOM) != 0) { - getroom(&CC->room, hold_rm); + if (CtdlGetRoom(&CC->room, USERCALENDARROOM) != 0) { + CtdlGetRoom(&CC->room, hold_rm); cprintf("%d You do not have a calendar.\n", ERROR + ROOM_NOT_FOUND); return; } @@ -1147,7 +1197,7 @@ void ical_hunt_for_conflicts(icalcomponent *cal) { ); cprintf("000\n"); - getroom(&CC->room, hold_rm); /* return to saved room */ + CtdlGetRoom(&CC->room, hold_rm); /* return to saved room */ } @@ -1162,7 +1212,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 ); @@ -1186,35 +1236,38 @@ 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 a VEVENT to be added */ -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 = icalperiodtype_null_period(); + icaltimetype dtstart; + icaltimetype dtend; - if (cal == NULL) return; - my_period = icalperiodtype_null_period(); + /* recur variables */ + icalproperty *rrule = NULL; + struct icalrecurrencetype recur; + icalrecur_iterator *ritr = NULL; + struct icaldurationtype dur; + int num_recur = 0; - if (icalcomponent_isa(cal) != ICAL_VEVENT_COMPONENT) { - ical_add_to_freebusy(fb, - icalcomponent_get_first_component( - cal, ICAL_VEVENT_COMPONENT - ) - ); - return; - } + if (!top_level_cal) return; + + /* Find the VEVENT component containing an event */ + cal = icalcomponent_get_first_component(top_level_cal, ICAL_VEVENT_COMPONENT); + if (!cal) return; /* If this event is not opaque, the user isn't publishing it as * busy time, so don't bother doing anything else. @@ -1229,58 +1282,103 @@ void ical_add_to_freebusy(icalcomponent *fb, icalcomponent *cal) { } } - /* Convert the DTSTART and DTEND properties to an icalperiod. */ + /* + * Now begin calculating the event start and end times. + */ p = icalcomponent_get_first_property(cal, ICAL_DTSTART_PROPERTY); - if (p != NULL) { - my_period.start = icalproperty_get_dtstart(p); - } + if (!p) return; + dtstart = icalproperty_get_dtstart(p); - p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY); - if (p != NULL) { - my_period.end = icalproperty_get_dtstart(p); - } - - /* Now add it. */ - icalcomponent_add_property(fb, - icalproperty_new_freebusy(my_period) - ); - - /* Make sure the DTSTART property of the freebusy *list* is set to - * the DTSTART property of the *earliest event*. - */ - p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY); - if (p == NULL) { - icalcomponent_set_dtstart(fb, - icalcomponent_get_dtstart(cal) ); + if (icaltime_is_utc(dtstart)) { + dtstart.zone = icaltimezone_get_utc_timezone(); } else { - if (icaltime_compare( - icalcomponent_get_dtstart(cal), - icalcomponent_get_dtstart(fb) - ) < 0) { - icalcomponent_set_dtstart(fb, - icalcomponent_get_dtstart(cal) ); + dtstart.zone = icalcomponent_get_timezone(top_level_cal, + icalparameter_get_tzid( + icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) + ) + ); + if (!dtstart.zone) { + dtstart.zone = get_default_icaltimezone(); } } - /* Make sure the DTEND property of the freebusy *list* is set to - * the DTEND property of the *latest event*. - */ - p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY); - if (p == NULL) { - icalcomponent_set_dtend(fb, - icalcomponent_get_dtend(cal) ); + dtend = icalcomponent_get_dtend(cal); + if (!icaltime_is_null_time(dtend)) { + dur = icaltime_subtract(dtend, dtstart); } else { - if (icaltime_compare( - icalcomponent_get_dtend(cal), - icalcomponent_get_dtend(fb) - ) > 0) { - icalcomponent_set_dtend(fb, - icalcomponent_get_dtend(cal) ); - } + memset (&dur, 0, sizeof(struct icaldurationtype)); + } + + /* Is a recurrence specified? If so, get ready to process it... */ + rrule = ical_ctdl_get_subprop(cal, ICAL_RRULE_PROPERTY); + if (rrule) { + recur = icalproperty_get_rrule(rrule); + ritr = icalrecur_iterator_new(recur, dtstart); } + do { + /* Convert the DTSTART and DTEND properties to an icalperiod. */ + this_event_period.start = dtstart; + + if (!icaltime_is_null_time(dtend)) { + this_event_period.end = dtend; + } + + /* Convert the timestamps to UTC. It's ok to do this because we've already expanded + * recurrences and this data is never going to get used again. + */ + this_event_period.start = icaltime_convert_to_zone( + this_event_period.start, + icaltimezone_get_utc_timezone() + ); + this_event_period.end = icaltime_convert_to_zone( + this_event_period.end, + icaltimezone_get_utc_timezone() + ); + + /* Now add it. */ + icalcomponent_add_property(fb, icalproperty_new_freebusy(this_event_period)); + + /* Make sure the DTSTART property of the freebusy *list* is set to + * the DTSTART property of the *earliest event*. + */ + p = icalcomponent_get_first_property(fb, ICAL_DTSTART_PROPERTY); + if (p == NULL) { + icalcomponent_set_dtstart(fb, this_event_period.start); + } + else { + if (icaltime_compare(this_event_period.start, icalcomponent_get_dtstart(fb)) < 0) { + icalcomponent_set_dtstart(fb, this_event_period.start); + } + } + + /* Make sure the DTEND property of the freebusy *list* is set to + * the DTEND property of the *latest event*. + */ + p = icalcomponent_get_first_property(fb, ICAL_DTEND_PROPERTY); + if (p == NULL) { + icalcomponent_set_dtend(fb, this_event_period.end); + } + else { + if (icaltime_compare(this_event_period.end, icalcomponent_get_dtend(fb)) > 0) { + icalcomponent_set_dtend(fb, this_event_period.end); + } + } + + if (rrule) { + dtstart = icalrecur_iterator_next(ritr); + if (!icaltime_is_null_time(dtend)) { + dtend = icaltime_add(dtstart, dur); + dtend.zone = dtstart.zone; + dtend.is_utc = dtstart.is_utc; + } + ++num_recur; + } + + } while ( (rrule) && (!icaltime_is_null_time(dtstart)) && (num_recur < MAX_RECUR) ) ; + icalrecur_iterator_free(ritr); } @@ -1294,11 +1392,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; @@ -1313,12 +1411,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); /* Add VEVENT times to VFREEBUSY */ + icalcomponent_free(ird.cal); + } } @@ -1342,16 +1438,16 @@ void ical_freebusy(char *who) { int config_lines = 0; /* First try an exact match. */ - found_user = getuser(&usbuf, who); + found_user = CtdlGetUser(&usbuf, who); /* If not found, try it as an unqualified email address. */ if (found_user != 0) { strcpy(buf, who); recp = validate_recipients(buf, NULL, 0); - CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); + syslog(LOG_DEBUG, "Trying <%s>\n", buf); if (recp != NULL) { if (recp->num_local == 1) { - found_user = getuser(&usbuf, recp->recp_local); + found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } @@ -1362,11 +1458,11 @@ void ical_freebusy(char *who) { */ if (found_user != 0) { snprintf(buf, sizeof buf, "%s@%s", who, config.c_fqdn); - CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); + syslog(LOG_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); + found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } @@ -1385,11 +1481,11 @@ void ical_freebusy(char *who) { if ( (!strcasecmp(type, "localhost")) || (!strcasecmp(type, "directory")) ) { snprintf(buf, sizeof buf, "%s@%s", who, host); - CtdlLogPrintf(CTDL_DEBUG, "Trying <%s>\n", buf); + syslog(LOG_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); + found_user = CtdlGetUser(&usbuf, recp->recp_local); } free_recipients(recp); } @@ -1402,24 +1498,24 @@ void ical_freebusy(char *who) { return; } - MailboxName(calendar_room_name, sizeof calendar_room_name, + CtdlMailboxName(calendar_room_name, sizeof calendar_room_name, &usbuf, USERCALENDARROOM); strcpy(hold_rm, CC->room.QRname); /* save current room */ - if (getroom(&CC->room, calendar_room_name) != 0) { + if (CtdlGetRoom(&CC->room, calendar_room_name) != 0) { cprintf("%d Cannot open calendar\n", ERROR + ROOM_NOT_FOUND); - getroom(&CC->room, hold_rm); + CtdlGetRoom(&CC->room, hold_rm); return; } /* Create a VFREEBUSY subcomponent */ - CtdlLogPrintf(CTDL_DEBUG, "Creating VFREEBUSY component\n"); + syslog(LOG_DEBUG, "Creating VFREEBUSY component\n"); fb = icalcomponent_new_vfreebusy(); if (fb == NULL) { cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR); - getroom(&CC->room, hold_rm); + CtdlGetRoom(&CC->room, hold_rm); return; } @@ -1441,7 +1537,7 @@ void ical_freebusy(char *who) { icalcomponent_add_property(fb, icalproperty_new_organizer(buf)); /* Add busy time from events */ - CtdlLogPrintf(CTDL_DEBUG, "Adding busy time from events\n"); + syslog(LOG_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 @@ -1455,26 +1551,26 @@ void ical_freebusy(char *who) { } /* Put the freebusy component into the calendar component */ - CtdlLogPrintf(CTDL_DEBUG, "Encapsulating\n"); + syslog(LOG_DEBUG, "Encapsulating\n"); encaps = ical_encapsulate_subcomponent(fb); if (encaps == NULL) { icalcomponent_free(fb); cprintf("%d Internal error: cannot allocate memory.\n", ERROR + INTERNAL_ERROR); - getroom(&CC->room, hold_rm); + CtdlGetRoom(&CC->room, hold_rm); return; } /* Set the method to PUBLISH */ - CtdlLogPrintf(CTDL_DEBUG, "Setting method\n"); + syslog(LOG_DEBUG, "Setting method\n"); icalcomponent_set_method(encaps, ICAL_METHOD_PUBLISH); /* Serialize it */ - CtdlLogPrintf(CTDL_DEBUG, "Serializing\n"); + syslog(LOG_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); + cprintf("%d Free/busy for %s\n", LISTING_FOLLOWS, usbuf.fullname); if (serialized_request != NULL) { client_write(serialized_request, strlen(serialized_request)); free(serialized_request); @@ -1482,7 +1578,7 @@ void ical_freebusy(char *who) { cprintf("\n000\n"); /* Go back to the room from which we came... */ - getroom(&CC->room, hold_rm); + CtdlGetRoom(&CC->room, hold_rm); } @@ -1537,7 +1633,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); } @@ -1562,8 +1675,7 @@ void ical_getics(void) encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - CtdlLogPrintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + syslog(LOG_ALERT, "ERROR: could not allocate component!\n"); cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR); return; } @@ -1588,11 +1700,26 @@ void ical_getics(void) ); 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), NULL); + } } @@ -1605,20 +1732,28 @@ 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 an iCalendar-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; } cprintf("%d Transmit data now\n", SEND_LISTING); - calstream = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0); + calstream = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0); if (calstream == NULL) { return; } @@ -1628,7 +1763,7 @@ void ical_putics(void) /* 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!) */ @@ -1648,7 +1783,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)) { + syslog(LOG_DEBUG, "Attaching timezone '%s'\n", (char*) 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); + } } } @@ -1733,22 +1916,22 @@ void cmd_ical(char *argbuf) /* * We don't know if the calendar room exists so we just create it at login */ -void ical_create_room(void) +void ical_CtdlCreateRoom(void) { struct ctdlroom qr; - struct visit vbuf; + visit vbuf; /* Create the calendar room if it doesn't already exist */ - create_room(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR); + CtdlCreateRoom(USERCALENDARROOM, 4, "", 0, 1, 0, VIEW_CALENDAR); /* Set expiration policy to manual; otherwise objects will be lost! */ - if (lgetroom(&qr, USERCALENDARROOM)) { - CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + if (CtdlGetRoomLock(&qr, USERCALENDARROOM)) { + syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_CALENDAR; /* 3 = calendar view */ - lputroom(&qr); + CtdlPutRoomLock(&qr); /* Set the view to a calendar view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); @@ -1756,16 +1939,16 @@ void ical_create_room(void) CtdlSetRelationship(&vbuf, &CC->user, &qr); /* Create the tasks list room if it doesn't already exist */ - create_room(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS); + CtdlCreateRoom(USERTASKSROOM, 4, "", 0, 1, 0, VIEW_TASKS); /* Set expiration policy to manual; otherwise objects will be lost! */ - if (lgetroom(&qr, USERTASKSROOM)) { - CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + if (CtdlGetRoomLock(&qr, USERTASKSROOM)) { + syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_TASKS; - lputroom(&qr); + CtdlPutRoomLock(&qr); /* Set the view to a task list view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); @@ -1773,16 +1956,16 @@ void ical_create_room(void) CtdlSetRelationship(&vbuf, &CC->user, &qr); /* Create the notes room if it doesn't already exist */ - create_room(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES); + CtdlCreateRoom(USERNOTESROOM, 4, "", 0, 1, 0, VIEW_NOTES); /* Set expiration policy to manual; otherwise objects will be lost! */ - if (lgetroom(&qr, USERNOTESROOM)) { - CtdlLogPrintf(CTDL_CRIT, "Couldn't get the user calendar room!\n"); + if (CtdlGetRoomLock(&qr, USERNOTESROOM)) { + syslog(LOG_CRIT, "Couldn't get the user calendar room!\n"); return; } qr.QRep.expire_mode = EXPIRE_MANUAL; qr.QRdefaultview = VIEW_NOTES; - lputroom(&qr); + CtdlPutRoomLock(&qr); /* Set the view to a notes view */ CtdlGetRelationship(&vbuf, &CC->user, &qr); @@ -1794,10 +1977,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; @@ -1811,16 +1998,25 @@ void ical_send_out_invitations(icalcomponent *cal) { char summary_string[SIZ]; icalproperty *summary = NULL; size_t reqsize; + icalproperty *p; + struct icaltimetype t; + const icaltimezone *attached_zones[5] = { NULL, NULL, NULL, NULL, NULL }; + int i; + const icaltimezone *z; + int num_zones_attached = 0; + int zone_already_attached; + icalparameter *tzidp = NULL; + const char *tzidc = NULL; if (cal == NULL) { - CtdlLogPrintf(CTDL_ERR, "ERROR: trying to reply to NULL event?\n"); + syslog(LOG_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 ) @@ -1831,7 +2027,7 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Clone the event */ the_request = icalcomponent_new_clone(cal); if (the_request == NULL) { - CtdlLogPrintf(CTDL_ERR, "ERROR: cannot clone calendar object\n"); + syslog(LOG_ERR, "ERROR: cannot clone calendar object\n"); return; } @@ -1850,24 +2046,22 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Determine who the recipients of this message are (the attendees) */ strcpy(attendees_string, ""); for (attendee = icalcomponent_get_first_property(the_request, ICAL_ATTENDEE_PROPERTY); attendee != NULL; attendee = icalcomponent_get_next_property(the_request, ICAL_ATTENDEE_PROPERTY)) { - if (icalproperty_get_attendee(attendee)) { - safestrncpy(this_attendee, icalproperty_get_attendee(attendee), sizeof this_attendee); - if (!strncasecmp(this_attendee, "MAILTO:", 7)) { - strcpy(this_attendee, &this_attendee[7]); - - if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */ - snprintf(&attendees_string[strlen(attendees_string)], - sizeof(attendees_string) - strlen(attendees_string), - "%s, ", - this_attendee + const char *ch = icalproperty_get_attendee(attendee); + if ((ch != NULL) && !strncasecmp(ch, "MAILTO:", 7)) { + safestrncpy(this_attendee, ch + 7, sizeof(this_attendee)); + + if (!CtdlIsMe(this_attendee, sizeof this_attendee)) { /* don't send an invitation to myself! */ + snprintf(&attendees_string[strlen(attendees_string)], + sizeof(attendees_string) - strlen(attendees_string), + "%s, ", + this_attendee ); - ++num_attendees; - } + ++num_attendees; } } } - CtdlLogPrintf(CTDL_DEBUG, "<%d> attendees: <%s>\n", num_attendees, attendees_string); + syslog(LOG_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! @@ -1880,8 +2074,7 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Encapsulate the VEVENT component into a complete VCALENDAR */ encaps = icalcomponent_new_vcalendar(); if (encaps == NULL) { - CtdlLogPrintf(CTDL_DEBUG, "Error at %s:%d - could not allocate component!\n", - __FILE__, __LINE__); + syslog(LOG_ALERT, "ERROR: could not allocate component!\n"); icalcomponent_free(the_request); return; } @@ -1895,7 +2088,84 @@ void ical_send_out_invitations(icalcomponent *cal) { /* Set the method to REQUEST */ icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST); - /* Here we go: put the VEVENT into the VCALENDAR. We now no longer + /* 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 + + /* Determine the tzid in order for some of the conditions below to work */ + tzidp = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER); + if (tzidp) { + tzidc = icalparameter_get_tzid(tzidp); + } + else { + tzidc = NULL; + } + + /* 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 not, try to determine the tzid from the parameter using attached zones */ + if ((!z) && (tzidc)) { + z = icalcomponent_get_timezone(top_level_cal, tzidc); + } + + /* Still no good? Try our internal database */ + if ((!z) && (tzidc)) { + z = icaltimezone_get_builtin_timezone_from_tzid(tzidc); + } + + if (z) { + /* We have a valid timezone. Good. Now we need to attach it. */ + + zone_already_attached = 0; + for (i=0; i<5; ++i) { + if (z == attached_zones[i]) { + /* We've already got this one, no need to attach another. */ + ++zone_already_attached; + } + } + if ((!zone_already_attached) && (num_zones_attached < 5)) { + /* This is a new one, so attach it. */ + 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; iuser, - "", /* No single recipient here */ - "", /* No single recipient here */ - CC->room.QRname, 0, FMT_RFC822, - "", - "", + msg = CtdlMakeMessage( + &CC->user, + NULL, /* No single recipient here */ + NULL, /* No single recipient here */ + CC->room.QRname, + 0, + FMT_RFC822, + NULL, + NULL, summary_string, /* Use summary for subject */ NULL, request_message_text, - NULL); + NULL + ); if (msg != NULL) { valid = validate_recipients(attendees_string, NULL, 0); @@ -1941,13 +2215,18 @@ void ical_send_out_invitations(icalcomponent *cal) { * and the user saving it is the organizer. If so, send out invitations * to any listed attendees. * + * This function is recursive. The caller can simply supply the same object + * as both arguments. When it recurses it will alter the second argument + * while holding on to the top level object. This allows us to go back and + * grab things like time zones which might be attached. + * */ -void ical_saving_vevent(icalcomponent *cal) { +void ical_saving_vevent(icalcomponent *top_level_cal, icalcomponent *cal) { icalcomponent *c; icalproperty *organizer = NULL; char organizer_string[SIZ]; - CtdlLogPrintf(CTDL_DEBUG, "ical_saving_vevent() has been called!\n"); + syslog(LOG_DEBUG, "ical_saving_vevent() has been called!\n"); /* Don't send out invitations unless the client wants us to. */ if (CIT_ICAL->server_generated_invitations == 0) { @@ -1980,7 +2259,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); } } } @@ -1990,7 +2269,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); } } @@ -2062,7 +2341,7 @@ void ical_obj_beforesave_backend(char *name, char *filename, char *partnum, free(msg->cm_fields['E']); } msg->cm_fields['E'] = strdup(buf); - CtdlLogPrintf(CTDL_DEBUG, "Saving calendar UID <%s>\n", buf); + syslog(LOG_DEBUG, "Saving calendar UID <%s>\n", buf); } } @@ -2075,7 +2354,7 @@ void ical_obj_beforesave_backend(char *name, char *filename, char *partnum, if (msg->cm_fields['U'] != NULL) { free(msg->cm_fields['U']); } - msg->cm_fields['U'] = strdup(buf); + msg->cm_fields['U'] = rfc2047encode(buf, strlen(buf)); } } @@ -2123,7 +2402,7 @@ int ical_obj_beforesave(struct CtdlMessage *msg) /* It must be an RFC822 message! */ if (msg->cm_format_type != 4) { - CtdlLogPrintf(CTDL_DEBUG, "Rejecting non-RFC822 message\n"); + syslog(LOG_DEBUG, "Rejecting non-RFC822 message\n"); return(1); /* You tried to save a non-RFC822 message! */ } @@ -2168,7 +2447,7 @@ void ical_obj_aftersave_backend(char *name, char *filename, char *partnum, || (!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); } } @@ -2189,7 +2468,7 @@ int ical_obj_aftersave(struct CtdlMessage *msg) */ /* First determine if this is our room */ - MailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); + CtdlMailboxName(roomname, sizeof roomname, &CC->user, USERCALENDARROOM); if (strcasecmp(roomname, CC->room.QRname)) { return(0); /* Not the Calendar room -- don't do anything. */ } @@ -2232,6 +2511,7 @@ void ical_fixed_output_backend(icalcomponent *cal, icalcomponent *c; icalproperty *p; char buf[256]; + const char *ch; p = icalcomponent_get_first_property(cal, ICAL_SUMMARY_PROPERTY); if (p != NULL) { @@ -2250,11 +2530,12 @@ void ical_fixed_output_backend(icalcomponent *cal, /* If the component has attendees, iterate through them. */ for (p = icalcomponent_get_first_property(cal, ICAL_ATTENDEE_PROPERTY); (p != NULL); p = icalcomponent_get_next_property(cal, ICAL_ATTENDEE_PROPERTY)) { - safestrncpy(buf, icalproperty_get_attendee(p), sizeof buf); - if (!strncasecmp(buf, "MAILTO:", 7)) { + ch = icalproperty_get_attendee(p); + if ((ch != NULL) && + !strncasecmp(ch, "MAILTO:", 7)) { /* screen name or email address */ - strcpy(buf, &buf[7]); + safestrncpy(buf, ch + 7, sizeof(buf)); striplt(buf); cprintf("%s ", buf); } @@ -2320,7 +2601,7 @@ CTDL_MODULE_INIT(calendar) /* Initialize our hook functions */ CtdlRegisterMessageHook(ical_obj_beforesave, EVT_BEFORESAVE); CtdlRegisterMessageHook(ical_obj_aftersave, EVT_AFTERSAVE); - CtdlRegisterSessionHook(ical_create_room, EVT_LOGIN); + CtdlRegisterSessionHook(ical_CtdlCreateRoom, EVT_LOGIN); CtdlRegisterProtoHook(cmd_ical, "ICAL", "Citadel iCal commands"); CtdlRegisterSessionHook(ical_session_startup, EVT_START); CtdlRegisterSessionHook(ical_session_shutdown, EVT_STOP); @@ -2329,7 +2610,6 @@ CTDL_MODULE_INIT(calendar) CtdlRegisterCleanupHook(serv_calendar_destroy); } - /* return our Subversion id for the Log */ - return "$Id$"; + /* return our module name for the log */ + return "calendar"; } -