Fix bug 419007 - Invalid ics files don\'t trigger INVALID_TIMEZONE error, times are assumed as floating. r=philipp
authorDaniel Boelzle [:dbo] <daniel.boelzle@sun.com>
Sun, 05 Oct 2008 19:21:01 +0200
changeset 523 0a454b8f8da7ebce3f96e8965a8755e4991d062b
parent 522 b7a5744f2be77e944e6892c4cd2f80c3a870c4b3
child 524 824f12a406080be5152f1d48052d0d0cd8965be4
push idunknown
push userunknown
push dateunknown
reviewersphilipp
bugs419007
Fix bug 419007 - Invalid ics files don\'t trigger INVALID_TIMEZONE error, times are assumed as floating. r=philipp
calendar/base/src/Makefile.in
calendar/base/src/calDateTime.cpp
calendar/base/src/calICSService.cpp
calendar/base/src/calIcsParser.js
calendar/base/src/calTimezone.cpp
calendar/base/src/calUtils.cpp
calendar/base/src/calUtils.jsm
calendar/lightning/jar.mn
calendar/locales/en-US/chrome/calendar/calendar.properties
calendar/sunbird/base/jar.mn
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -108,16 +108,20 @@ EXTRA_SCRIPTS = \
     calProviderUtils.js \
     calWeekInfoService.js \
     calTransactionManager.js \
     calFreeBusyService.js \
     calCalendarSearchService.js \
     calTimezoneService.js \
     $(NULL)
 
+EXTRA_JS_MODULES = \
+    calUtils.jsm \
+    $(NULL)
+
 # Use NSINSTALL to make the directory, as there's no mtime to preserve.
 libs:: $(EXTRA_SCRIPTS)
 	if test ! -d $(FINAL_TARGET)/js; then $(NSINSTALL) -D $(FINAL_TARGET)/js; fi
 	$(INSTALL) $^ $(FINAL_TARGET)/js
 
 # The install target must use SYSINSTALL, which is NSINSTALL in copy mode.
 install:: $(EXTRA_SCRIPTS)
 	$(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/js
--- a/calendar/base/src/calDateTime.cpp
+++ b/calendar/base/src/calDateTime.cpp
@@ -475,17 +475,21 @@ void calDateTime::FromIcalTime(icaltimet
     } else {
         mTimezone = cal::detectTimezone(t, nsnull);
     }
 #if defined(DEBUG)
     if (mTimezone) {
         if (t.is_utc) {
             NS_ASSERTION(SameCOMIdentity(mTimezone, cal::UTC()), "UTC mismatch!");
         } else if (!t.zone) {
-            NS_ASSERTION(SameCOMIdentity(mTimezone, cal::floating()), "floating mismatch!");
+            nsCAutoString tzid;
+            mTimezone->GetTzid(tzid);
+            if (tzid.EqualsLiteral("floating")) {
+                NS_ASSERTION(SameCOMIdentity(mTimezone, cal::floating()), "floating mismatch!");
+            }
         } else {
             nsCAutoString tzid;
             mTimezone->GetTzid(tzid);
             NS_ASSERTION(tzid.Equals(icaltimezone_get_tzid(const_cast<icaltimezone *>(t.zone))),
                          "tzid mismatch!");
         }
     }
 #endif
--- a/calendar/base/src/calICSService.cpp
+++ b/calendar/base/src/calICSService.cpp
@@ -433,16 +433,19 @@ nsresult calIcalProperty::getDatetime_(c
                         if (!icaltimezone_set_component(clonedZone, clonedZoneComp)) {
                             icaltimezone_free(clonedZone, 1 /* free struct */);
                             return NS_ERROR_INVALID_ARG;
                         }
                         nsCOMPtr<calIIcalComponent> const tzComp(new calIcalComponent(clonedZone, clonedZoneComp));
                         CAL_ENSURE_MEMORY(tzComp);
                         tz = new calTimezone(tzid, tzComp);
                         CAL_ENSURE_MEMORY(tz);
+                    } else { // install phantom timezone, so the data could be repaired:
+                        tz = new calTimezone(tzid, nsnull);
+                        CAL_ENSURE_MEMORY(tz);
                     }
                 }
             }
             if (comp && tz) {
                 // assure timezone is known:
                 comp->AddTimezoneReference(tz);
             }
         }
@@ -476,18 +479,19 @@ calIcalComponent::~calIcalComponent()
 
 NS_IMETHODIMP
 calIcalComponent::AddTimezoneReference(calITimezone *aTimezone)
 {
     NS_ENSURE_ARG_POINTER(aTimezone);
     nsCAutoString tzid;
     nsresult rv = aTimezone->GetTzid(tzid);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (!mReferencedTimezones.Put(tzid, aTimezone))
+    if (!mReferencedTimezones.Put(tzid, aTimezone)) {
         return NS_ERROR_OUT_OF_MEMORY;
+    }
     return NS_OK;
 }
 
 PR_STATIC_CALLBACK(PLDHashOperator)
 TimezoneHashToTimezoneArray(nsACString const& /*tzid*/, calITimezone * tz, void * arg)
 {
     calITimezone *** const arrayPtr = static_cast<calITimezone ***>(arg);
     NS_ADDREF(**arrayPtr = tz);
@@ -759,25 +763,38 @@ nsresult calIcalProperty::setDatetime_(c
 {
     NS_ENSURE_ARG_POINTER(prop);
     NS_ENSURE_ARG_POINTER(dt);
 
     icaltimetype itt;
     dt->ToIcalTime(&itt);
 
     if (parent) {
-        if (!itt.is_utc && itt.zone) {
+        if (!itt.is_utc) {
             nsCOMPtr<calITimezone> tz;
             nsresult rv = dt->GetTimezone(getter_AddRefs(tz));
             NS_ENSURE_SUCCESS(rv, rv);
-            rv = parent->getParentVCalendarOrThis()->AddTimezoneReference(tz);
-            NS_ENSURE_SUCCESS(rv, rv);
-            icalparameter * const param = icalparameter_new_from_value_string(
-                ICAL_TZID_PARAMETER, icaltimezone_get_tzid(const_cast<icaltimezone *>(itt.zone)));
-            icalproperty_set_parameter(prop, param);
+            if (itt.zone) {
+                rv = parent->getParentVCalendarOrThis()->AddTimezoneReference(tz);
+                NS_ENSURE_SUCCESS(rv, rv);
+                icalparameter * const param = icalparameter_new_from_value_string(
+                    ICAL_TZID_PARAMETER, icaltimezone_get_tzid(const_cast<icaltimezone *>(itt.zone)));
+                icalproperty_set_parameter(prop, param);
+            } else { // either floating or phantom:
+                PRBool b = PR_FALSE;
+                if (NS_FAILED(tz->GetIsFloating(&b)) || !b) {
+                    // restore the same phantom TZID:
+                    nsCAutoString tzid;
+                    rv = tz->GetTzid(tzid);
+                    NS_ENSURE_SUCCESS(rv, rv);
+                    icalparameter * const param = icalparameter_new_from_value_string(ICAL_TZID_PARAMETER,
+                                                                                      tzid.get());
+                    icalproperty_set_parameter(prop, param);
+                }
+            }
         }
     } else if (!itt.is_date && !itt.is_utc && itt.zone) {
         // no parent to add the CTIMEZONE to: coerce DATETIMEs to UTC, DATEs to floating
         icaltimezone_convert_time(&itt,
                                   const_cast<icaltimezone *>(itt.zone),
                                   icaltimezone_get_utc_timezone());
         itt.zone = icaltimezone_get_utc_timezone();
         itt.is_utc = 1;
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -30,16 +30,18 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
 function calIcsParser() {
     this.wrappedJSObject = this;
     this.mItems = new Array();
     this.mParentlessItems = new Array();
     this.mComponents = new Array();
     this.mProperties = new Array();
 }
 
@@ -65,16 +67,36 @@ function ip_parseString(aICSString, aTzP
     } else {
         calComp = rootComp.getFirstSubcomponent('VCALENDAR');
     }
 
     var unexpandedItems = [];
     var uid2parent = {};
     var excItems = [];
 
+    let tzErrors = {};
+    function checkTimezone(item, dt) {
+        if (dt && cal.isPhantomTimezone(dt.timezone)) {
+            let tzid = dt.timezone.tzid;
+            let hid = item.hashId + "#" + tzid;
+            if (tzErrors[hid] === undefined) {
+                // For now, publish errors to console and alert user.
+                // In future, maybe make them available through an interface method
+                // so this UI code can be removed from the parser, and caller can
+                // choose whether to alert, or show user the problem items and ask
+                // for fixes, or something else.
+                let msg = (calGetString("calendar", "unknownTimezoneInItem",
+                                        [tzid, item.title, cal.getDateFormatter().formatDateTime(dt)]) +
+                           "\n" + item.icalString);
+                cal.ERROR(msg);
+                tzErrors[hid] = true;
+            }
+        }
+    }
+
     while (calComp) {
 
         // Get unknown properties
         var prop = calComp.getFirstProperty("ANY");
         while (prop) {
             if (prop.propertyName != "VERSION" &&
                 prop.propertyName != "PRODID") {
                 this.mProperties.push(prop);
@@ -88,35 +110,38 @@ function ip_parseString(aICSString, aTzP
             isFromOldSunbird = prodId.value == "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN";
         }
 
         var subComp = calComp.getFirstSubcomponent("ANY");
         while (subComp) {
             var item = null;
             switch (subComp.componentType) {
             case "VEVENT":
-                item = Components.classes["@mozilla.org/calendar/event;1"]
-                                 .createInstance(Components.interfaces.calIEvent);
+                item = cal.createEvent();
+                item.icalComponent = subComp;
+                checkTimezone(item, item.startDate);
+                checkTimezone(item, item.endDate);
                 break;
             case "VTODO":
-                item = Components.classes["@mozilla.org/calendar/todo;1"]
-                                 .createInstance(Components.interfaces.calITodo);
+                item = cal.createTodo();
+                item.icalComponent = subComp;
+                checkTimezone(item, item.entryDate);
+                checkTimezone(item, item.dueDate);
+                // completed is defined to be in UTC
                 break;
             case "VTIMEZONE":
                 // this should already be attached to the relevant
                 // events in the calendar, so there's no need to
                 // do anything with it here.
                 break;
             default:
                 this.mComponents.push(subComp);
             }
 
             if (item) {
-                item.icalComponent = subComp;
-
                 // Only try to fix ICS from Sunbird 0.2 (and earlier) if it
                 // has an EXDATE.
                 hasExdate = subComp.getFirstProperty("EXDATE");
                 if (isFromOldSunbird && hasExdate) {
                     item = fixOldSunbirdExceptions(item);
                 }
 
                 var rid = item.recurrenceId;
@@ -144,16 +169,29 @@ function ip_parseString(aICSString, aTzP
         } else { // a parentless one
             this.mParentlessItems.push(item);
         }
     }
     
     for each (var item in unexpandedItems) {
         this.mItems.push(item);
     }
+
+    for (let e in tzErrors) { // if any error has occurred
+        // Use an alert rather than a prompt because problems may appear in
+        // remote subscribed calendars the user cannot change.
+        if (Components.classes["@mozilla.org/alerts-service;1"]) {
+            let notifier = Components.classes["@mozilla.org/alerts-service;1"]
+                                     .getService(Components.interfaces.nsIAlertsService);
+            let title = calGetString("calendar", "TimezoneErrorsAlertTitle")
+            let text = calGetString("calendar", "TimezoneErrorsSeeConsole");
+            notifier.showAlertNotification("", title, text, false, null, null, title);
+        }
+        break;
+     }
 };
 
 calIcsParser.prototype.parseFromStream =
 function ip_parseFromStream(aStream, aTzProvider) {
     // Read in the string. Note that it isn't a real string at this point, 
     // because likely, the file is utf8. The multibyte chars show up as multiple
     // 'chars' in this string. So call it an array of octets for now.
     
--- a/calendar/base/src/calTimezone.cpp
+++ b/calendar/base/src/calTimezone.cpp
@@ -54,17 +54,17 @@ NS_IMETHODIMP
 calTimezone::GetIsUTC(PRBool * _retval) {
     NS_ENSURE_ARG_POINTER(_retval);
     *_retval = PR_FALSE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calTimezone::GetDisplayName(nsAString & _retval) {
-    _retval.SetIsVoid(PR_TRUE);
+    _retval = NS_ConvertUTF8toUTF16(mTzid);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calTimezone::GetLatitude(nsACString & _retval) {
     _retval.SetIsVoid(PR_TRUE);
     return NS_OK;
 }
@@ -79,11 +79,15 @@ NS_IMETHODIMP
 calTimezone::GetProvider(calITimezoneProvider ** _retval) {
     NS_ENSURE_ARG_POINTER(_retval);
     *_retval = nsnull;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 calTimezone::ToString(nsACString & aResult) {
-    return mIcalComponent->ToString(aResult);
+    if (mIcalComponent) {
+        return mIcalComponent->ToString(aResult);
+    } else {
+        return GetTzid(aResult);
+    }
 }
 
--- a/calendar/base/src/calUtils.cpp
+++ b/calendar/base/src/calUtils.cpp
@@ -198,17 +198,17 @@ icaltimezone * getIcalTimezone(calITimez
     tz->GetIsUTC(&b);
     if (b) {
         icaltz = icaltimezone_get_utc_timezone();
     } else {
         nsCOMPtr<calIIcalComponent> tzComp;
         tz->GetIcalComponent(getter_AddRefs(tzComp));
         if (tzComp) {
             icaltz = tzComp->GetIcalTimezone();
-        } // else floating
+        } // else floating or phantom timezone
     }
     return icaltz;
 }
 
 XpcomBase::~XpcomBase() {
 }
 
 }
new file mode 100644
--- /dev/null
+++ b/calendar/base/src/calUtils.jsm
@@ -0,0 +1,77 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Sun Microsystems code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Sun Microsystems, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Daniel Boelzle <daniel.boelzle@sun.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// New code must not load/import calUtils.js, but should use calUtils.jsm.
+
+var EXPORTED_SYMBOLS = ["cal"];
+let cal = {
+    // new code should land here,
+    // and more code should be moved from calUtils.js into this object to avoid
+    // clashes with other extensions
+
+    getIOService: generateServiceAccessor("@mozilla.org/network/io-service;1",
+                                          Components.interfaces.nsIIOService2),
+
+    /**
+     * Checks whether a timezone lacks a definition.
+     */
+    isPhantomTimezone: function cal_isPhantomTimezone(tz) {
+        return (!tz.icalComponent && !tz.isUTC && !tz.isFloating);
+    }
+};
+
+// local to this module;
+// will be used to generate service accessor functions, getIOService()
+function generateServiceAccessor(id, iface) {
+    return function this_() {
+        if (this_.mService === undefined) {
+            this_.mService = Components.classes[id].getService(iface);
+        }
+        return this_.mService;
+    };
+}
+
+// Interim import of all symbols into cal:
+// This should serve as a clean start for new code, e.g. new code could use
+// cal.createDatetime instead of plain createDatetime NOW.
+let calUtils = __LOCATION__.parent.parent.clone();
+calUtils.append("js");
+calUtils.append("calUtils.js");
+Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+          .getService(Components.interfaces.mozIJSSubScriptLoader)
+          .loadSubScript(cal.getIOService().newFileURI(calUtils).spec, cal);
+
--- a/calendar/lightning/jar.mn
+++ b/calendar/lightning/jar.mn
@@ -1,9 +1,10 @@
 lightning.jar:
+% resource calendar .
 % content lightning %content/lightning/
 % content messagebody %content/messagebody/ contentaccessible=yes
 % override chrome://messagebody/skin/imip.css chrome://lightning/skin/imip.css
 % overlay chrome://messenger/content/messenger.xul chrome://lightning/content/lightning-migration.xul
 % overlay chrome://messenger/content/msgAccountCentral.xul chrome://lightning/content/messenger-overlay-accountCentral.xul
 % overlay chrome://messenger/content/messenger.xul chrome://lightning/content/messenger-overlay-sidebar.xul
 % overlay chrome://messenger/content/messageWindow.xul chrome://lightning/content/imip-bar-overlay.xul
 % overlay chrome://messenger/content/messageWindow.xul chrome://lightning/content/messenger-overlay-messageWindow.xul
--- a/calendar/locales/en-US/chrome/calendar/calendar.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar.properties
@@ -96,16 +96,21 @@ unableToWrite=Unable to write to file:
 defaultFileName=MozillaCalEvents
 HTMLTitle=Mozilla Calendar
 timezoneError=An unknown and undefined timezone was found while reading %1$S.
 duplicateError=%1$S item(s) were ignored since they exist in both the destination calendar and %2$S.
 unableToCreateProvider=An error was encountered preparing the calendar located at %1$S for use. It will not be available.
 unknownTimezonesError=An error was encountered preparing the calendar located at %1$S for use. The calendar might refer to unknown timezones. Please install the latest calendar-timezones.xpi.
 missingCalendarTimezonesError=No timezones found! Please install calendar-timezones.xpi.
 
+# Sample: Unknown timezone "USPacific" in "Dentist Appt".  Using the 'floating' local timezone instead: 2008/02/28 14:00:00
+unknownTimezoneInItem=Unknown timezone "%1$S" in "%2$S".  Treated as 'floating' local timezone instead: %3$S
+TimezoneErrorsAlertTitle=Timezone Errors
+TimezoneErrorsSeeConsole=See Error Console: Unknown timezones are treated as the 'floating' local timezone.
+
 unsubscribeCalendarTitle=Unsubscribe from Calendar
 unsubscribeCalendarMessage=Are you sure you want to unsubscribe from calendar "%1$S"?
 
 WeekTitle=Week %1$S
 # Used to format the Multiweek's labels, ie Weeks 2 - 7
 WeeksTitle=Weeks %1$S-%2$S
 None=None
 
--- a/calendar/sunbird/base/jar.mn
+++ b/calendar/sunbird/base/jar.mn
@@ -1,9 +1,10 @@
 calendar.jar:
+% resource calendar .
 % content calendar %content/calendar/
 *  content/calendar/aboutDialog.css              (content/aboutDialog.css)
 *  content/calendar/aboutDialog.js               (content/aboutDialog.js)
 *  content/calendar/aboutDialog.xul              (content/aboutDialog.xul)
 *  content/calendar/calendar.xul                 (content/calendar.xul)
    content/calendar/calendar-gotodate-dialog.xul (content/calendar-gotodate-dialog.xul)
    content/calendar/calendar-offline.js          (content/calendar-offline.js)
 *  content/calendar/credits.xhtml                (content/credits.xhtml)