397 строки
14 KiB
JavaScript
397 строки
14 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/. */
|
|
|
|
var EXPORTED_SYMBOLS = ["CalItipEmailTransport"];
|
|
|
|
var { MailServices } = ChromeUtils.import("resource:///modules/MailServices.jsm");
|
|
var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
|
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
var { ltn } = ChromeUtils.import("resource:///modules/calendar/ltnInvitationUtils.jsm");
|
|
|
|
function CalItipEmailTransport() {
|
|
this.wrappedJSObject = this;
|
|
this._initEmailTransport();
|
|
}
|
|
CalItipEmailTransport.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI(["calIItipTransport"]),
|
|
classID: Components.ID("{d4d7b59e-c9e0-4a7a-b5e8-5958f85515f0}"),
|
|
|
|
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) {
|
|
this.mSenderAddress = aValue;
|
|
},
|
|
|
|
sendItems(aRecipients, aItipItem) {
|
|
if (this.mHasXpcomMail) {
|
|
cal.LOG("sendItems: Preparing to send an invitation email...");
|
|
let items = this._prepareItems(aItipItem);
|
|
if (items === false) {
|
|
return false;
|
|
}
|
|
|
|
return this._sendXpcomMail(aRecipients, items.subject, items.body, aItipItem);
|
|
}
|
|
// sending xpcom mail is not available if no identity has been set
|
|
throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
|
|
},
|
|
|
|
_prepareItems(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 = Services.prefs.getBoolPref(
|
|
"calendar.itip.useInvitationSubjectPrefixes",
|
|
true
|
|
);
|
|
if (usePrefixes) {
|
|
let seq = item.getProperty("SEQUENCE");
|
|
let subjectKey = seq && seq > 0 ? "itipRequestUpdatedSubject2" : "itipRequestSubject2";
|
|
subject = cal.l10n.getLtnString(subjectKey, [summary]);
|
|
} else {
|
|
subject = summary;
|
|
}
|
|
body = cal.l10n.getLtnString("itipRequestBody", [
|
|
item.organizer ? item.organizer.toString() : "",
|
|
summary,
|
|
]);
|
|
break;
|
|
}
|
|
case "CANCEL": {
|
|
subject = cal.l10n.getLtnString("itipCancelSubject2", [summary]);
|
|
body = cal.l10n.getLtnString("itipCancelBody", [
|
|
item.organizer ? item.organizer.toString() : "",
|
|
summary,
|
|
]);
|
|
break;
|
|
}
|
|
case "DECLINECOUNTER": {
|
|
subject = cal.l10n.getLtnString("itipDeclineCounterSubject", [summary]);
|
|
body = cal.l10n.getLtnString("itipDeclineCounterBody", [
|
|
item.organizer ? item.organizer.toString() : "",
|
|
summary,
|
|
]);
|
|
break;
|
|
}
|
|
case "REPLY": {
|
|
// Get my participation status
|
|
let att = cal.itip.getInvitedAttendee(item, aItipItem.targetCalendar);
|
|
if (!att && aItipItem.identity) {
|
|
att = item.getAttendeeById(cal.email.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 = "itipReplySubjectAccept2";
|
|
bodyKey = "itipReplyBodyAccept";
|
|
break;
|
|
case "TENTATIVE":
|
|
subjectKey = "itipReplySubjectTentative2";
|
|
bodyKey = "itipReplyBodyAccept";
|
|
break;
|
|
case "DECLINED":
|
|
subjectKey = "itipReplySubjectDecline2";
|
|
bodyKey = "itipReplyBodyDecline";
|
|
break;
|
|
default:
|
|
subjectKey = "itipReplySubject2";
|
|
bodyKey = "itipReplyBodyAccept";
|
|
break;
|
|
}
|
|
subject = cal.l10n.getLtnString(subjectKey, [summary]);
|
|
body = cal.l10n.getLtnString(bodyKey, [name]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
subject,
|
|
body,
|
|
};
|
|
},
|
|
|
|
_initEmailTransport() {
|
|
this.mHasXpcomMail = true;
|
|
|
|
try {
|
|
this.mDefaultSmtpServer = MailServices.smtp.defaultServer;
|
|
this.mDefaultAccount = MailServices.accounts.defaultAccount;
|
|
this.mDefaultIdentity = this.mDefaultAccount ? this.mDefaultAccount.defaultIdentity : null;
|
|
|
|
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[0];
|
|
} 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");
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
// Then we must resort to operating system specific means
|
|
this.mHasXpcomMail = false;
|
|
}
|
|
},
|
|
|
|
_sendXpcomMail(aToList, aSubject, aBody, aItipItem) {
|
|
let identity = null;
|
|
let account;
|
|
if (aItipItem.targetCalendar) {
|
|
identity = aItipItem.targetCalendar.getProperty("imip.identity");
|
|
if (identity) {
|
|
identity = identity.QueryInterface(Ci.nsIMsgIdentity);
|
|
account = aItipItem.targetCalendar
|
|
.getProperty("imip.account")
|
|
.QueryInterface(Ci.nsIMsgAccount);
|
|
} else {
|
|
cal.WARN("No email identity configured for calendar " + aItipItem.targetCalendar.name);
|
|
}
|
|
}
|
|
if (!identity) {
|
|
// use some default identity/account:
|
|
identity = this.mDefaultIdentity;
|
|
account = this.mDefaultAccount;
|
|
if (!account || !identity) {
|
|
throw new Error("sendXpcomMail: No usable account and identity found");
|
|
}
|
|
}
|
|
|
|
switch (aItipItem.autoResponse) {
|
|
case Ci.calIItipItem.USER: {
|
|
cal.LOG("sendXpcomMail: Found USER autoResponse type.");
|
|
// We still need this as a last resort if a user just deletes or
|
|
// drags an invitation related event
|
|
let parent = Services.wm.getMostRecentWindow(null);
|
|
if (parent.closed) {
|
|
parent = cal.window.getCalendarWindow();
|
|
}
|
|
let cancelled = Services.prompt.confirmEx(
|
|
parent,
|
|
cal.l10n.getLtnString("imipSendMail.title"),
|
|
cal.l10n.getLtnString("imipSendMail.text"),
|
|
Services.prompt.STD_YES_NO_BUTTONS,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
{}
|
|
);
|
|
if (cancelled) {
|
|
cal.LOG("sendXpcomMail: Sending of invitation email aborted by user!");
|
|
break;
|
|
} // else go on with auto sending for now
|
|
}
|
|
// falls through intended
|
|
case Ci.calIItipItem.AUTO: {
|
|
// don't show log message in case of falling through
|
|
if (aItipItem.autoResponse == Ci.calIItipItem.AUTO) {
|
|
cal.LOG("sendXpcomMail: Found AUTO autoResponse type.");
|
|
}
|
|
let cbEmail = function(aVal, aInd, aArr) {
|
|
let email = cal.email.getAttendeeEmail(aVal, true);
|
|
if (!email.length) {
|
|
cal.LOG("sendXpcomMail: 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 = Cc["@mozilla.org/messengercompose/computils;1"].createInstance(
|
|
Ci.nsIMsgCompUtils
|
|
);
|
|
let messageId = composeUtils.msgGenerateMessageId(identity);
|
|
let mailFile = this._createTempImipFile(
|
|
toList,
|
|
aSubject,
|
|
aBody,
|
|
aItipItem,
|
|
identity,
|
|
messageId
|
|
);
|
|
if (mailFile) {
|
|
// compose fields for message: from/to etc need to be specified both here and in the file
|
|
let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(
|
|
Ci.nsIMsgCompFields
|
|
);
|
|
composeFields.to = toList;
|
|
let mailfrom = identity.fullName.length
|
|
? identity.fullName + " <" + identity.email + ">"
|
|
: identity.email;
|
|
composeFields.from =
|
|
cal.email.validateRecipientList(mailfrom) == mailfrom ? mailfrom : identity.email;
|
|
composeFields.replyTo = identity.replyTo;
|
|
composeFields.organization = identity.organization;
|
|
composeFields.messageId = messageId;
|
|
let validRecipients;
|
|
if (identity.doCc) {
|
|
validRecipients = cal.email.validateRecipientList(identity.doCcList);
|
|
if (validRecipients != "") {
|
|
// eslint-disable-next-line id-length
|
|
composeFields.cc = validRecipients;
|
|
}
|
|
}
|
|
if (identity.doBcc) {
|
|
validRecipients = cal.email.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.xhtml"
|
|
// i.e. bug 432662
|
|
let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(Ci.nsIMsgSend);
|
|
msgSend.sendMessageFile(
|
|
identity,
|
|
account.key,
|
|
composeFields,
|
|
mailFile,
|
|
true, // deleteSendFileOnCompletion
|
|
false, // digest_p
|
|
Services.io.offline ? Ci.nsIMsgSend.nsMsgQueueForLater : Ci.nsIMsgSend.nsMsgDeliverNow,
|
|
null, // nsIMsgDBHdr msgToReplace
|
|
null, // nsIMsgSendListener aListener
|
|
null, // nsIMsgStatusFeedback aStatusFeedback
|
|
""
|
|
); // password
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Ci.calIItipItem.NONE: {
|
|
// we shouldn't get here, as we stopped processing in this case
|
|
// earlier in checkAndSend in calItipUtils.jsm
|
|
cal.LOG("sendXpcomMail: Found NONE autoResponse type.");
|
|
break;
|
|
}
|
|
default: {
|
|
// Also of this case should have been taken care at the same place
|
|
throw new Error("sendXpcomMail: Unknown autoResponse type: " + aItipItem.autoResponse);
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
_createTempImipFile(aToList, aSubject, aBody, aItipItem, aIdentity, aMessageId) {
|
|
try {
|
|
let itemList = aItipItem.getItemList();
|
|
let serializer = Cc["@mozilla.org/calendar/ics-serializer;1"].createInstance(
|
|
Ci.calIIcsSerializer
|
|
);
|
|
serializer.addItems(itemList);
|
|
let methodProp = cal.getIcsService().createIcalProperty("METHOD");
|
|
methodProp.value = aItipItem.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);
|
|
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=" +
|
|
aItipItem.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";
|
|
cal.LOG("mail text:\n" + mailText);
|
|
|
|
let tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
|
tempFile.append("itipTemp");
|
|
tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
|
|
|
|
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
|
|
Ci.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;
|
|
}
|
|
},
|
|
};
|