res_pjsip: Use reasonable buffer lengths for endpoint identification
[asterisk/asterisk.git] / res / res_calendar_icalendar.c
index f235fa5..58876c1 100644 (file)
  */
 
 /*! \file
- * \brief Resource for handling iCalnedar calendars
+ * \brief Resource for handling iCalendar calendars
  */
 
 /*** MODULEINFO
        <depend>neon</depend>
        <depend>ical</depend>
+       <support_level>extended</support_level>
 ***/
-#include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk.h"
 
 #include <libical/ical.h>
-#include <neon/ne_session.h>
-#include <neon/ne_uri.h>
-#include <neon/ne_request.h>
-#include <neon/ne_auth.h>
+#include <ne_session.h>
+#include <ne_uri.h>
+#include <ne_request.h>
+#include <ne_auth.h>
+#include <ne_redirect.h>
 
 #include "asterisk/module.h"
+#include "asterisk/channel.h"
 #include "asterisk/calendar.h"
 #include "asterisk/lock.h"
 #include "asterisk/config.h"
@@ -64,11 +66,6 @@ struct icalendar_pvt {
        struct ao2_container *events;
 };
 
-static int cb_true(void *user_data, void *arg, int flags)
-{
-       return CMP_MATCH;
-}
-
 static void icalendar_destructor(void *obj)
 {
        struct icalendar_pvt *pvt = obj;
@@ -82,7 +79,7 @@ static void icalendar_destructor(void *obj)
        }
        ast_string_field_free_memory(pvt);
 
-       ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, cb_true, NULL);
+       ao2_callback(pvt->events, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
 
        ao2_ref(pvt->events, -1);
 }
@@ -135,6 +132,7 @@ static icalcomponent *fetch_icalendar(struct icalendar_pvt *pvt)
 
        if (!pvt) {
                ast_log(LOG_ERROR, "There is no private!\n");
+               return NULL;
        }
 
        if (!(response = ast_str_create(512))) {
@@ -146,6 +144,7 @@ static icalcomponent *fetch_icalendar(struct icalendar_pvt *pvt)
        ne_add_response_body_reader(req, ne_accept_2xx, fetch_response_reader, &response);
 
        ret = ne_request_dispatch(req);
+       ne_request_destroy(req);
        if (ret != NE_OK || !ast_str_strlen(response)) {
                ast_log(LOG_WARNING, "Unable to retrieve iCalendar '%s' from '%s': %s\n", pvt->owner->name, pvt->url, ne_get_error(pvt->session));
                ast_free(response);
@@ -160,6 +159,32 @@ static icalcomponent *fetch_icalendar(struct icalendar_pvt *pvt)
        return comp;
 }
 
+static time_t icalfloat_to_timet(icaltimetype time) 
+{
+       struct ast_tm tm = {0,};
+       struct timeval tv;
+
+       tm.tm_mday = time.day;
+       tm.tm_mon = time.month - 1;
+       tm.tm_year = time.year - 1900;
+       tm.tm_hour = time.hour;
+       tm.tm_min = time.minute;
+       tm.tm_sec = time.second;
+       tm.tm_isdst = -1;
+       tv = ast_mktime(&tm, NULL);
+
+       return tv.tv_sec;
+}
+
+/* span->start & span->end may be dates or floating times which have no timezone,
+ * which would mean that they should apply to the local timezone for all recipients.
+ * For example, if a meeting was set for 1PM-2PM floating time, people in different time
+ * zones would not be scheduled at the same local times.  Dates are often treated as
+ * floating times, so all day events will need to be converted--so we can trust the
+ * span here, and instead will grab the start and end from the component, which will
+ * allow us to test for floating times or dates.
+ */
+
 static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span, void *data)
 {
        struct icalendar_pvt *pvt = data;
@@ -180,11 +205,11 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
                return;
        }
 
-       start = icaltime_from_timet_with_zone(span->start, 0, utc);
-       end = icaltime_from_timet_with_zone(span->end, 0, utc);
-       event->start = span->start;
-       event->end = span->end;
+       start = icalcomponent_get_dtstart(comp);
+       end = icalcomponent_get_dtend(comp);
 
+       event->start = icaltime_get_tzid(start) ? span->start : icalfloat_to_timet(start);
+       event->end = icaltime_get_tzid(end) ? span->end : icalfloat_to_timet(end);
        event->busy_state = span->is_busy ? AST_CALENDAR_BS_BUSY : AST_CALENDAR_BS_FREE;
 
        if ((prop = icalcomponent_get_first_property(comp, ICAL_SUMMARY_PROPERTY))) {
@@ -203,6 +228,14 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
                ast_string_field_set(event, location, icalproperty_get_value_as_string(prop));
        }
 
+       if ((prop = icalcomponent_get_first_property(comp, ICAL_CATEGORIES_PROPERTY))) {
+               ast_string_field_set(event, categories, icalproperty_get_value_as_string(prop));
+       }
+
+       if ((prop = icalcomponent_get_first_property(comp, ICAL_PRIORITY_PROPERTY))) {
+               event->priority = icalvalue_get_integer(icalproperty_get_value(prop));
+       }
+
        if ((prop = icalcomponent_get_first_property(comp, ICAL_UID_PROPERTY))) {
                ast_string_field_set(event, uid, icalproperty_get_value_as_string(prop));
        } else {
@@ -211,12 +244,47 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
                        ast_string_field_set(event, uid, event->summary);
                } else {
                        char tmp[100];
-                       snprintf(tmp, sizeof(tmp), "%lu", event->start);
+                       snprintf(tmp, sizeof(tmp), "%ld", event->start);
                        ast_string_field_set(event, uid, tmp);
                }
        }
 
-       /* Get the attendees */
+       /*
+        * If comp has an RRULE and/or RDATE property, we need to check whether
+        * another vevent component supercedes this span. Such a component would
+        * have two characteristics:
+        *  - its UID is the same as comp
+        *  - its RECURRENCE-ID property is the same time as span->start
+        */
+       if (icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY)
+          || icalcomponent_get_first_property(comp, ICAL_RDATE_PROPERTY)) {
+               icalcompiter comp_iter;
+               icaltimetype span_start = icaltime_from_timet_with_zone(
+                       event->start, icaltime_is_date(start), icaltime_get_timezone(start));
+
+               icaltime_set_timezone(&span_start, icaltime_get_timezone(start));
+               for (comp_iter = icalcomponent_begin_component(pvt->data, ICAL_VEVENT_COMPONENT);
+                        icalcompiter_deref(&comp_iter);
+                        icalcompiter_next(&comp_iter)) {
+                       icalcomponent *vevent = icalcompiter_deref(&comp_iter);
+                       icalproperty *uid = icalcomponent_get_first_property(vevent, ICAL_UID_PROPERTY);
+
+                       if (uid && !strcmp(icalproperty_get_value_as_string(uid), event->uid)) {
+                               icaltimetype recurrence_id = icalcomponent_get_recurrenceid(vevent);
+
+                               /* Set the same timezone that we want to compare against */
+                               icaltime_set_timezone(&recurrence_id, icaltime_get_timezone(start));
+
+                               if (!icaltime_compare(recurrence_id, span_start)
+                                  && icaltime_is_date(span_start) == icaltime_is_date(recurrence_id)) {
+                                       event = ast_calendar_unref_event(event);
+                                       return;
+                               }
+                       }
+               }
+       }
+
+    /* Get the attendees */
        for (prop = icalcomponent_get_first_property(comp, ICAL_ATTENDEE_PROPERTY);
                        prop; prop = icalcomponent_get_next_property(comp, ICAL_ATTENDEE_PROPERTY)) {
                struct ast_calendar_attendee *attendee;
@@ -227,10 +295,12 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
                        return;
                }
                data = icalproperty_get_attendee(prop);
-               if (!ast_strlen_zero(data)) {
-                       attendee->data = ast_strdup(data);;
-                       AST_LIST_INSERT_TAIL(&event->attendees, attendee, next);
+               if (ast_strlen_zero(data)) {
+                       ast_free(attendee);
+                       continue;
                }
+               attendee->data = ast_strdup(data);;
+               AST_LIST_INSERT_TAIL(&event->attendees, attendee, next);
        }
 
 
@@ -266,7 +336,7 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
                /* XXX Technically you can check RELATED to see if the event fires from the END of the event
                 * But, I'm not sure I've ever seen anyone implement it in calendaring software, so I'm ignoring for now */
                tmp = icaltime_add(start, trigger.duration);
-               event->alarm = icaltime_as_timet_with_zone(tmp, utc);
+               event->alarm = icaltime_as_timet_with_zone(tmp, icaltime_get_timezone(start));
        }
 
        ao2_link(pvt->events, event);
@@ -298,7 +368,7 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
        start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
        end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
        end_time.second += pvt->owner->timeframe * 60;
-       icaltime_normalize(end_time);
+       end_time = icaltime_normalize(end_time);
 
        for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT);
             iter;
@@ -391,8 +461,9 @@ static void *ical_load_calendar(void *void_data)
        }
 
        pvt->session = ne_session_create(pvt->uri.scheme, pvt->uri.host, pvt->uri.port);
+       ne_redirect_register(pvt->session);
        ne_set_server_auth(pvt->session, auth_credentials, pvt);
-       if (!strncasecmp(pvt->uri.scheme, "https", sizeof(pvt->uri.scheme))) {
+       if (!strcasecmp(pvt->uri.scheme, "https")) {
                ne_ssl_trust_default_ca(pvt->session);
        }
 
@@ -431,6 +502,11 @@ static void *ical_load_calendar(void *void_data)
 
                ast_debug(10, "Refreshing after %d minute timeout\n", pvt->owner->refresh);
 
+               /* Free the old calendar data */
+               if (pvt->data) {
+                       icalcomponent_free(pvt->data);
+                       pvt->data = NULL;
+               }
                if (!(pvt->data = fetch_icalendar(pvt))) {
                        ast_log(LOG_WARNING, "Unable to parse iCalendar '%s'\n", pvt->owner->name);
                        continue;
@@ -460,7 +536,9 @@ static int unload_module(void)
        return 0;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk iCalendar .ics file integration",
-               .load = load_module,
-               .unload = unload_module,
-       );
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Asterisk iCalendar .ics file integration",
+       .support_level = AST_MODULE_SUPPORT_EXTENDED,
+       .load = load_module,
+       .unload = unload_module,
+       .load_pri = AST_MODPRI_DEVSTATE_PLUGIN,
+);