From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- comm/mailnews/compose/src/MimeMessageUtils.jsm | 1058 ++++++++++++++++++++++++ 1 file changed, 1058 insertions(+) create mode 100644 comm/mailnews/compose/src/MimeMessageUtils.jsm (limited to 'comm/mailnews/compose/src/MimeMessageUtils.jsm') 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..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..header.
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]; + }, +}; -- cgit v1.2.3