* More license declarations
[citadel.git] / citadel / modules / calendar / serv_calendar.c
index 90d9b24a1d96b68df46104181bbe1548a53c0a4f..1dd5935e045f3459d88804e52659b4e08e7685d0 100644 (file)
@@ -5,6 +5,22 @@
  * room on a Citadel server.  It handles iCalendar objects using the
  * iTIP protocol.  See RFCs 2445 and 2446.
  *
+ *
+ * Copyright (c) 1987-2009 by the citadel.org team
+ *
+ *  This program is free 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"
@@ -52,8 +68,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__);
+               CtdlLogPrintf(CTDL_CRIT, "ERROR: could not allocate component!\n");
                return NULL;
        }
 
@@ -317,7 +332,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,
@@ -808,59 +824,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);
 
-       /* 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);
+               return(1);
+       }
+
+       /* lprintf (9, "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);
+       /* lprintf(9, "first passed\n"); */
 
        /* If event 2 ends before event 1 starts, we're also ok. */
        if (icaltime_compare(t2end, t1start) <= 0) return(0);
+       /* lprintf(9, "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,
@@ -869,17 +900,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);
+       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 */
 
@@ -893,8 +926,10 @@ void ical_conflicts_phase6(struct icaltimetype t1start,
                                conflict_event_uid))) ? 1 : 0
                        )
                );
+               conflict_reported = 1;
        }
 
+       return(conflict_reported);
 }
 
 
@@ -934,10 +969,37 @@ 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);
        }
 
@@ -945,7 +1007,6 @@ void ical_conflicts_phase5(struct icaltimetype t1start,
        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 {
@@ -959,21 +1020,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);
 }
 
 
@@ -1011,10 +1079,38 @@ void ical_conflicts_phase4(icalcomponent *proposed_event,
        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(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);
        }
 
@@ -1022,7 +1118,6 @@ void ical_conflicts_phase4(icalcomponent *proposed_event,
        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);
@@ -1036,14 +1131,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);
 }
 
 
@@ -1123,7 +1219,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
                );
@@ -1147,35 +1243,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 = icaltime_null_time();
+       icaltimetype dtend = icaltime_null_time();
 
-       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.
@@ -1190,58 +1289,100 @@ 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);
-       }
-
-       p = icalcomponent_get_first_property(cal, ICAL_DTEND_PROPERTY);
-       if (p != NULL) {
-               my_period.end = icalproperty_get_dtstart(p);
-       }
+       if (!p) return;
+       dtstart = 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) );
-               }
+
+       /* 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);
 }
 
 
@@ -1255,11 +1396,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;
@@ -1274,12 +1415,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);
+       }
 }
 
 
@@ -1435,7 +1574,7 @@ void ical_freebusy(char *who) {
        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);
@@ -1498,7 +1637,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);
        }
@@ -1523,8 +1679,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__);
+               CtdlLogPrintf(CTDL_ALERT, "ERROR: could not allocate component!\n");
                cprintf("%d Could not allocate memory\n", ERROR+INTERNAL_ERROR);
                return;
        }
@@ -1549,11 +1704,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), generic_free_handler);
+       }
 }
 
 
@@ -1566,20 +1736,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("000", config.c_maxmsglen, NULL, 0, 0);
        if (calstream == NULL) {
                return;
        }
@@ -1589,7 +1767,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!)
         */
@@ -1609,7 +1787,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);
+                       }
                }
        }
 
@@ -1777,6 +2003,14 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal)
        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");
@@ -1846,8 +2080,7 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, 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__);
+               CtdlLogPrintf(CTDL_ALERT, "ERROR: could not allocate component!\n");
                icalcomponent_free(the_request);
                return;
        }
@@ -1861,7 +2094,7 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal)
        /* Set the method to REQUEST */
        icalcomponent_set_method(encaps, ICAL_METHOD_REQUEST);
 
-       /* FIXME: attach time zones here */
+       /* 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))
@@ -1880,11 +2113,65 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal)
                  || (icalproperty_isa(p) == ICAL_MINDATE_PROPERTY)
                  || (icalproperty_isa(p) == ICAL_RECURRENCEID_PROPERTY)
                ) {
-                       CtdlLogPrintf(CTDL_DEBUG, "FIXME: found a property that requires a vtimezone\n");
+                       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))
+                               );
+                       }
                }
        }
 
-       /* Here we go: put the VEVENT into the VCALENDAR.  We now no longer
+       /* Encapsulate any timezones we need */
+       if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
+               icalcomponent *zc;
+               zc = icalcomponent_new_clone(icaltimezone_get_component(attached_zones[i]));
+               icalcomponent_add_component(encaps, zc);
+       }
+
+       /* Here we go: encapsulate the VEVENT into the VCALENDAR.  We now no longer
         * are responsible for "the_request"'s memory -- it will be freed
         * when we free "encaps".
         */
@@ -1895,8 +2182,6 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal)
        icalcomponent_free(encaps);     /* Don't need this anymore. */
        if (serialized_request == NULL) return;
 
-       CtdlLogPrintf(CTDL_DEBUG, "SENDING INVITATIONS:\n%s\n", serialized_request);
-
        reqsize = strlen(serialized_request) + SIZ;
        request_message_text = malloc(reqsize);
        if (request_message_text != NULL) {
@@ -1905,16 +2190,20 @@ void ical_send_out_invitations(icalcomponent *top_level_cal, icalcomponent *cal)
                        serialized_request
                );
 
-               msg = CtdlMakeMessage(&CC->user,
-                       "",                     /* 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);
@@ -2328,107 +2617,3 @@ CTDL_MODULE_INIT(calendar)
        /* return our Subversion id for the Log */
        return "$Id$";
 }
-
-
-
-
-
-
-
-
-/* FIXME I just saved this down here so I can steal code from it */
-
-#if 0
-
-icalcomponent *ical_encapsulate_subcomponent(icalcomponent *subcomp) {
-       icalcomponent *encaps;
-       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;
-
-       if (subcomp == NULL) {
-               lprintf(3, "ERROR: ical_encapsulate_subcomponent() called with NULL argument\n");
-               return NULL;
-       }
-
-       /*
-        * If we're already looking at a full VCALENDAR component, this is probably an error.
-        */
-       if (icalcomponent_isa(subcomp) == ICAL_VCALENDAR_COMPONENT) {
-               lprintf(3, "ERROR: component sent to ical_encapsulate_subcomponent() already top level\n");
-               return subcomp;
-       }
-
-       /* search for... */
-       for (p = icalcomponent_get_first_property(subcomp, ICAL_ANY_PROPERTY);
-            p != NULL;
-            p = icalcomponent_get_next_property(subcomp, 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
-                       if ((icaltime_is_valid_time(t)) && (z=icaltime_get_timezone(t), z)) {
-                       
-                               zone_already_attached = 0;
-                               for (i=0; i<5; ++i) {
-                                       if (z == attached_zones[i]) {
-                                               ++zone_already_attached;
-                                               lprintf(9, "zone already attached!!\n");
-                                       }
-                               }
-                               if ((!zone_already_attached) && (num_zones_attached < 5)) {
-                                       lprintf(9, "attaching zone %d!\n", num_zones_attached);
-                                       attached_zones[num_zones_attached++] = z;
-                               }
-
-                               icalproperty_set_parameter(p,
-                                       icalparameter_new_tzid(icaltimezone_get_tzid((icaltimezone *)z))
-                               );
-                       }
-               }
-       }
-
-       /* Encapsulate the VEVENT component into a complete VCALENDAR */
-       encaps = icalcomponent_new(ICAL_VCALENDAR_COMPONENT);
-       if (encaps == NULL) {
-               lprintf(3, "ERROR: ical_encapsulate_subcomponent() could not allocate component\n");
-               return NULL;
-       }
-
-       /* Set the Product ID */
-       icalcomponent_add_property(encaps, icalproperty_new_prodid(PRODID));
-
-       /* Set the Version Number */
-       icalcomponent_add_property(encaps, icalproperty_new_version("2.0"));
-
-       /* Attach any timezones we need */
-       if (num_zones_attached > 0) for (i=0; i<num_zones_attached; ++i) {
-               icalcomponent *zc;
-               zc = icalcomponent_new_clone(icaltimezone_get_component((icaltimezone *)attached_zones[i]));
-               icalcomponent_add_component(encaps, zc);
-       }
-
-       /* Encapsulate the subcomponent inside */
-       icalcomponent_add_component(encaps, subcomp);
-
-       /* Return the object we just created. */
-       return(encaps);
-}
-
-#endif