summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose/src/MimeMessageUtils.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/compose/src/MimeMessageUtils.jsm')
-rw-r--r--comm/mailnews/compose/src/MimeMessageUtils.jsm1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/MimeMessageUtils.jsm b/comm/mailnews/compose/src/MimeMessageUtils.jsm
new file mode 100644
index 0000000000..65682b6d4c
--- /dev/null
+++ b/comm/mailnews/compose/src/MimeMessageUtils.jsm
@@ -0,0 +1,1058 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["MsgUtils"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+
+// Defined in ErrorList.h.
+const NS_ERROR_MODULE_BASE_OFFSET = 69;
+const NS_ERROR_MODULE_MAILNEWS = 16;
+
+/**
+ * Generate an NS_ERROR code from a MAILNEWS error code. See NS_ERROR_GENERATE
+ * in nsError.h and NS_MSG_GENERATE_FAILURE in nsComposeStrings.h.
+ *
+ * @param {number} code - The error code in MAILNEWS module.
+ * @returns {number}
+ */
+function generateNSError(code) {
+ return (
+ ((1 << 31) |
+ ((NS_ERROR_MODULE_MAILNEWS + NS_ERROR_MODULE_BASE_OFFSET) << 16) |
+ code) >>>
+ 0
+ );
+}
+
+/**
+ * Collection of helper functions for message sending process.
+ */
+var MsgUtils = {
+ /**
+ * Error codes defined in nsComposeStrings.h
+ */
+ NS_MSG_UNABLE_TO_OPEN_FILE: generateNSError(12500),
+ NS_MSG_UNABLE_TO_OPEN_TMP_FILE: generateNSError(12501),
+ NS_MSG_UNABLE_TO_SAVE_TEMPLATE: generateNSError(12502),
+ NS_MSG_UNABLE_TO_SAVE_DRAFT: generateNSError(12503),
+ NS_MSG_COULDNT_OPEN_FCC_FOLDER: generateNSError(12506),
+ NS_MSG_NO_SENDER: generateNSError(12510),
+ NS_MSG_NO_RECIPIENTS: generateNSError(12511),
+ NS_MSG_ERROR_WRITING_FILE: generateNSError(12512),
+ NS_ERROR_SENDING_FROM_COMMAND: generateNSError(12514),
+ NS_ERROR_SENDING_DATA_COMMAND: generateNSError(12516),
+ NS_ERROR_SENDING_MESSAGE: generateNSError(12517),
+ NS_ERROR_POST_FAILED: generateNSError(12518),
+ NS_ERROR_SMTP_SERVER_ERROR: generateNSError(12524),
+ NS_MSG_UNABLE_TO_SEND_LATER: generateNSError(12525),
+ NS_ERROR_COMMUNICATIONS_ERROR: generateNSError(12526),
+ NS_ERROR_BUT_DONT_SHOW_ALERT: generateNSError(12527),
+ NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS: generateNSError(12529),
+ NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY: generateNSError(12530),
+ NS_ERROR_MIME_MPART_ATTACHMENT_ERROR: generateNSError(12531),
+
+ // 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one
+
+ // For message sending report
+ NS_MSG_ERROR_READING_FILE: generateNSError(12563),
+
+ NS_MSG_ERROR_ATTACHING_FILE: generateNSError(12570),
+
+ NS_ERROR_SMTP_GREETING: generateNSError(12572),
+
+ NS_ERROR_SENDING_RCPT_COMMAND: generateNSError(12575),
+
+ NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS: generateNSError(12582),
+
+ NS_ERROR_SMTP_PASSWORD_UNDEFINED: generateNSError(12584),
+ NS_ERROR_SMTP_SEND_NOT_ALLOWED: generateNSError(12585),
+ NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: generateNSError(12586),
+ NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: generateNSError(12588),
+
+ NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER: generateNSError(12589),
+ NS_ERROR_SMTP_SEND_FAILED_REFUSED: generateNSError(12590),
+ NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED: generateNSError(12591),
+ NS_ERROR_SMTP_SEND_FAILED_TIMEOUT: generateNSError(12592),
+ NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON: generateNSError(12593),
+
+ NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL: generateNSError(12594),
+ NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL: generateNSError(12595),
+ NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT: generateNSError(12596),
+ NS_ERROR_SMTP_AUTH_FAILURE: generateNSError(12597),
+ NS_ERROR_SMTP_AUTH_GSSAPI: generateNSError(12598),
+ NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED: generateNSError(12599),
+
+ NS_ERROR_ILLEGAL_LOCALPART: generateNSError(12601),
+
+ NS_ERROR_CLIENTID: generateNSError(12610),
+ NS_ERROR_CLIENTID_PERMISSION: generateNSError(12611),
+
+ sendLogger: console.createInstance({
+ prefix: "mailnews.send",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.send.loglevel",
+ }),
+
+ smtpLogger: console.createInstance({
+ prefix: "mailnews.smtp",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.smtp.loglevel",
+ }),
+
+ /**
+ * NS_IS_MSG_ERROR in msgCore.h.
+ *
+ * @param {nsresult} err - The nsresult value.
+ * @returns {boolean}
+ */
+ isMsgError(err) {
+ return (
+ (((err >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff) ==
+ NS_ERROR_MODULE_MAILNEWS
+ );
+ },
+
+ /**
+ * Convert html to text to form a multipart/alternative message. The output
+ * depends on preference.
+ *
+ * @param {string} input - The HTML text to convert.
+ * @param {boolean} formatFlowed - A flag to enable OutputFormatFlowed.
+ * @returns {string}
+ */
+ convertToPlainText(input, formatFlowed) {
+ let wrapWidth = Services.prefs.getIntPref("mailnews.wraplength", 72);
+ if (wrapWidth == 0 || wrapWidth > 990) {
+ wrapWidth = 990;
+ } else if (wrapWidth < 10) {
+ wrapWidth = 10;
+ }
+
+ let flags =
+ Ci.nsIDocumentEncoder.OutputPersistNBSP |
+ Ci.nsIDocumentEncoder.OutputFormatted |
+ Ci.nsIDocumentEncoder.OutputDisallowLineBreaking;
+ if (formatFlowed) {
+ flags |= Ci.nsIDocumentEncoder.OutputFormatFlowed;
+ }
+
+ let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
+ Ci.nsIParserUtils
+ );
+ return parserUtils.convertToPlainText(input, flags, wrapWidth);
+ },
+
+ /**
+ * Get the list of default custom headers.
+ *
+ * @param {nsIMsgIdentity} userIdentity - User identity.
+ * @returns {{headerName: string, headerValue: string}[]}
+ */
+ getDefaultCustomHeaders(userIdentity) {
+ // mail.identity.<id#>.headers pref is a comma separated value of pref names
+ // containing headers to add headers are stored in
+ let headerAttributes = userIdentity
+ .getUnicharAttribute("headers")
+ .split(",");
+ let headers = [];
+ for (let attr of headerAttributes) {
+ // mail.identity.<id#>.header.<header name> grab all the headers
+ let attrValue = userIdentity.getUnicharAttribute(`header.${attr}`);
+ if (attrValue) {
+ let colonIndex = attrValue.indexOf(":");
+ headers.push({
+ headerName: attrValue.slice(0, colonIndex),
+ headerValue: attrValue.slice(colonIndex + 1).trim(),
+ });
+ }
+ }
+ return headers;
+ },
+
+ /**
+ * Get the fcc value.
+ *
+ * @param {nsIMsgIdentity} userIdentity - The user identity.
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {string} originalMsgURI - The original message uri, can be null.
+ * @param {MSG_ComposeType} compType - The compose type.
+ * @returns {string}
+ */
+ getFcc(userIdentity, compFields, originalMsgURI, compType) {
+ // Check if the default fcc has been overridden.
+ let fcc = "";
+ let useDefaultFcc = true;
+ if (compFields.fcc) {
+ if (compFields.fcc.startsWith("nocopy://")) {
+ useDefaultFcc = false;
+ fcc = "";
+ } else {
+ let folder = MailUtils.getExistingFolder(compFields.fcc);
+ if (folder) {
+ useDefaultFcc = false;
+ fcc = compFields.fcc;
+ }
+ }
+ }
+
+ // If the identity pref "fcc" is set to false, then we will not do the default
+ // FCC operation but still allow the override.
+ if (!userIdentity.doFcc) {
+ return fcc;
+ }
+
+ // We use default FCC setting if it's not set or was set to an invalid
+ // folder.
+ if (useDefaultFcc) {
+ // Only check whether the user wants the message in the original message
+ // folder if the msgcomptype is some kind of a reply.
+ if (
+ originalMsgURI &&
+ [
+ Ci.nsIMsgCompType.Reply,
+ Ci.nsIMsgCompType.ReplyAll,
+ Ci.nsIMsgCompType.ReplyToGroup,
+ Ci.nsIMsgCompType.ReplyToSender,
+ Ci.nsIMsgCompType.ReplyToSenderAndGroup,
+ Ci.nsIMsgCompType.ReplyToList,
+ Ci.nsIMsgCompType.ReplyWithTemplate,
+ ].includes(compType)
+ ) {
+ let msgHdr;
+ try {
+ msgHdr =
+ MailServices.messageServiceFromURI(
+ originalMsgURI
+ ).messageURIToMsgHdr(originalMsgURI);
+ } catch (e) {
+ console.warn(
+ `messageServiceFromURI failed for ${originalMsgURI}\n${e.stack}`
+ );
+ }
+ if (msgHdr) {
+ let folder = msgHdr.folder;
+ if (
+ folder &&
+ folder.canFileMessages &&
+ folder.server &&
+ folder.server.getCharValue("type") != "rss" &&
+ userIdentity.fccReplyFollowsParent
+ ) {
+ fcc = folder.URI;
+ useDefaultFcc = false;
+ }
+ }
+ }
+
+ if (useDefaultFcc) {
+ let uri = this.getMsgFolderURIFromPrefs(
+ userIdentity,
+ Ci.nsIMsgSend.nsMsgDeliverNow
+ );
+ fcc = uri == "nocopy://" ? "" : uri;
+ }
+ }
+
+ return fcc;
+ },
+
+ canSaveToFolder(folderUri) {
+ let folder = MailUtils.getOrCreateFolder(folderUri);
+ if (folder.server) {
+ return folder.server.canFileMessagesOnServer;
+ }
+ return false;
+ },
+
+ /**
+ * Get the To header value. When we don't have disclosed recipient but only
+ * Bcc, use the undisclosedRecipients entry from composeMsgs.properties as the
+ * To header value to prevent problem with some servers.
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {string}
+ */
+ getUndisclosedRecipients(compFields, deliverMode) {
+ // Newsgroups count as recipients.
+ let hasDisclosedRecipient =
+ compFields.to || compFields.cc || compFields.newsgroups;
+ // If we are saving the message as a draft, don't bother inserting the
+ // undisclosed recipients field. We'll take care of that when we really send
+ // the message.
+ if (
+ hasDisclosedRecipient ||
+ [
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ ].includes(deliverMode) ||
+ !Services.prefs.getBoolPref("mail.compose.add_undisclosed_recipients")
+ ) {
+ return "";
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+ let undisclosedRecipients = composeBundle.GetStringFromName(
+ "undisclosedRecipients"
+ );
+ let recipients = MailServices.headerParser.makeGroupObject(
+ undisclosedRecipients,
+ []
+ );
+ return recipients.toString();
+ },
+
+ /**
+ * Get the Mail-Followup-To header value.
+ * See bug #204339 and http://cr.yp.to/proto/replyto.html for details
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {nsIMsgIdentity} userIdentity - The user identity.
+ * @returns {string}
+ */
+ getMailFollowupToHeader(compFields, userIdentity) {
+ let mailLists = userIdentity.getUnicharAttribute(
+ "subscribed_mailing_lists"
+ );
+ if (!mailLists || !(compFields.to || compFields.cc)) {
+ return "";
+ }
+ let recipients = compFields.to;
+ if (recipients) {
+ if (compFields.cc) {
+ recipients += `,${compFields.cc}`;
+ }
+ } else {
+ recipients = compFields.cc;
+ }
+ let recipientsDedup =
+ MailServices.headerParser.removeDuplicateAddresses(recipients);
+ let recipientsWithoutMailList =
+ MailServices.headerParser.removeDuplicateAddresses(
+ recipientsDedup,
+ mailLists
+ );
+ if (recipientsDedup != recipientsWithoutMailList) {
+ return recipients;
+ }
+ return "";
+ },
+
+ /**
+ * Get the Mail-Reply-To header value.
+ * See bug #204339 and http://cr.yp.to/proto/replyto.html for details
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {nsIMsgIdentity} userIdentity - The user identity.
+ * @returns {string}
+ */
+ getMailReplyToHeader(compFields, userIdentity) {
+ let mailLists = userIdentity.getUnicharAttribute(
+ "replyto_mangling_mailing_lists"
+ );
+ if (
+ !mailLists ||
+ mailLists[0] == "*" ||
+ !(compFields.to || compFields.cc)
+ ) {
+ return "";
+ }
+ let recipients = compFields.to;
+ if (recipients) {
+ if (compFields.cc) {
+ recipients += `,${compFields.cc}`;
+ }
+ } else {
+ recipients = compFields.cc;
+ }
+ let recipientsDedup =
+ MailServices.headerParser.removeDuplicateAddresses(recipients);
+ let recipientsWithoutMailList =
+ MailServices.headerParser.removeDuplicateAddresses(
+ recipientsDedup,
+ mailLists
+ );
+ if (recipientsDedup != recipientsWithoutMailList) {
+ return compFields.replyTo || compFields.from;
+ }
+ return "";
+ },
+
+ /**
+ * Get the X-Mozilla-Draft-Info header value.
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @returns {string}
+ */
+ getXMozillaDraftInfo(compFields) {
+ let getCompField = (property, key) => {
+ let value = compFields[property] ? 1 : 0;
+ return `${key}=${value}; `;
+ };
+ let draftInfo = "internal/draft; ";
+ draftInfo += getCompField("attachVCard", "vcard");
+
+ let receiptValue = 0;
+ if (compFields.returnReceipt) {
+ // slight change compared to 4.x; we used to use receipt= to tell
+ // whether the draft/template has request for either MDN or DNS or both
+ // return receipt; since the DNS is out of the picture we now use the
+ // header type + 1 to tell whether user has requested the return receipt
+ receiptValue = compFields.receiptHeaderType + 1;
+ }
+ draftInfo += `receipt=${receiptValue}; `;
+
+ draftInfo += getCompField("DSN", "DSN");
+ draftInfo += "uuencode=0; ";
+ draftInfo += getCompField("attachmentReminder", "attachmentreminder");
+ draftInfo += `deliveryformat=${compFields.deliveryFormat}`;
+
+ return draftInfo;
+ },
+
+ /**
+ * Get the X-Mozilla-Cloud-Part header value.
+ *
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @param {nsIMsgAttachment} attachment - The cloud attachment.
+ * @returns {string}
+ */
+ getXMozillaCloudPart(deliverMode, attachment) {
+ let value = "";
+ if (attachment.sendViaCloud && attachment.contentLocation) {
+ value += `cloudFile; url=${attachment.contentLocation}`;
+
+ if (
+ (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft ||
+ deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) &&
+ attachment.cloudFileAccountKey &&
+ attachment.cloudPartHeaderData
+ ) {
+ value += `; provider=${attachment.cloudFileAccountKey}`;
+ value += `; ${this.rfc2231ParamFolding(
+ "data",
+ attachment.cloudPartHeaderData
+ )}`;
+ }
+ }
+ return value;
+ },
+
+ /**
+ * Get the X-Mozilla-Status header value. The header value will be used to set
+ * some nsMsgMessageFlags. Including the Read flag for message in a local
+ * folder.
+ *
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {string}
+ */
+ getXMozillaStatus(deliverMode) {
+ if (
+ ![
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ Ci.nsIMsgSend.nsMsgSendUnsent,
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ ].includes(deliverMode)
+ ) {
+ return "";
+ }
+ let flags = 0;
+ if (deliverMode == Ci.nsIMsgSend.nsMsgQueueForLater) {
+ flags |= Ci.nsMsgMessageFlags.Queued;
+ } else if (
+ deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft &&
+ deliverMode != Ci.nsIMsgSend.nsMsgDeliverBackground
+ ) {
+ flags |= Ci.nsMsgMessageFlags.Read;
+ }
+ return flags.toString(16).padStart(4, "0");
+ },
+
+ /**
+ * Get the X-Mozilla-Status2 header value. The header value will be used to
+ * set some nsMsgMessageFlags.
+ *
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {string}
+ */
+ getXMozillaStatus2(deliverMode) {
+ if (
+ ![
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ Ci.nsIMsgSend.nsMsgSendUnsent,
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ ].includes(deliverMode)
+ ) {
+ return "";
+ }
+ let flags = 0;
+ if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) {
+ flags |= Ci.nsMsgMessageFlags.Template;
+ } else if (
+ deliverMode == Ci.nsIMsgSend.nsMsgDeliverNow ||
+ deliverMode == Ci.nsIMsgSend.nsMsgSendUnsent
+ ) {
+ flags &= ~Ci.nsMsgMessageFlags.MDNReportNeeded;
+ flags |= Ci.nsMsgMessageFlags.MDNReportSent;
+ }
+ return flags.toString(16).padStart(8, "0");
+ },
+
+ /**
+ * Get the Disposition-Notification-To header value.
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {{dnt: string, rrt: string}}
+ */
+ getDispositionNotificationTo(compFields, deliverMode) {
+ if (
+ compFields.returnReceipt &&
+ deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft &&
+ deliverMode != Ci.nsIMsgSend.nsMsgSaveAsTemplate &&
+ compFields.receiptHeaderType != Ci.nsIMsgMdnGenerator.eRrtType
+ ) {
+ return compFields.from;
+ }
+ return "";
+ },
+
+ /**
+ * Get the Return-Receipt-To header value.
+ *
+ * @param {nsIMsgCompFields} compFields - The compose fields.
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {{dnt: string, rrt: string}}
+ */
+ getReturnReceiptTo(compFields, deliverMode) {
+ if (
+ compFields.returnReceipt &&
+ deliverMode != Ci.nsIMsgSend.nsMsgSaveAsDraft &&
+ deliverMode != Ci.nsIMsgSend.nsMsgSaveAsTemplate &&
+ compFields.receiptHeaderType != Ci.nsIMsgMdnGenerator.eDntType
+ ) {
+ return compFields.from;
+ }
+ return "";
+ },
+
+ /**
+ * Get the value of X-Priority header.
+ *
+ * @param {string} rawPriority - Raw X-Priority content.
+ * @returns {string}
+ */
+ getXPriority(rawPriority) {
+ rawPriority = rawPriority.toLowerCase();
+ let priorityValue = Ci.nsMsgPriority.Default;
+ let priorityValueString = "0";
+ let priorityName = "None";
+ if (rawPriority.startsWith("1") || rawPriority.startsWith("highest")) {
+ priorityValue = Ci.nsMsgPriority.highest;
+ priorityValueString = "1";
+ priorityName = "Highest";
+ } else if (
+ rawPriority.startsWith("2") ||
+ // "high" must be tested after "highest".
+ rawPriority.startsWith("high") ||
+ rawPriority.startsWith("urgent")
+ ) {
+ priorityValue = Ci.nsMsgPriority.high;
+ priorityValueString = "2";
+ priorityName = "High";
+ } else if (
+ rawPriority.startsWith("3") ||
+ rawPriority.startsWith("normal")
+ ) {
+ priorityValue = Ci.nsMsgPriority.normal;
+ priorityValueString = "3";
+ priorityName = "Normal";
+ } else if (
+ rawPriority.startsWith("5") ||
+ rawPriority.startsWith("lowest")
+ ) {
+ priorityValue = Ci.nsMsgPriority.lowest;
+ priorityValueString = "5";
+ priorityName = "Lowest";
+ } else if (
+ rawPriority.startsWith("4") ||
+ // "low" must be tested after "lowest".
+ rawPriority.startsWith("low")
+ ) {
+ priorityValue = Ci.nsMsgPriority.low;
+ priorityValueString = "4";
+ priorityName = "Low";
+ }
+ if (priorityValue == Ci.nsMsgPriority.Default) {
+ return "";
+ }
+ return `${priorityValueString} (${priorityName})`;
+ },
+
+ /**
+ * Get the References header value.
+ *
+ * @param {string} references - Raw References header content.
+ * @returns {string}
+ */
+ getReferences(references) {
+ if (references.length <= 986) {
+ return "";
+ }
+ // The References header should be kept under 998 characters: if it's too
+ // long, trim out the earliest references to make it smaller.
+ let newReferences = "";
+ let firstRef = references.indexOf("<");
+ let secondRef = references.indexOf("<", firstRef + 1);
+ if (secondRef > 0) {
+ newReferences = references.slice(0, secondRef);
+ let bracket = references.indexOf(
+ "<",
+ references.length + newReferences.length - 986
+ );
+ if (bracket > 0) {
+ newReferences += references.slice(bracket);
+ }
+ }
+ return newReferences;
+ },
+
+ /**
+ * Get the In-Reply-To header value.
+ *
+ * @param {string} references - Raw References header content.
+ * @returns {string}
+ */
+ getInReplyTo(references) {
+ // The In-Reply-To header is the last entry in the references header...
+ let bracket = references.lastIndexOf("<");
+ if (bracket >= 0) {
+ return references.slice(bracket);
+ }
+ return "";
+ },
+
+ /**
+ * Get the value of Newsgroups and X-Mozilla-News-Host header.
+ *
+ * @param {nsMsgDeliverMode} deliverMode - Message deliver mode.
+ * @param {string} newsgroups - Raw newsgroups header content.
+ * @returns {{newsgroups: string, newshost: string}}
+ */
+ getNewsgroups(deliverMode, newsgroups) {
+ let nntpService = Cc["@mozilla.org/messenger/nntpservice;1"].getService(
+ Ci.nsINntpService
+ );
+ let newsgroupsHeaderVal = {};
+ let newshostHeaderVal = {};
+ nntpService.generateNewsHeaderValsForPosting(
+ newsgroups,
+ newsgroupsHeaderVal,
+ newshostHeaderVal
+ );
+
+ // If we are here, we are NOT going to send this now. (i.e. it is a Draft,
+ // Send Later file, etc...). Because of that, we need to store what the user
+ // typed in on the original composition window for use later when rebuilding
+ // the headers
+ if (
+ deliverMode == Ci.nsIMsgSend.nsMsgDeliverNow ||
+ deliverMode == Ci.nsIMsgSend.nsMsgSendUnsent
+ ) {
+ // This is going to be saved for later, that means we should just store
+ // what the user typed into the "Newsgroup" line in the
+ // HEADER_X_MOZILLA_NEWSHOST header for later use by "Send Unsent
+ // Messages", "Drafts" or "Templates"
+ newshostHeaderVal.value = "";
+ }
+ return {
+ newsgroups: newsgroupsHeaderVal.value,
+ newshost: newshostHeaderVal.value,
+ };
+ },
+
+ /**
+ * Get the Content-Location header value.
+ *
+ * @param {string} baseUrl - The base url of an HTML attachment.
+ * @returns {string}
+ */
+ getContentLocation(baseUrl) {
+ let lowerBaseUrl = baseUrl.toLowerCase();
+ if (
+ !baseUrl.includes(":") ||
+ lowerBaseUrl.startsWith("news:") ||
+ lowerBaseUrl.startsWith("snews:") ||
+ lowerBaseUrl.startsWith("imap:") ||
+ lowerBaseUrl.startsWith("file:") ||
+ lowerBaseUrl.startsWith("mailbox:")
+ ) {
+ return "";
+ }
+ let transformMap = {
+ " ": "%20",
+ "\t": "%09",
+ "\n": "%0A",
+ "\r": "%0D",
+ };
+ let value = "";
+ for (let char of baseUrl) {
+ value += transformMap[char] || char;
+ }
+ return value;
+ },
+
+ /**
+ * Given a string, convert it to 'qtext' (quoted text) for RFC822 header
+ * purposes.
+ */
+ makeFilenameQtext(srcText, stripCRLFs) {
+ let size = srcText.length;
+ let ret = "";
+ for (let i = 0; i < size; i++) {
+ let char = srcText.charAt(i);
+ if (
+ char == "\\" ||
+ char == '"' ||
+ (!stripCRLFs &&
+ char == "\r" &&
+ (srcText[i + 1] != "\n" ||
+ (srcText[i + 1] == "\n" && i + 2 < size && srcText[i + 2] != " ")))
+ ) {
+ ret += "\\";
+ }
+
+ if (
+ stripCRLFs &&
+ char == "\r" &&
+ srcText[i + 1] == "\n" &&
+ i + 2 < size &&
+ srcText[i + 2] == " "
+ ) {
+ i += 3;
+ } else {
+ ret += char;
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * Encode parameter value according to RFC 2047.
+ *
+ * @param {string} value - The parameter value.
+ * @returns {string}
+ */
+ rfc2047EncodeParam(value) {
+ let converter = Cc["@mozilla.org/messenger/mimeconverter;1"].getService(
+ Ci.nsIMimeConverter
+ );
+
+ let encoded = converter.encodeMimePartIIStr_UTF8(
+ value,
+ false,
+ 0,
+ Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE
+ );
+
+ return this.makeFilenameQtext(encoded, false);
+ },
+
+ /**
+ * Encode parameter value according to RFC 2231.
+ *
+ * @param {string} paramName - The parameter name.
+ * @param {string} paramValue - The parameter value.
+ * @returns {string}
+ */
+ rfc2231ParamFolding(paramName, paramValue) {
+ // this is to guarantee the folded line will never be greater
+ // than 78 = 75 + CRLFLWSP
+ const PR_MAX_FOLDING_LEN = 75;
+
+ let needsEscape = false;
+ let encoder = new TextEncoder();
+ let dupParamValue = jsmime.mimeutils.typedArrayToString(
+ encoder.encode(paramValue)
+ );
+
+ if (/[\x80-\xff]/.test(dupParamValue)) {
+ needsEscape = true;
+ dupParamValue = Services.io.escapeString(
+ dupParamValue,
+ Ci.nsINetUtil.ESCAPE_ALL
+ );
+ } else {
+ dupParamValue = this.makeFilenameQtext(dupParamValue, true);
+ }
+
+ let paramNameLen = paramName.length;
+ let paramValueLen = dupParamValue.length;
+ paramNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___"
+ let foldedParam = "";
+
+ if (paramValueLen + paramNameLen + "UTF-8".length < PR_MAX_FOLDING_LEN) {
+ foldedParam = paramName;
+ if (needsEscape) {
+ foldedParam += "*=UTF-8''";
+ } else {
+ foldedParam += '="';
+ }
+ foldedParam += dupParamValue;
+ if (!needsEscape) {
+ foldedParam += '"';
+ }
+ } else {
+ let curLineLen = 0;
+ let counter = 0;
+ let start = 0;
+ let end = null;
+
+ while (paramValueLen > 0) {
+ curLineLen = 0;
+ if (counter == 0) {
+ foldedParam = paramName;
+ } else {
+ foldedParam += `;\r\n ${paramName}`;
+ }
+ foldedParam += `*${counter}`;
+ curLineLen += `*${counter}`.length;
+ if (needsEscape) {
+ foldedParam += "*=";
+ if (counter == 0) {
+ foldedParam += "UTF-8''";
+ curLineLen += "UTF-8".length;
+ }
+ } else {
+ foldedParam += '="';
+ }
+ counter++;
+ curLineLen += paramNameLen;
+ if (paramValueLen <= PR_MAX_FOLDING_LEN - curLineLen) {
+ end = start + paramValueLen;
+ } else {
+ end = start + (PR_MAX_FOLDING_LEN - curLineLen);
+ }
+
+ if (end && needsEscape) {
+ // Check to see if we are in the middle of escaped char.
+ // We use ESCAPE_ALL, so every third character is a '%'.
+ if (end - 1 > start && dupParamValue[end - 1] == "%") {
+ end -= 1;
+ } else if (end - 2 > start && dupParamValue[end - 2] == "%") {
+ end -= 2;
+ }
+ // *end is now a '%'.
+ // Check if the following UTF-8 octet is a continuation.
+ while (end - 3 > start && "89AB".includes(dupParamValue[end + 1])) {
+ end -= 3;
+ }
+ }
+ foldedParam += dupParamValue.slice(start, end);
+ if (!needsEscape) {
+ foldedParam += '"';
+ }
+ paramValueLen -= end - start;
+ start = end;
+ }
+ }
+
+ return foldedParam;
+ },
+
+ /**
+ * Get the target message folder to copy to.
+ *
+ * @param {nsIMsgIdentity} userIdentity - The user identity.
+ * @param {nsMsgDeliverMode} deliverMode - The deliver mode.
+ * @returns {string}
+ */
+ getMsgFolderURIFromPrefs(userIdentity, deliverMode) {
+ if (
+ deliverMode == Ci.nsIMsgSend.nsMsgQueueForLater ||
+ deliverMode == Ci.nsIMsgSend.nsMsgDeliverBackground
+ ) {
+ let uri = Services.prefs.getCharPref("mail.default_sendlater_uri");
+ // check if uri is unescaped, and if so, escape it and reset the pef.
+ if (!uri) {
+ return "anyfolder://";
+ } else if (uri.includes(" ")) {
+ uri.replaceAll(" ", "%20");
+ Services.prefs.setCharPref("mail.default_sendlater_uri", uri);
+ }
+ return uri;
+ } else if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft) {
+ return userIdentity.draftFolder;
+ } else if (deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate) {
+ return userIdentity.stationeryFolder;
+ }
+ if (userIdentity.doFcc) {
+ return userIdentity.fccFolder;
+ }
+ return "";
+ },
+
+ /**
+ * Get the error string name of an exit code. The name will corresponds to an
+ * entry in composeMsgs.properties.
+ *
+ * @param {nsresult} exitCode - Exit code of sending mail process.
+ * @returns {string}
+ */
+ getErrorStringName(exitCode) {
+ let codeNameMap = {
+ [this.NS_MSG_UNABLE_TO_OPEN_FILE]: "unableToOpenFile",
+ [this.NS_MSG_UNABLE_TO_OPEN_TMP_FILE]: "unableToOpenTmpFile",
+ [this.NS_MSG_UNABLE_TO_SAVE_TEMPLATE]: "unableToSaveTemplate",
+ [this.NS_MSG_UNABLE_TO_SAVE_DRAFT]: "unableToSaveDraft",
+ [this.NS_MSG_COULDNT_OPEN_FCC_FOLDER]: "couldntOpenFccFolder",
+ [this.NS_MSG_NO_SENDER]: "noSender",
+ [this.NS_MSG_NO_RECIPIENTS]: "noRecipients",
+ [this.NS_MSG_ERROR_WRITING_FILE]: "errorWritingFile",
+ [this.NS_ERROR_SENDING_FROM_COMMAND]: "errorSendingFromCommand",
+ [this.NS_ERROR_SENDING_DATA_COMMAND]: "errorSendingDataCommand",
+ [this.NS_ERROR_SENDING_MESSAGE]: "errorSendingMessage",
+ [this.NS_ERROR_POST_FAILED]: "postFailed",
+ [this.NS_ERROR_SMTP_SERVER_ERROR]: "smtpServerError",
+ [this.NS_MSG_UNABLE_TO_SEND_LATER]: "unableToSendLater",
+ [this.NS_ERROR_COMMUNICATIONS_ERROR]: "communicationsError",
+ [this.NS_ERROR_BUT_DONT_SHOW_ALERT]: "dontShowAlert",
+ [this.NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS]:
+ "couldNotGetUsersMailAddress2",
+ [this.NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY]:
+ "couldNotGetSendersIdentity",
+ [this.NS_ERROR_MIME_MPART_ATTACHMENT_ERROR]: "mimeMpartAttachmentError",
+ [this.NS_ERROR_NNTP_NO_CROSS_POSTING]: "nntpNoCrossPosting",
+ [this.NS_MSG_ERROR_READING_FILE]: "errorReadingFile",
+ [this.NS_MSG_ERROR_ATTACHING_FILE]: "errorAttachingFile",
+ [this.NS_ERROR_SMTP_GREETING]: "incorrectSmtpGreeting",
+ [this.NS_ERROR_SENDING_RCPT_COMMAND]: "errorSendingRcptCommand",
+ [this.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS]: "startTlsFailed",
+ [this.NS_ERROR_SMTP_PASSWORD_UNDEFINED]: "smtpPasswordUndefined",
+ [this.NS_ERROR_SMTP_SEND_NOT_ALLOWED]: "smtpSendNotAllowed",
+ [this.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED]: "smtpTooManyRecipients",
+ [this.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2]: "smtpPermSizeExceeded2",
+ [this.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER]:
+ "smtpSendFailedUnknownServer",
+ [this.NS_ERROR_SMTP_SEND_FAILED_REFUSED]: "smtpSendRequestRefused",
+ [this.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED]: "smtpSendInterrupted",
+ [this.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT]: "smtpSendTimeout",
+ [this.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON]:
+ "smtpSendFailedUnknownReason",
+ [this.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL]:
+ "smtpHintAuthEncryptToPlainNoSsl",
+ [this.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL]:
+ "smtpHintAuthEncryptToPlainSsl",
+ [this.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT]:
+ "smtpHintAuthPlainToEncrypt",
+ [this.NS_ERROR_SMTP_AUTH_FAILURE]: "smtpAuthFailure",
+ [this.NS_ERROR_SMTP_AUTH_GSSAPI]: "smtpAuthGssapi",
+ [this.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED]: "smtpAuthMechNotSupported",
+ [this.NS_ERROR_ILLEGAL_LOCALPART]: "errorIllegalLocalPart2",
+ [this.NS_ERROR_CLIENTID]: "smtpClientid",
+ [this.NS_ERROR_CLIENTID_PERMISSION]: "smtpClientidPermission",
+ };
+ return codeNameMap[exitCode] || "sendFailed";
+ },
+
+ /**
+ * Format the error message that will be shown to the user.
+ *
+ * @param {nsIMsgIdentity} userIdentity - User identity.
+ * @param {nsIStringBundle} composeBundle - Localized string bundle.
+ * @param {string} errorName - The error name derived from an exit code.
+ * @returns {string}
+ */
+ formatStringWithSMTPHostName(userIdentity, composeBundle, errorName) {
+ let smtpServer = MailServices.smtp.getServerByIdentity(userIdentity);
+ let smtpHostname = smtpServer.hostname;
+ return composeBundle.formatStringFromName(errorName, [smtpHostname]);
+ },
+
+ /**
+ * Generate random alphanumeric string.
+ *
+ * @param {number} size - The length of generated string.
+ * @returns {string}
+ */
+ randomString(size) {
+ let length = Math.round((size * 3) / 4);
+ return btoa(
+ String.fromCharCode(
+ ...[...Array(length)].map(() => Math.floor(Math.random() * 256))
+ )
+ )
+ .slice(0, size)
+ .replaceAll(/[+/=]/g, "0");
+ },
+
+ /**
+ * Generate a content id to be used by embedded images.
+ *
+ * @param {nsIMsgIdentity} userIdentity - User identity.
+ * @param {number} partNum - The number of embedded MimePart.
+ * @returns {string}
+ */
+ makeContentId(userIdentity, partNum) {
+ let domain = userIdentity.email.split("@")[1];
+ return `part${partNum}.${this.randomString(8)}.${this.randomString(
+ 8
+ )}@${domain}`;
+ },
+
+ /**
+ * Pick a file name from the file URL.
+ *
+ * @param {string} url - The file URL.
+ * @returns {string}
+ */
+ pickFileNameFromUrl(url) {
+ if (/^(news|snews|imap|mailbox):/i.test(url)) {
+ // No sensible file name in it,
+ return "";
+ }
+ if (/^data:/i.test(url)) {
+ let matches = /filename=(.*);/.exec(url);
+ if (matches && matches[1]) {
+ return decodeURIComponent(matches[1]);
+ }
+ let mimeType = url.slice(5, url.indexOf(";"));
+ let extname = "";
+ try {
+ extname = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getPrimaryExtension(mimeType, null);
+ if (!extname) {
+ return "";
+ }
+ } catch (e) {
+ return "";
+ }
+ return `${this.randomString(16)}.${extname}`;
+ }
+ // Take the part after the last / or \.
+ let lastSlash = url.lastIndexOf("\\");
+ if (lastSlash == -1) {
+ lastSlash = url.lastIndexOf("/");
+ }
+ // Strip any search or anchor.
+ return url
+ .slice(lastSlash + 1)
+ .split("?")[0]
+ .split("#")[0];
+ },
+};