Bug 457203 - iTIP overhaul. r=philipp
authorDaniel Boelzle [:dbo] <daniel.boelzle@sun.com>
Wed, 05 Nov 2008 12:08:51 +0100
changeset 1017 f21bf34efae5c9f4f091a3b9406b81548c627d1d
parent 1016 e5a9765b2d790f91b453997e0db9f9c291935bee
child 1018 1fd8c2b56030062b0000c14009bcb91c2d0a0933
push idunknown
push userunknown
push dateunknown
reviewersphilipp
bugs457203
Bug 457203 - iTIP overhaul. r=philipp
calendar/base/Makefile.in
calendar/base/content/calendar-invitations-manager.js
calendar/base/content/calendar-item-editing.js
calendar/base/content/calendar-summary-dialog.js
calendar/base/content/dialogs/calendar-event-dialog.js
calendar/base/content/dialogs/calendar-event-dialog.xul
calendar/base/content/dialogs/calendar-invitations-dialog.js
calendar/base/content/dialogs/calendar-invitations-dialog.xul
calendar/base/modules/Makefile.in
calendar/base/modules/calItipUtils.jsm
calendar/base/modules/calUtils.jsm
calendar/base/public/Makefile.in
calendar/base/public/calIItemBase.idl
calendar/base/public/calIItipItem.idl
calendar/base/public/calIItipProcessor.idl
calendar/base/src/Makefile.in
calendar/base/src/calEvent.js
calendar/base/src/calIcsParser.js
calendar/base/src/calIcsSerializer.js
calendar/base/src/calItemBase.js
calendar/base/src/calItemModule.js
calendar/base/src/calItipItem.js
calendar/base/src/calItipProcessor.js
calendar/base/src/calRecurrenceInfo.js
calendar/base/src/calTodo.js
calendar/base/src/calTransactionManager.js
calendar/base/src/calUtils.js
calendar/base/src/calUtils.jsm
calendar/installer/removed-files.in
calendar/installer/windows/packages-static
calendar/itip/calItipEmailTransport.js
calendar/libical/design-data/parameters.csv
calendar/libical/design-data/params-in-prop.txt
calendar/lightning/components/lightningTextCalendarConverter.js
calendar/lightning/content/imip-bar.js
calendar/lightning/content/lightning-utils.js
calendar/lightning/content/lightning.js
calendar/locales/en-US/chrome/calendar/calendar.properties
calendar/locales/en-US/chrome/lightning/lightning.properties
calendar/providers/base/calProviderBase.js
calendar/providers/caldav/calDavCalendar.js
calendar/providers/memory/calMemoryCalendar.js
calendar/providers/storage/calStorageCalendar.js
calendar/sunbird/app/profile/sunbird.js
--- a/calendar/base/Makefile.in
+++ b/calendar/base/Makefile.in
@@ -41,17 +41,17 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = calbase
 MODULE_NAME = calBaseModule
 
-DIRS = public src build
+DIRS = public src modules build
 
 # Select a theme from which to pull our skin goodness
 # OS X: pinstripe
 # Others: winstripe
 
 ifneq (,$(filter mac cocoa,$(MOZ_WIDGET_TOOLKIT)))
 THEME = pinstripe
 else
--- a/calendar/base/content/calendar-invitations-manager.js
+++ b/calendar/base/content/calendar-invitations-manager.js
@@ -31,16 +31,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/calItipUtils.jsm");
+
 var gInvitationsRequestManager = {
     mRequestStatusList: {},
 
     addRequestStatus: function IRM_addRequestStatus(calendar, op) {
         if (op) {
             this.mRequestStatusList[calendar.id] = op;
         }
     },
@@ -241,17 +243,17 @@ InvitationsManager.prototype = {
         operationListener.prototype = {
             onOperationComplete: function (aCalendar,
                                            aStatus,
                                            aOperationType,
                                            aId,
                                            aDetail) {
                 if (Components.isSuccessCode(aStatus) &&
                     aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
-                    checkAndSendItipMessage(aDetail, aOperationType, this.mOldItem);
+                    cal.itip.checkAndSend(aOperationType, aDetail, this.mOldItem);
                     this.mInvitationsManager.deleteItem(aDetail);
                     this.mInvitationsManager.addItem(aDetail);
                 }
                 this.mInvitationsManager.mJobsPending--;
                 if (this.mInvitationsManager.mJobsPending == 0 &&
                     this.mJobQueueFinishedCallBack) {
                     this.mJobQueueFinishedCallBack();
                 }
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -32,45 +32,16 @@
  * 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 ***** */
 
-function opCompleteListener(aOriginalItem, aOuterListener) {
-    this.mOriginalItem = aOriginalItem;
-    this.mOuterListener = aOuterListener;
-}
-
-opCompleteListener.prototype = {
-    mOriginalItem: null,
-    mOuterListener: null,
-
-    onOperationComplete: function oCL_onOperationComplete(aCalendar, aStatus, aOpType, aId, aItem) {
-        if (Components.isSuccessCode(aStatus)) {
-            // we may optionally shift the whole check and send mail messages to
-            // calProviderBase.notifyOperationComplete (with adding an oldItem parameter).
-            // I am not yet sure what to do for mixed mode invitations, e.g.
-            // some users on the attendee list are caldav users and get REQUESTs into their inbox,
-            // other get emailed... For now let's do both.
-            checkAndSendItipMessage(aItem, aOpType, this.mOriginalItem);
-        }
-        if (this.mOuterListener) {
-            this.mOuterListener.onOperationComplete.apply(this.mOuterListener,
-                                                          arguments);
-        }
-    },
-
-    onGetItem: function oCL_onGetResult(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-
-    }
-};
-
 /* all params are optional */
 function createEventWithDialog(calendar, startDate, endDate, summary, event, aForceAllday) {
     const kDefaultTimezone = calendarDefaultTimezone();
 
     var onNewEvent = function(item, calendar, originalItem, listener) {
         if (item.id) {
             // If the item already has an id, then this is the result of
             // saving the item without closing, and then saving again.
@@ -371,79 +342,28 @@ function promptOccurrenceModification(aI
             // Since we have not set past or futureItem, the return below will
             // take care.
             break;
     }
 
     return [pastItem, futureItem, type];
 }
 
-/**
- * Read default alarm settings from user preferences and apply them to
- * the event/todo passed in.
- *
- * @param aItem   The event or todo the settings should be applied to.
- */
-function setDefaultAlarmValues(aItem)
-{
-    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
-                                .getService(Components.interfaces.nsIPrefService);
-    var alarmsBranch = prefService.getBranch("calendar.alarms.");
-
-    if (isEvent(aItem)) {
-        try {
-            if (alarmsBranch.getIntPref("onforevents") == 1) {
-                var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
-                                            .createInstance(Components.interfaces.calIDuration);
-                var units = alarmsBranch.getCharPref("eventalarmunit");
-                alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
-                alarmOffset.isNegative = true;
-                aItem.alarmOffset = alarmOffset;
-                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
-            }
-        } catch (ex) {
-            Components.utils.reportError(
-                "Failed to apply default alarm settings to event: " + ex);
-        }
-    } else if (isToDo(aItem)) {
-        try {
-            if (alarmsBranch.getIntPref("onfortodos") == 1) {
-                // You can't have an alarm if the entryDate doesn't exist.
-                if (!aItem.entryDate) {
-                    aItem.entryDate = getSelectedDay() &&
-                                      getSelectedDay().clone() || now();
-                }
-                var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
-                                            .createInstance(Components.interfaces.calIDuration);
-                var units = alarmsBranch.getCharPref("todoalarmunit");
-                alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
-                alarmOffset.isNegative = true;
-                aItem.alarmOffset = alarmOffset;
-                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
-            }
-        } catch (ex) {
-            Components.utils.reportError(
-                "Failed to apply default alarm settings to task: " + ex);
-        }
-    }
-}
-
 // Undo/Redo code
 function getTransactionMgr() {
     return Components.classes["@mozilla.org/calendar/transactionmanager;1"]
                      .getService(Components.interfaces.calITransactionManager);
 }
 
 function doTransaction(aAction, aItem, aCalendar, aOldItem, aListener) {
-    var innerListener = new opCompleteListener(aOldItem, aListener);
     getTransactionMgr().createAndCommitTxn(aAction,
                                            aItem,
                                            aCalendar,
                                            aOldItem,
-                                           innerListener);
+                                           aListener ? aListener : null);
     updateUndoRedoMenu();
 }
 
 function undo() {
     if (canUndo()) {
         getTransactionMgr().undo();
         updateUndoRedoMenu();
     }
@@ -473,176 +393,8 @@ function canRedo() {
 
 /**
  * Update the undo and redo menu items
  */
 function updateUndoRedoMenu() {
     goUpdateCommand("cmd_undo");
     goUpdateCommand("cmd_redo");
 }
-
-/**
- * Checks to see if the attendees were added or changed between the original
- * and new item.  If there is a change, it launches the calIItipTransport
- * service and sends the invitations
- */
-function checkAndSendItipMessage(aItem, aOpType, aOriginalItem) {
-    var transport = aItem.calendar.getProperty("itip.transport");
-    if (!transport) { // Only send if there's a transport for the calendar
-        return;
-    }
-    transport = transport.QueryInterface(Components.interfaces.calIItipTransport);
-
-    var invitedAttendee = ((calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
-                            aItem.calendar.isInvitation(aItem))
-                           ? aItem.calendar.getInvitedAttendee(aItem) : null);
-    if (invitedAttendee) { // actually is an invitation copy, fix attendee list to send REPLY
-        if (aItem.calendar.canNotify("REPLY", aItem)) {
-            return; // provider does that
-        }
-
-        var origInvitedAttendee = (aOriginalItem && aOriginalItem.getAttendeeById(invitedAttendee.id));
-
-        if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
-            // in case the attendee has just deleted the item, we want to send out a DECLINED REPLY:
-            origInvitedAttendee = invitedAttendee;
-            invitedAttendee = invitedAttendee.clone();
-            invitedAttendee.participationStatus = "DECLINED";
-        }
-
-        // has this been a PARTSTAT change?
-        if (aItem.organizer &&
-            (!origInvitedAttendee ||
-             (origInvitedAttendee.participationStatus != invitedAttendee.participationStatus))) {
-
-            aItem = aItem.clone();
-            aItem.removeAllAttendees();
-            aItem.addAttendee(invitedAttendee);
-
-            var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
-                                     .createInstance(Components.interfaces.calIItipItem);
-            itipItem.init(calGetSerializedItem(aItem));
-            itipItem.targetCalendar = aItem.calendar;
-            itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
-            itipItem.responseMethod = "REPLY";
-            transport.sendItems(1, [aItem.organizer], itipItem);
-        }
-        return;
-    }
-
-    if (aItem.getProperty("X-MOZ-SEND-INVITATIONS") != "TRUE") { // Only send invitations/cancellations
-                                                                 // if the user checked the checkbox
-        return;
-    }
-
-    if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
-        calSendItipMessage(transport, aItem, "CANCEL", aItem.getAttendees({}));
-        return;
-    } // else ADD, MODIFY:
-
-    var originalAtt = (aOriginalItem ? aOriginalItem.getAttendees({}) : []);
-    var itemAtt = aItem.getAttendees({});
-    var canceledAttendees = [];
-
-    if (itemAtt.length > 0 || originalAtt.length > 0) {
-        var attMap = {};
-        for each (var att in originalAtt) {
-            attMap[att.id.toLowerCase()] = att;
-        }
-
-        for each (var att in itemAtt) {
-            if (att.id.toLowerCase() in attMap) {
-                // Attendee was in original item.
-                delete attMap[att.id.toLowerCase()];
-            }
-        }
-
-        for each (var cancAtt in attMap) {
-            canceledAttendees.push(cancAtt);
-        }
-    }
-
-    var autoResponse = false; // confirm to send email
-
-    // Check to see if some part of the item was updated, if so, re-send invites
-    if (!aOriginalItem || aItem.generation != aOriginalItem.generation) { // REQUEST
-        var requestItem = aItem.clone();
-
-        if (!requestItem.organizer) {
-            var organizer = Components.classes["@mozilla.org/calendar/attendee;1"]
-                                      .createInstance(Components.interfaces.calIAttendee);
-            organizer.id = requestItem.calendar.getProperty("organizerId");
-            organizer.commonName = requestItem.calendar.getProperty("organizerCN");
-            organizer.role = "REQ-PARTICIPANT";
-            organizer.participationStatus = "ACCEPTED";
-            organizer.isOrganizer = true;
-            requestItem.organizer = organizer;
-        }
-
-        // Fix up our attendees for invitations using some good defaults
-        var recipients = [];
-        var itemAtt = requestItem.getAttendees({});
-        requestItem.removeAllAttendees();
-        for each (var attendee in itemAtt) {
-            attendee = attendee.clone();
-            attendee.role = "REQ-PARTICIPANT";
-            attendee.participationStatus = "NEEDS-ACTION";
-            attendee.rsvp = "TRUE";
-            requestItem.addAttendee(attendee);
-            recipients.push(attendee);
-        }
-
-        if (recipients.length > 0) {
-            calSendItipMessage(transport, requestItem, "REQUEST", recipients, autoResponse);
-            autoResponse = true; // don't ask again
-        }
-    }
-
-    // Cancel the event for all canceled attendees
-    if (canceledAttendees.length > 0) {
-        var cancelItem = aOriginalItem.clone();
-        cancelItem.removeAllAttendees();
-        for each (var att in canceledAttendees) {
-            cancelItem.addAttendee(att);
-        }
-        calSendItipMessage(transport, cancelItem, "CANCEL", canceledAttendees, autoResponse);
-    }
-}
-
-function calSendItipMessage(aTransport, aItem, aMethod, aRecipientsList, autoResponse) {
-    if (aRecipientsList.length == 0) {
-        return;
-    }
-    if (calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
-        aItem.calendar.canNotify(aMethod, aItem)) {
-        return; // provider will handle that
-    }
-
-    var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
-                             .createInstance(Components.interfaces.calIItipItem);
-
-    // We have to modify our item a little, so we clone it.
-    var item = aItem.clone();
-
-    // We fake Sequence ID support.
-    item.setProperty("SEQUENCE", item.generation);
-
-    // Initialize and set our properties on the item
-    itipItem.init(calGetSerializedItem(item));
-    itipItem.responseMethod = aMethod;
-    itipItem.targetCalendar = item.calendar;
-    itipItem.autoResponse = (autoResponse
-                             ? Components.interfaces.calIItipItem.AUTO
-                             : Components.interfaces.calIItipItem.USER);
-    // XXX I don't know whether the below are used at all, since we don't use the itip processor
-    itipItem.isSend = true;
-
-    // Send it!
-    aTransport.sendItems(aRecipientsList.length, aRecipientsList, itipItem);
-}
-
-function calGetSerializedItem(aItem) {
-    var serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"]
-                               .createInstance(Components.interfaces.calIIcsSerializer);
-    serializer.addItems([aItem], 1);
-    return serializer.serializeToString();
-}
-
--- a/calendar/base/content/calendar-summary-dialog.js
+++ b/calendar/base/content/calendar-summary-dialog.js
@@ -31,16 +31,19 @@
  * 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");
+Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
+
 function onLoad() {
     var args = window.arguments[0];
     var item = args.calendarEvent;
     item = item.clone(); // use an own copy of the passed item
     var calendar = item.calendar;
     window.item = item;
 
     // the calling entity provides us with an object that is responsible
@@ -69,18 +72,18 @@ function onLoad() {
         }
     }
 
     window.readOnly = calendar.readOnly;
     if (!window.readOnly && calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         var attendee = calendar.getInvitedAttendee(item);
         if (attendee) {
             // if this is an unresponded invitation, preset our default alarm values:
-            if (attendee.participationStatus == "NEEDS-ACTION") {
-                setDefaultAlarmValues(item);
+            if (!item.alarmOffset && (attendee.participationStatus == "NEEDS-ACTION")) {
+                cal.setDefaultAlarmValues(item);
             }
 
             window.attendee = attendee.clone();
             // Since we don't have API to update an attendee in place, remove
             // and add again. Also, this is needed if the attendee doesn't exist
             // (i.e REPLY on a mailing list)
             item.removeAttendee(attendee);
             item.addAttendee(window.attendee);
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -346,26 +346,26 @@ function loadDialog(item) {
 
     updateDateTime();
 
     updateCalendar();
 
     // figure out what the title of the dialog should be and set it
     updateTitle();
 
-    var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
+    let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
     if (canNotifyAttendees(item.calendar, item)) {
         // visualize that the server will send out mail:
-        sendInvitesCheckbox.checked = true;
+        notifyCheckbox.checked = true;
     } else {
-        var itemProp = item.getProperty("X-MOZ-SEND-INVITATIONS");
-        sendInvitesCheckbox.checked = (item.calendar.getProperty("imip.identity") &&
-                                       ((itemProp === null)
-                                        ? getPrefSafe("calendar.itip.notify", true)
-                                        : (itemProp == "TRUE")));
+        let itemProp = item.getProperty("X-MOZ-SEND-INVITATIONS");
+        notifyCheckbox.checked = (item.calendar.getProperty("imip.identity") &&
+                                  ((itemProp === null)
+                                   ? getPrefSafe("calendar.itip.notify", true)
+                                   : (itemProp == "TRUE")));
     }
 
     updateAttendees();
     updateRepeat();
     updateReminder();
 
     gShowTimeAs = item.getProperty("TRANSP");
     updateShowTimeAs();
@@ -1534,19 +1534,19 @@ function attachmentLinkClicked(event) {
 function updateCalendar() {
     var item = window.calendarItem;
     var calendar = document.getElementById("item-calendar")
                            .selectedItem.calendar;
 
     gIsReadOnly = calendar.readOnly;
 
     if (!canNotifyAttendees(calendar, item) && calendar.getProperty("imip.identity")) {
-        enableElement("send-invitations-checkbox");
+        enableElement("notify-attendees-checkbox");
     } else {
-        disableElement("send-invitations-checkbox");
+        disableElement("notify-attendees-checkbox");
     }
 
     // update the accept button
     updateAccept();
 
     // TODO: the code above decided about whether or not the item is readonly.
     // below we enable/disable all controls based on this decision.
     // unfortunately some controls need to be disabled based on some other
@@ -1884,21 +1884,21 @@ function saveItem() {
     }
 
     item.removeAllAttendees();
     if (window.attendees) {
         for each (var attendee in window.attendees) {
            item.addAttendee(attendee);
         }
 
-        var sendInvitesCheckbox = document.getElementById("send-invitations-checkbox");
-        if (sendInvitesCheckbox.disabled || document.getElementById("event-grid-attendee-row-2").collapsed) {
+        let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
+        if (notifyCheckbox.disabled || document.getElementById("event-grid-attendee-row-2").collapsed) {
             item.deleteProperty("X-MOZ-SEND-INVITATIONS");
         } else {
-            item.setProperty("X-MOZ-SEND-INVITATIONS", sendInvitesCheckbox.checked ? "TRUE" : "FALSE");
+            item.setProperty("X-MOZ-SEND-INVITATIONS", notifyCheckbox.checked ? "TRUE" : "FALSE");
         }
     }
 
     return item;
 }
 
 function onCommandSave(aIsClosing) {
     var originalItem = window.calendarItem;
--- a/calendar/base/content/dialogs/calendar-event-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-event-dialog.xul
@@ -696,17 +696,17 @@
                      disable-on-readonly="true"/>
               <label id="attendee-list"
                      class="text-link"
                      crop="right"
                      onclick="showAttendeePopup(event)"/>
             </row>
             <row id="event-grid-attendee-row-2" align="center">
               <spacer/>
-              <checkbox id="send-invitations-checkbox"
+              <checkbox id="notify-attendees-checkbox"
                         class="lightning-only"
                         label="&newevent.attendees.notify.label;"
                         pack="start"/>
             </row>
 
             <separator class="groove" id="event-grid-basic-separator"/>
 
             <!-- All-Day -->
--- a/calendar/base/content/dialogs/calendar-invitations-dialog.js
+++ b/calendar/base/content/dialogs/calendar-invitations-dialog.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 onLoad() {
     var operationListener = {
         onOperationComplete: function oL_onOperationComplete(aCalendar,
                                                              aStatus,
                                                              aOperationType,
                                                              aId,
                                                              aDetail) {
             var updatingBox = document.getElementById("updating-box");
@@ -109,18 +111,18 @@ function fillJobQueue(queue) {
         var newStatus = richListItem.participationStatus;
         var oldStatus = richListItem.initialParticipationStatus;
         if (newStatus != oldStatus) {
             var actionString = "modify";
             var oldCalendarItem = richListItem.calendarItem;
             var newCalendarItem = oldCalendarItem.clone();
 
             // set default alarm on unresponded items that have not been declined:
-            if (oldStatus == "NEEDS-ACTION" && newStatus != "DECLINED") {
-                setDefaultAlarmValues(newCalendarItem);
+            if (!newCalendarItem.alarmOffset && (oldStatus == "NEEDS-ACTION") && (newStatus != "DECLINED")) {
+                cal.setDefaultAlarmValues(newCalendarItem);
             }
 
             richListItem.setCalendarItemParticipationStatus(newCalendarItem,
                 newStatus);
             var job = {
                 action: actionString,
                 oldItem: oldCalendarItem,
                 newItem: newCalendarItem
--- a/calendar/base/content/dialogs/calendar-invitations-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-invitations-dialog.xul
@@ -51,19 +51,17 @@
   ondialogcancel="return onCancel();"
   onload="return onLoad();"
   onunload="return onUnload();"
   persist="screenX screenY width height"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <!-- Javascript includes -->
   <script type="application/javascript" src="chrome://calendar/content/calendar-invitations-dialog.js"/>
-  <script type="application/javascript" src="chrome://calendar/content/calUtils.js"/>
   <script type="application/javascript" src="chrome://calendar/content/calendar-ui-utils.js"/>
-  <script type="application/javascript" src="chrome://calendar/content/calendar-item-editing.js"/>
 
   <script type="application/javascript" >
     var invitationsText = "&calendar.invitations.dialog.invitations.text;";
   </script>
 
   <vbox id="dialog-box" flex="1">
     <stack flex="1">
       <calendar-invitations-richlistbox id="invitations-listbox" flex="1"/>
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/Makefile.in
@@ -0,0 +1,52 @@
+# ***** 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 *****
+
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = calbase
+
+EXTRA_JS_MODULES = \
+    calUtils.jsm \
+    calItipUtils.jsm \
+    $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -0,0 +1,781 @@
+/* ***** 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>
+ *   Philipp Kewisch <mozilla@kewis.ch>
+ *   Clint Talbert <ctalbert.moz@gmail.com>
+ *   Matthew Willis <lilmatt@mozilla.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 ***** */
+
+Components.utils.import("resource://gre/modules/debug.js");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+/*
+ * Scheduling and iTIP helper code;
+ * don't use deliberately, because it'll be moved into interfaces/components.
+ *
+ * May replace the current calItipProcessor.js code soon.
+ */
+
+EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
+cal.itip = {
+    /**
+     * Gets the sequence/revision number, either of the passed item or
+     * the last received one of an attendee; see
+     * <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.1>.
+     */
+     getSequence: function cal_itip_getSequence(item) {
+        let seq = null;
+
+        if (item instanceof Components.interfaces.calIAttendee) {
+            seq = item.getProperty("RECEIVED-SEQUENCE");
+        } else if (item) {
+            // Unless the below is standardized, we store the last original
+            // REQUEST/PUBLISH SEQUENCE in X-MOZ-RECEIVED-SEQUENCE to test against it
+            // when updates come in:
+            seq = item.getProperty("X-MOZ-RECEIVED-SEQUENCE");
+            if (seq === null) {
+                seq = item.getProperty("SEQUENCE");
+            }
+
+            // Make sure we don't have a pre Outlook 2007 appointment, but if we do
+            // use Microsoft's Sequence number. I <3 MS
+            if ((seq === null) || (seq == "0")) {
+                seq = item.getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
+            }
+        }
+
+        if (seq === null) {
+            return 0;
+        } else {
+            seq = parseInt(seq, 10);
+            return (isNaN(seq) ? 0 : seq);
+        }
+    },
+
+    /**
+     * Gets the stamp date-time, either of the passed item or
+     * the last received one of an attendee; see
+     * <http://tools.ietf.org/html/draft-desruisseaux-caldav-sched-04#section-7.2>.
+     */
+    getStamp: function cal_itip_getStamp(item) {
+        let dtstamp = null;
+
+        if (item instanceof Components.interfaces.calIAttendee) {
+            let st = item.getProperty("RECEIVED-DTSTAMP");
+            if (st) {
+                dtstamp = cal.createDateTime(st);
+            }
+        } else if (item) {
+            // Unless the below is standardized, we store the last original
+            // REQUEST/PUBLISH DTSTAMP in X-MOZ-RECEIVED-DTSTAMP to test against it
+            // when updates come in:
+            let st = item.getProperty("X-MOZ-RECEIVED-DTSTAMP");
+            if (st) {
+                dtstamp = cal.createDateTime(st);
+            } else {
+                // xxx todo: are there similar X-MICROSOFT-CDO properties to be considered here?
+                dtstamp = item.stampTime;
+            }
+        }
+
+        return dtstamp;
+    },
+
+    /**
+     * Compares sequences and/or stamps of two parties; returns -1, 0, +1.
+     */
+    compare: function cal_itip_compare(item1, item2) {
+        let seq1 = cal.itip.getSequence(item1);
+        let seq2 = cal.itip.getSequence(item2);
+        if (seq1 > seq2) {
+            return 1;
+        } else if (seq1 < seq2) {
+            return -1;
+        } else {
+            let st1 = cal.itip.getStamp(item1);
+            let st2 = cal.itip.getStamp(item2);
+            if (st1 && st2) {
+                return st1.compare(st2);
+            } else if (!st1 && st2) {
+                return -1;
+            } else if (st1 && !st2) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    },
+
+    /**
+     * Scope: iTIP message receiver
+     *
+     * Checks the passed iTIP item and calls the passed function with options offered.
+     *
+     * @param itipItem iTIP item
+     * @param optionsFunc function being called with parameters: itipItem, resultCode, actionFunc
+     *                    The action func has a property |method| showing the options:
+     *                    * REFRESH -- send the latest item (sent by attendee(s))
+     *                    * PUBLISH -- initial publish, no reply (sent by organizer)
+     *                    * PUBLISH:UPDATE -- update of a published item (sent by organizer)
+     *                    * REQUEST -- initial invitation (sent by organizer)
+     *                    * REQUEST:UPDATE -- rescheduling invitation, has major change (sent by organizer)
+     *                    * REQUEST:UPDATE-MINOR -- update of invitation, minor change (sent by organizer)
+     *                    * REPLY -- invitation reply (sent by attendee(s))
+     *                    * CANCEL -- invitation cancel (sent by organizer)
+     */
+    processItipItem: function cal_itip_processItipItem(itipItem, optionsFunc) {
+        switch (itipItem.receivedMethod.toUpperCase()) {
+            case "REFRESH":
+            case "PUBLISH":
+            case "REQUEST":
+            case "CANCEL":
+            case "REPLY": {
+                // Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
+                // same ID, this simplifies our searching, we can just look for Item[0].id
+                let itemList = itipItem.getItemList({});
+                if (itemList.length > 0) {
+                    itipItem.targetCalendar.getItem(itemList[0].id,
+                                                    new ItipFindItemListener(itipItem, optionsFunc));
+                } else if (optionsFunc) {
+                    optionsFunc(itipItem, Components.results.NS_OK);
+                }
+                break;
+            }
+            default: {
+                if (optionsFunc) {
+                    optionsFunc(itipItem, Components.results.NS_ERROR_NOT_IMPLEMENTED);
+                }
+                break;
+            }
+        }
+    },
+
+    /**
+     * Scope: iTIP message sender
+     *
+     * Checks to see if e.g. attendees were added/removed or an item has been
+     * deleted and sends out appropriate iTIP messages.
+     */
+    checkAndSend: function cal_itip_checkAndSend(aOpType, aItem, aOriginalItem) {
+        if (aOriginalItem && aOriginalItem.recurrenceId && !aItem.recurrenceId && aItem.recurrenceInfo) {
+            // sanity check: assure aItem doesn't refer to the master
+            aItem = aItem.recurrenceInfo.getOccurrenceFor(aOriginalItem.recurrenceId);
+            cal.ASSERT(aItem, "unexpected!");
+            if (!aItem) {
+                return;
+            }
+        }
+
+        let autoResponse = { value: false }; // controls confirm to send email only once
+
+        let invitedAttendee = ((cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
+                                aItem.calendar.isInvitation(aItem))
+                               ? aItem.calendar.getInvitedAttendee(aItem) : null);
+        if (invitedAttendee) { // actually is an invitation copy, fix attendee list to send REPLY
+            if (aItem.organizer) {
+                let origInvitedAttendee = (aOriginalItem && aOriginalItem.getAttendeeById(invitedAttendee.id));
+
+                if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
+                    // in case the attendee has just deleted the item, we want to send out a DECLINED REPLY:
+                    origInvitedAttendee = invitedAttendee;
+                    invitedAttendee = invitedAttendee.clone();
+                    invitedAttendee.participationStatus = "DECLINED";
+                }
+
+                // We want to send a REPLY send if:
+                // - there has been a PARTSTAT change
+                // - in case of an organizer SEQUENCE bump we'd go and reconfirm our PARTSTAT
+                if (!origInvitedAttendee ||
+                    (origInvitedAttendee.participationStatus != invitedAttendee.participationStatus) ||
+                    (aOriginalItem && (cal.itip.getSequence(aItem) != cal.itip.getSequence(aOriginalItem)))) {
+                    aItem = aItem.clone();
+                    aItem.removeAllAttendees();
+                    aItem.addAttendee(invitedAttendee);
+                    sendMessage(aItem, "REPLY", [aItem.organizer], autoResponse);
+                }
+            }
+
+            return;
+        }
+
+        if (aItem.getProperty("X-MOZ-SEND-INVITATIONS") != "TRUE") { // Only send invitations/cancellations
+                                                                     // if the user checked the checkbox
+            return;
+        }
+
+        if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
+            sendMessage(aItem, "CANCEL", aItem.getAttendees({}), autoResponse);
+            return;
+        } // else ADD, MODIFY:
+
+        let originalAtt = (aOriginalItem ? aOriginalItem.getAttendees({}) : []);
+        let itemAtt = aItem.getAttendees({});
+        let canceledAttendees = [];
+
+        if (itemAtt.length > 0 || originalAtt.length > 0) {
+            let attMap = {};
+            for each (let att in originalAtt) {
+                attMap[att.id.toLowerCase()] = att;
+            }
+
+            for each (let att in itemAtt) {
+                if (att.id.toLowerCase() in attMap) {
+                    // Attendee was in original item.
+                    delete attMap[att.id.toLowerCase()];
+                }
+            }
+
+            for each (let cancAtt in attMap) {
+                canceledAttendees.push(cancAtt);
+            }
+        }
+
+        // Check to see if some part of the item was updated, if so, re-send REQUEST
+        if (!aOriginalItem || aItem.generation != aOriginalItem.generation) { // REQUEST
+            let requestItem = aItem.clone();
+
+            // check whether it's a simple UPDATE (no SEQUENCE change) or real (RE)REQUEST,
+            // in case of time or location/description change.
+            let isUpdate = (aOriginalItem && (cal.itip.getSequence(aItem) == cal.itip.getSequence(aOriginalItem)));
+
+            if (!requestItem.organizer) {
+                let organizer = createAttendee();
+                organizer.id = requestItem.calendar.getProperty("organizerId");
+                organizer.commonName = requestItem.calendar.getProperty("organizerCN");
+                organizer.role = "REQ-PARTICIPANT";
+                organizer.participationStatus = "ACCEPTED";
+                organizer.isOrganizer = true;
+                requestItem.organizer = organizer;
+            }
+
+            // Fix up our attendees for invitations using some good defaults
+            let recipients = [];
+            let itemAtt = requestItem.getAttendees({});
+            if (!isUpdate) {
+                requestItem.removeAllAttendees();
+            }
+            for each (let attendee in itemAtt) {
+                if (!isUpdate) {
+                    attendee = attendee.clone();
+                    attendee.role = "REQ-PARTICIPANT";
+                    attendee.participationStatus = "NEEDS-ACTION";
+                    attendee.rsvp = "TRUE";
+                    requestItem.addAttendee(attendee);
+                }
+                recipients.push(attendee);
+            }
+
+            if (recipients.length > 0) {
+                sendMessage(requestItem, "REQUEST", recipients, autoResponse);
+            }
+        }
+
+        // Cancel the event for all canceled attendees
+        if (canceledAttendees.length > 0) {
+            let cancelItem = aOriginalItem.clone();
+            cancelItem.removeAllAttendees();
+            for each (let att in canceledAttendees) {
+                cancelItem.addAttendee(att);
+            }
+            sendMessage(cancelItem, "CANCEL", canceledAttendees, autoResponse);
+        }
+    },
+
+    /**
+     * Bumps the SEQUENCE in case of a major change; XXX todo may need more fine-tuning.
+     */
+    prepareSequence: function cal_itip_prepareSequence(newItem, oldItem) {
+        if (cal.isInvitation(newItem)) {
+            return newItem; // invitation copies don't bump the SEQUENCE
+        }
+
+        if (newItem.recurrenceId && !oldItem.recurrenceId && oldItem.recurrenceInfo) {
+            // XXX todo: there's still the bug that modifyItem is called with mixed occurrence/parent,
+            //           find original occurrence
+            oldItem = oldItem.recurrenceInfo.getOccurrenceFor(newItem.recurrenceId);
+            cal.ASSERT(oldItem, "unexpected!");
+            if (!oldItem) {
+                return newItem;
+            }
+        }
+
+        function hashMajorProps(aItem) {
+            let propStrings = [];
+            function addProps(item) {
+                if (item) {
+                    const majorProps = {
+                        DTSTART: true,
+                        DTEND: true,
+                        DURATION: true,
+                        DUE: true,
+                        RDATE: true,
+                        RRULE: true,
+                        EXDATE: true,
+                        STATUS: true,
+                        LOCATION: true
+                    };
+                    cal.calIterateIcalComponent(
+                        item.icalComponent,
+                        function(subComp) {
+                            for (let prop = subComp.getFirstProperty("ANY");
+                                 prop;
+                                 prop = subComp.getNextProperty("ANY")) {
+                                if (majorProps[prop.propertyName]) {
+                                    propStrings.push(item.recurrenceId + "#" + prop.icalString);
+                                }
+                            }
+                        },
+                        cal.isEvent(item) ? "VEVENT" : "VTODO");
+                }
+            }
+            addProps(aItem);
+            let rec = (aItem && aItem.recurrenceInfo);
+            if (rec) {
+                rec.getExceptionIds({}).forEach(
+                    function(rid) {
+                        addProps(rec.getExceptionFor(rid, false));
+                    });
+            }
+            propStrings.sort();
+            return propStrings.join("");
+        }
+
+        let h1 = hashMajorProps(newItem);
+        let h2 = hashMajorProps(oldItem);
+        if (h1 != h2) {
+            newItem = newItem.clone();
+            // bump SEQUENCE, it never decreases (mind undo scenario here)
+            newItem.setProperty("SEQUENCE",
+                                String(Math.max(cal.itip.getSequence(oldItem),
+                                                cal.itip.getSequence(newItem)) + 1));
+        }
+
+        return newItem;
+    }
+};
+
+/** local to this module file
+ * Sets the received info either on the passed attendee or item object.
+ *
+ * @param item either  calIAttendee or calIItemBase
+ * @param itipItemItem received iTIP item
+ */
+function setReceivedInfo(item, itipItemItem) {
+    item.setProperty((item instanceof Components.interfaces.calIAttendee) ? "RECEIVED-SEQUENCE"
+                                                                          : "X-MOZ-RECEIVED-SEQUENCE",
+                     String(cal.itip.getSequence(itipItemItem)));
+    let dtstamp = cal.itip.getStamp(itipItemItem);
+    if (dtstamp) {
+        item.setProperty((item instanceof Components.interfaces.calIAttendee) ? "RECEIVED-DTSTAMP"
+                                                                              : "X-MOZ-RECEIVED-DTSTAMP",
+                         dtstamp.getInTimezone(cal.UTC()).icalString);
+    }
+}
+
+/** local to this module file
+ * Creates an organizer calIAttendee object based on the calendar's configured organizer id.
+ *
+ * @return calIAttendee object
+ */
+function createOrganizer(aCalendar) {
+    let orgId = aCalendar.getProperty("organizerId");
+    if (!orgId) {
+        return null;
+    }
+    let organizer = cal.createAttendee();
+    organizer.id = orgId;
+    organizer.commonName = aCalendar.getProperty("organizerCN");
+    organizer.role = "REQ-PARTICIPANT";
+    organizer.participationStatus = "ACCEPTED";
+    organizer.isOrganizer = true;
+    return organizer;
+}
+
+/** local to this module file
+ * Sends an iTIP message using the passed item's calendar transport.
+ *
+ * @param aItem iTIP item to be sent
+ * @param aMethod iTIP method
+ * @param aRecipientsList an array of calIAttendee objects the message should be sent to
+ * @param autoResponse an inout object whether the transport should ask before sending
+ */
+function sendMessage(aItem, aMethod, aRecipientsList, autoResponse) {
+    if (aRecipientsList.length == 0) {
+        return;
+    }
+    if (cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
+        aItem.calendar.canNotify(aMethod, aItem)) {
+        return; // provider will handle that
+    }
+
+    let aTransport = aItem.calendar.getProperty("itip.transport");
+    if (!aTransport) { // can only send if there's a transport for the calendar
+        return;
+    }
+    aTransport = aTransport.QueryInterface(Components.interfaces.calIItipTransport);
+
+    let itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
+                             .createInstance(Components.interfaces.calIItipItem);
+    itipItem.init(cal.getSerializedItem(aItem));
+    itipItem.responseMethod = aMethod;
+    itipItem.targetCalendar = aItem.calendar;
+    itipItem.autoResponse = ((autoResponse && autoResponse.value) ? Components.interfaces.calIItipItem.AUTO
+                                                                  : Components.interfaces.calIItipItem.USER);
+    if (autoResponse) {
+        autoResponse.value = true; // auto every following
+    }
+    // XXX I don't know whether the below are used at all, since we don't use the itip processor
+    itipItem.isSend = true;
+
+    aTransport.sendItems(aRecipientsList.length, aRecipientsList, itipItem);
+}
+
+/** local to this module file
+ * An operation listener that is used on calendar operations which checks and sends further iTIP
+ * messages based on the calendar action.
+ *
+ * @param opListener operation listener to forward
+ * @param oldItem the previous item before modification (if any) 
+ */
+function ItipOpListener(opListener, oldItem) {
+    this.mOpListener = opListener;
+    this.mOldItem = oldItem;
+}
+ItipOpListener.prototype = {
+    onOperationComplete: function ItipOpListener_onOperationComplete(aCalendar,
+                                                                     aStatus,
+                                                                     aOperationType,
+                                                                     aId,
+                                                                     aDetail) {
+        cal.ASSERT(Components.isSuccessCode(aStatus), "error on iTIP processing");
+        if (Components.isSuccessCode(aStatus)) {
+            cal.itip.checkAndSend(aOperationType, aDetail, this.mOldItem);
+        }
+        if (this.mOpListener) {
+            this.mOpListener.onOperationComplete(aCalendar,
+                                                 aStatus,
+                                                 aOperationType,
+                                                 aId,
+                                                 aDetail);
+        }
+    },
+    onGetResult: function ItipOpListener_onGetResult(aCalendar,
+                                                     aStatus,
+                                                     aItemType,
+                                                     aDetail,
+                                                     aCount,
+                                                     aItems) {
+    }
+};
+
+/** local to this module file
+ * An operation listener triggered by cal.itip.processItipItem() for lookup of the sent iTIP item's UID.
+ *
+ * @param itipItem sent iTIP item
+ * @param optionsFunc options func, see cal.itip.processItipItem()
+ */
+function ItipFindItemListener(itipItem, optionsFunc) {
+    this.mItipItem = itipItem;
+    this.mOptionsFunc = optionsFunc;
+    this.mFoundItems = [];
+}
+ItipFindItemListener.prototype = {
+    mItipItem: null,
+    mOptionsFunc: null,
+    mFoundItems: null,
+
+    onOperationComplete: function ItipFindItemListener_onOperationComplete(aCalendar,
+                                                                           aStatus,
+                                                                           aOperationType,
+                                                                           aId,
+                                                                           aDetail) {
+        let rc = Components.results.NS_OK;
+        const method = this.mItipItem.receivedMethod.toUpperCase();
+        let actionMethod = method;
+        let operations = [];
+
+        if (this.mFoundItems.length > 0) {
+            cal.LOG("iTIP on " + method + ": found items.");
+            switch (method) {
+                // XXX todo: there's still a potential flaw, if multiple PUBLISH/REPLY/REQUEST on
+                //           occurrences happen at once; those lead to multiple
+                //           occurrence modifications. Since those modifications happen
+                //           implicitly on the parent (ics/memory/storage calls modifyException),
+                //           the generation check will fail. We should really consider to allow
+                //           deletion/modification/addition of occurrences directly on the providers,
+                //           which would ease client code a lot.
+                case "REFRESH":
+                case "PUBLISH":
+                case "REQUEST":
+                case "REPLY":
+                    for each (let itipItemItem in this.mItipItem.getItemList({})) {
+                        for each (let item in this.mFoundItems) {
+                            let rid = itipItemItem.recurrenceId; //  XXX todo support multiple
+                            if (rid) { // actually a REPLY to single occurrence(s)
+                                if (item.recurrenceInfo) {
+                                    item = item.recurrenceInfo.getOccurrenceFor(rid);
+                                    if (!item) {
+                                        continue;
+                                    }
+                                } else if (item.recurrenceId && (item.recurrenceId.compare(rid) != 0)) {
+                                    // filter out non-parentless occurrences (future)
+                                    continue;
+                                }
+                                if (!itipItemItem.parentItem) { // install parent, if known from previous
+                                                                // REQUEST/PUBLISH messages
+                                    itipItemItem.parentItem = newItem.parentItem;
+                                }
+                            }
+                            switch (method) {
+                                case "REFRESH": { // xxx todo test
+                                    let attendees = itipItemItem.getAttendees({});
+                                    cal.ASSERT(attendees.length == 1, "invalid number of attendees in REFRESH!");
+                                    if (attendees.length > 0) {
+                                        let action = function(opListener) {
+                                            if (!item.organizer) {
+                                                let org = createOrganizer(item.calendar);
+                                                if (org) {
+                                                    item = item.clone();
+                                                    item.organizer = org;
+                                                }
+                                            }
+                                            sendMessage(item, "REQUEST", attendees, true /* don't ask */);
+                                        };
+                                        operations.push(action);
+                                    }
+                                    break;
+                                }
+                                case "PUBLISH":
+                                    cal.ASSERT(itipItemItem.getAttendees({}).length == 0,
+                                               "invalid number of attendees in PUBLISH!");
+                                    if (item.calendar.getProperty("itip.disableRevisionChecks") ||
+                                        cal.itip.compare(itipItemItem, item) > 0) {
+                                        let newItem = itipItemItem.clone(); // xxx todo, this is dirty
+                                        setReceivedInfo(newItem, itipItemItem);
+                                        newItem.calendar = item.calendar;
+                                        newItem.generation = item.generation;
+                                        newItem.alarmOffset = item.alarmOffset;
+                                        newItem.alarmRelated = item.alarmRelated;
+                                        newItem.alarmLastAck = item.alarmLastAck;
+                                        let action = function(opListener) {
+                                            return newItem.calendar.modifyItem(newItem, item, opListener);
+                                        };
+                                        actionMethod = method + ":UPDATE";
+                                        operations.push(action);
+                                    }
+                                    break;
+                                case "REQUEST":
+                                    if (item.calendar.getProperty("itip.disableRevisionChecks") ||
+                                        cal.itip.compare(itipItemItem, item) > 0) {
+                                        let newItem = itipItemItem.clone(); // xxx todo, this is dirty
+                                        setReceivedInfo(newItem, itipItemItem);
+                                        newItem.calendar = item.calendar;
+                                        newItem.generation = item.generation;
+                                        newItem.alarmOffset = item.alarmOffset;
+                                        newItem.alarmRelated = item.alarmRelated;
+                                        newItem.alarmLastAck = item.alarmLastAck;
+                                        let att = cal.getInvitedAttendee(newItem);
+                                        if (!att) { // fall back to using configured organizer
+                                            att = createOrganizer(newItem.calendar);
+                                            if (att) {
+                                                att.isOrganizer = false;
+                                                newItem.addAttendee(att);
+                                            }
+                                        }
+                                        if (att) {
+                                            let action = function(opListener, partStat) {
+                                                if (partStat) {
+                                                    att.participationStatus = partStat;
+                                                }
+                                                return newItem.calendar.modifyItem(
+                                                    newItem, item, new ItipOpListener(opListener, item));
+                                            };
+                                            let isMinorUpdate = (cal.itip.getSequence(newItem) ==
+                                                                 cal.itip.getSequence(item));
+                                            actionMethod = (isMinorUpdate ? method + ":UPDATE-MINOR"
+                                                                          : method + ":UPDATE");
+                                            operations.push(action);
+                                        }
+                                    }
+                                    break;
+                                case "REPLY": {
+                                    let attendees = itipItemItem.getAttendees({});
+                                    cal.ASSERT(attendees.length == 1, "invalid number of attendees in REPLY!");
+                                    if (attendees.length > 0 &&
+                                        (item.calendar.getProperty("itip.disableRevisionChecks") ||
+                                         (cal.itip.compare(itipItemItem, item.getAttendeeById(attendees[0].id)) > 0))) {
+                                        // accepts REPLYs from previously uninvited attendees:
+                                        let newItem = item.clone();
+                                        let att = (item.getAttendeeById(attendees[0].id) || attendees[0]);
+                                        newItem.removeAttendee(att);
+                                        att = att.clone();
+                                        setReceivedInfo(att, itipItemItem);
+                                        att.participationStatus = attendees[0].participationStatus;
+                                        newItem.addAttendee(att);
+                                        let action = function(opListener) {
+                                            return newItem.calendar.modifyItem(
+                                                newItem, item,
+                                                newItem.calendar.getProperty("itip.notify-replies")
+                                                ? new ItipOpListener(opListener, item)
+                                                : opListener);
+                                        };
+                                        operations.push(action);
+                                    }
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    break;
+                case "CANCEL": {
+                    let modifiedItems = {};
+                    for each (let itipItemItem in this.mItipItem.getItemList({})) {
+                        for each (let item in this.mFoundItems) {
+                            let rid = itipItemItem.recurrenceId; //  XXX todo support multiple
+                            if (rid) { // actually a CANCEL of occurrence(s)
+                                if (item.recurrenceInfo) {
+                                    // collect all occurrence deletions into a single parent modification:
+                                    let newItem = modifiedItems[item.id];
+                                    if (!newItem) {
+                                        newItem = item.clone();
+                                        modifiedItems[item.id] = newItem;
+                                        operations.push(
+                                            function(opListener) {
+                                                return newItem.calendar.modifyItem(newItem, item, opListener);
+                                            });
+                                    }
+                                    newItem.recurrenceInfo.removeOccurrenceAt(rid);
+                                } else if (item.recurrenceId && (item.recurrenceId.compare(rid) == 0)) {
+                                    // parentless occurrence to be deleted (future)
+                                    operations.push(
+                                        function(opListener) {
+                                            return item.calendar.deleteItem(item, opListener);
+                                        });
+                                }
+                            } else {
+                                operations.push(
+                                    function(opListener) {
+                                        return item.calendar.deleteItem(item, opListener);
+                                    });
+                            }
+                        }
+                    }
+                    break;
+                }
+                default:
+                    rc = Components.results.NS_ERROR_NOT_IMPLEMENTED;
+                    break;
+            }
+
+        } else { // not found:
+            cal.LOG("iTIP on " + method + ": no existing items.");
+
+            for each (let itipItemItem in this.mItipItem.getItemList({})) {
+                switch (method) {
+                    case "REQUEST":
+                    case "PUBLISH": {
+                        let this_ = this;
+                        let action = function(opListener, partStat) {
+                            let newItem = itipItemItem.clone();
+                            setReceivedInfo(newItem, itipItemItem);
+                            newItem.calendar = this_.mItipItem.targetCalendar;
+                            if (partStat) {
+                                if (partStat != "DECLINED") {
+                                    cal.setDefaultAlarmValues(newItem);
+                                }
+                                let att = cal.getInvitedAttendee(newItem);
+                                if (!att) { // fall back to using configured organizer
+                                    att = createOrganizer(newItem.calendar);
+                                    if (att) {
+                                        att.isOrganizer = false;
+                                        newItem.addAttendee(att);
+                                    }
+                                }
+                                if (att) {
+                                    att.participationStatus = partStat;
+                                } else {
+                                    cal.ASSERT(att, "no attendee to reply REQUEST!");
+                                    return;
+                                }
+                            } else {
+                                cal.ASSERT(itipItemItem.getAttendees({}).length == 0,
+                                           "invalid number of attendees in PUBLISH!");
+                            }
+                            return newItem.calendar.addItem(newItem,
+                                                            (method == "REQUEST")
+                                                            ? new ItipOpListener(opListener, null)
+                                                            : opListener);
+                        };
+                        operations.push(action);
+                        break;
+                    }
+                    case "CANCEL": // has already been processed
+                        break;
+                    default:
+                        rc = Components.results.NS_ERROR_NOT_IMPLEMENTED;
+                        break;
+                }
+            }
+        }
+
+        cal.LOG("iTIP operations: " + operations.length);
+        let actionFunc = null;
+        if (operations.length > 0) {
+            actionFunc = function execOperations(opListener, partStat) {
+                for each (let op in operations) {
+                    try {
+                        op(opListener, partStat);
+                    } catch (exc) {
+                        cal.ERROR(exc);
+                    }
+                }
+            };
+            actionFunc.method = actionMethod;
+        }
+
+        this.mOptionsFunc(this.mItipItem, rc, actionFunc);
+    },
+
+    onGetResult: function ItipFindItemListener_onGetResult(aCalendar,
+                                                           aStatus,
+                                                           aItemType,
+                                                           aDetail,
+                                                           aCount,
+                                                           aItems) {
+        if (Components.isSuccessCode(aStatus)) {
+            this.mFoundItems = this.mFoundItems.concat(aItems);
+        }
+    }
+};
rename from calendar/base/src/calUtils.jsm
rename to calendar/base/modules/calUtils.jsm
--- a/calendar/base/src/calUtils.jsm
+++ b/calendar/base/modules/calUtils.jsm
@@ -32,40 +32,41 @@
  * 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"];
+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),
+    getObserverService: generateServiceAccessor("@mozilla.org/observer-service;1",
+                                                Components.interfaces.nsIObserverService),
 
     /**
      * Loads an array of calendar scripts into the passed scope.
      *
      * @param scriptNames an array of calendar script names
      * @param scope       scope to load into
      * @param baseDir     base dir; defaults to calendar-js/
      */
     loadScripts: function cal_loadScripts(scriptNames, scope, baseDir) {
         let scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                                      .createInstance(Components.interfaces.mozIJSSubScriptLoader);
         let ioService = cal.getIOService();
 
         if (!baseDir) {
-            baseDir = __LOCATION__.parent.parent;
-            baseDir = baseDir.clone();
+            baseDir = __LOCATION__.parent.parent.clone();
             baseDir.append("calendar-js");
         }
 
         for each (let script in scriptNames) {
             let scriptFile = baseDir.clone();
             scriptFile.append(script);
             try {
                 scriptLoader.loadSubScript(ioService.newFileURI(scriptFile).spec, scope);
@@ -77,16 +78,74 @@ let cal = {
 
     /**
      * Checks whether a timezone lacks a definition.
      */
     isPhantomTimezone: function cal_isPhantomTimezone(tz) {
         return (!tz.icalComponent && !tz.isUTC && !tz.isFloating);
     },
 
+    /**
+     * Iterates an array of items, i.e. the passed item including all
+     * overridden instances of a recurring series.
+     *
+     * @param items array of items
+     */
+    itemIterator: function cal_itemIterator(items) {
+        return {
+            __iterator__: function itemIterator_() {
+                for each (let item in items) {
+                    yield item;
+                    let rec = item.recurrenceInfo;
+                    if (rec) {
+                        for each (let exid in rec.getExceptionIds({})) {
+                            yield rec.getExceptionFor(exid, false);
+                        }
+                    }
+                }
+            }
+        };
+    },
+
+    /**
+     * Shortcut function to serialize an item (including all overridden items).
+     */
+    getSerializedItem: function cal_getSerializedItem(aItem) {
+        let serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"]
+                                   .createInstance(Components.interfaces.calIIcsSerializer);
+        serializer.addItems([aItem], 1);
+        return serializer.serializeToString();
+    },
+
+    /**
+     * Shortcut function to check whether an item is an invitation copy.
+     */
+    isInvitation: function cal_isInvitation(aItem) {
+        let isInvitation = false;
+        let calendar = aItem.calendar;
+        if (cal.calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
+            isInvitation = calendar.isInvitation(aItem);
+        }
+        return isInvitation;
+    },
+
+    /**
+     * Shortcut function to get the invited attendee of an item.
+     */
+    getInvitedAttendee: function cal_getInvitedAttendee(aItem, aCalendar) {
+        if (!aCalendar) {
+            aCalendar = aItem.calendar;
+        }
+        let invitedAttendee = null;
+        if (cal.calInstanceOf(aCalendar, Components.interfaces.calISchedulingSupport)) {
+            invitedAttendee = aCalendar.getInvitedAttendee(aItem);
+        }
+        return invitedAttendee;
+    },
+
     // The below functions will move to some different place once the
     // unifinder tress are consolidated.
 
     compareNativeTime: function cal_compareNativeTime(a, b) {
       return (a < b ? -1 :
               a > b ?  1 : 0);
     },
 
@@ -240,17 +299,16 @@ let cal = {
      * Modifies aStringArray, returning it sorted.
      */
     sortArrayByLocaleCollator: function cal_sortArrayByLocaleCollator(aStringArray) {
         var localeCollator = cal.createLocaleCollator();
         function compare(a, b) { return localeCollator.compareString(0, a, b); }
         aStringArray.sort(compare);
         return aStringArray;
     }
-
 };
 
 // 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);
--- a/calendar/base/public/Makefile.in
+++ b/calendar/base/public/Makefile.in
@@ -66,17 +66,16 @@ XPIDLSRCS = calIAlarm.idl \
             calIEvent.idl \
             calIFreeBusyProvider.idl \
             calIIcsParser.idl \
             calIIcsSerializer.idl \
             calIICSService.idl \
             calIImportExport.idl \
             calIItemBase.idl \
             calIItipItem.idl \
-            calIItipProcessor.idl \
             calIItipTransport.idl \
             calIOperation.idl \
             calIPeriod.idl \
             calIPrintFormatter.idl \
             calIRecurrenceInfo.idl \
             calIRecurrenceDate.idl \
             calIRecurrenceDateSet.idl \
             calIRecurrenceItem.idl \
--- a/calendar/base/public/calIItemBase.idl
+++ b/calendar/base/public/calIItemBase.idl
@@ -109,19 +109,16 @@ interface calIItemBase : nsISupports
   readonly attribute calIDateTime creationDate;
 
   // last time any attribute was modified on this item, in UTC
   readonly attribute calIDateTime lastModifiedTime;
 
   // last time a "significant change" was made to this item
   readonly attribute calIDateTime stampTime;
 
-  // indicate such a "significant change"
-  void updateStampTime();
-
   // the calICalendar to which this event belongs
   attribute calICalendar calendar;
 
   // the ID of this event
   attribute AUTF8String id;
 
   // event title
   attribute AUTF8String title;
--- a/calendar/base/public/calIItipItem.idl
+++ b/calendar/base/public/calIItipItem.idl
@@ -129,37 +129,14 @@ interface calIItipItem : nsISupports
      * Get the list of items that are encapsulated in this calIItipItem
      * @returns An array of calIItemBase items that are inside this
      *          calIItipItem
      */
     void getItemList(out unsigned long itemCount,
                      [retval, array, size_is(itemCount)] out calIItemBase items);
 
     /**
-     * Get the first item from the iTIP message
-     * Bug XXX 351761: Need to find a way to make this use an nsISimpleEnumerator
-     * @return calIItemBase
-     
-    calIItemBase getFirstItem();
-*/
-    /**
-     * Get next item from the iTIP message. If there is no next item then it
-     * returns NULL
-     * @return calIItemBase
-     
-    calIItemBase getNextItem();
-*/
-    /**
-     * Modifies a calIItemBase that is in the component list. Internally, the
-     * interface will update the proper component. It does this via the
-     * UID of the component by calling hasSameIds().
-     * @param in parameter - item to modify
-     * @return returns the new calIItemBase object for convienence
-     */
-    calIItemBase modifyItem(in calIItemBase item);
-
-    /**
      * Modifies the state of the given attendee in the item's ics
      * @param attendeeId - AString containing attendee address
      * @param status - AString containing the new attendee status
      */
     void setAttendeeStatus(in AString attendeeId, in AString status);
 };
deleted file mode 100644
--- a/calendar/base/public/calIItipProcessor.idl
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- Mode: idl; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** 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 Simdesk Technologies code.
- *
- * The Initial Developer of the Original Code is Simdesk Technologies Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Clint Talbert <ctalbert.moz@gmail.com>
- *   Eva Or <evaor1012@yahoo.ca>
- *   Matthew Willis <lilmatt@mozilla.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 ***** */
-
-#include "nsISupports.idl"
-
-interface calIItipItem;
-interface calIOperationListener;
-
-[scriptable, uuid(9787876b-0780-4464-8282-b7f86fb221e8)]
-interface calIItipProcessor : nsISupports
-{
-    /**
-     * Processes the given calItipItem based on the settings inside it.
-     * @param in calIItipItem itipItem  A calItipItem to process.
-     * @param in calIIOperationListener An operation Listener to report status
-     * @return PRBool                 Whether processing succeeded or not.
-     */
-    boolean processItipItem(in calIItipItem itipItem, in calIOperationListener aListener);
-};
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -89,35 +89,30 @@ EXTRA_SCRIPTS = \
     calCachedCalendar.js \
     calDateTimeFormatter.js \
     calEvent.js \
     calFilter.js \
     calIcsParser.js \
     calIcsSerializer.js \
     calItemBase.js \
     calItipItem.js \
-    calItipProcessor.js \
     calProtocolHandler.js \
     calRecurrenceInfo.js \
     calRelation.js \
     calTodo.js \
     calUtils.js \
     calAuthUtils.js \
     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)/calendar-js; then $(NSINSTALL) -D $(FINAL_TARGET)/calendar-js; fi
 	$(INSTALL) $^ $(FINAL_TARGET)/calendar-js
 
 # The install target must use SYSINSTALL, which is NSINSTALL in copy mode.
 install:: $(EXTRA_SCRIPTS)
 	$(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/calendar-js
--- a/calendar/base/src/calEvent.js
+++ b/calendar/base/src/calEvent.js
@@ -1,9 +1,8 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* ***** 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/
  *
@@ -46,17 +45,16 @@
 // constructor
 //
 function calEvent() {
     this.initItemBase();
 
     this.eventPromotedProps = {
         "DTSTART": true,
         "DTEND": true,
-        "DTSTAMP": true,
         __proto__: this.itemBasePromotedProps
     }
 }
 
 var calEventClassInfo = {
     getInterfaces: function (count) {
         var ifaces = [
             Components.interfaces.nsISupports,
@@ -169,16 +167,17 @@ calEvent.prototype = {
     set icalComponent(event) {
         this.modify();
         if (event.componentType != "VEVENT") {
             event = event.getFirstSubcomponent("VEVENT");
             if (!event)
                 throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
+        // xxx todo: Bug 463195 make sure object is properly reset
         this.setItemBaseFromICS(event);
         this.mapPropsFromICS(event, this.icsEventPropMap);
 
         this.importUnpromotedProperties(event, this.eventPromotedProps);
 
         // Importing didn't really change anything
         this.mDirty = false;
     },
--- a/calendar/base/src/calIcsParser.js
+++ b/calendar/base/src/calIcsParser.js
@@ -1,10 +1,9 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
+/* ***** 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,
@@ -52,30 +51,30 @@ function QueryInterface(aIID) {
         throw Components.results.NS_ERROR_NO_INTERFACE;
     }
 
     return this;
 };
 
 calIcsParser.prototype.parseString =
 function ip_parseString(aICSString, aTzProvider) {
-    var rootComp = getIcsService().parseICS(aICSString, aTzProvider);
-    var calComp;
+    let rootComp = getIcsService().parseICS(aICSString, aTzProvider);
+    let calComp;
     // libical returns the vcalendar component if there is just one vcalendar.
     // If there are multiple vcalendars, it returns an xroot component, with
     // those vcalendar children. We need to handle both.
     if (rootComp.componentType == 'VCALENDAR') {
         calComp = rootComp;
     } else {
         calComp = rootComp.getFirstSubcomponent('VCALENDAR');
     }
 
-    var unexpandedItems = [];
-    var uid2parent = {};
-    var excItems = [];
+    let uid2parent = {};
+    let excItems = [];
+    let fakedParents = {};
 
     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.
@@ -90,34 +89,34 @@ function ip_parseString(aICSString, aTzP
                 tzErrors[hid] = true;
             }
         }
     }
 
     while (calComp) {
 
         // Get unknown properties
-        var prop = calComp.getFirstProperty("ANY");
+        let prop = calComp.getFirstProperty("ANY");
         while (prop) {
             if (prop.propertyName != "VERSION" &&
                 prop.propertyName != "PRODID") {
                 this.mProperties.push(prop);
             }
             prop = calComp.getNextProperty("ANY");
         }
 
-        var prodId = calComp.getFirstProperty("PRODID");
-        var isFromOldSunbird;
+        let prodId = calComp.getFirstProperty("PRODID");
+        let isFromOldSunbird;
         if (prodId) {
             isFromOldSunbird = prodId.value == "-//Mozilla.org/NONSGML Mozilla Calendar V1.0//EN";
         }
 
-        var subComp = calComp.getFirstSubcomponent("ANY");
+        let subComp = calComp.getFirstSubcomponent("ANY");
         while (subComp) {
-            var item = null;
+            let item = null;
             switch (subComp.componentType) {
             case "VEVENT":
                 item = cal.createEvent();
                 item.icalComponent = subComp;
                 checkTimezone(item, item.startDate);
                 checkTimezone(item, item.endDate);
                 break;
             case "VTODO":
@@ -139,47 +138,59 @@ function ip_parseString(aICSString, aTzP
             if (item) {
                 // 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;
+                let rid = item.recurrenceId;
                 if (!rid) {
-                    unexpandedItems.push(item);
+                    this.mItems.push(item);
                     if (item.recurrenceInfo) {
                         uid2parent[item.id] = item;
                     }
                 } else {
                     // force no recurrence info:
                     item.recurrenceInfo = null;
                     excItems.push(item);
                 }
             }
             subComp = calComp.getNextSubcomponent("ANY");
         }
         calComp = rootComp.getNextSubcomponent("VCALENDAR");
     }
 
     // tag "exceptions", i.e. items with rid:
-    for each (var item in excItems) {
-        var parent = uid2parent[item.id];
-        if (parent) {
-            parent.recurrenceInfo.modifyException(item, true);
-        } else { // a parentless one
+    for each (let item in excItems) {
+        let parent = uid2parent[item.id];
+
+        if (!parent) { // a parentless one, fake a master and override it's occurrence
+            parent = isEvent(item) ? createEvent() : createTodo();
+            parent.id = item.id;
+            parent.setProperty("DTSTART", item.recurrenceId);
+            parent.setProperty("X-MOZ-FAKED-MASTER", "1"); // this tag might be useful in the future
+            parent.recurrenceInfo = cal.createRecurrenceInfo(parent);
+            fakedParents[item.id] = true;
+            uid2parent[item.id] = parent;
+            this.mItems.push(parent);
+        }
+        if (item.id in fakedParents) { 
+            let rdate = Components.classes["@mozilla.org/calendar/recurrence-date;1"]
+                                  .createInstance(Components.interfaces.calIRecurrenceDate);
+            rdate.date = item.recurrenceId;
+            parent.recurrenceInfo.appendRecurrenceItem(rdate);
+            // we'll keep the parentless-API until we switch over using itip-process for import (e.g. in dnd code)
             this.mParentlessItems.push(item);
         }
+
+        parent.recurrenceInfo.modifyException(item, true);
     }
     
-    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");
--- a/calendar/base/src/calIcsSerializer.js
+++ b/calendar/base/src/calIcsSerializer.js
@@ -30,17 +30,17 @@
  * 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 ***** */
 
-// Import
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 function calIcsSerializer() {
     this.wrappedJSObject = this;
     this.mItems = [];
     this.mProperties = [];
     this.mComponents = [];
 }
 
@@ -97,30 +97,24 @@ function is_serializeToStream(aStream) {
     convStream.close();
 };
 
 calIcsSerializer.prototype.getIcalComponent =
 function is_getIcalComponent() {
     var calComp = getIcsService().createIcalComponent("VCALENDAR");
     calSetProdidVersion(calComp);
 
+    // xxx todo: think about that the below code doesn't clone the properties/components,
+    //           thus ownership is moved to returned VCALENDAR...
+
     for each (var prop in this.mProperties) {
         calComp.addProperty(prop);
     }
     for each (var comp in this.mComponents) {
         calComp.addSubcomponent(comp);
     }
 
-    for each (var item in this.mItems) {
+    for each (let item in cal.itemIterator(this.mItems)) {
         calComp.addSubcomponent(item.icalComponent);
-        var rec = item.recurrenceInfo;
-        if (rec != null) {
-            var exceptions = rec.getExceptionIds({});
-            for each (var exid in exceptions) {
-                var ex = rec.getExceptionFor(exid, false);
-                if (ex != null) {
-                    calComp.addSubcomponent(ex.icalComponent);
-                }
-            }
-        }
     }
+
     return calComp;
 }
--- a/calendar/base/src/calItemBase.js
+++ b/calendar/base/src/calItemBase.js
@@ -1,9 +1,8 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* ***** 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/
  *
@@ -132,25 +131,27 @@ calItemBase.prototype = {
     mDirty: false,
     modify: function() {
         if (this.mImmutable)
             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
         this.mDirty = true;
     },
 
     ensureNotDirty: function() {
-        if (!this.mDirty)
+        if (!this.mDirty) {
             return;
-
+        }
         if (this.mImmutable) {
             dump ("### Something tried to undirty a dirty immutable event!\n");
             throw Components.results.NS_ERROR_OBJECT_IS_IMMUTABLE;
         }
 
-        this.setProperty("LAST-MODIFIED", jsDateToDateTime(new Date()));
+        var now = jsDateToDateTime(new Date());
+        this.setProperty("LAST-MODIFIED", now);
+        this.setProperty("DTSTAMP", now.clone());
         this.mDirty = false;
     },
 
     makeItemBaseImmutable: function() {
         if (this.mImmutable) {
             return;
         }
 
@@ -197,19 +198,17 @@ calItemBase.prototype = {
     // initialize this class's members
     initItemBase: function () {
         this.wrappedJSObject = this;
         var now = jsDateToDateTime(new Date());
 
         this.mProperties = new calPropertyBag();
         this.mPropertyParams = {};
 
-        this.setProperty("CREATED", now.clone());
-        this.setProperty("LAST-MODIFIED", now.clone());
-        this.setProperty("DTSTAMP", now);
+        this.setProperty("CREATED", now);
 
         this.mAttendees = null;
 
         this.mRecurrenceInfo = null;
 
         this.mAttachments = null;
 
         this.mRelations = null;
@@ -217,18 +216,16 @@ calItemBase.prototype = {
 
     clone: function () {
         return this.cloneShallow(this.mParentItem);
     },
 
     // for subclasses to use; copies the ItemBase's values
     // into m. aNewParent is optional
     cloneItemBaseInto: function (m, aNewParent) {
-        this.ensureNotDirty();
-
         m.mImmutable = false;
         m.mIsProxy = this.mIsProxy;
         m.mParentItem = (calTryWrappedJSObject(aNewParent) || this.mParentItem);
         m.mHashId = this.mHashId;
         m.mCalendar = this.mCalendar;
         if (this.mRecurrenceInfo) {
             m.mRecurrenceInfo = calTryWrappedJSObject(this.mRecurrenceInfo.clone());
             m.mRecurrenceInfo.item = m;
@@ -264,18 +261,16 @@ calItemBase.prototype = {
                 var newBucket = {};
                 for (var param in propBucket) {
                     newBucket[param] = propBucket[param];
                 }
                 m.mPropertyParams[name] = newBucket;
             }
         }
 
-        m.mDirty = false;
-
         if (this.mAttachments) {
             m.mAttachments = this.mAttachments.concat([]);
         }
 
         if (this.mRelations) {
             m.mRelations = this.mRelations.concat([]);
         }
 
@@ -291,16 +286,18 @@ calItemBase.prototype = {
         }
         if (this.alarmLastAck) {
             m.alarmLastAck = this.alarmLastAck.clone();
         } else {
             m.alarmLastAck = null;
         }
         m.alarmRelated = this.alarmRelated;
 
+        m.mDirty = this.mDirty;
+
         return m;
     },
 
     get alarmOffset() {
         if (this.mIsProxy && (this.mAlarmOffset === undefined)) {
             return this.parentItem.alarmOffset;
         } else {
             return this.mAlarmOffset;
@@ -326,29 +323,18 @@ calItemBase.prototype = {
     },
 
     get lastModifiedTime() {
         this.ensureNotDirty();
         return this.getProperty("LAST-MODIFIED");
     },
 
     get stampTime() {
-        var prop = this.getProperty("DTSTAMP");
-        if (prop && prop.isValid)
-            return prop;
-        return this.getProperty("LAST-MODIFIED");
-    },
-
-    updateStampTime: function() {
-        // can't update the stamp time on an immutable event
-        if (this.mImmutable)
-            return;
-
-        this.modify();
-        this.setProperty("DTSTAMP", jsDateToDateTime(new Date()));
+        this.ensureNotDirty();
+        return this.getProperty("DTSTAMP");
     },
 
     get propertyEnumerator() {
         if (this.mIsProxy) {
             ASSERT(this.parentItem != this);
             return { // nsISimpleEnumerator:
                 mProxyEnum: this.mProperties.enumerator,
                 mParentEnum: this.mParentItem.propertyEnumerator,
@@ -405,36 +391,38 @@ calItemBase.prototype = {
         return aValue;
     },
 
     hasProperty: function (aName) {
         return (this.getProperty(aName.toUpperCase()) != null);
     },
 
     setProperty: function (aName, aValue) {
-        if (aName == "LAST-MODIFIED") {
-            this.mDirty = false;
-        } else {
-            this.modify();
-        }
+        this.modify();
+        aName = aName.toUpperCase();
         if (aValue || !isNaN(parseInt(aValue, 10))) {
-            this.mProperties.setProperty(aName.toUpperCase(), aValue);
+            this.mProperties.setProperty(aName, aValue);
         } else {
             this.deleteProperty(aName);
         }
+        if (aName == "LAST-MODIFIED") {
+            // setting LAST-MODIFIED cleans/undirties the item, we use this for preserving DTSTAMP
+            this.mDirty = false;
+        }
     },
 
     deleteProperty: function (aName) {
         this.modify();
+        aName = aName.toUpperCase();
         if (this.mIsProxy) {
             // deleting a proxy's property will mark the bag's item as null, so we could
             // distinguish it when enumerating/getting properties from the undefined ones.
-            this.mProperties.setProperty(aName.toUpperCase(), null);
+            this.mProperties.setProperty(aName, null);
         } else {
-            this.mProperties.deleteProperty(aName.toUpperCase());
+            this.mProperties.deleteProperty(aName);
         }
     },
 
     getPropertyParameter: function getPP(aPropName, aParamName) {
         return this.mPropertyParams[aPropName][aParamName];
     },
 
     getAttendees: function (countObj) {
@@ -634,17 +622,18 @@ calItemBase.prototype = {
         "DTSTAMP": true,
         "RRULE": true,
         "EXDATE": true,
         "RDATE": true,
         "ATTENDEE": true,
         "ATTACH": true,
         "CATEGORIES": true,
         "ORGANIZER": true,
-        "RECURRENCE-ID": true
+        "RECURRENCE-ID": true,
+        "X-MOZ-LASTACK": true
     },
 
     icsBasePropMap: [
     { cal: "CREATED", ics: "createdTime" },
     { cal: "LAST-MODIFIED", ics: "lastModified" },
     { cal: "DTSTAMP", ics: "stampTime" },
     { cal: "UID", ics: "uid" },
     { cal: "SUMMARY", ics: "summary" },
@@ -818,18 +807,16 @@ calItemBase.prototype = {
     set generation(aValue) {
         this.modify();
         this.mGeneration = aValue;
         this.setProperty("X-MOZ-GENERATION", String(aValue));
         return aValue;
     },
 
     fillIcalComponentFromBase: function (icalcomp) {
-        // Make sure that the LMT and ST are updated
-        this.updateStampTime();
         this.ensureNotDirty();
 
         this.mapPropsToICS(icalcomp, this.icsBasePropMap);
 
         if (this.mOrganizer)
             icalcomp.addProperty(this.mOrganizer.icalProperty);
         var attendees = this.getAttendees({});
         if (attendees.length > 0) {
--- a/calendar/base/src/calItemModule.js
+++ b/calendar/base/src/calItemModule.js
@@ -179,21 +179,16 @@ const componentData =
      script: "calWeekInfoService.js",
      constructor: "calWeekInfoService"},
 
     {cid: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"),
      contractid: "@mozilla.org/calendar/itip-item;1",
      script: "calItipItem.js",
      constructor: "calItipItem"},
 
-    {cid: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"),
-     contractid: "@mozilla.org/calendar/itip-processor;1",
-     script: "calItipProcessor.js",
-     constructor: "calItipProcessor"},
-
     {cid: Components.ID("{1e2fc0e2-bf5f-4d60-9f1e-5e92cf517c0b}"),
      contractid: "@mozilla.org/network/protocol;1?name=webcal",
      script: "calProtocolHandler.js",
      constructor: "calProtocolHandler"},
 
     {cid: Components.ID("{b2ee6f91-b061-4527-97a1-b85361775fc1}"),
      contractid: "@mozilla.org/network/protocol;1?name=webcals",
      script: "calProtocolHandler.js",
--- a/calendar/base/src/calItipItem.js
+++ b/calendar/base/src/calItipItem.js
@@ -38,21 +38,23 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Constructor of calItipItem object
  */
 function calItipItem() {
     this.wrappedJSObject = this;
-    this.mIsInitialized = false;
     this.mCurrentItemIndex = 0;
 }
 
 calItipItem.prototype = {
+    mIsInitialized: false,
+
+    // nsIClassInfo:
     getInterfaces: function ciiGI(count) {
         var ifaces = [
             Components.interfaces.nsIClassInfo,
             Components.interfaces.nsISupports,
             Components.interfaces.calIItipItem
         ];
         count.value = ifaces.length;
         return ifaces;
@@ -63,79 +65,46 @@ calItipItem.prototype = {
     },
 
     contractID: "@mozilla.org/calendar/itip-item;1",
     classDescription: "Calendar iTIP item",
     classID: Components.ID("{f41392ab-dcad-4bad-818f-b3d1631c4d93}"),
     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
     flags: 0,
 
-    QueryInterface: function ciiQI(aIid) {
-        if (!aIid.equals(Components.interfaces.nsISupports) &&
-            !aIid.equals(Components.interfaces.calIItipItem)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-
-        return this;
+    QueryInterface: function ciiQI(aIID) {
+        return doQueryInterface(this, calItipItem.prototype, aIID, null, this);
     },
 
     mIsSend: false,
     get isSend() {
         return this.mIsSend;
     },
     set isSend(aValue) {
         return (this.mIsSend = aValue);
     },
 
-    mReceivedMethod: null,
+    mReceivedMethod: "REQUEST",
     get receivedMethod() {
         return this.mReceivedMethod;
     },
     set receivedMethod(aMethod) {
         return (this.mReceivedMethod = aMethod.toUpperCase());
     },
 
-    mResponseMethod: null,
+    mResponseMethod: "REPLY",
     get responseMethod() {
-        if (this.mIsInitialized) {
-            var method = null;
-            for each (var prop in this.mPropertiesList) {
-                if (prop.propertyName == "METHOD") {
-                    method = prop.value;
-                    break;
-                }
-            }
-            return method;
-        } else {
-            throw Components.results.NS_ERROR_NOT_INITIALIZED;
-        }
-    },
-    set responseMethod(aMethod) {
-        this.mResponseMethod = aMethod.toUpperCase();
-        // Setting this also sets the global method attribute inside the
-        // encapsulated VCALENDAR.
-        if (this.mIsInitialized) {
-            var methodExists = false;
-            for each (var prop in this.mPropertiesList) {
-                if (prop.propertyName == "METHOD") {
-                    methodExists = true;
-                    prop.value = this.mResponseMethod;
-                }
-            }
-
-            if (!methodExists) {
-                var newProp = { propertyName: "METHOD",
-                                value: this.mResponseMethod };
-                this.mPropertiesList.push(newProp);
-            }
-        } else {
+        if (!this.mIsInitialized) {
             throw Components.results.NS_ERROR_NOT_INITIALIZED;
         }
         return this.mResponseMethod;
     },
+    set responseMethod(aMethod) {
+        return (this.mResponseMethod = aMethod.toUpperCase());
+    },
 
     mAutoResponse: null,
     get autoResponse() {
         return this.mAutoResponse;
     },
     set autoResponse(aValue) {
         return (this.mAutoResponse = aValue);
     },
@@ -159,131 +128,119 @@ calItipItem.prototype = {
     mLocalStatus: null,
     get localStatus() {
         return this.mLocalStatus;
     },
     set localStatus(aValue) {
         return (this.mLocalStatus = aValue);
      },
 
-    modifyItem: function ciiMI(item) {
-        // Bug 348666: This will be used when we support delegation and COUNTER.
-        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    },
-
-    mItemList: null,
-    mPropertiesList: null,
+    mItemList: {},
 
     init: function ciiI(aIcalString) {
-        var parser = Components.classes["@mozilla.org/calendar/ics-parser;1"].
-                     createInstance(Components.interfaces.calIIcsParser);
+        let parser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
+                               .createInstance(Components.interfaces.calIIcsParser);
         parser.parseString(aIcalString, null);
-        this.mItemList = parser.getItems({});
-        this.mPropertiesList = parser.getProperties({});
+
+        // - User specific alarms as well as X-MOZ- properties are irrelevant w.r.t. iTIP messages,
+        //   should not be sent out and should not be relevant for incoming messages
+        // - faked master items
+        // so clean them out:
 
-        // User specific alarms as well as X-MOZ- properties are irrelevant w.r.t. iTIP messages,
-        // should not be sent out and should not be relevant for incoming messages,
-        // so clean them out:
-        for each (var item in this.mItemList) {
+        function cleanItem(item) {
+            // the following changes will bump LAST-MODIFIED/DTSTAMP, we want to preserve the originals:
+            let stamp = item.stampTime;
+            let lastModified = item.lastModifiedTime;
             item.alarmOffset = null;
-            var propEnum = item.propertyEnumerator;
+            item.alarmLastAck = null;
+            item.deleteProperty("RECEIVED-SEQUENCE");
+            item.deleteProperty("RECEIVED-DTSTAMP");
+            let propEnum = item.propertyEnumerator;
             while (propEnum.hasMoreElements()) {
-                var prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
-                if (prop.name.substr(0, "X-MOZ-".length) == "X-MOZ-") {
+                let prop = propEnum.getNext().QueryInterface(Components.interfaces.nsIProperty);
+                let pname = prop.name;
+                if (pname != "X-MOZ-FAKED-MASTER" && pname.substr(0, "X-MOZ-".length) == "X-MOZ-") {
                     item.deleteProperty(prop.name);
                 }
             }
+            // never publish an organizer's RECEIVED params:
+            item.getAttendees({}).forEach(
+                function(att) {
+                    att.deleteProperty("RECEIVED-SEQUENCE");
+                    att.deleteProperty("RECEIVED-DTSTAMP");
+                });
+            item.setProperty("DTSTAMP", stamp);
+            item.setProperty("LAST-MODIFIED", lastModified); // need to be last to undirty the item
+        }
+
+        this.mItemList = [];
+        for each (let item in cal.itemIterator(parser.getItems({}))) {
+            cleanItem(item);
+            // only push non-faked master items or
+            // the overridden instances of faked master items
+            // to the list:
+            if (item == item.parentItem) {
+                if (!item.hasProperty("X-MOZ-FAKED-MASTER")) {
+                    this.mItemList.push(item);
+                }
+            } else if (item.parentItem.hasProperty("X-MOZ-FAKED-MASTER")) {
+                this.mItemList.push(item);
+            }
         }
 
         // We set both methods now for safety's sake. It's the ItipProcessor's
         // responsibility to properly ascertain what the correct response
         // method is (using user feedback, prefs, etc.) for the given
         // receivedMethod.  The RFC tells us to treat items without a METHOD
         // as if they were METHOD:REQUEST.
-        var method;
-        for each (var prop in this.mPropertiesList) {
+        for each (var prop in parser.getProperties({})) {
             if (prop.propertyName == "METHOD") {
-                method = prop.value;
+                this.mReceivedMethod = prop.value;
+                this.mResponseMethod = prop.value;
                 break;
             }
         }
-        this.mReceivedMethod = method;
-        this.mResponseMethod = method;
 
         this.mIsInitialized = true;
     },
 
     clone: function ciiC() {
-        // Iterate through all the calItems in the original calItipItem to
-        // concatenate all the calItems' icalStrings.
-        var icalString = "";
-
-        var itemList = this.getItemList({ });
-        for (var i = 0; i < itemList.length; i++) {
-            icalString += itemList[i].icalString;
-        }
-
-        // Create a new calItipItem and initialize it using the icalString
-        // from above.
-        var newItem = Components.classes["@mozilla.org/calendar/itip-item;1"].
-                      createInstance(Components.interfaces.calIItipItem);
-        newItem.init(icalString);
-
-        // Copy over the exposed attributes.
-        newItem.receivedMethod = this.receivedMethod;
-        newItem.responseMethod = this.responseMethod;
-        newItem.autoResponse = this.autoResponse;
-        newItem.targetCalendar = this.targetCalendar;
-        newItem.identity = this.identity;
-        newItem.localStatus = this.localStatus;
-        newItem.isSend = this.isSend;
-
+        let newItem = new calItipItem();
+        newItem.mItemList = this.mItemList.map(function(item) { return item.clone(); });
+        newItem.mReceivedMethod = this.mReceivedMethod;
+        newItem.mResponseMethod = this.mResponseMethod;
+        newItem.mAutoResponse = this.mAutoResponse;
+        newItem.mTargetCalendar = this.mTargetCalendar;
+        newItem.mIdentity = this.mIdentity;
+        newItem.mLocalStatus = this.mLocalStatus;
+        newItem.mIsSend = this.mIsSend;
+        newItem.mIsInitialized = this.mIsInitialized;
         return newItem;
     },
 
     /**
      * This returns both the array and the number of items. An easy way to
      * call it is: var itemArray = itipItem.getItemList({ });
      */
     getItemList: function ciiGIL(itemCountRef) {
-        if (!this.mIsInitialized || (this.mItemList.length == 0)) {
+        if (!this.mIsInitialized) {
             throw Components.results.NS_ERROR_NOT_INITIALIZED;
         }
         itemCountRef.value = this.mItemList.length;
         return this.mItemList;
     },
 
-    /*getFirstItem: function ciiGFI() {
-        if (!this.mIsInitialized || (this.mItemList.length == 0)) {
-            throw Components.results.NS_ERROR_NOT_INITIALIZED;
-        }
-        this.mCurrentItemIndex = 0;
-        return this.mItemList[0];
-    },
-
-    getNextItem: function ciiGNI() {
-        if (!this.mIsInitialized || (this.mItemList.length == 0)) {
-            throw Components.results.NS_ERROR_NOT_INITIALIZED;
-        }
-        ++this.mCurrentItemIndex;
-        if (this.mCurrentItemIndex < this.mItemList.length) {
-            return this.mItemList[this.mCurrentItemIndex];
-        } else {
-            return null;
-        }
-    },*/
-
     /**
      * Note that this code forces the user to respond to all items in the same
      * way, which is a current limitation of the spec.
      */
     setAttendeeStatus: function ciiSAS(aAttendeeId, aStatus) {
         // Append "mailto:" to the attendee if it is missing it.
         aAttendeeId = aAttendeeId.toLowerCase();
-        if (!aAttendeeId.match(/mailto:/i)) {
+        if (!aAttendeeId.match(/^mailto:/i)) {
             aAttendeeId = ("mailto:" + aAttendeeId);
         }
 
         for each (var item in this.mItemList) {
             var attendee = item.getAttendeeById(aAttendeeId);
             if (attendee) {
                 // Replies should not have the RSVP property.
                 // XXX BUG 351589: workaround for updating an attendee
deleted file mode 100644
--- a/calendar/base/src/calItipProcessor.js
+++ /dev/null
@@ -1,444 +0,0 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* ***** 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 Simdesk Technologies code.
- *
- * The Initial Developer of the Original Code is Simdesk Technologies Inc.
- * Portions created by the Initial Developer are Copyright (C) 2007
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Clint Talbert <ctalbert.moz@gmail.com>
- *   Eva Or <evaor1012@yahoo.ca>
- *   Matthew Willis <lilmatt@mozilla.com>
- *   Philipp Kewisch <mozilla@kewis.ch>
- *   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 ***** */
-
-
-// Operations on the calendar
-const CAL_ITIP_PROC_ADD_OP = 1;
-const CAL_ITIP_PROC_UPDATE_OP = 2;
-const CAL_ITIP_PROC_DELETE_OP = 3;
-
-/**
- * Constructor of calItipItem object
- */
-function calItipProcessor() {
-    this.wrappedJSObject = this;
-}
-
-calItipProcessor.prototype = {
-    getInterfaces: function cipGI(count) {
-        var ifaces = [
-            Components.interfaces.nsIClassInfo,
-            Components.interfaces.nsISupports,
-            Components.interfaces.calIItipProcessor
-        ];
-        count.value = ifaces.length;
-        return ifaces;
-    },
-
-    getHelperForLanguage: function cipGHFL(aLanguage) {
-        return null;
-    },
-
-    contractID: "@mozilla.org/calendar/itip-processor;1",
-    classDescription: "Calendar iTIP processor",
-    classID: Components.ID("{9787876b-0780-4464-8282-b7f86fb221e8}"),
-    implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
-    flags: 0,
-
-    QueryInterface: function cipQI(aIid) {
-        if (!aIid.equals(Components.interfaces.nsIClassInfo) &&
-            !aIid.equals(Components.interfaces.nsISupports) &&
-            !aIid.equals(Components.interfaces.calIItipProcessor))
-        {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-
-        return this;
-    },
-
-    mIsUserInvolved: false,
-    get isUserInvolved() {
-        return this.mIsUserInvolved;
-    },
-    set isUserInvolved(aValue) {
-        return (this.mIsUserInvolved = aValue);
-    },
-
-    /**
-     * Processes the given calItipItem based on the settings inside it.
-     * @param calIItipItem  A calItipItem to process.
-     * @param calIOperationListener A calIOperationListener to return status
-     * @return boolean  Whether processing succeeded or not.
-     */
-    processItipItem: function cipPII(aItipItem, aListener) {
-        // Sanity check the input
-        if (!aItipItem) {
-            throw new Components.Exception("processItipItem: " +
-                                           "Invalid or non-existant " +
-                                           "itipItem passed in.",
-                                           Components.results.NS_ERROR_INVALID_ARG);
-        }
-        // Clone the passed in itipItem like a sheep.
-        var respItipItem = aItipItem.clone();
-
-        var recvMethod = respItipItem.receivedMethod;
-        respItipItem.responseMethod = this._suggestResponseMethod(recvMethod);
-        var respMethod = respItipItem.responseMethod;
-
-        var autoResponse = respItipItem.autoResponse;
-        var targetCalendar = respItipItem.targetCalendar;
-
-        // Sanity checks using the first item
-        var itemList = respItipItem.getItemList({ });
-        var calItem = itemList[0];
-        if (!calItem) {
-            throw new Error ("processItipItem: " +
-                             "getFirstItem() found no items!");
-        }
-
-        var calItemType = this._getCalItemType(calItem);
-        if (!calItemType) {
-            throw new Error ("processItipItem: " +
-                             "_getCalItemType() found no item type!");
-        }
-
-        // Sanity check that mRespMethod is a valid response per the spec.
-        if (!this._isValidResponseMethod(recvMethod, respMethod, calItemType)) {
-            throw new Error ("processItipItem: " +
-                             "_isValidResponseMethod() found an invalid " +
-                             "response method: " + respMethod);
-        }
-
-        // Check to see if we have an existing item or not, then continue
-        // processing appropriately
-        for (var i = 0; i < itemList.length; i++) {
-            this._isExistingItem(itemList[i], aItipItem, recvMethod, respMethod,
-                                 targetCalendar, aListener);
-        }
-
-        // Send the appropriate response
-        // figure out a good way to determine when a response is needed!
-        if (recvMethod != respMethod) {
-            // XXX discuss: does it make sense to check targetCalendar.canNotify(respMethod, ...) here?
-            //              _isExistingItem will store the item
-            this._getTransport(targetCalendar).sendItems(1, [calItem.organizer], respItipItem);
-        }
-    },
-
-    /* Continue processing the iTip Item now that we have determined whether
-     * there is an existing item or not.
-     */
-    _continueProcessingItem: function cipCPI(newItem, existingItem, aItipItem,
-                                             recvMethod, respMethod, calAction,
-                                             targetCalendar, aListener) {
-        var invitedAttendee = null;
-        // we should make calISchedulingSupport mandatory
-        if (calInstanceOf(newItem.calendar, Components.interfaces.calISchedulingSupport)) {
-            invitedAttendee = newItem.calendar.getInvitedAttendee(newItem);
-        }
-        if (!invitedAttendee && aItipItem.identity) { // try to fall back to itip item's identity
-            invitedAttendee = newItem.getAttendeeById(this._getTransport(aItipItem.targetCalendar).scheme + ":" +
-                                                      aItipItem.identity);
-        }
-
-        switch (recvMethod) {
-            case "REQUEST":
-                if (invitedAttendee) {
-                    // Only add to calendar if we accepted invite
-                    if (invitedAttendee.participationStatus == "DECLINED") {
-                        break;
-                    }
-                } else {
-                    LOG("no attendee found.");
-                    return;
-                } // else fall through
-            case "PUBLISH":
-                if (!this._processCalendarAction(newItem,
-                                                 existingItem,
-                                                 calAction,
-                                                 targetCalendar,
-                                                 aListener))
-                {
-                    throw new Error ("processItipItem: " +
-                                     "_processCalendarAction failed!");
-                }
-                break;
-            case "REPLY":
-            case "REFRESH":
-            case "ADD":
-            case "CANCEL":
-            case "COUNTER":
-            case "DECLINECOUNTER":
-                throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-
-            default:
-                throw new Error("processItipItem: " +
-                                "Received unknown method: " +
-                                recvMethod);
-        }
-
-        // The below is somehow hard to understand --
-        // _isExistingItem modifies the itip item's calendar items, we should change that.
-        // Moreover it doesn't work if getItem is performed async.
-
-        // TODO bug 431127: This is email specific -> Move to transport
-        // When replying, the reply must only contain the ORGANIZER and the
-        // status of the ATTENDEE that represents ourselves. Therefore we must
-        // remove all other ATTENDEEs from the itipItem we send back.
-        if (respMethod == "REPLY") {
-            // Get the id that represents me.
-            newItem.removeAllAttendees();
-            ASSERT(invitedAttendee, "attendee unknown!");
-            newItem.addAttendee(invitedAttendee);
-        }
-    },
-
-
-    /**
-     * @return integer  The next recommended iTIP state.
-     */
-    _suggestResponseMethod: function cipSRM(aRecvMethod) {
-        switch (aRecvMethod) {
-            case "REQUEST":
-                return "REPLY";
-
-            case "REFRESH":
-            case "COUNTER":
-                return "REQUEST";
-
-            case "PUBLISH":
-            case "REPLY":
-            case "ADD":
-            case "CANCEL":
-            case "DECLINECOUNTER":
-                return aRecvMethod;
-
-            default:
-                throw new Error("_suggestResponseMethod: " +
-                                "Received unknown method: " +
-                                aRecvMethod);
-        }
-    },
-
-    /**
-     * Given mRecvMethod and mRespMethod, this checks that mRespMethod is
-     * valid according to the spec.
-     *
-     * @return boolean  Whether or not mRespMethod is valid.
-     */
-    _isValidResponseMethod: function cipIAR(aRecvMethod,
-                                            aRespMethod,
-                                            aCalItemType) {
-        switch (aRecvMethod) {
-            // We set response to ADD automatically, but if the GUI did not
-            // find the event the user may set it to REFRESH as per the spec.
-            // These are the only two valid responses.
-            case "ADD":
-                if (!(aRespMethod == "ADD" ||
-                     (aRespMethod == "REFRESH" &&
-                     // REFRESH is not a valid response to an ADD for VJOURNAL
-                     (aCalItemType == Components.interfaces.calIEvent ||
-                      aCalItemType == Components.interfaces.calITodo))))
-                {
-                    return false;
-                }
-                break;
-
-            // Valid responses to COUNTER are REQUEST or DECLINECOUNTER.
-            case "COUNTER":
-                if (!(aRespMethod == "REQUEST" ||
-                      aRespMethod == "DECLINECOUNTER"))
-                {
-                    return false;
-                }
-                break;
-
-            // Valid responses to REQUEST are:
-            //     REPLY   (accept or error)
-            //     REQUEST (delegation, inviting someone else)
-            //     COUNTER (propose a change)
-            case "REQUEST":
-                if (!(aRespMethod == "REPLY" ||
-                      aRespMethod == "REQUEST" ||
-                      aRespMethod == "COUNTER"))
-                {
-                    return false;
-                }
-                break;
-
-            // REFRESH should respond with a request
-            case "REFRESH":
-                if (aRespMethod == "REQUEST") {
-                    return false;
-                }
-                break;
-
-            // The rest are easiest represented as:
-            //     (aRecvMethod != aRespMethod) == return false
-            case "PUBLISH":
-            case "CANCEL":
-            case "REPLY":
-            case "PUBLISH":
-            case "DECLINECOUNTER":
-                if (aRespMethod != aRecvMethod) {
-                    return false;
-                }
-                break;
-
-            default:
-                throw new Error("_isValidResponseMethod: " +
-                                "Received unknown method: " +
-                                aRecvMethod);
-        }
-
-        // If we got to here, then the combination is valid.
-        return true;
-    },
-
-    /**
-     * Helper to return whether an item is an event, todo, etc.
-     */
-    _getCalItemType: function cipGCIT(aCalItem) {
-        if (isEvent(aCalItem)) {
-            return Components.interfaces.calIEvent;
-        } else if (isToDo(aCalItem)) {
-            return Components.interfaces.calITodo;
-        }
-
-        throw new Error ("_getCalItemType: " +
-                         "mCalItem item type is unknown");
-    },
-
-    /**
-     * This performs the actual add/update/delete of an event on the user's
-     * calendar.
-     */
-    _processCalendarAction: function cipPCA(aCalItem,
-                                            aExistingItem,
-                                            aOperation,
-                                            aTargetCalendar,
-                                            aListener) {
-        switch (aOperation) {
-            case CAL_ITIP_PROC_ADD_OP:
-                aTargetCalendar.addItem(aCalItem, aListener);
-
-                // XXX Change this to reflect the success or failure of adding
-                //     the item to the calendar.
-                return true;
-
-            case CAL_ITIP_PROC_UPDATE_OP:
-                // To udpate, we must require the existing item to be set
-                if (!aExistingItem)
-                    throw new Error("_processCalendarAction: Item to update not found");
-
-                // TODO: Handle generation properly - Bug 418345
-                aCalItem.generation = aExistingItem.generation;
-                // We also have to ensure that the calendar is set properly on
-                // the new item, or items with alarms will throw during the
-                // notification process
-                aCalItem.calendar = aExistingItem.calendar;
-                aTargetCalendar.modifyItem(aCalItem, aExistingItem, aListener);
-                return true;
-
-            case CAL_ITIP_PROC_DELETE_OP:
-                throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-
-            default:
-                throw new Error("_processCalendarAction: " +
-                                "Undefined Operation: " + aOperation);
-        }
-
-        // If you got to here, something went horribly, horribly wrong.
-        return false;
-    },
-
-    /**
-     * Helper function to determine if this item already exists on this calendar
-     * or not.  It then calls _continueProcessingItem setting calAction and
-     * existingItem appropirately
-     */
-    _isExistingItem: function cipIEI(aCalItem, aItipItem, aRecvMethod, aRespMethod,
-                                     aTargetCal, aListener) {
-        var foundItemListener = {
-            itipProcessor: this,
-            mFoundItem: null,
-            onOperationComplete:
-            function (aCalendar, aStatus, aOperationType, aId, aDetail) {
-                if (Components.isSuccessCode(aStatus)) {
-                    this.itipProcessor._continueProcessingItem(aCalItem,
-                                                               this.mFoundItem,
-                                                               aItipItem,
-                                                               aRecvMethod,
-                                                               aRespMethod,
-                                                               (this.mFoundItem
-                                                                ? CAL_ITIP_PROC_UPDATE_OP
-                                                                : CAL_ITIP_PROC_ADD_OP),
-                                                               aTargetCal,
-                                                               aListener);
-                }
-            },
-            onGetResult:
-            function onget(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-                if (Components.isSuccessCode(aStatus) && aCount && aItems[0]) {
-                    this.mFoundItem = aItems[0]; // take any
-                }
-            }
-        };
-        if (aTargetCal) {
-            aTargetCal.getItem(aCalItem.id, foundItemListener);
-        } else {
-            // Then we do not have a target calendar to search,
-            // this is probably a DECLINE reply or some other such response,
-            // allow it to pass through
-            this._continueProcessingItem(aCalItem, null, aItipItem, aRecvMethod,
-                                         aRespMethod, null, aTargetCal, aListener);
-        }
-    },
-
-    /**
-     * Centralized location for obtaining the proper transport. If a calendar is
-     * specified, the transport is taken from the provider. Otherwise, the
-     * default email transport is returned.
-     *
-     * Its ok to assume there is an itip.transport here, since if it would
-     * return null (i.e imip is disabled) then we never get here, since the
-     * respective calendar will not be available as a target calendar.
-     */
-    _getTransport: function cipGT(aCalendar) {
-        if (aCalendar) {
-            return aCalendar.getProperty("itip.transport")
-                            .QueryInterface(Components.interfaces.calIItipTransport);
-        } else {
-            return Components.classes["@mozilla.org/calendar/itip-transport;1?type=email"]
-                             .getService(Components.interfaces.calIItipTransport);
-        }
-    }
-}
--- a/calendar/base/src/calRecurrenceInfo.js
+++ b/calendar/base/src/calRecurrenceInfo.js
@@ -744,17 +744,16 @@ calRecurrenceInfo.prototype = {
 
         var itemtoadd;
         if (aTakeOverOwnership && anItem.isMutable) {
             itemtoadd = anItem;
             itemtoadd.parentItem = this.mBaseItem;
         } else {
             itemtoadd = anItem.cloneShallow(this.mBaseItem);
         }
-        itemtoadd.makeImmutable();
 
         // we're going to assume that the recurrenceId is valid here,
         // because presumably the item came from one of our functions
 
         var exKey = getRidKey(itemtoadd.recurrenceId);
         this.mExceptionMap[exKey] = itemtoadd;
     },
 
--- a/calendar/base/src/calTodo.js
+++ b/calendar/base/src/calTodo.js
@@ -1,9 +1,8 @@
-/* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* ***** 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/
  *
@@ -46,17 +45,16 @@
 // constructor
 //
 function calTodo() {
     this.initItemBase();
 
     this.todoPromotedProps = {
         "DTSTART": true,
         "DTEND": true,
-        "DTSTAMP": true,
         "DUE": true,
         "COMPLETED": true,
         __proto__: this.itemBasePromotedProps
     };
 }
 
 var calTodoClassInfo = {
     getInterfaces: function (count) {
@@ -197,16 +195,17 @@ calTodo.prototype = {
     set icalComponent(todo) {
         this.modify();
         if (todo.componentType != "VTODO") {
             todo = todo.getFirstSubcomponent("VTODO");
             if (!todo)
                 throw Components.results.NS_ERROR_INVALID_ARG;
         }
 
+        // xxx todo: Bug 463195 make sure object is properly reset
         this.setItemBaseFromICS(todo);
         this.mapPropsFromICS(todo, this.icsEventPropMap);
         this.mIsAllDay = this.mStartDate && this.mStartDate.isDate;
 
         this.importUnpromotedProperties(todo, this.todoPromotedProps);
         // Importing didn't really change anything
         this.mDirty = false;
     },
--- a/calendar/base/src/calTransactionManager.js
+++ b/calendar/base/src/calTransactionManager.js
@@ -148,23 +148,29 @@ calTransaction.prototype = {
         return this;
     },
 
     onOperationComplete: function cT_onOperationComplete(aCalendar,
                                                          aStatus,
                                                          aOperationType,
                                                          aId,
                                                          aDetail) {
-        if (aStatus == Components.results.NS_OK &&
-            (aOperationType == Components.interfaces.calIOperationListener.ADD ||
-             aOperationType == Components.interfaces.calIOperationListener.MODIFY)) {
-            if (this.mIsDoTransaction) {
-                this.mItem = aDetail;
-            } else {
-                this.mOldItem = aDetail;
+        if (Components.isSuccessCode(aStatus)) {
+
+            cal.itip.checkAndSend(aOperationType,
+                                  aDetail,
+                                  this.mIsDoTransaction ? this.mOldItem : this.mItem);
+
+            if (aOperationType == Components.interfaces.calIOperationListener.ADD ||
+                aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
+                if (this.mIsDoTransaction) {
+                    this.mItem = aDetail;
+                } else {
+                    this.mOldItem = aDetail;
+                }
             }
         }
         if (this.mListener) {
             this.mListener.onOperationComplete(aCalendar,
                                                aStatus,
                                                aOperationType,
                                                aId,
                                                aDetail);
@@ -194,17 +200,17 @@ calTransaction.prototype = {
                 this.mCalendar.addItem(this.mItem, this);
                 break;
             case 'modify':
                 if (this.mItem.calendar.id != this.mOldItem.calendar.id) {
                     this.mOldCalendar = this.mOldItem.calendar;
                     this.mOldCalendar.deleteItem(this.mOldItem, this);
                     this.mCalendar.addItem(this.mItem, this);
                 } else {
-                    this.mCalendar.modifyItem(this.mItem,
+                    this.mCalendar.modifyItem(cal.itip.prepareSequence(this.mItem, this.mOldItem),
                                               this.mOldItem,
                                               this);
                 }
                 break;
             case 'delete':
                 this.mCalendar.deleteItem(this.mItem, this);
                 break;
             default:
@@ -220,17 +226,18 @@ calTransaction.prototype = {
             case 'add':
                 this.mCalendar.deleteItem(this.mItem, this);
                 break;
             case 'modify':
                 if (this.mOldItem.calendar.id != this.mItem.calendar.id) {
                     this.mCalendar.deleteItem(this.mItem, this);
                     this.mOldCalendar.addItem(this.mOldItem, this);
                 } else {
-                    this.mCalendar.modifyItem(this.mOldItem, this.mItem, this);
+                    this.mCalendar.modifyItem(cal.itip.prepareSequence(this.mOldItem, this.mItem),
+                                              this.mItem, this);
                 }
                 break;
             case 'delete':
                 this.mCalendar.addItem(this.mItem, this);
                 break;
             default:
                 throw new Components.Exception("Invalid action specified",
                                                Components.results.NS_ERROR_ILLEGAL_VALUE);
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -32,16 +32,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://gre/modules/debug.js");
+
 /* This file contains commonly used functions in a centralized place so that
  * various components (and other js scopes) don't need to replicate them. Note
  * that loading this file twice in the same scope will throw errors.
  */
 
 /* Returns a clean new calIEvent */
 function createEvent() {
     return Components.classes["@mozilla.org/calendar/event;1"].
@@ -910,16 +912,17 @@ function LOG(aArg) {
         for (var prop in aArg) {
             string += prop + ': ' + aArg[prop] + '\n';
         }
         string += "End object\n";
     } else {
         string = aArg;
     }
 
+    // xxx todo consider using function debug()
     dump(string + '\n');
     getConsoleService().logStringMessage(string);
 }
 
 /**
  * Dumps a warning to both console and js console.
  *
  * @param aMessage warning message
@@ -980,22 +983,21 @@ function STACK(aDepth, aSkip) {
  *                    if false, code flow will continue
  *                    may be a result code
  */
 function ASSERT(aCondition, aMessage, aCritical) {
     if (aCondition) {
         return;
     }
 
-    var string = "Assert failed: " + aMessage + '\n' + STACK(null, 1);
+    NS_ASSERT(aCondition, aMessage);
     if (aCritical) {
+        let string = "Assert failed: " + aMessage + '\n' + STACK(null, 1);
         throw new Components.Exception(string,
                                        aCritical === true ? Components.results.NS_ERROR_UNEXPECTED : aCritical);
-    } else {
-        Components.utils.reportError(string);
     }
 }
 
 /**
  * Uses the prompt service to display an error message.
  *
  * @param aMsg The message to be shown
  */
@@ -1403,26 +1405,34 @@ function sameDay(date1, date2) {
         }
     }
     return false;
 }
 
 /**
  * Iterates all components inside the passed ical component and calls the passed function.
  * If the called function returns false, iteration is stopped.
+ *
+ * @param icalComp an ICS component
+ * @param func functor that will be executed on sub components
+ * @param compType optional component type to filter, defaults to "ANY"
  */
-function calIterateIcalComponent(icalComp, func) {
+function calIterateIcalComponent(icalComp, func, compType) {
     if (icalComp) {
-        if (icalComp.componentType != "VCALENDAR") {
-            return func(icalComp);
+        if (!compType) {
+            compType = "ANY";
+        }
+        var ctype = icalComp.componentType;
+        if (ctype != "VCALENDAR") {
+            return (ctype == compType ? func(icalComp) : true);
         }
         for (var subComp = icalComp.getFirstSubcomponent("ANY");
              subComp;
              subComp = icalComp.getNextSubcomponent("ANY")) {
-            if (!calIterateIcalComponent(subComp, func)) {
+            if (!calIterateIcalComponent(subComp, func, compType)) {
                 return false;
             }
         }
     }
     return true;
 }
 
 /**
@@ -1863,16 +1873,66 @@ function binaryInsert(itemArray, item, c
                 comptor(itemArray[Math.min(newIndex, itemArray.length - 1)], item) != 0) {
         // Only add the item if duplicates should not be discarded, or if
         // they should and itemArray[newIndex] == item.
         itemArray.splice(newIndex, 0, item);
     }
     return newIndex;
 }
 
+/**
+ * Read default alarm settings from user preferences and apply them to
+ * the event/todo passed in.
+ *
+ * @param aItem   The event or todo the settings should be applied to.
+ */
+function setDefaultAlarmValues(aItem)
+{
+    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+                                .getService(Components.interfaces.nsIPrefService);
+    var alarmsBranch = prefService.getBranch("calendar.alarms.");
+
+    if (isEvent(aItem)) {
+        try {
+            if (alarmsBranch.getIntPref("onforevents") == 1) {
+                var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
+                                            .createInstance(Components.interfaces.calIDuration);
+                var units = alarmsBranch.getCharPref("eventalarmunit");
+                alarmOffset[units] = alarmsBranch.getIntPref("eventalarmlen");
+                alarmOffset.isNegative = true;
+                aItem.alarmOffset = alarmOffset;
+                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+            }
+        } catch (ex) {
+            Components.utils.reportError(
+                "Failed to apply default alarm settings to event: " + ex);
+        }
+    } else if (isToDo(aItem)) {
+        try {
+            if (alarmsBranch.getIntPref("onfortodos") == 1) {
+                // You can't have an alarm if the entryDate doesn't exist.
+                if (!aItem.entryDate) {
+                    aItem.entryDate = getSelectedDay() &&
+                                      getSelectedDay().clone() || now();
+                }
+                var alarmOffset = Components.classes["@mozilla.org/calendar/duration;1"]
+                                            .createInstance(Components.interfaces.calIDuration);
+                var units = alarmsBranch.getCharPref("todoalarmunit");
+                alarmOffset[units] = alarmsBranch.getIntPref("todoalarmlen");
+                alarmOffset.isNegative = true;
+                aItem.alarmOffset = alarmOffset;
+                aItem.alarmRelated = Components.interfaces.calIItemBase.ALARM_RELATED_START;
+            }
+        } catch (ex) {
+            Components.utils.reportError(
+                "Failed to apply default alarm settings to task: " + ex);
+        }
+    }
+}
+
 function getCompositeCalendar() {
     if (getCompositeCalendar.mObject === undefined) {
         getCompositeCalendar.mObject = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
                                                  .createInstance(Components.interfaces.calICompositeCalendar);
         getCompositeCalendar.mObject.prefPrefix = 'calendar-main';
 
         try {
             if (gCalendarStatusFeedback) {
--- a/calendar/installer/removed-files.in
+++ b/calendar/installer/removed-files.in
@@ -355,16 +355,18 @@ res/cmessage.txt
 xpicleanup.exe
 #else
 xpicleanup
 #endif
 
 # bug 446366 (renamed interface)
 js/calWeekTitleService.js
 
+js/calItipProcessor.js
+
 # bug 431775 (remove unused gopher images)
 res/html/gopher-audio.gif
 res/html/gopher-binary.gif
 res/html/gopher-find.gif
 res/html/gopher-image.gif
 res/html/gopher-menu.gif
 res/html/gopher-movie.gif
 res/html/gopher-sound.gif
--- a/calendar/installer/windows/packages-static
+++ b/calendar/installer/windows/packages-static
@@ -231,17 +231,16 @@ bin\calendar-js\calFilter.js
 bin\calendar-js\calFreeBusyService.js
 bin\calendar-js\calHtmlExport.js
 bin\calendar-js\calICSCalendar.js
 bin\calendar-js\calIcsImportExport.js
 bin\calendar-js\calIcsParser.js
 bin\calendar-js\calIcsSerializer.js
 bin\calendar-js\calItemBase.js
 bin\calendar-js\calItipItem.js
-bin\calendar-js\calItipProcessor.js
 bin\calendar-js\calListFormatter.js
 bin\calendar-js\calMemoryCalendar.js
 bin\calendar-js\calMonthGridPrinter.js
 bin\calendar-js\calOutlookCSVImportExport.js
 bin\calendar-js\calProtocolHandler.js
 bin\calendar-js\calProviderBase.js
 bin\calendar-js\calProviderUtils.js
 bin\calendar-js\calRecurrenceInfo.js
--- a/calendar/itip/calItipEmailTransport.js
+++ b/calendar/itip/calItipEmailTransport.js
@@ -208,33 +208,19 @@ calItipEmailTransport.prototype = {
             if (identity) {
                 identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity);
                 account = aItem.targetCalendar.getProperty("imip.account")
                                               .QueryInterface(Components.interfaces.nsIMsgAccount);
             } else {
                 WARN("No email identity configured for calendar " + aItem.targetCalendar.name);
             }
         }
-        if (!identity) {
-            if (aItem.identity) {
-                // try to find proper identity/account for the itipItem's identity:
-                var itipIdentity = aItem.identity.toLowerCase();
-                calIterateEmailIdentities(
-                    function(identity_, account_) {
-                        if (identity_.email.toLowerCase() == itipIdentity) {
-                            identity = identity_;
-                            account = account_;
-                            return false;
-                        }
-                        return true;
-                    });
-            } else { // use some default identity/account:
-                identity = this.mDefaultIdentity;
-                account = this.mDefaultAccount;
-            }
+        if (!identity) { // use some default identity/account:
+            identity = this.mDefaultIdentity;
+            account = this.mDefaultAccount;
         }
 
         var compatMode = 0;
         switch (aItem.autoResponse) {
             case (Components.interfaces.calIItipItem.USER): {
                 LOG("sendXpcomMail: Found USER autoResponse type.\n" +
                     "This type is currently unsupported, the compose API will always enter a text/plain\n" +
                     "or text/html part as first part of the message.\n" +
@@ -338,17 +324,17 @@ calItipEmailTransport.prototype = {
             // Home-grown mail composition; I'd love to use nsIMimeEmitter, but it's not clear to me whether
             // it can cope with nested attachments,
             // like multipart/alternative with enclosed text/calendar and text/plain.
             var mailText = ("MIME-version: 1.0\r\n" +
                             (aIdentity.replyTo
                              ? "Return-path: " + aIdentity.replyTo + "\r\n" : "") +
                             "From: " + aIdentity.email + "\r\n" +
                             "To: " + aToList + "\r\n" +
-                            encodeMimeHeader("Subject: " + aSubject) + "\r\n");
+                            encodeMimeHeader("Subject: " + aSubject.replace(/(\n|\r\n)/, "|")) + "\r\n");
             switch (compatMode) {
                 case 1:
                     mailText += ("Content-class: urn:content-classes:calendarmessage\r\n" +
                                  "Content-type: text/calendar; method=" + aItem.responseMethod + "; charset=UTF-8\r\n" +
                                  "Content-transfer-encoding: 8BIT\r\n" +
                                  "\r\n" +
                                  utf8CalText +
                                  "\r\n");
--- a/calendar/libical/design-data/parameters.csv
+++ b/calendar/libical/design-data/parameters.csv
@@ -13,16 +13,18 @@
 "MEMBER","const char*",
 "PARTSTAT","icalparameter_partstat","NEEDS-ACTION;ACCEPTED;DECLINED;TENTATIVE;DELEGATED;COMPLETED;INPROCESS"
 "RANGE","icalparameter_range","THISANDPRIOR;THISANDFUTURE"
 "RELATED","icalparameter_related","START;END"
 "RELTYPE","icalparameter_reltype","PARENT;CHILD;SIBLING"
 "ROLE","icalparameter_role","CHAIR;REQ-PARTICIPANT;OPT-PARTICIPANT;NON-PARTICIPANT"
 "RSVP","icalparameter_rsvp","TRUE;FALSE"
 "SENT-BY","const char*",
+"RECEIVED-SEQUENCE","const char*",
+"RECEIVED-DTSTAMP","const char*",
 "TZID","const char*",
 "VALUE","icalparameter_value","BINARY;BOOLEAN;DATE;DURATION;FLOAT;INTEGER;PERIOD;RECUR;TEXT;URI;ERROR;DATE-TIME;UTC-OFFSET;CAL-ADDRESS"
 "X","const char*",
 "X-LIC-ERRORTYPE","icalparameter_xlicerrortype","COMPONENT-PARSE-ERROR;PROPERTY-PARSE-ERROR;PARAMETER-NAME-PARSE-ERROR;PARAMETER-VALUE-PARSE-ERROR;VALUE-PARSE-ERROR;INVALID-ITIP;UNKNOWN-VCAL-PROP-ERROR;MIME-PARSE-ERROR;VCAL-PROP-PARSE-ERROR"
 "X-LIC-COMPARETYPE","icalparameter_xliccomparetype","EQUAL;NOTEQUAL;LESS;GREATER;LESSEQUAL;GREATEREQUAL;REGEX;ISNULL;ISNOTNULL"
 "#CAP Parameters","Draft 8",
 "#this parameter should really be called ACTION, but this conflicts with the ACTION property"
 "ACTIONPARAM","icalparameter_action","ASK;ABORT"
--- a/calendar/libical/design-data/params-in-prop.txt
+++ b/calendar/libical/design-data/params-in-prop.txt
@@ -1,11 +1,11 @@
 ACTION               VALUE X
 ATTACH               FMTTYPE ENCODING VALUE X
-ATTENDEE             CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY X
+ATTENDEE             CN CUTYPE DELEGATED-FROM DELEGATED-TO DIR LANGUAGE MEMBER PARTSTAT ROLE RSVP SENT-BY RECEIVED-SEQUENCE RECEIVED-DTSTAMP X
 CALSCALE             X
 CATEGORIES           LANGUAGE X
 CLASS                X
 CMD		     ACTIONPARAM ID LATENCY LOCALIZE OPTIONS X
 COMMENT              ALTREP LANGUAGE X
 COMPLETED            X
 CONTACT              ALTREP LANGUAGE X
 CREATED              X
--- a/calendar/lightning/components/lightningTextCalendarConverter.js
+++ b/calendar/lightning/components/lightningTextCalendarConverter.js
@@ -195,33 +195,34 @@ ltnMimeConverter.prototype = {
     get uri() {
         return this.mUri;
     },
     set uri(aUri) {
         return (this.mUri = aUri);
     },
 
     convertToHTML: function lmcCTH(contentType, data) {
+        var parser = Components.classes["@mozilla.org/calendar/ics-parser;1"]
+                               .createInstance(Components.interfaces.calIIcsParser);
+        parser.parseString(data, null);
         var event = null;
-        calIterateIcalComponent(getIcsService().parseICS(data, null),
-                                function(icalComp) {
-                                    if (icalComp.componentType == "VEVENT") {
-                                        event = createEvent();
-                                        event.icalComponent = icalComp;
-                                    }
-                                    return (icalComp.componentType != "VEVENT");
-                                });
+        for each (var item in parser.getItems({})) {
+            if (isEvent(item)) {
+                event = item;
+                break;
+            }
+        }
         if (!event) {
             return;
         }
         var html = createHtml(event);
 
         try {
-            var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"].
-                           createInstance(Components.interfaces.calIItipItem);
+            var itipItem = Components.classes["@mozilla.org/calendar/itip-item;1"]
+                                     .createInstance(Components.interfaces.calIItipItem);
             itipItem.init(data);
 
             // this.mUri is the message URL that we are processing.
             // We use it to get the nsMsgHeaderSink to store the calItipItem.
             if (this.mUri) {
                 var msgUrl = this.mUri.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
                 var sinkProps = msgUrl.msgWindow.msgHeaderSink.properties;
                 sinkProps.setPropertyAsInterface("itipItem", itipItem);
--- a/calendar/lightning/content/imip-bar.js
+++ b/calendar/lightning/content/imip-bar.js
@@ -32,340 +32,51 @@
  * 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");
+Components.utils.import("resource://calendar/modules/calItipUtils.jsm");
+
 /**
  * This bar lives inside the message window.
  * Its lifetime is the lifetime of the main thunderbird message window.
  */
 
-var gItipItem;
-var gCalItemsArrayFound = [];
-
-const onItipItem = {
-    observe: function observe(subject, topic, state) {
-        if (topic == "onItipItemCreation") {
-            checkForItipItem(subject);
-        }
-    }
-};
-
-/**
- * Function to get a composite calendar of all registered read-write calendars.
- *
- * @return composite calendar
- */
-function createItipCompositeCalendar() {
-    var compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
-                            .createInstance(Components.interfaces.calICompositeCalendar);
-    getCalendarManager().getCalendars({}).filter(isCalendarWritable).forEach(
-        function(cal) {
-            compCal.addCalendar(cal);
-        });
-    return compCal;
-}
-
-function checkForItipItem(subject) {
-    var itipItem;
-    try {
-        if (!subject) {
-            var msgUri = GetLoadedMessage();
-            var sinkProps = msgWindow.msgHeaderSink.properties;
-            // This property was set by LightningTextCalendarConverter.js
-            itipItem = sinkProps.getPropertyAsInterface("itipItem",
-                                                        Components.interfaces.calIItipItem);
-        }
-    } catch (e) {
-        // This will throw on every message viewed that doesn't have the
-        // itipItem property set on it. So we eat the errors and move on.
-
-        // XXX TODO: Only swallow the errors we need to. Throw all others.
-        return;
-    }
-
-    // Get the recipient identity and save it with the itip item.
-    itipItem.identity = getMsgRecipient();
-
-    // We are only called upon receipt of an invite, so ensure that isSend
-    // is false.
-    itipItem.isSend = false;
-
-    // XXX Get these from preferences
-    itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
-
-    var imipMethod = getMsgImipMethod();
-    if (imipMethod &&
-        imipMethod.length != 0 &&
-        imipMethod.toLowerCase() != "nomethod")
-    {
-        itipItem.receivedMethod = imipMethod;
-    } else {
-        // There is no METHOD in the content-type header (spec violation).
-        // Fall back to using the one from the itipItem's ICS.
-        imipMethod = itipItem.receivedMethod;
-    }
-
-    gItipItem = itipItem;
-
-    // XXX Bug 351742: no S/MIME or spoofing protection yet
-    // handleImipSecurity(imipMethod);
-
-    setupBar(imipMethod);
-}
-
-addEventListener("messagepane-loaded", imipOnLoad, true);
-addEventListener("messagepane-unloaded", imipOnUnload, true);
-
-/**
- * Add self to gMessageListeners defined in msgHdrViewOverlay.js
- */
-function imipOnLoad() {
-    var listener = {};
-    listener.onStartHeaders = onImipStartHeaders;
-    listener.onEndHeaders = onImipEndHeaders;
-    gMessageListeners.push(listener);
-
-    // Set up our observers
-    var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
-                                .getService(Components.interfaces.nsIObserverService);
-    observerSvc.addObserver(onItipItem, "onItipItemCreation", false);
-}
-
-function imipOnUnload() {
-    removeEventListener("messagepane-loaded", imipOnLoad, true);
-    removeEventListener("messagepane-unloaded", imipOnUnload, true);
-
-    var observerSvc = Components.classes["@mozilla.org/observer-service;1"]
-                                .getService(Components.interfaces.nsIObserverService);
-    observerSvc.removeObserver(onItipItem, "onItipItemCreation");
-
-    gItipItem = null;
-    gCalItemsArrayFound = [];
-}
-
-function onImipStartHeaders() {
-    var imipBar = document.getElementById("imip-bar");
-    imipBar.setAttribute("collapsed", "true");
-    hideElement("imip-button1");
-    hideElement("imip-button2");
-    hideElement("imip-button3");
-
-    // A new message is starting.
-    // Clear our iMIP/iTIP stuff so it doesn't contain stale information.
-    imipMethod = "";
-    gItipItem = null;
-}
-
-/**
- * Required by MessageListener. no-op
- */
-function onImipEndHeaders() {
-    // no-op
-}
-
-function setupBar(imipMethod) {
-    // XXX - Bug 348666 - Currently we only do REQUEST requests
-    // In the future this function will set up the proper actions
-    // and attributes for the buttons as based on the iMIP Method
-    var imipBar = document.getElementById("imip-bar");
-    imipBar.setAttribute("collapsed", "false");
-
-    if (imipMethod.toUpperCase() == "REQUEST") {
-        // Check if this is an update or initial request and display things accordingly
-        processRequestMsg();
-    } else if (imipMethod.toUpperCase() == "REPLY") {
-        // Check if this is an reply and display things accordingly
-        processReplyMsg();
-    } else if (imipMethod.toUpperCase() == "CANCEL") {
-        // Check if this is an cancel and display things accordingly
-        processCancelMsg();
-    } else if (imipMethod.toUpperCase() == "PUBLISH") {
-        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
-
-        var button = document.getElementById("imip-button1");
-        showElement(button);
-        button.setAttribute("label", ltnGetString("lightning", "imipAddToCalendar.label"));
-        button.setAttribute("oncommand", "setAttendeeResponse('PUBLISH', '');");
-    } else {
-        // Bug xxxx TBD: Something went wrong or we found a message we don't
-        // support yet. We can show a "This method is not supported in this
-        // version" or simply hide the iMIP bar at this point
-        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
-        Components.utils.reportError("Unknown imipMethod: " + imipMethod);
-    }
-}
-
-function processCancelMsg() {
-    var imipBar = document.getElementById("imip-bar");
-    imipBar.setAttribute("label", ltnGetString("lightning", "imipBarCancelText"));
-
-    var compCal = createItipCompositeCalendar();
-    // Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
-    // same ID, this simplifies our searching, we can just look for Item[0].id
-    var itemList = gItipItem.getItemList({});
-    var onFindItemListener = {
-        onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            if (gCalItemsArrayFound.length > 0) {
-                displayCancel();
-            }
-        },
-
-        onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-            for each (var item in aItems) {
-                gCalItemsArrayFound.push(aItems[0]);
-            }
-        }
-    }
-    gCalItemsArrayFound = [];
-    // Search for item:
-    compCal.getItem(itemList[0].id, onFindItemListener);
-}
-
-function processReplyMsg() {
-    var imipBar = document.getElementById("imip-bar");
-    imipBar.setAttribute("label", ltnGetString("lightning", "imipBarReplyText"));
-
-    var compCal = createItipCompositeCalendar();
-    // Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
-    // same ID, this simplifies our searching, we can just look for Item[0].id
-    var itemList = gItipItem.getItemList({});
-    var itipItemDate = itemList[0].stampTime;
-    // check if ITIP DTSTAMP is in the future
-    var nowDate = jsDateToDateTime(new Date());
-    if (itipItemDate.compare(nowDate) > 0) {
-        itipItemDate = nowDate;
-    }
-
-    var onFindItemListener = {
-        onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            if (gCalItemsArrayFound.length > 0) {
-                displayReply();
-            }
-        },
-
-        onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-            for each (var item in aItems) {
-                if (aCalendar.getProperty("itip.disableRevisionChecks") ||
-                    itipItemDate.compare(item.stampTime) > 0) {
-                    gCalItemsArrayFound.push(aItems[0]);
-                }
-            }
-        }
-    };
-    gCalItemsArrayFound = [];
-    // Search for item:
-    compCal.getItem(itemList[0].id, onFindItemListener);
-}
-
-function displayCancel() {
-    var button = document.getElementById("imip-button1");
-    showElement(button);
-    button.setAttribute("label", ltnGetString("lightning", "imipCancelInvitation.label"));
-    button.setAttribute("oncommand", "deleteItemFromCancel()");
-}
-
-function displayReply() {
-    var button = document.getElementById("imip-button1");
-    showElement(button);
-    button.setAttribute("label", ltnGetString("lightning", "imipUpdateInvitation.label"));
-    button.setAttribute("oncommand", "updateItemStatusFromReply()");
-}
-
-function deleteItemFromCancel() {
-    var operationListener = {
-        onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            // Call finishItipAction to set the status of the operation
-            finishItipAction(aOperationType, aStatus, aDetail);
-        },
-
-        onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-        }
-    };
-
-    var itemArray = gItipItem.getItemList({});
-    for each (var calItemFound in gCalItemsArrayFound) {
-        calItemFound.calendar.deleteItem(calItemFound, operationListener);
-    }
-
-    return true;
-}
-
-function updateItemStatusFromReply() {
-    var operationListener = {
-        onOperationComplete: function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            // Call finishItipAction to set the status of the operation
-            finishItipAction(aOperationType, aStatus, aDetail);
-        },
-
-        onGetResult: function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-        }
-    };
-
-    // Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
-    // same ID, this simplifies our searching, we can just look for Item[0].id
-    var itemArray = gItipItem.getItemList({});
-    var itipItem = itemArray[0];
-    for each (var calItemFound in gCalItemsArrayFound) {
-        var newItem = calItemFound.clone();
-        for each (var itipAttendee in itipItem.getAttendees({})) {
-            var att = newItem.getAttendeeById(itipAttendee.id);
-            if (att) {
-                var newAtt = att.clone();
-                newItem.removeAttendee(att);
-                newAtt.participationStatus = itipAttendee.participationStatus;
-                newItem.addAttendee(newAtt);
-            }
-        }
-        newItem.calendar.modifyItem(newItem, calItemFound, operationListener);
-    }
-
-    return true;
-}
-
-function getMsgImipMethod() {
-    return messenger.msgHdrFromURI(GetLoadedMessage()).getStringProperty("imip_method");
-}
-
-function getMsgRecipient() {
-    var imipRecipient = "";
+function ltnGetMsgRecipient() {
     var msgURI = GetLoadedMessage();
     var msgHdr = messenger.msgHdrFromURI(msgURI);
     if (!msgHdr) {
         return null;
     }
 
-    var acctmgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
-                            .getService(Components.interfaces.nsIMsgAccountManager);
     var identities;
     if (msgHdr.accountKey) {
         // First, check if the message has an account key. If so, we can use the
         // account identities to find the correct recipient
-        identities = acctmgr.getAccount(msgHdr.accountKey).identities;
+        identities = getAccountManager().getAccount(msgHdr.accountKey).identities;
     } else {
         // Without an account key, we have to revert back to using the server
-        identities = acctmgr.GetIdentitiesForServer(msgHdr.folder.server);
+        identities = getAccountManager().GetIdentitiesForServer(msgHdr.folder.server);
     }
 
     var emailMap = {};
     if (identities.Count() == 0) {
         // If we were not able to retrieve identities above, then we have no
         // choice but to revert to the default identity
-        var acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
-                                .getService(Components.interfaces.nsIMsgAccountManager);
-        var identity = acctMgr.defaultAccount.defaultIdentity;
+        var identity = getAccountManager().defaultAccount.defaultIdentity;
         if (!identity) {
             // If there isn't a default identity (i.e Local Folders is your
             // default identity), then go ahead and use the first available
             // identity.
-            var allIdentities = acctMgr.allIdentities;
+            var allIdentities = getAccountManager().allIdentities;
             if (allIdentities.Count() > 0) {
                 identity = allIdentities.GetElementAt(0)
                                         .QueryInterface(Components.interfaces.nsIMsgIdentity);
             } else {
                 // If there are no identities at all, we cannot get a recipient.
                 return null;
             }
         }
@@ -401,337 +112,301 @@ function getMsgRecipient() {
             return recipient;
         }
     }
 
     // Hrmpf. Looks like delegation or maybe Bcc.
     return null;
 }
 
+function ltnIsSchedulingCalendar(cal) {
+    return (isCalendarWritable(cal) &&
+            cal.getProperty("organizerId") &&
+            cal.getProperty("itip.transport"));
+}
+
+const ltnOnItipItem = {
+    observe: function ltnOnItipItem_observe(subject, topic, state) {
+        if (topic == "onItipItemCreation") {
+            let itipItem = null;
+            try {
+                if (!subject) {
+                    let sinkProps = msgWindow.msgHeaderSink.properties;
+                    // This property was set by lightningTextCalendarConverter.js
+                    itipItem = sinkProps.getPropertyAsInterface("itipItem", Components.interfaces.calIItipItem);
+                }
+            } catch (e) {
+                // This will throw on every message viewed that doesn't have the
+                // itipItem property set on it. So we eat the errors and move on.
+                
+                // XXX TODO: Only swallow the errors we need to. Throw all others.
+            }
+            if (!itipItem) {
+                return;
+            }
+
+            // Get the recipient identity and save it with the itip item.
+            itipItem.identity = ltnGetMsgRecipient();
+
+            // We are only called upon receipt of an invite, so ensure that isSend
+            // is false.
+            itipItem.isSend = false;
+
+            // XXX Get these from preferences
+            itipItem.autoResponse = Components.interfaces.calIItipItem.USER;
+
+            let imipMethod = messenger.msgHdrFromURI(GetLoadedMessage()).getStringProperty("imip_method");
+            if (imipMethod && imipMethod.length != 0 && imipMethod.toLowerCase() != "nomethod") {
+                itipItem.receivedMethod = imipMethod.toUpperCase();
+            } else { // There is no METHOD in the content-type header (spec violation).
+                     // Fall back to using the one from the itipItem's ICS.
+                imipMethod = itipItem.receivedMethod;
+            }
+            cal.LOG("iTIP method: " + imipMethod);
+
+            let writableCalendars = getCalendarManager().getCalendars({}).filter(ltnIsSchedulingCalendar);
+            if (writableCalendars.length > 0) {
+                let compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
+                                        .createInstance(Components.interfaces.calICompositeCalendar);
+                writableCalendars.forEach(compCal.addCalendar, compCal);
+                itipItem.targetCalendar = compCal;
+
+                let imipBar = document.getElementById("imip-bar");
+                imipBar.setAttribute("collapsed", "false");
+                switch (itipItem.receivedMethod) {
+                    case "REFRESH":
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRefreshText"));
+                        break;
+                    case "REQUEST":
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
+                        break;
+                    case "PUBLISH":
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarPublishText"));
+                        break;
+                    case "CANCEL":
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarCancelText"));
+                        break;
+                    case "REPLY":
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarReplyText"));
+                        break;
+                    default:
+                        // Bug xxxx TBD: Something went wrong or we found a message we don't
+                        // support yet. We can show a "This method is not supported in this
+                        // version" or simply hide the iMIP bar at this point
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
+                        cal.ERROR("Unknown iTIP method: " + itipItem.receivedMethod);
+                        return;
+                }
+                cal.itip.processItipItem(itipItem, ltnItipOptions);
+            }
+        }
+    }
+};
+
 /**
- * Call the calendar picker
+ * Add self to gMessageListeners defined in msgHdrViewOverlay.js
  */
-function getTargetCalendar() {
-    function filterCalendars(c) {
-        // Only consider calendars that are writable and have a transport.
-        return isCalendarWritable(c) &&
-               c.getProperty("itip.transport") != null;
+function ltnImipOnLoad() {
+    let listener = {
+        onStartHeaders: function onImipStartHeaders() {
+            var imipBar = document.getElementById("imip-bar");
+            imipBar.setAttribute("collapsed", "true");
+            hideElement("imip-button1");
+            hideElement("imip-button2");
+            hideElement("imip-button3");
+            // A new message is starting.
+            // Clear our iMIP/iTIP stuff so it doesn't contain stale information.
+            gItipItem = null;
+        },
+        onEndHeaders: function onImipEndHeaders() {
+        }
+    };
+    gMessageListeners.push(listener);
+
+    // Set up our observers
+    cal.getObserverService().addObserver(ltnOnItipItem, "onItipItemCreation", false);
+}
+
+function ltnImipOnUnload() {
+    removeEventListener("messagepane-loaded", ltnImipOnLoad, true);
+    removeEventListener("messagepane-unloaded", ltnImipOnUnload, true);
+
+    cal.getObserverService().removeObserver(ltnOnItipItem, "onItipItemCreation");
+
+    gItipItem = null;
+    gCalItemsArrayFound = [];
+}
+
+addEventListener("messagepane-loaded", ltnImipOnLoad, true);
+addEventListener("messagepane-unloaded", ltnImipOnUnload, true);
+
+var gItipItem = null;
+var gActionFunc = null;
+
+function ltnExecAction(partStat) {
+    switch (gActionFunc.method) {
+        // methods that don't require the calendar chooser:
+        case "REFRESH":
+        case "PUBLISH:UPDATE":
+        case "REPLY":
+        case "CANCEL":
+            break;
+        default: {
+            let cal = ltnGetTargetCalendar(gItipItem);
+            if (!cal) {
+                return true; // cancelled
+            }
+            gItipItem.targetCalendar = cal;
+            break;
+        }
     }
 
-    var calendarToReturn;
-    var calendars = getCalendarManager().getCalendars({}).filter(filterCalendars);
-    // XXXNeed an error message if there is no writable calendar
+    // hide the buttons now, to disable pressing them twice...
+    hideElement("imip-button1");
+    hideElement("imip-button2");
+    hideElement("imip-button3");
+
+    let opListener = {
+        onOperationComplete: function ltnItipActionListener_onOperationComplete(aCalendar,
+                                                                                aStatus,
+                                                                                aOperationType,
+                                                                                aId,
+                                                                                aDetail) {
+            // For now, we just state the status for the user something very simple
+            let imipBar = document.getElementById("imip-bar");
+            if (Components.isSuccessCode(aStatus)) {
+                switch (aOperationType) {
+                    case Components.interfaces.calIOperationListener.ADD:
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipAddedItemToCal"));
+                        break;
+                    case Components.interfaces.calIOperationListener.MODIFY:
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipUpdatedItem"));
+                        break;
+                    case Components.interfaces.calIOperationListener.DELETE:
+                        imipBar.setAttribute("label", ltnGetString("lightning", "imipCanceledItem"));
+                        break;
+                }
+            } else {
+                let msg = ltnGetString("lightning", "imipBarProcessingFailed", [aStatus.toString(16)]);
+                imipBar.setAttribute("label", msg);
+                showError(msg);
+            }
+        },
+        onGetResult: function ltnItipActionListener_onGetResult(aCalendar,
+                                                                aStatus,
+                                                                aItemType,
+                                                                aDetail,
+                                                                aCount,
+                                                                aItems) {
+        }
+    };
+
+    try {
+        gActionFunc(opListener, partStat);
+    } catch (exc) {
+        Components.utils.reportError(exc);
+    }
+
+    return true;
+}
+
+function ltnItipOptions(itipItem, rc, actionFunc) {
+    var imipBar = document.getElementById("imip-bar");
+    if (Components.isSuccessCode(rc)) {
+        if (!actionFunc) {
+            // This case, they clicked on an old message that has already been
+            // added/updated, we want to tell them that.
+            imipBar.setAttribute("label", ltnGetString("lightning", "imipBarAlreadyProcessedText"));
+            return;
+        }
+
+        gItipItem = itipItem;
+        gActionFunc = actionFunc;
 
-    // try to further limit down the list to those calendars that are configured to a matching attendee;
-    var item = gItipItem.getItemList({})[0];
-    var matchingCals = calendars.filter(
-        function(cal) {
-            var identity = cal.getProperty("imip.identity");
-            if (identity !== null) {
-                identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email;
-                return ((gItipItem.identity && (identity.toLowerCase() == gItipItem.identity.toLowerCase())) ||
-                        item.getAttendeeById("mailto:" + identity));
+        let button1 = document.getElementById("imip-button1");
+        let button2 = document.getElementById("imip-button2");
+        let button3 = document.getElementById("imip-button3");
+        cal.LOG("iTIP options on: " + actionFunc.method);
+        switch (actionFunc.method) {
+            case "REPLY":
+                // fall-thru intended
+            case "PUBLISH:UPDATE":
+            case "REQUEST:UPDATE-MINOR":
+                imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
+                button1.setAttribute("label", ltnGetString("lightning", "imipUpdate.label"));
+                button1.setAttribute("oncommand", "return ltnExecAction();");
+                showElement(button1);
+                break;
+            case "PUBLISH":
+                button1.setAttribute("label", ltnGetString("lightning", "imipAddToCalendar.label"));
+                button1.setAttribute("oncommand", "return ltnExecAction();");
+                showElement(button1);
+                break;
+            case "REQUEST:UPDATE":
+                imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
+                // fall-thru intended
+            case "REQUEST": {
+                button1.setAttribute("label", ltnGetString("lightning", "imipAcceptInvitation.label"));
+                button1.setAttribute("oncommand", "return ltnExecAction('ACCEPTED');");
+                button2.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
+                button2.setAttribute("oncommand", "return ltnExecAction('DECLINED');");
+                button3.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
+                button3.setAttribute("oncommand", "return ltnExecAction('TENTATIVE');");
+                showElement(button1);
+                showElement(button2);
+                showElement(button3);
+                break;
             }
-            return false;
-        });
-    // if there's none, we will show the whole list of calendars:
-    if (matchingCals.length > 0) {
-        calendars = matchingCals;
+            case "CANCEL": {
+                button1.setAttribute("label", ltnGetString("lightning", "imipCancelInvitation.label"));
+                button1.setAttribute("oncommand", "return ltnExecAction();");
+                showElement(button1);
+                break;
+            }
+            case "REFRESH": {
+                button1.setAttribute("label", ltnGetString("lightning", "imipSend.label"));
+                button1.setAttribute("oncommand", "return ltnExecAction();");
+                showElement(button1);
+                break;
+            }
+            default:
+                imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
+                break;
+        }
+    } else {
+        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUnsupportedText"));
+    }
+}
+
+function ltnGetTargetCalendar(itipItem) {
+    let calendarToReturn = null;
+    let calendars = getCalendarManager().getCalendars({}).filter(ltnIsSchedulingCalendar);
+    // XXXNeed an error message if there is no calendar
+
+    if (itipItem.receivedMethod == "REQUEST") {
+        // try to further limit down the list to those calendars that are configured to a matching attendee;
+        let item = itipItem.getItemList({})[0];
+        let matchingCals = calendars.filter(
+            function(calendar) {
+                return (cal.getInvitedAttendee(item, calendar) != null);
+            });
+        // if there's none, we will show the whole list of calendars:
+        if (matchingCals.length > 0) {
+            calendars = matchingCals;
+        }
     }
 
     if (calendars.length == 1) {
         // There's only one calendar, so it's silly to ask what calendar
         // the user wants to import into.
         calendarToReturn = calendars[0];
     } else {
         // Ask what calendar to import into
-        var args = new Object();
-        var aCal;
+        var args = {};
         args.calendars = calendars;
         args.onOk = function selectCalendar(aCal) { calendarToReturn = aCal; };
         args.promptText = calGetString("calendar", "importPrompt");
         openDialog("chrome://calendar/content/chooseCalendarDialog.xul",
                    "_blank", "chrome,titlebar,modal,resizable", args);
     }
 
-    if (calendarToReturn) {
-        // assure gItipItem.identity is set to the configured email address:
-        var identity = calendarToReturn.getProperty("imip.identity");
-        if (identity) {
-            gItipItem.identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity).email;
-        }
-    }
     return calendarToReturn;
 }
-
-/**
- * Type is type of response
- * event_status is an optional directive to set the Event STATUS property
- */
-function setAttendeeResponse(type, eventStatus) {
-    if (type && gItipItem) {
-        // Some methods need a target calendar. Prompt for it first.
-        switch (type) {
-            case "ACCEPTED":
-            case "TENTATIVE":
-            case "REPLY":
-            case "PUBLISH":
-                gItipItem.targetCalendar = getTargetCalendar();
-                if (!gItipItem.targetCalendar) {
-                    // The dialog was canceled, we are done.
-                    return;
-                }
-        }
-
-        // Now set the attendee status and perform the iTIP action. If the
-        // method is not mentioned here, no further action will be taken.
-        switch (type) {
-            case "ACCEPTED":
-            case "TENTATIVE":
-            case "DECLINED": {
-                var attId = null;
-                var attCN = null;
-                if (gItipItem.targetCalendar) {
-                    var identity = gItipItem.targetCalendar.getProperty("imip.identity");
-                    if (identity) { // configured email supersedes found msg recipient:
-                        identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity);
-                        attId = ("mailto:" + identity.email);
-                        attCN = identity.fullName;
-                    }
-                }
-                if (!attId && gItipItem.identity) {
-                    attId = ("mailto:" + gItipItem.identity);
-                }
-                if (!attId) {
-                    // Bug 420516 -- we don't support delegation yet TODO: Localize this?
-                    throw new Error("setAttendeeResponse: " +
-                                    "You are not on the list of invited attendees, delegation " +
-                                    "is not supported yet.  See bug 420516 for details.");
-                }
-                for each (var item in gItipItem.getItemList({})) {
-                    if (!item.getAttendeeById(attId)) { // add if not existing, e.g. on mailing list REQUEST
-                        var att = Components.classes["@mozilla.org/calendar/attendee;1"]
-                                            .createInstance(Components.interfaces.calIAttendee);
-                        att.id = attId;
-                        att.commonName = attCN;
-                        item.addAttendee(att);
-                    }
-                }
-                gItipItem.setAttendeeStatus(attId, type); // workaround for bug 351589 (fixing RSVP)
-                // Fall through
-            }
-            case "REPLY":
-            case "PUBLISH":
-                doResponse(eventStatus);
-                break;
-        }
-    }
-}
-
-/**
- * doResponse performs the iTIP action for the current ItipItem that we
- * parsed from the email.
- * @param  aLocalStatus  optional parameter to set the event STATUS property.
- *         aLocalStatus can be empty, "TENTATIVE", "CONFIRMED", or "CANCELLED"
- */
-function doResponse(aLocalStatus) {
-    // calIOperationListener so that we can properly return status to the
-    // imip-bar
-    var operationListener = {
-        onOperationComplete:
-        function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            // Call finishItipAction to set the status of the operation
-            finishItipAction(aOperationType, aStatus, aDetail);
-        },
-
-        onGetResult:
-        function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-            // no-op
-        }
-    };
-
-    // The spec is unclear if we must add all the items or if the
-    // user should get to pick which item gets added.
-
-    if (aLocalStatus != null) {
-        gItipItem.localStatus = aLocalStatus;
-    }
-
-    var itipProc = Components.classes["@mozilla.org/calendar/itip-processor;1"]
-                             .getService(Components.interfaces.calIItipProcessor);
-
-    itipProc.processItipItem(gItipItem, operationListener);
-}
-
-/**
- * Bug 348666 (complete iTIP support) - This gives the user an indication
- * that the Action occurred.
- *
- * In the future, this will store the status of the invitation in the
- * invitation manager.  This will enable us to provide the ability to request
- * updates from the organizer and to suggest changes to invitations.
- *
- * Currently, this is called from our calIOperationListener that is sent to
- * the ItipProcessor. This conveys the status of the local iTIP processing
- * on your calendar. It does not convey the success or failure of sending a
- * response to the ItipItem.
- */
-function finishItipAction(aOperationType, aStatus, aDetail) {
-    // For now, we just state the status for the user something very simple
-    var imipBar = document.getElementById("imip-bar");
-    if (Components.isSuccessCode(aStatus)) {
-        if (aOperationType == Components.interfaces.calIOperationListener.ADD) {
-            imipBar.setAttribute("label", ltnGetString("lightning", "imipAddedItemToCal"));
-        } else if (aOperationType == Components.interfaces.calIOperationListener.MODIFY) {
-            imipBar.setAttribute("label", ltnGetString("lightning", "imipUpdatedItem"));
-        } else if (aOperationType == Components.interfaces.calIOperationListener.DELETE) {
-            imipBar.setAttribute("label", ltnGetString("lightning", "imipCanceledItem"));
-        }
-
-        hideElement("imip-button1");
-        hideElement("imip-button2");
-        hideElement("imip-button3");
-    } else {
-        // Bug 348666: When we handle more iTIP methods, we need to create
-        // more sophisticated error handling.
-        // TODO L10N localize
-        imipBar.setAttribute("collapsed", "true");
-        var msg = "Invitation could not be processed. Status: " + aStatus;
-        if (aDetail) {
-            msg += "\nDetails: " + aDetail;
-        }
-        showError(msg);
-    }
-}
-
-/**
- * Walks through the list of events in the iTipItem and discovers whether or not
- * these events already exist on a calendar. Calls displayRequestMethod.
- */
-function processRequestMsg() {
-    // According to the specification, we have to determine if the event ID
-    // already exists on the calendar of the user - that means we have to search
-    // them all. :-(
-    var existingItemSequence = -1;
-
-    var compCal = createItipCompositeCalendar();
-
-    // Per iTIP spec (new Draft 4), multiple items in an iTIP message MUST have
-    // same ID, this simplifies our searching, we can just look for Item[0].id
-    var itemList = gItipItem.getItemList({ });
-    var newSequence = itemList[0].getProperty("SEQUENCE");
-
-    // Make sure we don't have a pre Outlook 2007 appointment, but if we do
-    // use Microsoft's Sequence number. I <3 MS
-    if ((newSequence == "0") &&
-        itemList[0].hasProperty("X-MICROSOFT-CDO-APPT-SEQUENCE")) {
-        newSequence = itemList[0].getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
-    }
-
-    var onFindItemListener = {
-        onOperationComplete:
-        function ooc(aCalendar, aStatus, aOperationType, aId, aDetail) {
-            if (!this.processedId){
-                // Then the ID doesn't exist, don't call us twice
-                this.processedId = true;
-                displayRequestMethod(newSequence, -1);
-            }
-        },
-
-        onGetResult:
-        function ogr(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
-            if (aCount && aItems[0] && !this.processedId) {
-                this.processedId = true;
-                var existingSequence = aItems[0].getProperty("SEQUENCE");
-
-                // Handle the microsoftism foolishness
-                if ((existingSequence == "0") &&
-                    itemList[0].hasProperty("X-MICROSOFT-CDO-APPT-SEQUENCE")) {
-                    existingSequence = aItems[0].getProperty("X-MICROSOFT-CDO-APPT-SEQUENCE");
-                }
-
-                if (aCalendar.getProperty("itip.disableRevisionChecks")) {
-                    displayRequestMethod(1, 0); // force to be an update
-                } else {
-                    displayRequestMethod(newSequence, existingSequence);
-                }
-            }
-        }
-    };
-    // Search
-    compCal.getItem(itemList[0].id, onFindItemListener);
-}
-
-function displayRequestMethod(newItemSequence, existingItemSequence) {
-
-    // Three states here:
-    // 0 = the new event does not exist on the calendar (therefore, this is an add)
-    //     (Item does not exist yet: existingItemSequence == -1)
-    // 1 = the event does exist and contains a proper update (this is an update)
-    //     (Item has been updated: newSequence > existingSequence)
-    // 2 = the event clicked on is an old update and should NOT be applied
-    //     (Item is an old message that has already been added/updated: new <= existing)
-    var updateValue = 0;
-
-    if (existingItemSequence == -1) {
-        updateValue = 0;
-    } else if (newItemSequence > existingItemSequence) {
-        updateValue = 1;
-    } else {
-        updateValue = 2;
-    }
-
-    // now display the proper message for this update type:
-
-    var imipBar = document.getElementById("imip-bar");
-    if (updateValue) {
-        // This is a message updating existing event(s). But updateValue could
-        // indicate that this update has already been applied, check that first.
-        if (updateValue == 2) {
-            // This case, they clicked on an old message that has already been
-            // added/updated, we want to tell them that.
-            imipBar.setAttribute("label", ltnGetString("lightning", "imipBarAlreadyAddedText"));
-
-            hideElement("imip-button1");
-            hideElement("imip-button2");
-            hideElement("imip-button3");
-        } else {
-            // Legitimate update, let's offer the update path
-            imipBar.setAttribute("label", ltnGetString("lightning", "imipBarUpdateText"));
-
-            var button = document.getElementById("imip-button1");
-            showElement(button);
-            button.setAttribute("label", ltnGetString("lightning", "imipUpdateInvitation.label"));
-            button.setAttribute("oncommand", "setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
-
-            // Create a DECLINE button (user chooses not to attend the updated event)
-            button = document.getElementById("imip-button2");
-            showElement(button);
-            button.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
-            button.setAttribute("oncommand", "setAttendeeResponse('DECLINED', 'CONFIRMED');");
-
-            // Create a ACCEPT TENTATIVE button
-            button = document.getElementById("imip-button3");
-            showElement(button);
-            button.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
-            button.setAttribute("oncommand", "setAttendeeResponse('TENTATIVE', 'CONFIRMED');");
-        }
-    } else {
-        imipBar.setAttribute("label", ltnGetString("lightning", "imipBarRequestText"));
-
-        var button = document.getElementById("imip-button1");
-        showElement(button);
-        button.setAttribute("label", ltnGetString("lightning", "imipAcceptInvitation.label"));
-        button.setAttribute("oncommand", "setAttendeeResponse('ACCEPTED', 'CONFIRMED');");
-
-        // Create a DECLINE button
-        button = document.getElementById("imip-button2");
-        showElement(button);
-        button.setAttribute("label", ltnGetString("lightning", "imipDeclineInvitation.label"));
-        button.setAttribute("oncommand", "setAttendeeResponse('DECLINED', 'CONFIRMED');");
-
-        // Create a ACCEPT TENTATIVE button
-        button = document.getElementById("imip-button3");
-        showElement(button);
-        button.setAttribute("label", ltnGetString("lightning", "imipAcceptTentativeInvitation.label"));
-        button.setAttribute("oncommand", "setAttendeeResponse('TENTATIVE', 'CONFIRMED');");
-    }
-}
--- a/calendar/lightning/content/lightning-utils.js
+++ b/calendar/lightning/content/lightning-utils.js
@@ -33,45 +33,28 @@
  * 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");
+
 /**
  * Gets the value of a string in a .properties file from the lightning bundle
  *
  * @param aBundleName  the name of the properties file.  It is assumed that the
  *                     file lives in chrome://lightning/locale/
  * @param aStringName  the name of the string within the properties file
  * @param aParams      optional array of parameters to format the string
  */
 function ltnGetString(aBundleName, aStringName, aParams) {
-    if (ltnGetString.mSBS === undefined) {
-        ltnGetString.mSBS = Components.classes["@mozilla.org/intl/stringbundle;1"]
-                            .getService(Components.interfaces.nsIStringBundleService);
-    }
-
-    try {
-        var propName = "chrome://lightning/locale/"+aBundleName+".properties";
-        var props = ltnGetString.mSBS.createBundle(propName);
-
-        if (aParams && aParams.length) {
-            return props.formatStringFromName(aStringName, aParams, aParams.length);
-        } else {
-            return props.GetStringFromName(aStringName);
-        }
-    } catch (ex) {
-        var s = "Failed to read '" + aStringName + "' from " +
-                "'chrome://lightning/locale/" + aBundleName + ".properties'.";
-        Components.utils.reportError(s + " Error: " + ex);
-        return s;
-    }
+    return cal.calGetString(aBundleName, aStringName, aParams, "lightning");
 }
 
 // shared by lightning-calendar-properties.js and lightning-calendar-creation.js:
 function ltnInitMailIdentitiesRow() {
     if (!gCalendar) {
         collapseElement("calendar-email-identity-row");
     }
 
--- a/calendar/lightning/content/lightning.js
+++ b/calendar/lightning/content/lightning.js
@@ -80,16 +80,19 @@ pref("calendar.invitations.autorefresh.t
 // 0 -- Outlook 2003 and following with text/plain and application/ics (default)
 // 1 -- all Outlook, but no text/plain nor application/ics
 // We may extend the compat mode if necessary.
 pref("calendar.itip.compatSendMode", 0);
 
 // whether "notify" is checked by default when creating new events/todos with attendees
 pref("calendar.itip.notify", true);
 
+// whether the organizer propagates replies of attendees to all attendees
+pref("calendar.itip.notify-replies", false);
+
 // whether CalDAV (experimental) scheduling is enabled or not.
 pref("calendar.caldav.sched.enabled", false);
 
 // 0=Sunday, 1=Monday, 2=Tuesday, etc.  One day we might want to move this to
 // a locale specific file.
 pref("calendar.week.start", 0);
 pref("calendar.weeks.inview", 4);
 pref("calendar.previousweeks.inview", 0);
--- a/calendar/locales/en-US/chrome/calendar/calendar.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar.properties
@@ -153,17 +153,17 @@ filepickerTitleExport=Export
 # wildmat used to filter files by extension, such as (*.html; *.htm).
 filterIcs=iCalendar (%1$S)
 filterXml=XML Document (%1$S)
 filterHtml=Web Page (%1$S)
 filterOutlookCsv=Outlook Comma Separated Values (%1$S)
 filterWav=Waveform Audio (%1$S)
 
 # Remote calendar errors
-errorTitle=Error getting calendar
+errorTitle=An error has occurred.
 httpPutError=Publishing the calendar file failed.\nStatus code: %1$S: %2$S
 otherPutError=Publishing the calendar file failed.\nStatus code: 0x%1$S
 readOnlyMode=There has been an error reading data for calendar: %1$S. It has been placed in read-only mode, since changes to this calendar will likely result in data-loss.  You may change this setting by choosing 'Edit Calendar'.
 disabledMode=There has been an error reading data for calendar: %1$S. It has been disabled until it is safe to use it.
 minorError=There has been an error reading data for calendar: %1$S.  However, this error is believed to be minor, so the program will attempt to continue.
 stillReadOnlyError=There has been an error reading data for calendar: %1$S.
 utf8DecodeError=An error occured while decoding an iCalendar (ics) file as UTF-8. Check that the file, including symbols and accented letters, is encoded using the UTF-8 character encoding.
 icsMalformedError=Parsing an iCalendar (ics) file failed. Check that the file conforms to iCalendar (ics) file syntax.
--- a/calendar/locales/en-US/chrome/lightning/lightning.properties
+++ b/calendar/locales/en-US/chrome/lightning/lightning.properties
@@ -52,28 +52,32 @@ imipHtml.organizer=Organizer:
 imipHtml.description=Description:
 imipHtml.comment=Comment:
 
 imipAddToCalendar.label=Add To Calendar
 imipAddedItemToCal=Event Added to Calendar
 imipCanceledItem=Event has been deleted
 imipUpdatedItem=Event has been updated
 imipBarCancelText=This message contains an event cancellation.
+imipBarRefreshText=This message contains an event inquery.
+imipBarPublishText=This message contains an event.
 imipBarRequestText=This message contains an invitation to an event.
 imipBarUpdateText=This message contains an update to an existing event.
-imipBarAlreadyAddedText=This message contains an event that has already been added to your calendar.
+imipBarAlreadyProcessedText=This message contains an event that has already been processed.
 imipBarReplyText=This message contains a reply to an invitation.
 imipBarUnsupportedText=This message contains an event that this version of Lightning cannot process.
+imipBarProcessingFailed=Processing message failed. Status: %1$S.
 imipAcceptInvitation.label=Accept
 imipCancelInvitation.label=Delete
 imipDeclineInvitation.label=Decline
-imipUpdateInvitation.label=Update
+imipUpdate.label=Update
 imipAcceptTentativeInvitation.label=Tentative
-imipSendMailTitle=Notify Attendees
-imipSendMail=Would you like to send out notification E-Mails now?
+imipSend.label=Send
+imipSendMailTitle=E-Mail Notification
+imipSendMail=Would you like to send out notification E-Mail now?
 imipSendMailOutlook2000CompatMode=Support Outlook 2000 and Outlook 2002/XP
 imipNoIdentity=None
 
 itipReplySubject=Event Invitation Reply: %1$S
 itipReplyBodyAccept=%1$S has accepted your event invitation.
 itipReplyBodyDecline=%1$S has declined your event invitation.
 itipRequestSubject=Event Invitation: %1$S
 itipRequestBody=%1$S has invited you to %2$S
--- a/calendar/providers/base/calProviderBase.js
+++ b/calendar/providers/base/calProviderBase.js
@@ -288,18 +288,20 @@ calProviderBase.prototype = {
     },
     set transientProperties cPB_transientProperties(value) {
         return (this.mTransientPropertiesMode = value);
     },
 
     // nsIVariant getProperty(in AUTF8String aName);
     getProperty: function cPB_getProperty(aName) {
         switch (aName) {
-            case "itip.transport": // itip/imip default:
+            case "itip.transport": // iTIP/iMIP default:
                 return calGetImipTransport(this);
+            case "itip.notify-replies": // iTIP/iMIP default:
+                 return getPrefSafe("calendar.itip.notify-replies", false);
             // temporary hack to get the uncached calendar instance:
             case "cache.uncachedCalendar":
                 return this;
         }
 
         var ret = this.mProperties[aName];
         if (ret === undefined) {
             ret = null;
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -451,17 +451,18 @@ calDavCalendar.prototype = {
             this.notifyOperationComplete(aListener,
                                          Components.results.NS_ERROR_FAILURE,
                                          Components.interfaces.calIOperationListener.ADD,
                                          aItem.id,
                                          "Can't set ID on non-mutable item to addItem");
             return;
         }
 
-        var locationPath = this.getItemLocationPath(aItem);
+        let parentItem = aItem.parentItem;
+        var locationPath = this.getItemLocationPath(parentItem);
         var itemUri = this.makeUri(locationPath);
         LOG("CalDAV: itemUri.spec = " + itemUri.spec);
 
         var addListener = {};
         var thisCalendar = this;
         addListener.onStreamComplete =
             function onPutComplete(aLoader, aContext, aStatus, aResultLength,
                                    aResult) {
@@ -481,28 +482,27 @@ calDavCalendar.prototype = {
             //
             if (status == 201 || status == 204) {
                 LOG("CalDAV: Item added successfully");
 
                 // Some CalDAV servers will modify items on PUT (add X-props,
                 // for instance) so we'd best re-fetch in order to know
                 // the current state of the item
                 // Observers will be notified in getUpdatedItem()
-                thisCalendar.getUpdatedItem(aItem, aListener);
+                thisCalendar.getUpdatedItem(parentItem, aListener);
             } else {
                 if (status > 999) {
                     status = "0x" + status.toString(16);
                 }
                 LOG("CalDAV: Unexpected status adding item: " + status);
                 thisCalendar.reportDavError(Components.interfaces.calIErrors.DAV_PUT_ERROR);
             }
         };
 
-        aItem.calendar = this.superCalendar;
-        aItem.generation = 1;
+        parentItem.calendar = this.superCalendar;
 
         var httpchannel = calPrepHttpChannel(itemUri,
                                              this.getSerializedItem(aItem),
                                              "text/calendar; charset=utf-8",
                                              this);
 
 
         if (!aIgnoreEtag) {
--- a/calendar/providers/memory/calMemoryCalendar.js
+++ b/calendar/providers/memory/calMemoryCalendar.js
@@ -147,20 +147,21 @@ calMemoryCalendar.prototype = {
                                              Components.interfaces.calIErrors.DUPLICATE_ID,
                                              Components.interfaces.calIOperationListener.ADD,
                                              aItem.id,
                                              "ID already exists for addItem");
                 return;
             }
         }
 
-        aItem.calendar = this.superCalendar;
+        let parentItem = aItem.parentItem
+        parentItem.calendar = this.superCalendar;
 
-        aItem.makeImmutable();
-        this.mItems[aItem.id] = aItem;
+        parentItem.makeImmutable();
+        this.mItems[aItem.id] = parentItem;
 
         // notify the listener
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.ADD,
                                      aItem.id,
                                      aItem);
         // notify observers
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -403,23 +403,16 @@ calStorageCalendar.prototype = {
         if (this.readOnly) {
             this.notifyOperationComplete(aListener,
                                          Components.interfaces.calIErrors.CAL_IS_READONLY,
                                          Components.interfaces.calIOperationListener.ADD,
                                          null,
                                          "Calendar is readonly");
             return;
         }
-        // Ensure that we're looking at the base item
-        // if we were given an occurrence.  Later we can
-        // optimize this.
-        if (aItem.parentItem != aItem) {
-            aItem.parentItem.recurrenceInfo.modifyException(aItem, false);
-        }
-        aItem = aItem.parentItem;
 
         if (aItem.id == null) {
             // is this an error?  Or should we generate an IID?
             aItem.id = getUUID();
         } else {
             var olditem = this.getItemById(aItem.id);
             if (olditem) {
                 if (this.relaxedMode) {
@@ -431,20 +424,21 @@ calStorageCalendar.prototype = {
                                                  Components.interfaces.calIOperationListener.ADD,
                                                  aItem.id,
                                                  "ID already exists for addItem");
                     return;
                 }
             }
         }
 
-        aItem.calendar = this.superCalendar;
-        aItem.makeImmutable();
+        let parentItem = aItem.parentItem;
+        parentItem.calendar = this.superCalendar;
+        parentItem.makeImmutable();
 
-        this.flushItem (aItem, null);
+        this.flushItem(parentItem, null);
 
         // notify the listener
         this.notifyOperationComplete(aListener,
                                      Components.results.NS_OK,
                                      Components.interfaces.calIOperationListener.ADD,
                                      aItem.id,
                                      aItem);
 
@@ -2254,19 +2248,20 @@ calStorageCalendar.prototype = {
             ip.time_created = tmp.nativeTime;
         if ((tmp = item.getProperty("LAST-MODIFIED")))
             ip.last_modified = tmp.nativeTime;
 
         ip.title = item.getProperty("SUMMARY");
         ip.priority = item.getProperty("PRIORITY");
         ip.privacy = item.getProperty("CLASS");
         ip.ical_status = item.getProperty("STATUS");
-
-        if (!item.parentItem)
-            ip.event_stamp = item.stampTime.nativeTime;
+        tmp = item.stampTime;
+        if (tmp) {
+            ip.event_stamp = tmp.nativeTime;
+        }
 
         if (item.alarmOffset) {
             ip.alarm_offset = item.alarmOffset.inSeconds;
             ip.alarm_related = item.alarmRelated;
         }
         if (item.alarmLastAck) {
             ip.alarm_last_ack = item.alarmLastAck.nativeTime;
         }
--- a/calendar/sunbird/app/profile/sunbird.js
+++ b/calendar/sunbird/app/profile/sunbird.js
@@ -64,16 +64,22 @@ pref("calendar.view.daystarthour", 8);
 pref("calendar.view.dayendhour", 17);
 pref("calendar.view.visiblehours", 9);
 pref("calendar.weeks.inview", 4);
 pref("calendar.previousweeks.inview", 0);
 
 // default transparency of allday items; could be switched to e.g. "OPAQUE":
 pref("calendar.allday.defaultTransparency", "TRANSPARENT");
 
+// whether "notify" is checked by default when creating new events/todos with attendees
+pref("calendar.itip.notify", true);
+
+// whether the organizer propagates replies of attendees
+pref("calendar.itip.notify-replies", false);
+
 // whether CalDAV (experimental) scheduling is enabled or not.
 pref("calendar.caldav.sched.enabled", false);
 
 // pref("startup.homepage_override_url","chrome://browser-region/locale/region.properties");
 pref("general.startup.calendar", true);
 
 pref("toolkit.defaultChromeURI","chrome://sunbird/content/calendar.xul");
 pref("browser.hiddenWindowChromeURL", "chrome://sunbird/content/hiddenWindow.xul");