[california] Handle malformed .ics and VCALENDAR w/o METHOD: Closes bug #729108



commit 7378dce72a12b050c6a730d9af0b3ead67c3e59f
Author: Jim Nelson <jim yorba org>
Date:   Mon Apr 28 16:24:12 2014 -0700

    Handle malformed .ics and VCALENDAR w/o METHOD: Closes bug #729108
    
    EventBrite offers iCalendar VEVENTS with no UID or DTSTAMPs, so deal
    with that situation when it arises by generating our own.  Likewise,
    their VCALENDARs don't have a METHOD, so use PUBLISH.

 .../backing-eds-calendar-source-subscription.vala  |   10 ++++--
 src/component/component-event.vala                 |    5 ++-
 src/component/component-icalendar.vala             |   16 ++++++++-
 src/component/component-instance.vala              |   39 +++++++++++++++-----
 vapi/libical.vapi                                  |    2 +-
 5 files changed, 56 insertions(+), 16 deletions(-)
---
diff --git a/src/backing/eds/backing-eds-calendar-source-subscription.vala 
b/src/backing/eds/backing-eds-calendar-source-subscription.vala
index e4faf20..8378711 100644
--- a/src/backing/eds/backing-eds-calendar-source-subscription.vala
+++ b/src/backing/eds/backing-eds-calendar-source-subscription.vala
@@ -124,14 +124,18 @@ internal class EdsCalendarSourceSubscription : CalendarSourceSubscription {
     
     private void on_objects_modified(SList<weak iCal.icalcomponent> objects) {
         foreach (weak iCal.icalcomponent ical_component in objects) {
+            Component.Event? event = null;
+            
             // only update known objects
-            Component.Event? event = for_uid(new Component.UID(ical_component.get_uid()))
-                as Component.Event;
+            string? uid = ical_component.get_uid();
+            if (!String.is_empty(uid))
+                event = for_uid(new Component.UID(uid)) as Component.Event;
+            
             if (event == null)
                 continue;
             
             try {
-                event.full_update(ical_component);
+                event.full_update(ical_component, null);
             } catch (Error err) {
                 debug("Unable to update event %s: %s", event.to_string(), err.message);
             }
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 3923383..a54834e 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -101,8 +101,9 @@ public class Event : Instance, Gee.Comparable<Event> {
     /**
      * @inheritDoc
      */
-    protected override void update_from_component(iCal.icalcomponent ical_component) throws Error {
-        base.update_from_component(ical_component);
+    protected override void update_from_component(iCal.icalcomponent ical_component, UID? supplied_uid)
+        throws Error {
+        base.update_from_component(ical_component, supplied_uid);
         
         summary = ical_component.get_summary();
         description = ical_component.get_description();
diff --git a/src/component/component-icalendar.vala b/src/component/component-icalendar.vala
index e9e9b92..a6daa9a 100644
--- a/src/component/component-icalendar.vala
+++ b/src/component/component-icalendar.vala
@@ -22,6 +22,16 @@ namespace California.Component {
 
 public class iCalendar : BaseObject {
     /**
+     * Default METHOD when one is not supplied with iCalendar.
+     *
+     * NONE is not viable, as some backends will choke and require one.  PUBLISH is a good
+     * general METHOD for VCALENDARs lacking a METHOD.
+     *
+     * @see method
+     */
+    public const iCal.icalproperty_method DEFAULT_METHOD = iCal.icalproperty_method.PUBLISH;
+    
+    /**
      * The VCALENDAR's PRODID.
      *
      * See [[https://tools.ietf.org/html/rfc5545#section-3.7.3]]
@@ -41,7 +51,7 @@ public class iCalendar : BaseObject {
      *
      * See [[https://tools.ietf.org/html/rfc5545#section-3.7.2]]
      */
-    public iCal.icalproperty_method method { get; private set; default = iCal.icalproperty_method.NONE; }
+    public iCal.icalproperty_method method { get; private set; default = DEFAULT_METHOD; }
     
     /**
      * The VCALENDAR's CALSCALE.
@@ -86,9 +96,13 @@ public class iCalendar : BaseObject {
         if (prop != null)
             calscale = prop.get_calscale();
         
+        // METHOD is important ... if not present, be sure it's set (important for adding, some
+        // backends may require its presence)
         prop = root.get_first_property(iCal.icalproperty_kind.METHOD_PROPERTY);
         if (prop != null)
             method = prop.get_method();
+        else
+            root.set_method(DEFAULT_METHOD);
         
         //
         // Contained components
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index f5c62ba..40cb7e8 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -128,9 +128,10 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
         _ical_component = ical_component.clone();
         
         // this needs to be stored before calling update() or the equality check there will fail
-        uid = new UID(_ical_component.get_uid());
+        string? ical_uid = _ical_component.get_uid();
+        uid = !String.is_empty(ical_uid) ? new UID(ical_uid) : UID.generate();
         
-        full_update(_ical_component);
+        full_update(_ical_component, uid);
         
         // watch for property changes and update ical_component when happens
         notify.connect(on_notify);
@@ -176,14 +177,16 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      * This is also called by the Instance base class constructor to give subclasses a single
      * code path for updating their state.
      *
+     * The { link UID} may be supplied if the iCal component does not have one.
+     *
      * @throws BackingError if eds_component is not for this Instance.
      */
-    public void full_update(iCal.icalcomponent ical_component) throws Error {
+    public void full_update(iCal.icalcomponent ical_component, UID? supplied_uid) throws Error {
         in_full_update = true;
         
         bool notify = false;
         try {
-            update_from_component(ical_component);
+            update_from_component(ical_component, supplied_uid);
             notify = true;
         } finally {
             in_full_update = false;
@@ -200,18 +203,36 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
      * It's highly recommended the subclass call the base class update_from_component() first to
      * allow it to do basic sanity checking before proceeding to update its own state.
      *
+     * If supplied_uid is non-null, it should be used in preference over the UID in the iCal
+     * component.  In either case, at least one method should return a valid UID.
+     *
      * @see full_update
      */
-    protected virtual void update_from_component(iCal.icalcomponent ical_component) throws Error {
-        Component.UID other_uid = new Component.UID(ical_component.get_uid());
+    protected virtual void update_from_component(iCal.icalcomponent ical_component, UID? supplied_uid)
+        throws Error {
+        if (supplied_uid == null)
+            assert(!String.is_empty(ical_component.get_uid()));
+        
+        // use the supplied UID before using the one in the iCal component (for dealing with
+        // malformed iCal w/ no UID ... I'm looking at you, EventBrite)
+        Component.UID other_uid = supplied_uid ?? new Component.UID(ical_component.get_uid());
         if (!uid.equal_to(other_uid)) {
             throw new BackingError.MISMATCH("Attempt to update component %s with component %s",
                 this.uid.to_string(), other_uid.to_string());
         }
         
-        DateTime dt_stamp = new DateTime(ical_component, iCal.icalproperty_kind.DTSTAMP_PROPERTY);
-        if (!dt_stamp.is_date)
-            dtstamp = dt_stamp.to_exact_time();
+        try {
+            DateTime dt_stamp = new DateTime(ical_component, iCal.icalproperty_kind.DTSTAMP_PROPERTY);
+            if (!dt_stamp.is_date)
+                dtstamp = dt_stamp.to_exact_time();
+        } catch (ComponentError comperr) {
+            // if unavailable, generate a DTSTAMP ... like UID, this is for malformed iCal with
+            // no DTSTAMP, i.e. EventBrite
+            if (!(comperr is ComponentError.UNAVAILABLE))
+                throw comperr;
+            
+            dtstamp = Calendar.System.now;
+        }
         
         try {
             rid = new DateTime(ical_component, iCal.icalproperty_kind.RECURRENCEID_PROPERTY);
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index a04d261..c583a74 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -133,7 +133,7 @@ namespace iCal {
                [CCode (cname = "icalcomponent_get_timezone")]
                public unowned iCal.icaltimezone get_timezone (string tzid);
                [CCode (cname = "icalcomponent_get_uid")]
-               public unowned string get_uid ();
+               public unowned string? get_uid ();
                [CCode (cname = "icalcomponent_is_valid")]
                public int is_valid ();
                [CCode (cname = "icalcomponent_isa")]


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]