1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is Simdesk Technologies code.
15 *
16 * The Initial Developer of the Original Code is
17 * Simdesk Technologies.
18 * Portions created by the Initial Developer are Copyright (C) 2007
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Contributor(s):
22 * Clint Talbert <ctalbert.moz@gmail.com>
23 * Matthew Willis <lilmatt@mozilla.com>
24 *
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
36 *
37 * ***** END LICENSE BLOCK ***** */
38
39 /**
40 * Constructor of calItipEmailTransport object
41 */
calItipEmailTransport
42 function calItipEmailTransport() {
43 this.wrappedJSObject = this;
44 this._initEmailTransport();
45 }
46
47 calItipEmailTransport.prototype = {
48
cietQI
49 QueryInterface: function cietQI(aIid) {
50 if (!aIid.equals(Components.interfaces.nsISupports) &&
51 !aIid.equals(Components.interfaces.calIItipTransport))
52 {
53 throw Components.results.NS_ERROR_NO_INTERFACE;
54 }
55
56 return this;
57 },
58
59 mHasXpcomMail: false,
60 mAccountMgrSvc: null,
61 mDefaultAccount: null,
62 mDefaultSmtpServer: null,
63
64 mDefaultIdentity: null,
get_defaultIdentity
65 get defaultIdentity() {
66 return this.mDefaultIdentity.email;
67 },
68
69 mSenderAddress: null,
get_senderAddress
70 get senderAddress() {
71 return this.mSenderAddress;
72 },
set_senderAddress
73 set senderAddress(aValue) {
74 return (this.mSenderAddress = aValue);
75 },
76
get_type
77 get type() {
78 return "email";
79 },
80
81 /**
82 * Pass the transport an itipItem and have it figure out what to do with
83 * it based on the itipItem's methods.
84 */
cietSSR
85 simpleSendResponse: function cietSSR(aItem) {
86 var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"].
87 getService(Components.interfaces.nsIStringBundleService);
88 var sb = sbs.createBundle("chrome://lightning/locale/lightning.properties");
89
90 var itemList = aItem.getItemList({ });
91
92 // Get my participation status
93 var myPartStat;
94 var foundMyself = false;
95 var attendee = itemList[0].getAttendeeById("mailto:" +
96 this.mDefaultIdentity.email);
97 if (attendee) {
98 myPartStat = attendee.participationStatus;
99 foundMyself = true;
100 }
101
102 if (!foundMyself) {
103 // I didn't find myself in the attendee list.
104 //
105 // This can happen when invitations are sent to email aliases
106 // instead of mDefaultIdentity.email (ex: lilmatt vs. mwillis),
107 // or if an invitation is sent to a listserv.
108 //
109 // We'll need to make more decisions regarding how to handle this
110 // in the future. (ex: Prompt? Find myself in list?) For now, we
111 // just don't send a response.
112 return;
113 }
114
115 var name = this.mDefaultIdentity.email;
116 if (this.mDefaultIdentity.fullName) {
117 name = this.mDefaultIdentity.fullName + " <" + name + ">";
118 }
119
120 var summary;
121 if (itemList[0].getProperty("SUMMARY")) {
122 summary = itemList[0].getProperty("SUMMARY");
123 } else {
124 summary = "";
125 }
126 var subj = sb.formatStringFromName("itipReplySubject", [summary], 1);
127
128 // Generate proper body from my participation status
129 var body;
130 dump("\n\nthis is partstat: " + myPartStat + "\n");
131 if (myPartStat == "DECLINED") {
132 body = sb.formatStringFromName("itipReplyBodyDecline",
133 [name], 1);
134 } else {
135 body = sb.formatStringFromName("itipReplyBodyAccept",
136 [name], 1);
137 }
138
139 var recipients = [itemList[0].organizer];
140
141 this.sendItems(recipients.length, recipients, subj, body, aItem);
142 },
143
cietSI
144 sendItems: function cietSI(aCount, aRecipients, aSubject, aBody, aItem) {
145 LOG("sendItems: Sending Email...");
146 if (this.mHasXpcomMail) {
147 this._sendXpcomMail(aCount, aRecipients, aSubject, aBody, aItem);
148 } else {
149 // Sunbird case: Call user's default mailer on system.
150 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
151 }
152 },
153
cietCFI
154 checkForInvitations: function cietCFI(searchStart) {
155 // We only need to do trigger a check for incoming invitations if we
156 // are not Thunderbird.
157 if (!this.mHasXpcomMail) {
158 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
159 }
160 },
161
cietIES
162 _initEmailTransport: function cietIES() {
163 this.mHasXpcomMail = true;
164
165 try {
166 this.mAccountMgrSvc =
167 Components.classes["@mozilla.org/messenger/account-manager;1"].
168 getService(Components.interfaces.nsIMsgAccountManager);
169
170 var smtpSvc = Components.classes["@mozilla.org/messengercompose/smtp;1"].
171 getService(Components.interfaces.nsISmtpService);
172 this.mSmtpServer = smtpSvc.defaultServer;
173
174 this.mDefaultAccount = this.mAccountMgrSvc.defaultAccount;
175 this.mDefaultIdentity = this.mDefaultAccount.defaultIdentity;
176
177 if (!this.mDefaultIdentity) {
178 // If there isn't a default identity (i.e Local Folders is your
179 // default identity, then go ahead and use the first available
180 // identity.
181 var allIdentities = this.mAccountMgrSvc.allIdentities;
182 if (allIdentities.Count() > 0) {
183 this.mDefaultIdentity = allIdentities.GetElementAt(0)
184 .QueryInterface(Components.interfaces.nsIMsgIdentity);
185 } else {
186 // If there are no identities, then we are in the same
187 // situation as if we didn't have Xpcom Mail.
188 this.mHasXpcomMail = false;
189 LOG("initEmailService: No XPCOM Mail available: " + e);
190 }
191 }
192 } catch (ex) {
193 // Then we must resort to operating system specific means
194 this.mHasXpcomMail = false;
195 }
196 },
197
cietSXM
198 _sendXpcomMail: function cietSXM(aCount, aToList, aSubject, aBody, aItem) {
199 // Save calItipItem to a temporary file.
200 LOG("sendXpcomMail: Creating temp file for attachment.");
201 var msgAttachment = Components.classes["@mozilla.org/messengercompose/attachment;1"].
202 createInstance(Components.interfaces.nsIMsgAttachment);
203 msgAttachment.url = this._createTempIcsFile(aItem);
204 if (!msgAttachment.url) {
205 LOG("sendXpcomMail: No writeable path in profile!");
206 // XXX Is there an "out of disk space" error?
207 throw Components.results.NS_ERROR_FAILURE;
208 }
209
210 msgAttachment.name = "calendar.ics";
211 msgAttachment.contentType = "text/calendar";
212 msgAttachment.contentTypeParam = "method=" + aItem.responseMethod;
213 // Destroy the attachment after sending
214 msgAttachment.temporary = true;
215
216 // compose fields for message
217 var composeFields = Components.classes["@mozilla.org/messengercompose/composefields;1"].
218 createInstance(Components.interfaces.nsIMsgCompFields);
219 composeFields.useMultipartAlternative = true;
220 composeFields.characterSet = "UTF-8";
221 // TODO: xxx make this use the currently selected mail account, if
222 // possible, and default to the default account if the selection
223 // is unclear.
224 var toList = "";
225 for each (var recipient in aToList) {
226 // Strip leading "mailto:" if it exists.
227 var rId = recipient.id.replace(/^mailto:/i, "");
228
229 // Prevent trailing commas.
230 if (toList.length > 0) {
231 toList += ",";
232 }
233
234 // Add this recipient id to the list.
235 toList += rId;
236 }
237 composeFields.to = toList;
238 composeFields.from = this.mDefaultIdentity.email;
239 composeFields.replyTo = this.mDefaultIdentity.replyTo;
240 composeFields.subject = aSubject;
241 composeFields.body = aBody;
242 composeFields.addAttachment(msgAttachment);
243
244 // Message paramaters
245 var composeParams = Components.classes["@mozilla.org/messengercompose/composeparams;1"].
246 createInstance(Components.interfaces.nsIMsgComposeParams);
247 composeParams.composeFields = composeFields;
248 // TODO: xxx: Make this a pref or read the default pref
249 composeParams.format = Components.interfaces.nsIMsgCompFormat.PlainText;
250 composeParams.type = Components.interfaces.nsIMsgCompType.New;
251
252 var composeService = Components.classes["@mozilla.org/messengercompose;1"].
253 getService(Components.interfaces.nsIMsgComposeService);
254 switch (aItem.autoResponse) {
255 case (Components.interfaces.calIItipItem.USER):
256 LOG("sendXpcomMail: Found USER autoResponse type.");
257
258 // Open a compose window
259 var url = "chrome://messenger/content/messengercompose/messengercompose.xul"
260 composeService.OpenComposeWindowWithParams(url, composeParams);
261 break;
262 case (Components.interfaces.calIItipItem.AUTO):
263 LOG("sendXpcomMail: Found AUTO autoResponse type.");
264
265 var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"].
266 getService(Components.interfaces.nsIWindowWatcher);
267 var win = ww.activeWindow;
268 var msgCompose = Components.classes["@mozilla.org/messengercompose/compose;1"].
269 createInstance(Components.interfaces.nsIMsgCompose);
270 msgCompose.Initialize(win, composeParams);
271 // Fourth param is message window, fifth param is progress.
272 // TODO: xxx decide on whether or not we want progress window
273 msgCompose.SendMsg(Components.interfaces.nsIMsgCompDeliverMode.Now,
274 this.mDefaultIdentity,
275 this.mDefaultAccount.key,
276 null,
277 null);
278 break;
279 case (Components.interfaces.calIItipItem.NONE):
280 LOG("sendXpcomMail: Found NONE autoResponse type.");
281
282 // No response
283 break;
284 default:
285 // Unknown autoResponse type
286 throw new Error("sendXpcomMail: " +
287 "Unknown autoResponse type: " +
288 aItem.autoResponse);
289 }
290 },
291
cietCTIF
292 _createTempIcsFile: function cietCTIF(aItem) {
293 var path;
294 var itemList = aItem.getItemList({ });
295 // This is a workaround until bug 353369 is fixed.
296 // Without it, we cannot roundtrip the METHOD property, so we must
297 // re-add it to the ICS data as we serialize it.
298 //
299 // Look at the implicit assumption in the code at:
300 // http://lxr.mozilla.org/seamonkey/source/calendar/base/src/calEvent.js#162
301 // and it's easy to see why.
302 itemList[0].setProperty("METHOD", aItem.responseMethod);
303 var calText = "";
304 for (var i = 0; i < itemList.length; i++) {
305 calText += itemList[i].icalString;
306 }
307
308 LOG("ICS to be emailed: " + calText);
309
310 try {
311 var dirUtils = Components.classes["@mozilla.org/file/directory_service;1"].
312 createInstance(Components.interfaces.nsIProperties);
313 var tempFile = dirUtils.get("TmpD", Components.interfaces.nsIFile);
314 tempFile.append("itipTemp");
315 tempFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
316
317 var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
318 createInstance(Components.interfaces.nsIFileOutputStream);
319 var utf8CalText = this._convertFromUnicode("UTF-8", calText);
320
321 // Let's write the file - constants from file-utils.js
322 const MODE_WRONLY = 0x02;
323 const MODE_CREATE = 0x08;
324 const MODE_TRUNCATE = 0x20;
325 outputStream.init(tempFile,
326 MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE,
327 0600, 0);
328 outputStream.write(utf8CalText, utf8CalText.length);
329 outputStream.close();
330
331 var ioService = Components.classes["@mozilla.org/network/io-service;1"].
332 getService(Components.interfaces.nsIIOService);
333 path = ioService.newFileURI(tempFile).spec;
334 } catch (ex) {
335 LOG("createTempItipFile failed! " + ex);
336 path = null;
337 }
338 LOG("createTempItipFile path: " + path);
339 return path;
340 },
341
cietCFU
342 _convertFromUnicode: function cietCFU(aCharset, aSrc) {
343 var unicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
344 createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
345 unicodeConverter.charset = aCharset;
346 return unicodeConverter.ConvertFromUnicode(aSrc);
347 }
348 };
349
350 // nsIFactory
351 const calItipEmailTransportFactory = {
createInstance
352 createInstance: function (outer, iid) {
353 if (outer != null)
354 throw Components.results.NS_ERROR_NO_AGGREGATION;
355 return (new calItipEmailTransport()).QueryInterface(iid);
356 }
357 };
358
359 /****
360 **** module registration
361 ****/
362
363 var calItipEmailTransportModule = {
364
365 mCID: Components.ID("{d4d7b59e-c9e0-4a7a-b5e8-5958f85515f0}"),
366 mContractID: "@mozilla.org/calendar/itip-transport;1?type=email",
367
368 mUtilsLoaded: false,
itipEmailLoadUtils
369 loadUtils: function itipEmailLoadUtils() {
370 if (this.mUtilsLoaded)
371 return;
372
373 const jssslContractID = "@mozilla.org/moz/jssubscript-loader;1";
374 const jssslIID = Components.interfaces.mozIJSSubScriptLoader;
375
376 const iosvcContractID = "@mozilla.org/network/io-service;1";
377 const iosvcIID = Components.interfaces.nsIIOService;
378
379 var loader = Components.classes[jssslContractID].getService(jssslIID);
380 var iosvc = Components.classes[iosvcContractID].getService(iosvcIID);
381
382 // Note that unintuitively, __LOCATION__.parent == .
383 // We expect to find utils in ./../js
384 var appdir = __LOCATION__.parent.parent;
385 appdir.append("js");
386 var scriptName = "calUtils.js";
387
388 var f = appdir.clone();
389 f.append(scriptName);
390
391 try {
392 var fileurl = iosvc.newFileURI(f);
393 loader.loadSubScript(fileurl.spec, this.__parent__.__parent__);
394 } catch (e) {
395 dump("Error while loading " + fileurl.spec + "\n");
396 throw e;
397 }
398
399 this.mUtilsLoaded = true;
400 },
401
registerSelf
402 registerSelf: function (compMgr, fileSpec, location, type) {
403 compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
404 compMgr.registerFactoryLocation(this.mCID,
405 "Calendar iTIP Email Transport",
406 this.mContractID,
407 fileSpec,
408 location,
409 type);
410 },
411
getClassObject
412 getClassObject: function (compMgr, cid, iid) {
413 if (!cid.equals(this.mCID))
414 throw Components.results.NS_ERROR_NO_INTERFACE;
415
416 if (!iid.equals(Components.interfaces.nsIFactory))
417 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
418
419 this.loadUtils();
420
421 return calItipEmailTransportFactory;
422 },
423
canUnload
424 canUnload: function(compMgr) {
425 return true;
426 }
427 };
428
NSGetModule
429 function NSGetModule(compMgr, fileSpec) {
430 return calItipEmailTransportModule;
431 }
432
LOG
433 function LOG(aString) {
434 if (!getPrefSafe("calendar.itip.email.log", false)) {
435 return;
436 }
437 var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
438 getService(Components.interfaces.nsIConsoleService);
439 consoleService.logStringMessage(aString);
440 dump(aString + "\n");
441 }