From ba509eecbb934b8b629fd23899b632374fc79e91 Mon Sep 17 00:00:00 2001 From: "ctalbert.moz%gmail.com" Date: Wed, 7 Mar 2007 20:53:35 +0000 Subject: [PATCH] bug 334685 adding iMIP/iTIP support r=dbo git-svn-id: svn://10.0.0.236/trunk@221473 18797224-902f-48f8-a5cc-f745e15eee43 --- .../base/public/calIItipProcessor.idl | 55 +++ .../base/public/calIItipTransport.idl | 103 +++++ mozilla/calendar/base/src/calItipProcessor.js | 384 ++++++++++++++++ mozilla/calendar/itip/Makefile.in | 49 ++ .../calendar/itip/calItipEmailTransport.js | 428 ++++++++++++++++++ 5 files changed, 1019 insertions(+) create mode 100644 mozilla/calendar/base/public/calIItipProcessor.idl create mode 100644 mozilla/calendar/base/public/calIItipTransport.idl create mode 100644 mozilla/calendar/base/src/calItipProcessor.js create mode 100644 mozilla/calendar/itip/Makefile.in create mode 100644 mozilla/calendar/itip/calItipEmailTransport.js diff --git a/mozilla/calendar/base/public/calIItipProcessor.idl b/mozilla/calendar/base/public/calIItipProcessor.idl new file mode 100644 index 00000000000..b3efcaf5e7d --- /dev/null +++ b/mozilla/calendar/base/public/calIItipProcessor.idl @@ -0,0 +1,55 @@ +/* -*- 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 + * Eva Or + * Matthew Willis + * + * 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); +}; diff --git a/mozilla/calendar/base/public/calIItipTransport.idl b/mozilla/calendar/base/public/calIItipTransport.idl new file mode 100644 index 00000000000..786a7618d86 --- /dev/null +++ b/mozilla/calendar/base/public/calIItipTransport.idl @@ -0,0 +1,103 @@ +/* -*- 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 + * + * 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 calIAttendee; +interface calIDateTime; + +/** + * calIItipTransport is a generic transport interface that is implemented + * by transports (eg: email, XMPP, etc.) wishing to send calIItipItems + */ +[scriptable, uuid(d9e439da-1014-42f9-be40-aa7e878b8a7d)] +interface calIItipTransport : nsISupports +{ + /** + * Default identity for me within this transport. For example, this is + * your default email address in Thunderbird. + */ + readonly attribute AUTF8String defaultIdentity; + + /** + * Sending identity. This can be set to change the "sender" identity from + * defaultIdentity above. + */ + attribute AUTF8String senderAddress; + + /** + * Type of the transport: email, xmpp, etc. + */ + readonly attribute AUTF8String type; + + /** + * Pass the transport an itipItem and have it figure out what to do with + * it based on the itipItem's methods. + */ + void simpleSendResponse(in calIItipItem item); + + /** + * Sends a calIItipItem to the recipients using the specified title and + * alternative representation. If a calIItipItem is attached, then an ICS + * representation of those objects are generated and attached to the email. + * If the calIItipItem is null, then the item(s) is sent without any + * text/calendar mime part. + * @param count size of recipient array + * @param recipientArray array of recipients + * @param title title of the exchange + * @param altrep an alternative representation (usually text) + in case the recipient cannot handle the + text/calendar mime-type + * @param calIItipItem set of calIItems encapsulated as calIItipItems + */ + void sendItems(in PRUint32 count, + [array, size_is(count)] in calIAttendee recipientArray, + in AUTF8String title, + in AUTF8String altrep, + in calIItipItem item); + + /** + * Checks the configured transport for any incoming messages with + * text/calendar mime parts (invitations) that are more recent than the + * specified date. It returns a calIItipItem object containing the found + * items + * @param searchStart Defines on what date to start the search. + * @return calIItipItem The items it finds in a calIItipItem object. + */ + calIItipItem checkForInvitations(in calIDateTime searchStart); +}; diff --git a/mozilla/calendar/base/src/calItipProcessor.js b/mozilla/calendar/base/src/calItipProcessor.js new file mode 100644 index 00000000000..34aa3d8c459 --- /dev/null +++ b/mozilla/calendar/base/src/calItipProcessor.js @@ -0,0 +1,384 @@ +/* -*- 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 + * Eva Or + * Matthew Willis + * + * 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 = [ + Ci.nsIClassInfo, + Ci.nsISupports, + Ci.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: Ci.nsIProgrammingLanguage.JAVASCRIPT, + flags: 0, + + QueryInterface: function cipQI(aIid) { + if (!aIid.equals(Ci.nsIClassInfo) && !aIid.equals(Ci.nsISupports) && + !aIid.equals(Ci.calIItipProcessor)) + { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + + return this; + }, + + mIsUserInvolved: false, + get isUserInvolved() { + return this.mIsUserInvolved; + }, + set isUserInvolved(aValue) { + return (this.mIsUserInvolved = aValue); + }, + + mRecvMethod: null, + mRespMethod: null, + mAutoResponse: null, + mTargetCalendar: null, + mCalItem: null, + mCalItemType: null, + + /** + * 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(); + + this.mRecvMethod = respItipItem.receivedMethod; + respItipItem.responseMethod = this._suggestResponseMethod(respItipItem); + this.mRespMethod = respItipItem.responseMethod; + + this.mAutoResponse = respItipItem.autoResponse; + this.mTargetCalendar = respItipItem.targetCalendar; + + // XXX Support for transports other than email go here. + // For now we just assume it's email. + var transport = Cc["@mozilla.org/calendar/itip-transport;1?type=email"]. + createInstance(Ci.calIItipTransport); + + // Sanity checks using the first item + var itemList = respItipItem.getItemList({ }); + this.mCalItem = itemList[0]; + if (!this.mCalItem) { + throw new Error ("processItipItem: " + + "getFirstItem() found no items!"); + } + + this.mCalItemType = this._getCalItemType(this.mCalItem); + if (!this.mCalItemType) { + throw new Error ("processItipItem: " + + "_getCalItemType() found no item type!"); + } + + // Sanity check that mRespMethod is a valid response per the spec. + if (!this._isValidResponseMethod()) { + throw new Error ("processItipItem: " + + "_isValidResponseMethod() found an invalid " + + "response method: " + mRespMethod); + } + + var i = 0; + while (this.mCalItem) { + switch (this.mRecvMethod) { + case "REQUEST": + // Only add to calendar if we accepted invite + var replyStat = this._getReplyStatus(this.mCalItem, + transport.defaultIdentity); + if (replyStat != "DECLINED") { + if (!this._processCalendarAction(this.mCalItem, + CAL_ITIP_PROC_ADD_OP, + aListener)) + { + throw new Error ("processItipItem: " + + "_processCalendarAction failed!"); + } + } + break; + + case "PUBLISH": + 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: " + + this.mRecvMethod); + } + ++i; + this.mCalItem = itemList[i]; + } + + // 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 (this.mRespMethod == "REPLY") { + // Get the id that represents me. + // XXX Note that this doesn't take into consideration invitations + // sent to email aliases. (ex: lilmatt vs mwillis) + var me; + var idPrefix; + if (transport.type == "email") { + me = transport.defaultIdentity; + idPrefix = "mailto:"; + } else { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + } + + for (var j=0; j < itemList.length; ++j) { + var respCalItem = itemList[j]; + var attendees = respCalItem.getAttendees({}); + + for each (var attendee in attendees) { + // Leave the ORGANIZER alone. + if (!attendee.isOrganizer) { + // example: mailto:joe@domain.com + var meString = idPrefix + me; + if (attendee.id.toLowerCase() != meString.toLowerCase()) { + respCalItem.removeAttendee(attendee); + } + } + } + } + } + + // Send the appropriate response + transport.simpleSendResponse(respItipItem); + + // Yay it worked! + // XXX TODO: Actually tie this to success/failure of the transport + return true; + }, + + + /** + * @return integer The next recommended iTIP state. + */ + _suggestResponseMethod: function cipSRM() { + switch (this.mRecvMethod) { + case "REQUEST": + return "REPLY"; + + case "REFRESH": + case "COUNTER": + return "REQUEST"; + + case "PUBLISH": + case "REPLY": + case "ADD": + case "CANCEL": + case "DECLINECOUNTER": + return this.mRecvMethod; + + default: + throw new Error("_suggestResponseMethod: " + + "Received unknown method: " + + this.mRecvMethod); + } + }, + + /** + * 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() { + switch (this.mRecvMethod) { + // 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 (!(this.mRespMethod == "ADD" || + (this.mRespMethod == "REFRESH" && + // REFRESH is not a valid response to an ADD for VJOURNAL + (this.mCalItemType == Ci.calIEvent || + this.mCalItemType == Ci.calITodo)))) + { + return false; + } + break; + + // Valid responses to COUNTER are REQUEST or DECLINECOUNTER. + case "COUNTER": + if (!(this.mRespMethod == "REQUEST" || + this.mRespMethod == "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 (!(this.mRespMethod == "REPLY" || + this.mRespMethod == "REQUEST" || + this.mRespMethod == "COUNTER")) + { + return false; + } + break; + + // REFRESH should respond with a request + case "REFRESH": + if (this.mRespMethod == "REQUEST") { + return false; + } + break; + + // The rest are easiest represented as: + // (mRecvMethod != mRespMethod) == return false + case "PUBLISH": + case "CANCEL": + case "REPLY": + case "PUBLISH": + case "DECLINECOUNTER": + if (this.mRespMethod != this.mRecvMethod) { + return false; + } + break; + + default: + throw new Error("_isValidResponseMethod: " + + "Received unknown method: " + + this.mRecvMethod); + } + + // 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() { + if (this.mCalItem instanceof Ci.calIEvent) { + return Ci.calIEvent; + } else if (aCalItem instanceof Ci.calITodo) { + return Ci.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, aOperation, aListener) { + switch (aOperation) { + case CAL_ITIP_PROC_ADD_OP: + // We assume all adds take place on the target calendar + this.mTargetCalendar.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: + case CAL_ITIP_PROC_DELETE_OP: + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + default: + throw new Error("_processCalendarAction: " + + "Undefined Operator: " + aOperator); + } + + // If you got to here, something went horribly, horribly wrong. + return false; + }, + + /** + * Helper function to get the PARTSTAT value from the given calendar + * item for the specified attendee + */ + _getReplyStatus: function cipGRS(aCalItem, aAttendeeId) { + var idPrefix = "mailto:"; + var replyStatus; + + // example: mailto:joe@domain.com + var idString = idPrefix + aAttendeeId; + var attendee = aCalItem.getAttendeeById(idString); + if (attendee) { + replyStatus = attendee.participationStatus; + } + return replyStatus; + } +} diff --git a/mozilla/calendar/itip/Makefile.in b/mozilla/calendar/itip/Makefile.in new file mode 100644 index 00000000000..62ecc2b672f --- /dev/null +++ b/mozilla/calendar/itip/Makefile.in @@ -0,0 +1,49 @@ +# +# ***** 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. +# Portions created by the Initial Developer are Copyright (C) 2007 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Clint Talbert +# +# 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 = calItipEmailTransport + +EXTRA_COMPONENTS = calItipEmailTransport.js + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/calendar/itip/calItipEmailTransport.js b/mozilla/calendar/itip/calItipEmailTransport.js new file mode 100644 index 00000000000..a1372ac5679 --- /dev/null +++ b/mozilla/calendar/itip/calItipEmailTransport.js @@ -0,0 +1,428 @@ +/* ***** 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. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Clint Talbert + * Matthew Willis + * + * 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 ***** */ + +/** + * Constructor of calItipEmailTransport object + */ +function calItipEmailTransport() { + this.wrappedJSObject = this; + this._initEmailTransport(); +} + +calItipEmailTransport.prototype = { + + QueryInterface: function cietQI(aIid) { + if (!aIid.equals(Components.interfaces.nsISupports) && + !aIid.equals(Components.interfaces.calIItipTransport)) + { + throw Components.results.NS_ERROR_NO_INTERFACE; + } + + return this; + }, + + mHasXpcomMail: false, + mAccountMgrSvc: null, + mDefaultAccount: null, + mDefaultSmtpServer: null, + + mDefaultIdentity: null, + get defaultIdentity() { + return this.mDefaultIdentity.email; + }, + + mSenderAddress: null, + get senderAddress() { + return this.mSenderAddress; + }, + set senderAddress(aValue) { + return (this.mSenderAddress = aValue); + }, + + get type() { + return "email"; + }, + + /** + * Pass the transport an itipItem and have it figure out what to do with + * it based on the itipItem's methods. + */ + simpleSendResponse: function cietSSR(aItem) { + var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]. + getService(Components.interfaces.nsIStringBundleService); + var sb = sbs.createBundle("chrome://lightning/locale/lightning.properties"); + + var itemList = aItem.getItemList({ }); + + // Get my participation status + var myPartStat; + var foundMyself = false; + var attendee = itemList[0].getAttendeeById("mailto:" + + this.mDefaultIdentity.email); + if (attendee) { + myPartStat = attendee.participationStatus; + foundMyself = true; + } + + if (!foundMyself) { + // I didn't find myself in the attendee list. + // + // This can happen when invitations are sent to email aliases + // instead of mDefaultIdentity.email (ex: lilmatt vs. mwillis), + // or if an invitation is sent to a listserv. + // + // We'll need to make more decisions regarding how to handle this + // in the future. (ex: Prompt? Find myself in list?) For now, we + // just don't send a response. + return; + } + + var name = this.mDefaultIdentity.email; + if (this.mDefaultIdentity.fullName) { + name = this.mDefaultIdentity.fullName + " <" + name + ">"; + } + + var summary; + if (itemList[0].getProperty("SUMMARY")) { + summary = itemList[0].getProperty("SUMMARY"); + } else { + summary = ""; + } + var subj = sb.formatStringFromName("itipReplySubject", [summary], 1); + + // Generate proper body from my participation status + var body; + dump("\n\nthis is partstat: " + myPartStat + "\n"); + if (myPartStat == "DECLINED") { + body = sb.formatStringFromName("itipReplyBodyDecline", + [name], 1); + } else { + body = sb.formatStringFromName("itipReplyBodyAccept", + [name], 1); + } + + var recipients = [itemList[0].organizer]; + + this.sendItems(recipients.length, recipients, subj, body, aItem); + }, + + sendItems: function cietSI(aCount, aRecipients, aSubject, aBody, aItem) { + LOG("sendItems: Sending Email..."); + if (this.mHasXpcomMail) { + this._sendXpcomMail(aCount, aRecipients, aSubject, aBody, aItem); + } else { + // Sunbird case: Call user's default mailer on system. + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + } + }, + + checkForInvitations: function cietCFI(searchStart) { + // We only need to do trigger a check for incoming invitations if we + // are not Thunderbird. + if (!this.mHasXpcomMail) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + } + }, + + _initEmailTransport: function cietIES() { + try { + this.mAccountMgrSvc = + Components.classes["@mozilla.org/messenger/account-manager;1"]. + getService(Components.interfaces.nsIMsgAccountManager); + + this.mDefaultAccount = this.mAccountMgrSvc.defaultAccount; + this.mDefaultIdentity = this.mDefaultAccount.defaultIdentity; + + var smtpSvc = Components.classes["@mozilla.org/messengercompose/smtp;1"]. + getService(Components.interfaces.nsISmtpService); + this.mSmtpServer = smtpSvc.defaultServer; + + } catch (ex) { + // Then we must resort to operating system specific means + this.mHasXpcomMail = false; + } + + // But if none of that threw then we can send an email through XPCOM. + this.mHasXpcomMail = true; + LOG("initEmailService: hasXpcomMail: " + this.mHasXpcomMail); + }, + + _sendXpcomMail: function cietSXM(aCount, aToList, aSubject, aBody, aItem) { + // Save calItipItem to a temporary file. + LOG("sendXpcomMail: Creating temp file for attachment."); + var msgAttachment = Components.classes["@mozilla.org/messengercompose/attachment;1"]. + createInstance(Components.interfaces.nsIMsgAttachment); + msgAttachment.url = this._createTempIcsFile(aItem); + if (!msgAttachment.url) { + LOG("sendXpcomMail: No writeable path in profile!"); + // XXX Is there an "out of disk space" error? + throw Components.results.NS_ERROR_FAILURE; + } + + msgAttachment.name = "calendar.ics"; + msgAttachment.contentType = "text/calendar"; + msgAttachment.contentTypeParam = "method=" + aItem.responseMethod; + // Destroy the attachment after sending + msgAttachment.temporary = true; + + // compose fields for message + var composeFields = Components.classes["@mozilla.org/messengercompose/composefields;1"]. + createInstance(Components.interfaces.nsIMsgCompFields); + composeFields.useMultipartAlternative = true; + composeFields.characterSet = "UTF-8"; + // TODO: xxx make this use the currently selected mail account, if + // possible, and default to the default account if the selection + // is unclear. + var toList = ""; + for each (var recipient in aToList) { + // Strip leading "mailto:" if it exists. + var rId = recipient.id.replace(/^mailto:/i, ""); + + // Prevent trailing commas. + if (toList.length > 0) { + toList += ","; + } + + // Add this recipient id to the list. + toList += rId; + } + composeFields.to = toList; + composeFields.from = this.mDefaultIdentity.email; + composeFields.replyTo = this.mDefaultIdentity.replyTo; + composeFields.subject = aSubject; + composeFields.body = aBody; + composeFields.addAttachment(msgAttachment); + + // Message paramaters + var composeParams = Components.classes["@mozilla.org/messengercompose/composeparams;1"]. + createInstance(Components.interfaces.nsIMsgComposeParams); + composeParams.composeFields = composeFields; + // TODO: xxx: Make this a pref or read the default pref + composeParams.format = Components.interfaces.nsIMsgCompFormat.PlainText; + composeParams.type = Components.interfaces.nsIMsgCompType.New; + + var composeService = Components.classes["@mozilla.org/messengercompose;1"]. + getService(Components.interfaces.nsIMsgComposeService); + switch (aItem.autoResponse) { + case (Components.interfaces.calIItipItem.USER): + LOG("sendXpcomMail: Found USER autoResponse type."); + + // Open a compose window + var url = "chrome://messenger/content/messengercompose/messengercompose.xul" + composeService.OpenComposeWindowWithParams(url, composeParams); + break; + case (Components.interfaces.calIItipItem.AUTO): + LOG("sendXpcomMail: Found AUTO autoResponse type."); + + var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Components.interfaces.nsIWindowWatcher); + var win = ww.activeWindow; + var msgCompose = Components.classes["@mozilla.org/messengercompose/compose;1"]. + createInstance(Components.interfaces.nsIMsgCompose); + msgCompose.Initialize(win, composeParams); + // Fourth param is message window, fifth param is progress. + // TODO: xxx decide on whether or not we want progress window + msgCompose.SendMsg(Components.interfaces.nsIMsgCompDeliverMode.Now, + this.mDefaultIdentity, + this.mDefaultAccount.key, + null, + null); + break; + case (Components.interfaces.calIItipItem.NONE): + LOG("sendXpcomMail: Found NONE autoResponse type."); + + // No response + break; + default: + // Unknown autoResponse type + throw new Error("sendXpcomMail: " + + "Unknown autoResponse type: " + + aItem.autoResponse); + } + }, + + _createTempIcsFile: function cietCTIF(aItem) { + var path; + var itemList = aItem.getItemList({ }); + // This is a workaround until bug 353369 is fixed. + // Without it, we cannot roundtrip the METHOD property, so we must + // re-add it to the ICS data as we serialize it. + // + // Look at the implicit assumption in the code at: + // http://lxr.mozilla.org/seamonkey/source/calendar/base/src/calEvent.js#162 + // and it's easy to see why. + itemList[0].setProperty("METHOD", aItem.responseMethod); + var calText = ""; + for (var i = 0; i < itemList.length; i++) { + calText += itemList[i].icalString; + } + + LOG("ICS to be emailed: " + calText); + + try { + var dirUtils = Components.classes["@mozilla.org/file/directory_service;1"]. + createInstance(Components.interfaces.nsIProperties); + var tempFile = dirUtils.get("TmpD", Components.interfaces.nsIFile); + tempFile.append("itipTemp"); + tempFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600); + + var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]. + createInstance(Components.interfaces.nsIFileOutputStream); + var utf8CalText = this._convertFromUnicode("UTF-8", calText); + + // Let's write the file - constants from file-utils.js + const MODE_WRONLY = 0x02; + const MODE_CREATE = 0x08; + const MODE_TRUNCATE = 0x20; + outputStream.init(tempFile, + MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, + 0600, 0); + outputStream.write(utf8CalText, utf8CalText.length); + outputStream.close(); + + var ioService = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + path = ioService.newFileURI(tempFile).spec; + } catch (ex) { + LOG("createTempItipFile failed! " + ex); + path = null; + } + LOG("createTempItipFile path: " + path); + return path; + }, + + _convertFromUnicode: function cietCFU(aCharset, aSrc) { + var unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + unicodeConverter.charset = aCharset; + return unicodeConverter.ConvertFromUnicode(aSrc); + } +}; + + +/**** + **** module registration + ****/ + +var calItipEmailTransportModule = { + + mCID: Components.ID("{d4d7b59e-c9e0-4a7a-b5e8-5958f85515f0}"), + mContractID: "@mozilla.org/calendar/itip-transport;1?type=email", + + mUtilsLoaded: false, + loadUtils: function itipEmailLoadUtils() { + if (this.mUtilsLoaded) + return; + + const jssslContractID = "@mozilla.org/moz/jssubscript-loader;1"; + const jssslIID = Components.interfaces.mozIJSSubScriptLoader; + + const iosvcContractID = "@mozilla.org/network/io-service;1"; + const iosvcIID = Components.interfaces.nsIIOService; + + var loader = Components.classes[jssslContractID].getService(jssslIID); + var iosvc = Components.classes[iosvcContractID].getService(iosvcIID); + + // Note that unintuitively, __LOCATION__.parent == . + // We expect to find utils in ./../js + var appdir = __LOCATION__.parent.parent; + appdir.append("js"); + var scriptName = "calUtils.js"; + + var f = appdir.clone(); + f.append(scriptName); + + try { + var fileurl = iosvc.newFileURI(f); + loader.loadSubScript(fileurl.spec, this.__parent__.__parent__); + } catch (e) { + dump("Error while loading " + fileurl.spec + "\n"); + throw e; + } + + this.mUtilsLoaded = true; + }, + + registerSelf: function (compMgr, fileSpec, location, type) { + compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + compMgr.registerFactoryLocation(this.mCID, + "Calendar iTIP Email Transport", + this.mContractID, + fileSpec, + location, + type); + }, + + getClassObject: function (compMgr, cid, iid) { + if (!cid.equals(this.mCID)) + throw Components.results.NS_ERROR_NO_INTERFACE; + + if (!iid.equals(Components.interfaces.nsIFactory)) + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + + this.loadUtils(); + + return this.mFactory; + }, + + mFactory: { + createInstance: function (outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return (new calItipEmailTransport()).QueryInterface(iid); + } + }, + + canUnload: function(compMgr) { + return true; + } +}; + +function NSGetModule(compMgr, fileSpec) { + return calItipEmailTransportModule; +} + +function LOG(aString) { + if (!getPrefSafe("calendar.itip.email.log", false)) { + return; + } + var consoleService = Components.classes["@mozilla.org/consoleservice;1"]. + getService(Components.interfaces.nsIConsoleService); + consoleService.logStringMessage(aString); + dump(aString + "\n"); +}