releases-comm-central/calendar/itip/calItipEmailTransport.js

437 строки
20 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
Components.utils.import("resource:///modules/mailServices.js");
Components.utils.import("resource://calendar/modules/calUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Preferences.jsm");
Components.utils.import("resource://calendar/modules/ltnInvitationUtils.jsm");
/**
* Constructor of calItipEmailTransport object
*/
function calItipEmailTransport() {
this.wrappedJSObject = this;
this._initEmailTransport();
}
var calItipEmailTransportClassID = Components.ID("{d4d7b59e-c9e0-4a7a-b5e8-5958f85515f0}");
var calItipEmailTransportInterfaces = [Components.interfaces.calIItipTransport];
calItipEmailTransport.prototype = {
classID: calItipEmailTransportClassID,
QueryInterface: XPCOMUtils.generateQI(calItipEmailTransportInterfaces),
classInfo: XPCOMUtils.generateCI({
classID: calItipEmailTransportClassID,
contractID: "@mozilla.org/calendar/itip-transport;1?type=email",
classDescription: "Calendar iTIP Email Transport",
interfaces: calItipEmailTransportInterfaces,
}),
mHasXpcomMail: false,
mDefaultAccount: null,
mDefaultIdentity: null,
mDefaultSmtpServer: null,
get scheme() { return "mailto"; },
get type() { return "email"; },
mSenderAddress: null,
get senderAddress() {
return this.mSenderAddress;
},
set senderAddress(aValue) {
return (this.mSenderAddress = aValue);
},
sendItems: function(aCount, aRecipients, aItipItem) {
if (this.mHasXpcomMail) {
cal.LOG("sendItems: Sending Email...");
let items = this._prepareItems(aItipItem);
if (items === false) {
return false;
} else {
return this._sendXpcomMail(aRecipients, items.subject, items.body, aItipItem);
}
} else {
// Sunbird case: Call user's default mailer on system.
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
},
_prepareItems: function(aItipItem) {
let item = aItipItem.getItemList({})[0];
// Get ourselves some default text - when we handle organizer properly
// We'll need a way to configure the Common Name attribute and we should
// use it here rather than the email address
let summary = item.getProperty("SUMMARY") || "";
let subject = "";
let body = "";
switch (aItipItem.responseMethod) {
case "REQUEST": {
let usePrefixes = Preferences.get(
"calendar.itip.useInvitationSubjectPrefixes",
true
);
if (usePrefixes) {
let seq = item.getProperty("SEQUENCE");
let subjectKey = seq && seq > 0
? "itipRequestUpdatedSubject"
: "itipRequestSubject";
subject = cal.calGetString(
"lightning",
subjectKey,
[summary],
"lightning"
);
} else {
subject = summary;
}
body = cal.calGetString(
"lightning",
"itipRequestBody",
[item.organizer ? item.organizer.toString() : "", summary],
"lightning"
);
break;
}
case "CANCEL": {
subject = cal.calGetString(
"lightning",
"itipCancelSubject",
[summary],
"lightning"
);
body = cal.calGetString(
"lightning",
"itipCancelBody",
[item.organizer ? item.organizer.toString() : "", summary],
"lightning"
);
break;
}
case "DECLINECOUNTER": {
subject = cal.calGetString(
"lightning",
"itipDeclineCounterSubject",
[summary],
"lightning"
);
body = cal.calGetString(
"lightning",
"itipDeclineCounterBody",
[item.organizer ? item.organizer.toString() : "", summary],
"lightning"
);
break;
}
case "REPLY": {
// Get my participation status
let att = cal.getInvitedAttendee(item, aItipItem.targetCalendar);
if (!att && aItipItem.identity) {
att = item.getAttendeeById(cal.prependMailTo(aItipItem.identity));
}
if (!att) { // should not happen anymore
return false;
}
// work around BUG 351589, the below just removes RSVP:
aItipItem.setAttendeeStatus(att.id, att.participationStatus);
let myPartStat = att.participationStatus;
let name = att.toString();
// Generate proper body from my participation status
let subjectKey, bodyKey;
switch (myPartStat) {
case "ACCEPTED":
subjectKey = "itipReplySubjectAccept";
bodyKey = "itipReplyBodyAccept";
break;
case "TENTATIVE":
subjectKey = "itipReplySubjectTentative";
bodyKey = "itipReplyBodyAccept";
break;
case "DECLINED":
subjectKey = "itipReplySubjectDecline";
bodyKey = "itipReplyBodyDecline";
break;
default:
subjectKey = "itipReplySubject";
bodyKey = "itipReplyBodyAccept";
break;
}
subject = cal.calGetString(
"lightning",
subjectKey,
[summary],
"lightning"
);
body = cal.calGetString(
"lightning",
bodyKey,
[name],
"lightning"
);
break;
}
}
return {
subject: subject,
body: body
};
},
_initEmailTransport: function() {
this.mHasXpcomMail = true;
try {
this.mDefaultSmtpServer = MailServices.smtp.defaultServer;
this.mDefaultAccount = MailServices.accounts.defaultAccount;
this.mDefaultIdentity = this.mDefaultAccount.defaultIdentity;
if (!this.mDefaultIdentity) {
// If there isn't a default identity (i.e Local Folders is your
// default identity, then go ahead and use the first available
// identity.
let allIdentities = MailServices.accounts.allIdentities;
if (allIdentities.length > 0) {
this.mDefaultIdentity = allIdentities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
} else {
// If there are no identities, then we are in the same
// situation as if we didn't have Xpcom Mail.
this.mHasXpcomMail = false;
cal.LOG("initEmailService: No XPCOM Mail available: " + e);
}
}
} catch (ex) {
// Then we must resort to operating system specific means
this.mHasXpcomMail = false;
}
},
_sendXpcomMail: function(aToList, aSubject, aBody, aItem) {
let identity = null;
let account;
if (aItem.targetCalendar) {
identity = aItem.targetCalendar.getProperty("imip.identity");
if (identity) {
identity = identity.QueryInterface(Components.interfaces.nsIMsgIdentity);
account = aItem.targetCalendar.getProperty("imip.account")
.QueryInterface(Components.interfaces.nsIMsgAccount);
} else {
cal.WARN("No email identity configured for calendar " + aItem.targetCalendar.name);
}
}
if (!identity) { // use some default identity/account:
identity = this.mDefaultIdentity;
account = this.mDefaultAccount;
}
let compatMode = 0;
switch (aItem.autoResponse) {
case Components.interfaces.calIItipItem.USER: {
cal.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" +
"This will disable OL (up to 2003) to consume the mail as an iTIP invitation showing\n" +
"the usual calendar buttons.");
// To somehow have a last resort before sending spam, the user can choose to send the mail.
let prefCompatMode = Preferences.get("calendar.itip.compatSendMode", 0);
let inoutCheck = { value: prefCompatMode == 1 };
let parent = Services.wm.getMostRecentWindow(null);
if (parent.closed) {
parent = cal.getCalendarWindow();
}
if (Services.prompt.confirmEx(parent,
cal.calGetString("lightning", "imipSendMail.title", null, "lightning"),
cal.calGetString("lightning", "imipSendMail.text", null, "lightning"),
Services.prompt.STD_YES_NO_BUTTONS,
null,
null,
null,
cal.calGetString("lightning", "imipSendMail.Outlook2000CompatMode.text", null, "lightning"),
inoutCheck)) {
break;
} // else go on with auto sending for now
compatMode = (inoutCheck.value ? 1 : 0);
if (compatMode != prefCompatMode) {
Preferences.set("calendar.itip.compatSendMode", compatMode);
}
}
// falls through, based on prompting above
case Components.interfaces.calIItipItem.AUTO: {
// don't show log message in case of falling through
if (aItem.autoResponse == Components.interfaces.calIItipItem.AUTO) {
cal.LOG("sendXpcomMail: Found AUTO autoResponse type.");
}
let cbEmail = function(aVal, aInd, aArr) {
let email = cal.getAttendeeEmail(aVal, true);
if (!email.length) {
cal.LOG("Invalid recipient for email transport: " + aVal.toString());
}
return email;
};
let toMap = aToList.map(cbEmail).filter(value => value.length);
if (toMap.length < aToList.length) {
// at least one invalid recipient, so we skip sending for this message
return false;
}
let toList = toMap.join(", ");
let composeUtils = Components.classes["@mozilla.org/messengercompose/computils;1"]
.createInstance(Components.interfaces.nsIMsgCompUtils);
let messageId = composeUtils.msgGenerateMessageId(identity);
let mailFile = this._createTempImipFile(compatMode, toList, aSubject, aBody, aItem, identity, messageId);
if (mailFile) {
// compose fields for message: from/to etc need to be specified both here and in the file
let composeFields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
.createInstance(Components.interfaces.nsIMsgCompFields);
composeFields.characterSet = "UTF-8";
composeFields.to = toList;
let mailfrom = (identity.fullName.length ? identity.fullName + " <" + identity.email + ">" : identity.email);
composeFields.from = (cal.validateRecipientList(mailfrom) == mailfrom ? mailfrom : identity.email);
composeFields.replyTo = identity.replyTo;
composeFields.organization = identity.organization;
composeFields.messageId = messageId;
let validRecipients;
if (identity.doCc) {
validRecipients = cal.validateRecipientList(identity.doCcList);
if (validRecipients != "") {
// eslint-disable-next-line id-length
composeFields.cc = validRecipients;
}
}
if (identity.doBcc) {
validRecipients = cal.validateRecipientList(identity.doBccList);
if (validRecipients != "") {
composeFields.bcc = validRecipients;
}
}
// xxx todo: add send/progress UI, maybe recycle
// "@mozilla.org/messengercompose/composesendlistener;1"
// and/or "chrome://messenger/content/messengercompose/sendProgress.xul"
// i.e. bug 432662
let msgSend = Components.classes["@mozilla.org/messengercompose/send;1"]
.createInstance(Components.interfaces.nsIMsgSend);
msgSend.sendMessageFile(identity,
account.key,
composeFields,
mailFile,
true /* deleteSendFileOnCompletion */,
false /* digest_p */,
(Services.io.offline ? Components.interfaces.nsIMsgSend.nsMsgQueueForLater
: Components.interfaces.nsIMsgSend.nsMsgDeliverNow),
null /* nsIMsgDBHdr msgToReplace */,
null /* nsIMsgSendListener aListener */,
null /* nsIMsgStatusFeedback aStatusFeedback */,
"" /* password */);
return true;
}
break;
}
case Components.interfaces.calIItipItem.NONE: {
cal.LOG("sendXpcomMail: Found NONE autoResponse type.");
// No response
break;
}
default: {
// Unknown autoResponse type
throw new Error("sendXpcomMail: " +
"Unknown autoResponse type: " +
aItem.autoResponse);
}
}
return false;
},
_createTempImipFile: function(compatMode, aToList, aSubject, aBody, aItem, aIdentity, aMessageId) {
try {
let itemList = aItem.getItemList({});
let serializer = Components.classes["@mozilla.org/calendar/ics-serializer;1"]
.createInstance(Components.interfaces.calIIcsSerializer);
serializer.addItems(itemList, itemList.length);
let methodProp = cal.getIcsService().createIcalProperty("METHOD");
methodProp.value = aItem.responseMethod;
serializer.addProperty(methodProp);
let calText = serializer.serializeToString();
let utf8CalText = ltn.invitation.encodeUTF8(calText);
// 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.
let mailText = ltn.invitation.getHeaderSection(aMessageId, aIdentity, aToList, aSubject);
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";
break;
default:
mailText += "Content-type: multipart/mixed; boundary=\"Boundary_(ID_qyG4ZdjoAsiZ+Jo19dCbWQ)\"\r\n" +
"\r\n\r\n" +
"--Boundary_(ID_qyG4ZdjoAsiZ+Jo19dCbWQ)\r\n" +
"Content-type: multipart/alternative;\r\n" +
" boundary=\"Boundary_(ID_ryU4ZdJoASiZ+Jo21dCbwA)\"\r\n" +
"\r\n\r\n" +
"--Boundary_(ID_ryU4ZdJoASiZ+Jo21dCbwA)\r\n" +
"Content-type: text/plain; charset=UTF-8\r\n" +
"Content-transfer-encoding: 8BIT\r\n" +
"\r\n" +
ltn.invitation.encodeUTF8(aBody) +
"\r\n\r\n\r\n" +
"--Boundary_(ID_ryU4ZdJoASiZ+Jo21dCbwA)\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\r\n" +
"--Boundary_(ID_ryU4ZdJoASiZ+Jo21dCbwA)--\r\n" +
"\r\n" +
"--Boundary_(ID_qyG4ZdjoAsiZ+Jo19dCbWQ)\r\n" +
"Content-type: application/ics; name=invite.ics\r\n" +
"Content-transfer-encoding: 8BIT\r\n" +
"Content-disposition: attachment; filename=invite.ics\r\n" +
"\r\n" +
utf8CalText +
"\r\n\r\n" +
"--Boundary_(ID_qyG4ZdjoAsiZ+Jo19dCbWQ)--\r\n";
break;
}
cal.LOG("mail text:\n" + mailText);
let tempFile = Services.dirsvc.get("TmpD", Components.interfaces.nsIFile);
tempFile.append("itipTemp");
tempFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE,
parseInt("0600", 8));
let outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
// 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,
parseInt("0600", 8),
0);
outputStream.write(mailText, mailText.length);
outputStream.close();
cal.LOG("_createTempImipFile path: " + tempFile.path);
return tempFile;
} catch (exc) {
cal.ASSERT(false, exc);
return null;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([calItipEmailTransport]);