summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose/src
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/compose/src')
-rw-r--r--comm/mailnews/compose/src/MailtoProtocolHandler.jsm38
-rw-r--r--comm/mailnews/compose/src/MessageSend.jsm1434
-rw-r--r--comm/mailnews/compose/src/MimeEncoder.jsm430
-rw-r--r--comm/mailnews/compose/src/MimeMessage.jsm625
-rw-r--r--comm/mailnews/compose/src/MimeMessageUtils.jsm1058
-rw-r--r--comm/mailnews/compose/src/MimePart.jsm378
-rw-r--r--comm/mailnews/compose/src/SMTPProtocolHandler.jsm39
-rw-r--r--comm/mailnews/compose/src/SmtpClient.jsm1344
-rw-r--r--comm/mailnews/compose/src/SmtpServer.jsm519
-rw-r--r--comm/mailnews/compose/src/SmtpService.jsm350
-rw-r--r--comm/mailnews/compose/src/components.conf197
-rw-r--r--comm/mailnews/compose/src/moz.build60
-rw-r--r--comm/mailnews/compose/src/nsComposeStrings.cpp106
-rw-r--r--comm/mailnews/compose/src/nsComposeStrings.h76
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachedFile.cpp179
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachedFile.h13
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachment.cpp263
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachment.h42
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachmentData.cpp103
-rw-r--r--comm/mailnews/compose/src/nsMsgAttachmentData.h131
-rw-r--r--comm/mailnews/compose/src/nsMsgCompFields.cpp559
-rw-r--r--comm/mailnews/compose/src/nsMsgCompFields.h212
-rw-r--r--comm/mailnews/compose/src/nsMsgCompUtils.cpp1164
-rw-r--r--comm/mailnews/compose/src/nsMsgCompUtils.h116
-rw-r--r--comm/mailnews/compose/src/nsMsgCompose.cpp5094
-rw-r--r--comm/mailnews/compose/src/nsMsgCompose.h245
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp119
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeContentHandler.h19
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeParams.cpp157
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeParams.h30
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp40
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeProgressParams.h19
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeService.cpp1411
-rw-r--r--comm/mailnews/compose/src/nsMsgComposeService.h68
-rw-r--r--comm/mailnews/compose/src/nsMsgCopy.cpp471
-rw-r--r--comm/mailnews/compose/src/nsMsgCopy.h108
-rw-r--r--comm/mailnews/compose/src/nsMsgPrompts.cpp94
-rw-r--r--comm/mailnews/compose/src/nsMsgPrompts.h28
-rw-r--r--comm/mailnews/compose/src/nsMsgQuote.cpp188
-rw-r--r--comm/mailnews/compose/src/nsMsgQuote.h51
-rw-r--r--comm/mailnews/compose/src/nsMsgSendLater.cpp1406
-rw-r--r--comm/mailnews/compose/src/nsMsgSendLater.h142
-rw-r--r--comm/mailnews/compose/src/nsMsgSendReport.cpp385
-rw-r--r--comm/mailnews/compose/src/nsMsgSendReport.h45
-rw-r--r--comm/mailnews/compose/src/nsSmtpUrl.cpp755
-rw-r--r--comm/mailnews/compose/src/nsSmtpUrl.h144
46 files changed, 20455 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/MailtoProtocolHandler.jsm b/comm/mailnews/compose/src/MailtoProtocolHandler.jsm
new file mode 100644
index 0000000000..a129e7d750
--- /dev/null
+++ b/comm/mailnews/compose/src/MailtoProtocolHandler.jsm
@@ -0,0 +1,38 @@
+/* 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 = ["MailtoProtocolHandler"];
+
+/**
+ * Protocol handler for mailto: url.
+ *
+ * @implements {nsIProtocolHandler}
+ */
+class MailtoProtocolHandler {
+ QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolHandler]);
+
+ scheme = "mailto";
+ allowPort = false;
+
+ newChannel(uri, loadInfo) {
+ // Create an empty pipe to get an inputStream.
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0);
+ pipe.outputStream.close();
+
+ // Create a channel so that we can set contentType onto it.
+ let streamChannel = Cc[
+ "@mozilla.org/network/input-stream-channel;1"
+ ].createInstance(Ci.nsIInputStreamChannel);
+ streamChannel.setURI(uri);
+ streamChannel.contentStream = pipe.inputStream;
+
+ let channel = streamChannel.QueryInterface(Ci.nsIChannel);
+ // With this set, a nsIContentHandler instance will take over to open a
+ // compose window.
+ channel.contentType = "application/x-mailto";
+ channel.loadInfo = loadInfo;
+ return channel;
+ }
+}
diff --git a/comm/mailnews/compose/src/MessageSend.jsm b/comm/mailnews/compose/src/MessageSend.jsm
new file mode 100644
index 0000000000..914694fa79
--- /dev/null
+++ b/comm/mailnews/compose/src/MessageSend.jsm
@@ -0,0 +1,1434 @@
+/* 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 = ["MessageSend"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ jsmime: "resource:///modules/jsmime.jsm",
+ MimeMessage: "resource:///modules/MimeMessage.jsm",
+ MsgUtils: "resource:///modules/MimeMessageUtils.jsm",
+});
+
+// nsMsgKey_None from MailNewsTypes.h.
+const nsMsgKey_None = 0xffffffff;
+
+/**
+ * A class to manage sending processes.
+ *
+ * @implements {nsIMsgSend}
+ * @implements {nsIWebProgressListener}
+ */
+class MessageSend {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIMsgSend",
+ "nsIWebProgressListener",
+ ]);
+ classID = Components.ID("{028b9c1e-8d0a-4518-80c2-842e07846eaa}");
+
+ async createAndSendMessage(
+ editor,
+ userIdentity,
+ accountKey,
+ compFields,
+ isDigest,
+ dontDeliver,
+ deliverMode,
+ msgToReplace,
+ bodyType,
+ body,
+ parentWindow,
+ progress,
+ listener,
+ smtpPassword,
+ originalMsgURI,
+ compType
+ ) {
+ this._userIdentity = userIdentity;
+ this._accountKey = accountKey || this._accountKeyForIdentity(userIdentity);
+ this._compFields = compFields;
+ this._dontDeliver = dontDeliver;
+ this._deliverMode = deliverMode;
+ this._msgToReplace = msgToReplace;
+ this._sendProgress = progress;
+ this._smtpPassword = smtpPassword;
+ this._sendListener = listener;
+ this._parentWindow = parentWindow;
+ this._originalMsgURI = originalMsgURI;
+ this._shouldRemoveMessageFile = true;
+
+ this._sendReport = Cc[
+ "@mozilla.org/messengercompose/sendreport;1"
+ ].createInstance(Ci.nsIMsgSendReport);
+ this._composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+
+ // Initialize the error reporting mechanism.
+ this.sendReport.reset();
+ this.sendReport.deliveryMode = deliverMode;
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("assemblingMailInformation")
+ );
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_BuildMessage;
+
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("assemblingMessage")
+ );
+
+ this._fcc = lazy.MsgUtils.getFcc(
+ userIdentity,
+ compFields,
+ originalMsgURI,
+ compType
+ );
+ let { embeddedAttachments, embeddedObjects } =
+ this._gatherEmbeddedAttachments(editor);
+
+ let bodyText = this._getBodyFromEditor(editor) || body;
+ // Convert to a binary string. This is because MimeMessage requires it and:
+ // 1. An attachment content is BinaryString.
+ // 2. Body text and attachment contents are handled in the same way by
+ // MimeEncoder to pick encoding and encode.
+ bodyText = lazy.jsmime.mimeutils.typedArrayToString(
+ new TextEncoder().encode(bodyText)
+ );
+
+ this._restoreEditorContent(embeddedObjects);
+ this._message = new lazy.MimeMessage(
+ userIdentity,
+ compFields,
+ this._fcc,
+ bodyType,
+ bodyText,
+ deliverMode,
+ originalMsgURI,
+ compType,
+ embeddedAttachments,
+ this.sendReport
+ );
+
+ this._messageKey = nsMsgKey_None;
+
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("creatingMailMessage")
+ );
+ lazy.MsgUtils.sendLogger.debug("Creating message file");
+ let messageFile;
+ try {
+ // Create a local file from MimeMessage, then pass it to _deliverMessage.
+ messageFile = await this._message.createMessageFile();
+ } catch (e) {
+ lazy.MsgUtils.sendLogger.error(e);
+ let errorMsg = "";
+ if (e.result == lazy.MsgUtils.NS_MSG_ERROR_ATTACHING_FILE) {
+ errorMsg = this._composeBundle.formatStringFromName(
+ "errorAttachingFile",
+ [e.data.name || e.data.url]
+ );
+ }
+ this.fail(e.result || Cr.NS_ERROR_FAILURE, errorMsg);
+ this.notifyListenerOnStopSending(null, e.result, null, null);
+ return null;
+ }
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("assemblingMessageDone")
+ );
+ lazy.MsgUtils.sendLogger.debug("Message file created");
+ return this._deliverMessage(messageFile);
+ }
+
+ sendMessageFile(
+ userIdentity,
+ accountKey,
+ compFields,
+ messageFile,
+ deleteSendFileOnCompletion,
+ digest,
+ deliverMode,
+ msgToReplace,
+ listener,
+ statusFeedback,
+ smtpPassword
+ ) {
+ this._userIdentity = userIdentity;
+ this._accountKey = accountKey || this._accountKeyForIdentity(userIdentity);
+ this._compFields = compFields;
+ this._deliverMode = deliverMode;
+ this._msgToReplace = msgToReplace;
+ this._smtpPassword = smtpPassword;
+ this._sendListener = listener;
+ this._statusFeedback = statusFeedback;
+ this._shouldRemoveMessageFile = deleteSendFileOnCompletion;
+
+ this._sendReport = Cc[
+ "@mozilla.org/messengercompose/sendreport;1"
+ ].createInstance(Ci.nsIMsgSendReport);
+ this._composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+
+ // Initialize the error reporting mechanism.
+ this.sendReport.reset();
+ this.sendReport.deliveryMode = deliverMode;
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("assemblingMailInformation")
+ );
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_BuildMessage;
+
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("assemblingMessage")
+ );
+
+ this._fcc = lazy.MsgUtils.getFcc(
+ userIdentity,
+ compFields,
+ null,
+ Ci.nsIMsgCompType.New
+ );
+
+ // nsMsgKey_None from MailNewsTypes.h.
+ this._messageKey = 0xffffffff;
+
+ return this._deliverMessage(messageFile);
+ }
+
+ // @see nsIMsgSend
+ createRFC822Message(
+ userIdentity,
+ compFields,
+ bodyType,
+ bodyText,
+ isDraft,
+ attachedFiles,
+ embeddedObjects,
+ listener
+ ) {
+ this._userIdentity = userIdentity;
+ this._compFields = compFields;
+ this._dontDeliver = true;
+ this._sendListener = listener;
+
+ this._sendReport = Cc[
+ "@mozilla.org/messengercompose/sendreport;1"
+ ].createInstance(Ci.nsIMsgSendReport);
+ this._composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+
+ // Initialize the error reporting mechanism.
+ this.sendReport.reset();
+ let deliverMode = isDraft
+ ? Ci.nsIMsgSend.nsMsgSaveAsDraft
+ : Ci.nsIMsgSend.nsMsgDeliverNow;
+ this.sendReport.deliveryMode = deliverMode;
+
+ // Convert nsIMsgAttachedFile[] to nsIMsgAttachment[]
+ for (let file of attachedFiles) {
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ attachment.name = file.realName;
+ attachment.url = file.origUrl.spec;
+ attachment.contentType = file.type;
+ compFields.addAttachment(attachment);
+ }
+
+ // Convert nsIMsgEmbeddedImageData[] to nsIMsgAttachment[]
+ let embeddedAttachments = embeddedObjects.map(obj => {
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ attachment.name = obj.name;
+ attachment.contentId = obj.cid;
+ attachment.url = obj.uri.spec;
+ return attachment;
+ });
+
+ this._message = new lazy.MimeMessage(
+ userIdentity,
+ compFields,
+ null,
+ bodyType,
+ bodyText,
+ deliverMode,
+ null,
+ Ci.nsIMsgCompType.New,
+ embeddedAttachments,
+ this.sendReport
+ );
+
+ this._messageKey = nsMsgKey_None;
+
+ // Create a local file from MimeMessage, then pass it to _deliverMessage.
+ this._message
+ .createMessageFile()
+ .then(messageFile => this._deliverMessage(messageFile));
+ }
+
+ // nsIWebProgressListener.
+ onLocationChange(webProgress, request, location, flags) {}
+ onProgressChange(
+ webProgress,
+ request,
+ curSelfProgress,
+ maxSelfProgress,
+ curTotalProgress,
+ maxTotalProgress
+ ) {}
+ onStatusChange(webProgress, request, status, message) {}
+ onSecurityChange(webProgress, request, state) {}
+ onContentBlockingEvent(webProgress, request, event) {}
+ onStateChange(webProgress, request, stateFlags, status) {
+ if (
+ stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ !Components.isSuccessCode(status)
+ ) {
+ lazy.MsgUtils.sendLogger.debug("onStateChange with failure. Aborting.");
+ this._isRetry = false;
+ this.abort();
+ }
+ }
+
+ abort() {
+ if (this._aborting) {
+ return;
+ }
+ this._aborting = true;
+ if (this._smtpRequest?.value) {
+ this._smtpRequest.value.cancel(Cr.NS_ERROR_ABORT);
+ this._smtpRequest = null;
+ }
+ if (this._msgCopy) {
+ MailServices.copy.notifyCompletion(
+ this._copyFile,
+ this._msgCopy.dstFolder,
+ Cr.NS_ERROR_ABORT
+ );
+ } else {
+ // If already in the fcc step, notifyListenerOnStopCopy will do the clean up.
+ this._cleanup();
+ }
+ if (!this._failed) {
+ // Emit stopsending event if the sending is cancelled by user, so that
+ // listeners can do necessary clean up, e.g. reset the sending button.
+ this.notifyListenerOnStopSending(null, Cr.NS_ERROR_ABORT, null, null);
+ }
+ this._aborting = false;
+ }
+
+ fail(exitCode, errorMsg) {
+ this._failed = true;
+ if (!Components.isSuccessCode(exitCode) && exitCode != Cr.NS_ERROR_ABORT) {
+ lazy.MsgUtils.sendLogger.error(
+ `Sending failed; ${errorMsg}, exitCode=${exitCode}, originalMsgURI=${this._originalMsgURI}`
+ );
+ this._sendReport.setError(
+ Ci.nsIMsgSendReport.process_Current,
+ exitCode,
+ false
+ );
+ if (errorMsg) {
+ this._sendReport.setMessage(
+ Ci.nsIMsgSendReport.process_Current,
+ errorMsg,
+ false
+ );
+ }
+ exitCode = this._sendReport.displayReport(this._parentWindow, true, true);
+ }
+ this.abort();
+
+ return exitCode;
+ }
+
+ getPartForDomIndex(domIndex) {
+ throw Components.Exception(
+ "getPartForDomIndex not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ getProgress() {
+ return this._sendProgress;
+ }
+
+ /**
+ * NOTE: This is a copy of the C++ code, msgId and msgSize are only
+ * placeholders. Maybe refactor this after nsMsgSend is gone.
+ */
+ notifyListenerOnStartSending(msgId, msgSize) {
+ lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartSending");
+ if (this._sendListener) {
+ this._sendListener.onStartSending(msgId, msgSize);
+ }
+ }
+
+ notifyListenerOnStartCopy() {
+ lazy.MsgUtils.sendLogger.debug("notifyListenerOnStartCopy");
+ if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) {
+ this._sendListener.OnStartCopy();
+ }
+ }
+
+ notifyListenerOnProgressCopy(progress, progressMax) {
+ lazy.MsgUtils.sendLogger.debug("notifyListenerOnProgressCopy");
+ if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) {
+ this._sendListener.OnProgress(progress, progressMax);
+ }
+ }
+
+ notifyListenerOnStopCopy(status) {
+ lazy.MsgUtils.sendLogger.debug(
+ `notifyListenerOnStopCopy; status=${status}`
+ );
+ this._msgCopy = null;
+
+ if (!this._isRetry) {
+ let statusMsgEntry = Components.isSuccessCode(status)
+ ? "copyMessageComplete"
+ : "copyMessageFailed";
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName(statusMsgEntry)
+ );
+ } else if (Components.isSuccessCode(status)) {
+ // We got here via retry and the save to sent, drafts or template
+ // succeeded so take down our progress dialog. We don't need it any more.
+ this._sendProgress.unregisterListener(this);
+ this._sendProgress.closeProgressDialog(false);
+ this._isRetry = false;
+ }
+
+ if (!Components.isSuccessCode(status)) {
+ let localFoldersAccountName =
+ MailServices.accounts.localFoldersServer.prettyName;
+ let folder = lazy.MailUtils.getOrCreateFolder(this._folderUri);
+ let accountName = folder?.server.prettyName;
+ if (!this._fcc || !localFoldersAccountName || !accountName) {
+ this.fail(Cr.NS_OK, null);
+ return;
+ }
+
+ let params = [folder.name, accountName, localFoldersAccountName];
+ let promptMsg;
+ switch (this._deliverMode) {
+ case Ci.nsIMsgSend.nsMsgDeliverNow:
+ case Ci.nsIMsgSend.nsMsgSendUnsent:
+ promptMsg = this._composeBundle.formatStringFromName(
+ "promptToSaveSentLocally2",
+ params
+ );
+ break;
+ case Ci.nsIMsgSend.nsMsgSaveAsDraft:
+ promptMsg = this._composeBundle.formatStringFromName(
+ "promptToSaveDraftLocally2",
+ params
+ );
+ break;
+ case Ci.nsIMsgSend.nsMsgSaveAsTemplate:
+ promptMsg = this._composeBundle.formatStringFromName(
+ "promptToSaveTemplateLocally2",
+ params
+ );
+ break;
+ }
+ if (promptMsg) {
+ let showCheckBox = { value: false };
+ let buttonFlags =
+ Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
+ Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE +
+ Ci.nsIPrompt.BUTTON_POS_2 * Ci.nsIPrompt.BUTTON_TITLE_SAVE;
+ let dialogTitle =
+ this._composeBundle.GetStringFromName("SaveDialogTitle");
+ let buttonLabelRety =
+ this._composeBundle.GetStringFromName("buttonLabelRetry2");
+ let buttonPressed = Services.prompt.confirmEx(
+ this._parentWindow,
+ dialogTitle,
+ promptMsg,
+ buttonFlags,
+ buttonLabelRety,
+ null,
+ null,
+ null,
+ showCheckBox
+ );
+ if (buttonPressed == 0) {
+ // retry button clicked
+ // Check we have a progress dialog.
+ if (
+ this._sendProgress.processCanceledByUser &&
+ Services.prefs.getBoolPref("mailnews.show_send_progress")
+ ) {
+ let progress = Cc[
+ "@mozilla.org/messenger/progress;1"
+ ].createInstance(Ci.nsIMsgProgress);
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeprogressparameters;1"
+ ].createInstance(Ci.nsIMsgComposeProgressParams);
+ params.subject = this._parentWindow.gMsgCompose.compFields.subject;
+ params.deliveryMode = this._deliverMode;
+
+ progress.openProgressDialog(
+ this._parentWindow,
+ this._sendProgress.msgWindow,
+ "chrome://messenger/content/messengercompose/sendProgress.xhtml",
+ false,
+ params
+ );
+
+ progress.onStateChange(
+ null,
+ null,
+ Ci.nsIWebProgressListener.STATE_START,
+ Cr.NS_OK
+ );
+
+ // We want to hear when this is cancelled.
+ progress.registerListener(this);
+
+ this._sendProgress = progress;
+ this._isRetry = true;
+ }
+ // Ensure statusFeedback is set so progress percent bargraph occurs.
+ this._sendProgress.msgWindow.statusFeedback = this._sendProgress;
+
+ this._mimeDoFcc();
+ return;
+ } else if (buttonPressed == 2) {
+ try {
+ // Try to save to Local Folders/<account name>. Pass null to save
+ // to local folders and not the configured fcc.
+ this._mimeDoFcc(null, true, Ci.nsIMsgSend.nsMsgDeliverNow);
+ return;
+ } catch (e) {
+ Services.prompt.alert(
+ this._parentWindow,
+ null,
+ this._composeBundle.GetStringFromName("saveToLocalFoldersFailed")
+ );
+ }
+ }
+ }
+ this.fail(Cr.NS_OK, null);
+ }
+
+ if (
+ !this._fcc2Handled &&
+ this._messageKey != nsMsgKey_None &&
+ [Ci.nsIMsgSend.nsMsgDeliverNow, Ci.nsIMsgSend.nsMsgSendUnsent].includes(
+ this._deliverMode
+ )
+ ) {
+ try {
+ this._filterSentMessage();
+ } catch (e) {
+ this.onStopOperation(e.result);
+ }
+ return;
+ }
+
+ this._doFcc2();
+ }
+
+ notifyListenerOnStopSending(msgId, status, msg, returnFile) {
+ lazy.MsgUtils.sendLogger.debug(
+ `notifyListenerOnStopSending; status=${status}`
+ );
+ try {
+ this._sendListener?.onStopSending(msgId, status, msg, returnFile);
+ } catch (e) {}
+ }
+
+ notifyListenerOnTransportSecurityError(msgId, status, secInfo, location) {
+ lazy.MsgUtils.sendLogger.debug(
+ `notifyListenerOnTransportSecurityError; status=${status}, location=${location}`
+ );
+ if (!this._sendListener) {
+ return;
+ }
+ try {
+ this._sendListener.onTransportSecurityError(
+ msgId,
+ status,
+ secInfo,
+ location
+ );
+ } catch (e) {}
+ }
+
+ /**
+ * Called by nsIMsgFilterService.
+ */
+ onStopOperation(status) {
+ lazy.MsgUtils.sendLogger.debug(`onStopOperation; status=${status}`);
+ if (Components.isSuccessCode(status)) {
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("filterMessageComplete")
+ );
+ } else {
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("filterMessageFailed")
+ );
+ Services.prompt.alert(
+ this._parentWindow,
+ null,
+ this._composeBundle.GetStringFromName("errorFilteringMsg")
+ );
+ }
+
+ this._doFcc2();
+ }
+
+ /**
+ * Handle the exit code of message delivery.
+ *
+ * @param {nsIURI} url - The delivered message uri.
+ * @param {boolean} isNewsDelivery - The message was delivered to newsgroup.
+ * @param {nsreault} exitCode - The exit code of message delivery.
+ */
+ _deliveryExitProcessing(url, isNewsDelivery, exitCode) {
+ lazy.MsgUtils.sendLogger.debug(
+ `Delivery exit processing; exitCode=${exitCode}`
+ );
+ if (!Components.isSuccessCode(exitCode)) {
+ let isNSSError = false;
+ let errorName = lazy.MsgUtils.getErrorStringName(exitCode);
+ let errorMsg;
+ if (
+ [
+ lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER,
+ lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_REFUSED,
+ lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED,
+ lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT,
+ lazy.MsgUtils.NS_ERROR_SMTP_PASSWORD_UNDEFINED,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_GSSAPI,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL,
+ lazy.MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT,
+ lazy.MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS,
+ ].includes(exitCode)
+ ) {
+ errorMsg = lazy.MsgUtils.formatStringWithSMTPHostName(
+ this._userIdentity,
+ this._composeBundle,
+ errorName
+ );
+ } else {
+ let nssErrorsService = Cc[
+ "@mozilla.org/nss_errors_service;1"
+ ].getService(Ci.nsINSSErrorsService);
+ try {
+ // This is a server security issue as determined by the Mozilla
+ // platform. To the Mozilla security message string, appended a string
+ // having additional information with the server name encoded.
+ errorMsg = nssErrorsService.getErrorMessage(exitCode);
+ errorMsg +=
+ "\n" +
+ lazy.MsgUtils.formatStringWithSMTPHostName(
+ this._userIdentity,
+ this._composeBundle,
+ "smtpSecurityIssue"
+ );
+ isNSSError = true;
+ } catch (e) {
+ if (url.errorMessage) {
+ // url.errorMessage is an already localized message, usually
+ // combined with the error message from SMTP server.
+ errorMsg = url.errorMessage;
+ } else if (errorName != "sendFailed") {
+ // Not the default string. A mailnews error occurred that does not
+ // require the server name to be encoded. Just print the descriptive
+ // string.
+ errorMsg = this._composeBundle.GetStringFromName(errorName);
+ } else {
+ errorMsg = this._composeBundle.GetStringFromName(
+ "sendFailedUnexpected"
+ );
+ // nsIStringBundle.formatStringFromName doesn't work with %X.
+ errorMsg.replace("%X", `0x${exitCode.toString(16)}`);
+ errorMsg =
+ "\n" +
+ lazy.MsgUtils.formatStringWithSMTPHostName(
+ this._userIdentity,
+ this._composeBundle,
+ "smtpSendFailedUnknownReason"
+ );
+ }
+ }
+ }
+ if (isNSSError) {
+ let u = url.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ this.notifyListenerOnTransportSecurityError(
+ null,
+ exitCode,
+ u.failedSecInfo,
+ u.asciiHostPort
+ );
+ }
+ this.notifyListenerOnStopSending(null, exitCode, null, null);
+ this.fail(exitCode, errorMsg);
+ return;
+ }
+
+ if (
+ isNewsDelivery &&
+ (this._compFields.to || this._compFields.cc || this._compFields.bcc)
+ ) {
+ this._deliverAsMail();
+ return;
+ }
+
+ this.notifyListenerOnStopSending(
+ this._compFields.messageId,
+ exitCode,
+ null,
+ null
+ );
+
+ this._doFcc();
+ }
+
+ sendDeliveryCallback(url, isNewsDelivery, exitCode) {
+ if (isNewsDelivery) {
+ if (
+ !Components.isSuccessCode(exitCode) &&
+ exitCode != Cr.NS_ERROR_ABORT &&
+ !lazy.MsgUtils.isMsgError(exitCode)
+ ) {
+ exitCode = lazy.MsgUtils.NS_ERROR_POST_FAILED;
+ }
+ return this._deliveryExitProcessing(url, isNewsDelivery, exitCode);
+ }
+ if (!Components.isSuccessCode(exitCode)) {
+ switch (exitCode) {
+ case Cr.NS_ERROR_UNKNOWN_HOST:
+ case Cr.NS_ERROR_UNKNOWN_PROXY_HOST:
+ exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER;
+ break;
+ case Cr.NS_ERROR_CONNECTION_REFUSED:
+ case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
+ exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_REFUSED;
+ break;
+ case Cr.NS_ERROR_NET_INTERRUPT:
+ exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED;
+ break;
+ case Cr.NS_ERROR_NET_TIMEOUT:
+ case Cr.NS_ERROR_NET_RESET:
+ exitCode = lazy.MsgUtils.NS_ERROR_SMTP_SEND_FAILED_TIMEOUT;
+ break;
+ default:
+ break;
+ }
+ }
+ return this._deliveryExitProcessing(url, isNewsDelivery, exitCode);
+ }
+
+ get folderUri() {
+ return this._folderUri;
+ }
+
+ /**
+ * @type {nsMsgKey}
+ */
+ set messageKey(key) {
+ this._messageKey = key;
+ }
+
+ /**
+ * @type {nsMsgKey}
+ */
+ get messageKey() {
+ return this._messageKey;
+ }
+
+ get sendReport() {
+ return this._sendReport;
+ }
+
+ _setStatusMessage(msg) {
+ if (this._sendProgress) {
+ this._sendProgress.onStatusChange(null, null, Cr.NS_OK, msg);
+ }
+ }
+
+ /**
+ * Deliver a message.
+ *
+ * @param {nsIFile} file - The message file to deliver.
+ */
+ async _deliverMessage(file) {
+ if (this._dontDeliver) {
+ this.notifyListenerOnStopSending(null, Cr.NS_OK, null, file);
+ return;
+ }
+
+ this._messageFile = file;
+ if (
+ [
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ ].includes(this._deliverMode)
+ ) {
+ await this._mimeDoFcc();
+ return;
+ }
+
+ let warningSize = Services.prefs.getIntPref(
+ "mailnews.message_warning_size"
+ );
+ if (warningSize > 0 && file.fileSize > warningSize) {
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let msg = this._composeBundle.formatStringFromName(
+ "largeMessageSendWarning",
+ [messenger.formatFileSize(file.fileSize)]
+ );
+ if (!Services.prompt.confirm(this._parentWindow, null, msg)) {
+ this.fail(lazy.MsgUtils.NS_ERROR_BUT_DONT_SHOW_ALERT, msg);
+ throw Components.Exception(
+ "Cancelled sending large message",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ }
+
+ this._deliveryFile = await this._createDeliveryFile();
+ if (this._compFields.newsgroups) {
+ this._deliverAsNews();
+ return;
+ }
+ await this._deliverAsMail();
+ }
+
+ /**
+ * Strip Bcc header, create the file to be actually delivered.
+ *
+ * @returns {nsIFile}
+ */
+ async _createDeliveryFile() {
+ if (!this._compFields.bcc) {
+ return this._messageFile;
+ }
+ let deliveryFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ deliveryFile.append("nsemail.tmp");
+ deliveryFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ let content = await IOUtils.read(this._messageFile.path);
+ let bodyIndex = content.findIndex(
+ (el, index) =>
+ // header and body are separated by \r\n\r\n
+ el == 13 &&
+ content[index + 1] == 10 &&
+ content[index + 2] == 13 &&
+ content[index + 3] == 10
+ );
+ let header = new TextDecoder("UTF-8").decode(content.slice(0, bodyIndex));
+ let lastLinePruned = false;
+ let headerToWrite = "";
+ for (let line of header.split("\r\n")) {
+ if (line.startsWith("Bcc") || (line.startsWith(" ") && lastLinePruned)) {
+ lastLinePruned = true;
+ continue;
+ }
+ lastLinePruned = false;
+ headerToWrite += `${line}\r\n`;
+ }
+ let encodedHeader = new TextEncoder().encode(headerToWrite);
+ // Prevent extra \r\n, which was already added to the last head line.
+ let body = content.slice(bodyIndex + 2);
+ let combinedContent = new Uint8Array(encodedHeader.length + body.length);
+ combinedContent.set(encodedHeader);
+ combinedContent.set(body, encodedHeader.length);
+ await IOUtils.write(deliveryFile.path, combinedContent);
+ return deliveryFile;
+ }
+
+ /**
+ * Create the file to be copied to the Sent folder, add X-Mozilla-Status and
+ * X-Mozilla-Status2 if needed.
+ *
+ * @returns {nsIFile}
+ */
+ async _createCopyFile() {
+ if (!this._folderUri.startsWith("mailbox:")) {
+ return this._messageFile;
+ }
+
+ // Add a `From - Date` line, so that nsLocalMailFolder.cpp won't add a
+ // dummy envelope. The date string will be parsed by PR_ParseTimeString.
+ // TODO: this should not be added to Maildir, see bug 1686852.
+ let contentToWrite = `From - ${new Date().toUTCString()}\r\n`;
+ let xMozillaStatus = lazy.MsgUtils.getXMozillaStatus(this._deliverMode);
+ let xMozillaStatus2 = lazy.MsgUtils.getXMozillaStatus2(this._deliverMode);
+ if (xMozillaStatus) {
+ contentToWrite += `X-Mozilla-Status: ${xMozillaStatus}\r\n`;
+ }
+ if (xMozillaStatus2) {
+ contentToWrite += `X-Mozilla-Status2: ${xMozillaStatus2}\r\n`;
+ }
+
+ // Create a separate copy file when there are extra headers.
+ let copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ copyFile.append("nscopy.tmp");
+ copyFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ await IOUtils.writeUTF8(copyFile.path, contentToWrite);
+ await IOUtils.write(
+ copyFile.path,
+ await IOUtils.read(this._messageFile.path),
+ {
+ mode: "append",
+ }
+ );
+ return copyFile;
+ }
+
+ /**
+ * Start copy operation according to this._fcc value.
+ */
+ async _doFcc() {
+ if (!this._fcc || !lazy.MsgUtils.canSaveToFolder(this._fcc)) {
+ this.notifyListenerOnStopCopy(Cr.NS_OK);
+ return;
+ }
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Copy;
+ this._mimeDoFcc(this._fcc, false, Ci.nsIMsgSend.nsMsgDeliverNow);
+ }
+
+ /**
+ * Copy a message to a folder, or fallback to a folder depending on pref and
+ * deliverMode, usually Drafts/Sent.
+ *
+ * @param {string} [fccHeader=this._fcc] - The target folder uri to copy the
+ * message to.
+ * @param {boolean} [throwOnError=false] - By default notifyListenerOnStopCopy
+ * is called on error. When throwOnError is true, the caller can handle the
+ * error by itself.
+ * @param {nsMsgDeliverMode} [deliverMode=this._deliverMode] - The deliver mode.
+ */
+ async _mimeDoFcc(
+ fccHeader = this._fcc,
+ throwOnError = false,
+ deliverMode = this._deliverMode
+ ) {
+ let folder;
+ let folderUri;
+ if (fccHeader) {
+ folder = lazy.MailUtils.getExistingFolder(fccHeader);
+ }
+ if (
+ [Ci.nsIMsgSend.nsMsgDeliverNow, Ci.nsIMsgSend.nsMsgSendUnsent].includes(
+ deliverMode
+ ) &&
+ folder
+ ) {
+ this._folderUri = fccHeader;
+ } else if (fccHeader == null) {
+ // Set fcc_header to a special folder in Local Folders "account" since can't
+ // save to Sent mbox, typically because imap connection is down. This
+ // folder is created if it doesn't yet exist.
+ let rootFolder = MailServices.accounts.localFoldersServer.rootMsgFolder;
+ folderUri = rootFolder.URI + "/";
+
+ // Now append the special folder name folder to the local folder uri.
+ if (
+ [
+ Ci.nsIMsgSend.nsMsgDeliverNow,
+ Ci.nsIMsgSend.nsMsgSendUnsent,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ ].includes(this._deliverMode)
+ ) {
+ // Typically, this appends "Sent-", "Drafts-" or "Templates-" to folder
+ // and then has the account name appended, e.g., .../Sent-MyImapAccount.
+ let folder = lazy.MailUtils.getOrCreateFolder(this._folderUri);
+ folderUri += folder.name + "-";
+ }
+ if (this._fcc) {
+ // Get the account name where the "save to" failed.
+ let accountName = lazy.MailUtils.getOrCreateFolder(this._fcc).server
+ .prettyName;
+
+ // Now append the imap account name (escaped) to the folder uri.
+ folderUri += accountName;
+ this._folderUri = folderUri;
+ }
+ } else {
+ this._folderUri = lazy.MsgUtils.getMsgFolderURIFromPrefs(
+ this._userIdentity,
+ this._deliverMode
+ );
+ if (
+ (this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft &&
+ this._compFields.draftId) ||
+ (this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsTemplate &&
+ this._compFields.templateId)
+ ) {
+ // Turn the draft/template ID into a folder URI string.
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ try {
+ // This can fail if the user renames/removed/moved the folder.
+ folderUri = messenger.msgHdrFromURI(
+ this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft
+ ? this._compFields.draftId
+ : this._compFields.templateId
+ ).folder.URI;
+ } catch (ex) {
+ console.warn(ex);
+ }
+ // Only accept it if it's a subfolder of the identity's draft/template folder.
+ if (folderUri?.startsWith(this._folderUri)) {
+ this._folderUri = folderUri;
+ }
+ }
+ }
+ lazy.MsgUtils.sendLogger.debug(
+ `Processing fcc; folderUri=${this._folderUri}`
+ );
+
+ this._msgCopy = Cc[
+ "@mozilla.org/messengercompose/msgcopy;1"
+ ].createInstance(Ci.nsIMsgCopy);
+ this._copyFile = await this._createCopyFile();
+ lazy.MsgUtils.sendLogger.debug("fcc file created");
+
+ // Notify nsMsgCompose about the saved folder.
+ if (this._sendListener) {
+ this._sendListener.onGetDraftFolderURI(
+ this._compFields.messageId,
+ this._folderUri
+ );
+ }
+ folder = lazy.MailUtils.getOrCreateFolder(this._folderUri);
+ let statusMsg = this._composeBundle.formatStringFromName(
+ "copyMessageStart",
+ [folder?.name || "?"]
+ );
+ this._setStatusMessage(statusMsg);
+ lazy.MsgUtils.sendLogger.debug("startCopyOperation");
+ try {
+ this._msgCopy.startCopyOperation(
+ this._userIdentity,
+ this._copyFile,
+ this._deliverMode,
+ this,
+ this._folderUri,
+ this._msgToReplace
+ );
+ } catch (e) {
+ lazy.MsgUtils.sendLogger.warn(
+ `startCopyOperation failed with ${e.result}`
+ );
+ if (throwOnError) {
+ throw Components.Exception("startCopyOperation failed", e.result);
+ }
+ this.notifyListenerOnStopCopy(e.result);
+ }
+ }
+
+ /**
+ * Handle the fcc2 field. Then notify OnStopCopy and clean up.
+ */
+ _doFcc2() {
+ // Handle fcc2 only once.
+ if (!this._fcc2Handled && this._compFields.fcc2) {
+ lazy.MsgUtils.sendLogger.debug("Processing fcc2");
+ this._fcc2Handled = true;
+ this._mimeDoFcc(
+ this._compFields.fcc2,
+ false,
+ Ci.nsIMsgSend.nsMsgDeliverNow
+ );
+ return;
+ }
+
+ // NOTE: When nsMsgCompose receives OnStopCopy, it will release nsIMsgSend
+ // instance and close the compose window, which prevents the Promise from
+ // resolving in MsgComposeCommands.js. Use setTimeout to work around it.
+ lazy.setTimeout(() => {
+ try {
+ if (this._sendListener instanceof Ci.nsIMsgCopyServiceListener) {
+ this._sendListener.OnStopCopy(0);
+ }
+ } catch (e) {
+ // Ignore the return value of OnStopCopy. Non-zero nsresult will throw
+ // when going through XPConnect. In this case, we don't care about it.
+ console.warn(
+ `OnStopCopy failed with 0x${e.result.toString(16)}\n${e.stack}`
+ );
+ }
+ this._cleanup();
+ });
+ }
+
+ /**
+ * Run filters on the just sent message.
+ */
+ _filterSentMessage() {
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_Filter;
+ let folder = lazy.MailUtils.getExistingFolder(this._folderUri);
+ let msgHdr = folder.GetMessageHeader(this._messageKey);
+ let msgWindow = this._sendProgress?.msgWindow;
+ return MailServices.filters.applyFilters(
+ Ci.nsMsgFilterType.PostOutgoing,
+ [msgHdr],
+ folder,
+ msgWindow,
+ this
+ );
+ }
+
+ _cleanup() {
+ lazy.MsgUtils.sendLogger.debug("Clean up temporary files");
+ if (this._copyFile && this._copyFile != this._messageFile) {
+ IOUtils.remove(this._copyFile.path).catch(console.error);
+ this._copyFile = null;
+ }
+ if (this._deliveryFile && this._deliveryFile != this._messageFile) {
+ IOUtils.remove(this._deliveryFile.path).catch(console.error);
+ this._deliveryFile = null;
+ }
+ if (this._messageFile && this._shouldRemoveMessageFile) {
+ IOUtils.remove(this._messageFile.path).catch(console.error);
+ this._messageFile = null;
+ }
+ }
+
+ /**
+ * Send this._deliveryFile to smtp service.
+ */
+ async _deliverAsMail() {
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_SMTP;
+ this._setStatusMessage(
+ this._composeBundle.GetStringFromName("sendingMessage")
+ );
+ let recipients = [
+ this._compFields.to,
+ this._compFields.cc,
+ this._compFields.bcc,
+ ].filter(Boolean);
+ this._collectAddressesToAddressBook(recipients);
+ let converter = Cc["@mozilla.org/messenger/mimeconverter;1"].getService(
+ Ci.nsIMimeConverter
+ );
+ let encodedRecipients = encodeURIComponent(
+ converter.encodeMimePartIIStr_UTF8(
+ recipients.join(","),
+ true,
+ 0,
+ Ci.nsIMimeConverter.MIME_ENCODED_WORD_SIZE
+ )
+ );
+ lazy.MsgUtils.sendLogger.debug(
+ `Delivering mail message <${this._compFields.messageId}>`
+ );
+ let deliveryListener = new MsgDeliveryListener(this, false);
+ let msgStatus =
+ this._sendProgress instanceof Ci.nsIMsgStatusFeedback
+ ? this._sendProgress
+ : this._statusFeedback;
+ this._smtpRequest = {};
+ // Do async call. This is necessary to ensure _smtpRequest is set so that
+ // cancel function can be obtained.
+ await MailServices.smtp.wrappedJSObject.sendMailMessage(
+ this._deliveryFile,
+ encodedRecipients,
+ this._userIdentity,
+ this._compFields.from,
+ this._smtpPassword,
+ deliveryListener,
+ msgStatus,
+ null,
+ this._compFields.DSN,
+ this._compFields.messageId,
+ {},
+ this._smtpRequest
+ );
+ }
+
+ /**
+ * Send this._deliveryFile to nntp service.
+ */
+ _deliverAsNews() {
+ this.sendReport.currentProcess = Ci.nsIMsgSendReport.process_NNTP;
+ lazy.MsgUtils.sendLogger.debug("Delivering news message");
+ let deliveryListener = new MsgDeliveryListener(this, true);
+ let msgWindow;
+ try {
+ msgWindow =
+ this._sendProgress?.msgWindow ||
+ MailServices.mailSession.topmostMsgWindow;
+ } catch (e) {}
+ MailServices.nntp.postMessage(
+ this._deliveryFile,
+ this._compFields.newsgroups,
+ this._accountKey,
+ deliveryListener,
+ msgWindow,
+ null
+ );
+ }
+
+ /**
+ * Collect outgoing addresses to address book.
+ *
+ * @param {string[]} recipients - Outgoing addresses including to/cc/bcc.
+ */
+ _collectAddressesToAddressBook(recipients) {
+ let createCard = Services.prefs.getBoolPref(
+ "mail.collect_email_address_outgoing",
+ false
+ );
+
+ let addressCollector = Cc[
+ "@mozilla.org/addressbook/services/addressCollector;1"
+ ].getService(Ci.nsIAbAddressCollector);
+ for (let recipient of recipients) {
+ addressCollector.collectAddress(recipient, createCard);
+ }
+ }
+
+ /**
+ * Check if link text is equivalent to the href.
+ *
+ * @param {string} text - The innerHTML of a <a> element.
+ * @param {string} href - The href of a <a> element.
+ * @returns {boolean} true if text is equivalent to href.
+ */
+ _isLinkFreeText(text, href) {
+ href = href.trim();
+ if (href.startsWith("mailto:")) {
+ return this._isLinkFreeText(text, href.slice("mailto:".length));
+ }
+ text = text.trim();
+ return (
+ text == href ||
+ (text.endsWith("/") && text.slice(0, -1) == href) ||
+ (href.endsWith("/") && href.slice(0, -1) == text)
+ );
+ }
+
+ /**
+ * Collect embedded objects as attachments.
+ *
+ * @returns {object} collected
+ * @returns {nsIMsgAttachment[]} collected.embeddedAttachments
+ * @returns {object[]} collected.embeddedObjects objects {element, url}
+ */
+ _gatherEmbeddedAttachments(editor) {
+ let embeddedAttachments = [];
+ let embeddedObjects = [];
+
+ if (!editor || !editor.document) {
+ return { embeddedAttachments, embeddedObjects };
+ }
+ let nodes = [];
+ nodes.push(...editor.document.querySelectorAll("img"));
+ nodes.push(...editor.document.querySelectorAll("a"));
+ let body = editor.document.querySelector("body[background]");
+ if (body) {
+ nodes.push(body);
+ }
+
+ let urlCidCache = {};
+ for (let element of nodes) {
+ if (element.tagName == "A" && element.href) {
+ if (this._isLinkFreeText(element.innerHTML, element.href)) {
+ // Set this special classname, which is recognized by nsIParserUtils,
+ // so that links are not duplicated in text/plain.
+ element.classList.add("moz-txt-link-freetext");
+ }
+ }
+ let isImage = false;
+ let url;
+ let name;
+ let mozDoNotSend = element.getAttribute("moz-do-not-send");
+ if (mozDoNotSend && mozDoNotSend != "false") {
+ // Only empty or moz-do-not-send="false" may be accepted later.
+ continue;
+ }
+ if (element.tagName == "BODY" && element.background) {
+ isImage = true;
+ url = element.background;
+ } else if (element.tagName == "IMG" && element.src) {
+ isImage = true;
+ url = element.src;
+ name = element.name;
+ } else if (element.tagName == "A" && element.href) {
+ url = element.href;
+ name = element.name;
+ } else {
+ continue;
+ }
+ let acceptObject = false;
+ // Before going further, check what scheme we're dealing with. Files need to
+ // be converted to data URLs during composition. "Attaching" means
+ // sending as a cid: part instead of original URL.
+ if (/^https?:\/\//i.test(url)) {
+ acceptObject =
+ (isImage &&
+ Services.prefs.getBoolPref(
+ "mail.compose.attach_http_images",
+ false
+ )) ||
+ mozDoNotSend == "false";
+ }
+ if (/^(data|news|snews|nntp):/i.test(url)) {
+ acceptObject = true;
+ }
+ if (!acceptObject) {
+ continue;
+ }
+
+ let cid;
+ if (urlCidCache[url]) {
+ // If an url has already been inserted as MimePart, just reuse the cid.
+ cid = urlCidCache[url];
+ } else {
+ cid = lazy.MsgUtils.makeContentId(
+ this._userIdentity,
+ embeddedAttachments.length + 1
+ );
+ urlCidCache[url] = cid;
+
+ let attachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ attachment.name = name || lazy.MsgUtils.pickFileNameFromUrl(url);
+ attachment.contentId = cid;
+ attachment.url = url;
+ embeddedAttachments.push(attachment);
+ }
+ embeddedObjects.push({
+ element,
+ url,
+ });
+
+ let newUrl = `cid:${cid}`;
+ if (element.tagName == "BODY") {
+ element.background = newUrl;
+ } else if (element.tagName == "IMG") {
+ element.src = newUrl;
+ } else if (element.tagName == "A") {
+ element.href = newUrl;
+ }
+ }
+ return { embeddedAttachments, embeddedObjects };
+ }
+
+ /**
+ * Restore embedded objects in editor to their original urls.
+ *
+ * @param {object[]} embeddedObjects - An array of embedded objects.
+ * @param {Element} embeddedObjects.element
+ * @param {string} embeddedObjects.url
+ */
+ _restoreEditorContent(embeddedObjects) {
+ for (let { element, url } of embeddedObjects) {
+ if (element.tagName == "BODY") {
+ element.background = url;
+ } else if (element.tagName == "IMG") {
+ element.src = url;
+ } else if (element.tagName == "A") {
+ element.href = url;
+ }
+ }
+ }
+
+ /**
+ * Get the message body from an editor.
+ *
+ * @param {nsIEditor} editor - The editor instance.
+ * @returns {string}
+ */
+ _getBodyFromEditor(editor) {
+ if (!editor) {
+ return "";
+ }
+
+ let flags =
+ Ci.nsIDocumentEncoder.OutputFormatted |
+ Ci.nsIDocumentEncoder.OutputNoFormattingInPre |
+ Ci.nsIDocumentEncoder.OutputDisallowLineBreaking;
+ // bodyText is UTF-16 string.
+ let bodyText = editor.outputToString("text/html", flags);
+
+ // No need to do conversion if forcing plain text.
+ if (!this._compFields.forcePlainText) {
+ let cs = Cc["@mozilla.org/txttohtmlconv;1"].getService(
+ Ci.mozITXTToHTMLConv
+ );
+ let csFlags = Ci.mozITXTToHTMLConv.kURLs;
+ if (Services.prefs.getBoolPref("mail.send_struct", false)) {
+ csFlags |= Ci.mozITXTToHTMLConv.kStructPhrase;
+ }
+ bodyText = cs.scanHTML(bodyText, csFlags);
+ }
+
+ return bodyText;
+ }
+
+ /**
+ * Get the first account key of an identity.
+ *
+ * @param {nsIMsgIdentity} identity - The identity.
+ * @returns {string}
+ */
+ _accountKeyForIdentity(identity) {
+ let servers = MailServices.accounts.getServersForIdentity(identity);
+ return servers.length
+ ? MailServices.accounts.FindAccountForServer(servers[0])?.key
+ : null;
+ }
+}
+
+/**
+ * A listener to be passed to the SMTP service.
+ *
+ * @implements {nsIUrlListener}
+ */
+class MsgDeliveryListener {
+ QueryInterface = ChromeUtils.generateQI(["nsIUrlListener"]);
+
+ /**
+ * @param {nsIMsgSend} msgSend - Send instance to use.
+ * @param {boolean} isNewsDelivery - Whether this is an nntp message delivery.
+ */
+ constructor(msgSend, isNewsDelivery) {
+ this._msgSend = msgSend;
+ this._isNewsDelivery = isNewsDelivery;
+ }
+
+ OnStartRunningUrl(url) {
+ this._msgSend.notifyListenerOnStartSending(null, 0);
+ }
+
+ OnStopRunningUrl(url, exitCode) {
+ lazy.MsgUtils.sendLogger.debug(`OnStopRunningUrl; exitCode=${exitCode}`);
+ let mailUrl = url.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ mailUrl.UnRegisterListener(this);
+
+ this._msgSend.sendDeliveryCallback(url, this._isNewsDelivery, exitCode);
+ }
+}
diff --git a/comm/mailnews/compose/src/MimeEncoder.jsm b/comm/mailnews/compose/src/MimeEncoder.jsm
new file mode 100644
index 0000000000..ab4c60de42
--- /dev/null
+++ b/comm/mailnews/compose/src/MimeEncoder.jsm
@@ -0,0 +1,430 @@
+/* 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 = ["MimeEncoder"];
+
+const LINELENGTH_ENCODING_THRESHOLD = 990;
+const MESSAGE_RFC822 = "message/rfc822";
+
+/**
+ * A class to pick Content-Transfer-Encoding for a MimePart, and encode MimePart
+ * body accordingly.
+ */
+class MimeEncoder {
+ /**
+ * Create a MimeEncoder.
+ *
+ * @param {string} charset
+ * @param {string} contentType
+ * @param {boolean} forceMsgEncoding
+ * @param {boolean} isMainBody
+ * @param {string} content
+ */
+ constructor(charset, contentType, forceMsgEncoding, isMainBody, content) {
+ this._charset = charset;
+ this._contentType = contentType.toLowerCase();
+ this._forceMsgEncoding = forceMsgEncoding;
+ this._isMainBody = isMainBody;
+ this._body = content;
+ this._bodySize = content.length;
+
+ // The encoding value will be used to set Content-Transfer-Encoding header
+ // and encode this._body.
+ this._encoding = "";
+
+ // Flags used to pick encoding.
+ this._highBitCount = 0;
+ this._unPrintableCount = 0;
+ this._ctrlCount = 0;
+ this._nullCount = 0;
+ this._hasCr = 0;
+ this._hasLf = 0;
+ this._hasCrLf = 0;
+ this._maxColumn = 0;
+ }
+
+ /**
+ * @type {string}
+ */
+ get encoding() {
+ return this._encoding;
+ }
+
+ /**
+ * Use the combination of charset, content type and scanning this._body to
+ * decide what encoding it should have.
+ */
+ pickEncoding() {
+ this._analyzeBody();
+
+ let strictlyMime = Services.prefs.getBoolPref("mail.strictly_mime");
+ let needsB64 = false;
+ let isUsingQP = false;
+
+ // Allow users to override our percentage-wise guess on whether
+ // the file is text or binary.
+ let forceB64 = Services.prefs.getBoolPref("mail.file_attach_binary");
+
+ // If the content-type is "image/" or something else known to be binary or
+ // several flavors of newlines are present, use base64 unless we're attaching
+ // a message (so that we don't get confused by newline conversions).
+ if (
+ !this._isMainBody &&
+ (forceB64 ||
+ this._requiresB64() ||
+ this._hasCr + this._hasLf + this._hasCrLf != 1) &&
+ this._contentType != MESSAGE_RFC822
+ ) {
+ needsB64 = true;
+ } else {
+ // Otherwise, we need to pick an encoding based on the contents of the
+ // document.
+ let encodeP = false;
+
+ // Force quoted-printable if the sender does not allow conversion to 7bit.
+ if (
+ this._forceMsgEncoding ||
+ this._maxColumn > LINELENGTH_ENCODING_THRESHOLD ||
+ (strictlyMime && this._unPrintableCount) ||
+ this._nullCount
+ ) {
+ if (
+ this._isMainBody &&
+ this._contentType == "text/plain" &&
+ // From rfc3676#section-4.2, Quoted-Printable encoding SHOULD NOT be
+ // used with Format=Flowed unless absolutely necessary.
+ Services.prefs.getBoolPref("mailnews.send_plaintext_flowed")
+ ) {
+ needsB64 = true;
+ } else {
+ encodeP = true;
+ }
+ }
+
+ // MIME requires a special case that these types never be encoded.
+ if (
+ this._contentType.startsWith("message") ||
+ this._contentType.startsWith("multipart")
+ ) {
+ encodeP = false;
+ }
+
+ let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService(
+ Ci.nsICharsetConverterManager
+ );
+ let isCharsetMultiByte = false;
+ try {
+ isCharsetMultiByte =
+ manager.getCharsetData(this._charset, ".isMultibyte") == "true";
+ } catch {}
+
+ // If the Mail charset is multibyte, we force it to use Base64 for
+ // attachments.
+ if (
+ !this._isMainBody &&
+ this._charset &&
+ isCharsetMultiByte &&
+ (this._contentType.startsWith("text") ||
+ // text/vcard synonym
+ this._contentType == "application/directory")
+ ) {
+ needsB64 = true;
+ } else if (this._charset == "ISO-2022-JP") {
+ this._encoding = "7bit";
+ } else if (encodeP && this._unPrintableCount > this._bodySize / 10) {
+ // If the document contains more than 10% unprintable characters,
+ // then that seems like a good candidate for base64 instead of
+ // quoted-printable.
+ needsB64 = true;
+ } else if (encodeP) {
+ this._encoding = "quoted-printable";
+ isUsingQP = true;
+ } else if (this._highBitCount > 0) {
+ this._encoding = "8bit";
+ } else {
+ this._encoding = "7bit";
+ }
+ }
+
+ // Always base64 binary data.
+ if (needsB64) {
+ this._encoding = "base64";
+ }
+
+ // According to RFC 821 we must always have lines shorter than 998 bytes.
+ // To encode "long lines" use a CTE that will transmit shorter lines.
+ // Switch to base64 if we are not already using "quoted printable".
+
+ // We don't do this for message/rfc822 attachments, since we can't
+ // change the original Content-Transfer-Encoding of the message we're
+ // attaching. We rely on the original message complying with RFC 821,
+ // if it doesn't we won't either. Not ideal.
+ if (
+ this._contentType != MESSAGE_RFC822 &&
+ this._maxColumn > LINELENGTH_ENCODING_THRESHOLD &&
+ !isUsingQP
+ ) {
+ this._encoding = "base64";
+ }
+ }
+
+ /**
+ * Encode this._body according to the value of this.encoding.
+ */
+ encode() {
+ let output;
+ if (this.encoding == "base64") {
+ output = this._encodeBase64();
+ } else if (this.encoding == "quoted-printable") {
+ output = this._encodeQP();
+ } else {
+ output = this._body.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n");
+ }
+ if (!output.endsWith("\r\n")) {
+ output += "\r\n";
+ }
+ return output;
+ }
+
+ /**
+ * Scan this._body to set flags that will be used by pickEncoding.
+ */
+ _analyzeBody() {
+ let currentColumn = 0;
+ let prevCharWasCr = false;
+
+ for (let i = 0; i < this._bodySize; i++) {
+ let ch = this._body.charAt(i);
+ let charCode = this._body.charCodeAt(i);
+ if (charCode > 126) {
+ this._highBitCount++;
+ this._unPrintableCount++;
+ } else if (ch < " " && !"\t\r\n".includes(ch)) {
+ this._unPrintableCount++;
+ this._ctrlCount++;
+ if (ch == "\0") {
+ this._nullCount++;
+ }
+ }
+
+ if ("\r\n".includes(ch)) {
+ if (ch == "\r") {
+ if (prevCharWasCr) {
+ this._hasCr = 1;
+ } else {
+ prevCharWasCr = true;
+ }
+ } else if (prevCharWasCr) {
+ if (currentColumn == 0) {
+ this._hasCrLf = 1;
+ } else {
+ this._hasCr = 1;
+ this._hasLf = 1;
+ }
+ prevCharWasCr = false;
+ } else {
+ this._hasLf = 1;
+ }
+
+ if (this._maxColumn < currentColumn) {
+ this._maxColumn = currentColumn;
+ }
+ currentColumn = 0;
+ } else {
+ currentColumn++;
+ }
+ }
+
+ if (this._maxColumn < currentColumn) {
+ this._maxColumn = currentColumn;
+ }
+ }
+
+ /**
+ * Determine if base64 is required according to contentType.
+ */
+ _requiresB64() {
+ if (this._contentType == "application/x-unknown-content-type") {
+ // Unknown types don't necessarily require encoding. (Note that
+ // "unknown" and "application/octet-stream" aren't the same.)
+ return false;
+ }
+ if (
+ this._contentType.startsWith("image/") ||
+ this._contentType.startsWith("audio/") ||
+ this._contentType.startsWith("video/") ||
+ this._contentType.startsWith("application/")
+ ) {
+ // The following types are application/ or image/ types that are actually
+ // known to contain textual data (meaning line-based, not binary, where
+ // CRLF conversion is desired rather than disastrous.) So, if the type
+ // is any of these, it does not *require* base64, and if we do need to
+ // encode it for other reasons, we'll probably use quoted-printable.
+ // But, if it's not one of these types, then we assume that any subtypes
+ // of the non-"text/" types are binary data, where CRLF conversion would
+ // corrupt it, so we use base64 right off the bat.
+ // The reason it's desirable to ship these as text instead of just using
+ // base64 all the time is mainly to preserve the readability of them for
+ // non-MIME users: if I mail a /bin/sh script to someone, it might not
+ // need to be encoded at all, so we should leave it readable if we can.
+ // This list of types was derived from the comp.mail.mime FAQ, section
+ // 10.2.2, "List of known unregistered MIME types" on 2-Feb-96.
+ const typesWhichAreReallyText = [
+ "application/mac-binhex40", // APPLICATION_BINHEX
+ "application/pgp", // APPLICATION_PGP
+ "application/pgp-keys",
+ "application/x-pgp-message", // APPLICATION_PGP2
+ "application/postscript", // APPLICATION_POSTSCRIPT
+ "application/x-uuencode", // APPLICATION_UUENCODE
+ "application/x-uue", // APPLICATION_UUENCODE2
+ "application/uue", // APPLICATION_UUENCODE4
+ "application/uuencode", // APPLICATION_UUENCODE3
+ "application/sgml",
+ "application/x-csh",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "application/x-latex",
+ "application/x-macbinhex40",
+ "application/x-ns-proxy-autoconfig",
+ "application/x-www-form-urlencoded",
+ "application/x-perl",
+ "application/x-sh",
+ "application/x-shar",
+ "application/x-tcl",
+ "application/x-tex",
+ "application/x-texinfo",
+ "application/x-troff",
+ "application/x-troff-man",
+ "application/x-troff-me",
+ "application/x-troff-ms",
+ "application/x-troff-ms",
+ "application/x-wais-source",
+ "image/x-bitmap",
+ "image/x-pbm",
+ "image/x-pgm",
+ "image/x-portable-anymap",
+ "image/x-portable-bitmap",
+ "image/x-portable-graymap",
+ "image/x-portable-pixmap", // IMAGE_PPM
+ "image/x-ppm",
+ "image/x-xbitmap", // IMAGE_XBM
+ "image/x-xbm", // IMAGE_XBM2
+ "image/xbm", // IMAGE_XBM3
+ "image/x-xpixmap",
+ "image/x-xpm",
+ ];
+ if (typesWhichAreReallyText.includes(this._contentType)) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Base64 encoding. See RFC 2045 6.8. We use the built-in `btoa`, then ensure
+ * line width is no more than 72.
+ */
+ _encodeBase64() {
+ let encoded = btoa(this._body);
+ let ret = "";
+ let length = encoded.length;
+ let i = 0;
+ let limit = 72;
+ while (true) {
+ if (i * limit > length) {
+ break;
+ }
+ ret += encoded.substr(i * limit, limit) + "\r\n";
+ i++;
+ }
+ return ret;
+ }
+
+ /**
+ * Quoted-printable encoding. See RFC 2045 6.7.
+ */
+ _encodeQP() {
+ let currentColumn = 0;
+ let hexdigits = "0123456789ABCDEF";
+ let white = false;
+ let out = "";
+
+ function encodeChar(ch) {
+ let charCode = ch.charCodeAt(0);
+ let ret = "=";
+ ret += hexdigits[charCode >> 4];
+ ret += hexdigits[charCode & 0xf];
+ return ret;
+ }
+
+ for (let i = 0; i < this._bodySize; i++) {
+ let ch = this._body.charAt(i);
+ let charCode = this._body.charCodeAt(i);
+ if (ch == "\r" || ch == "\n") {
+ // If it's CRLF, swallow two chars instead of one.
+ if (i + 1 < this._bodySize && ch == "\r" && this._body[i + 1] == "\n") {
+ i++;
+ }
+
+ // Whitespace cannot be allowed to occur at the end of the line, so we
+ // back up and replace the whitespace with its code.
+ if (white) {
+ let whiteChar = out.slice(-1);
+ out = out.slice(0, -1);
+ out += encodeChar(whiteChar);
+ }
+
+ // Now write out the newline.
+ out += "\r";
+ out += "\n";
+ white = false;
+ currentColumn = 0;
+ } else if (
+ currentColumn == 0 &&
+ (ch == "." ||
+ (ch == "F" &&
+ (i >= this._bodySize - 1 || this._body[i + 1] == "r") &&
+ (i >= this._bodySize - 2 || this._body[i + 2] == "o") &&
+ (i >= this._bodySize - 3 || this._body[i + 3] == "m") &&
+ (i >= this._bodySize - 4 || this._body[i + 4] == " ")))
+ ) {
+ // Just to be SMTP-safe, if "." appears in column 0, encode it.
+ // If this line begins with "From " (or it could but we don't have enough
+ // data in the buffer to be certain), encode the 'F' in hex to avoid
+ // potential problems with BSD mailbox formats.
+ white = false;
+ out += encodeChar(ch);
+ currentColumn += 3;
+ } else if (
+ (charCode >= 33 && charCode <= 60) ||
+ (charCode >= 62 && charCode <= 126)
+ ) {
+ // Printable characters except for '='
+ white = false;
+ out += ch;
+ currentColumn++;
+ } else if (ch == " " || ch == "\t") {
+ // Whitespace
+ white = true;
+ out += ch;
+ currentColumn++;
+ } else {
+ white = false;
+ out += encodeChar(ch);
+ currentColumn += 3;
+ }
+
+ if (currentColumn >= 73) {
+ // Soft line break for readability
+ out += "=\r\n";
+ white = false;
+ currentColumn = 0;
+ }
+ }
+
+ return out;
+ }
+}
diff --git a/comm/mailnews/compose/src/MimeMessage.jsm b/comm/mailnews/compose/src/MimeMessage.jsm
new file mode 100644
index 0000000000..9423e84004
--- /dev/null
+++ b/comm/mailnews/compose/src/MimeMessage.jsm
@@ -0,0 +1,625 @@
+/* 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 = ["MimeMessage"];
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+let { MimeMultiPart, MimePart } = ChromeUtils.import(
+ "resource:///modules/MimePart.jsm"
+);
+let { MsgUtils } = ChromeUtils.import(
+ "resource:///modules/MimeMessageUtils.jsm"
+);
+let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+
+/**
+ * A class to create a top MimePart and write to a tmp file. It works like this:
+ * 1. collect top level MIME headers (_gatherMimeHeaders)
+ * 2. collect HTML/plain main body as MimePart[] (_gatherMainParts)
+ * 3. collect attachments as MimePart[] (_gatherAttachmentParts)
+ * 4. construct a top MimePart with above headers and MimePart[] (_initMimePart)
+ * 5. write the top MimePart to a tmp file (createMessageFile)
+ * NOTE: It's possible we will want to replace nsIMsgSend with the interfaces of
+ * MimeMessage. As a part of it, we will add a `send` method to this class.
+ */
+class MimeMessage {
+ /**
+ * Construct a MimeMessage.
+ *
+ * @param {nsIMsgIdentity} userIdentity
+ * @param {nsIMsgCompFields} compFields
+ * @param {string} fcc - The FCC header value.
+ * @param {string} bodyType
+ * @param {BinaryString} bodyText - This is ensured to be a 8-bit string, to
+ * be handled the same as attachment content.
+ * @param {nsMsgDeliverMode} deliverMode
+ * @param {string} originalMsgURI
+ * @param {MSG_ComposeType} compType
+ * @param {nsIMsgAttachment[]} embeddedAttachments - Usually Embedded images.
+ * @param {nsIMsgSendReport} sendReport - Used by _startCryptoEncapsulation.
+ */
+ constructor(
+ userIdentity,
+ compFields,
+ fcc,
+ bodyType,
+ bodyText,
+ deliverMode,
+ originalMsgURI,
+ compType,
+ embeddedAttachments,
+ sendReport
+ ) {
+ this._userIdentity = userIdentity;
+ this._compFields = compFields;
+ this._fcc = fcc;
+ this._bodyType = bodyType;
+ this._bodyText = bodyText;
+ this._deliverMode = deliverMode;
+ this._compType = compType;
+ this._embeddedAttachments = embeddedAttachments;
+ this._sendReport = sendReport;
+ }
+
+ /**
+ * Write a MimeMessage to a tmp file.
+ *
+ * @returns {nsIFile}
+ */
+ async createMessageFile() {
+ let topPart = this._initMimePart();
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("nsemail.eml");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ let fstream = Cc[
+ "@mozilla.org/network/file-output-stream;1"
+ ].createInstance(Ci.nsIFileOutputStream);
+ this._fstream = Cc[
+ "@mozilla.org/network/buffered-output-stream;1"
+ ].createInstance(Ci.nsIBufferedOutputStream);
+ fstream.init(file, -1, -1, 0);
+ this._fstream.init(fstream, 16 * 1024);
+
+ this._composeSecure = this._getComposeSecure();
+ if (this._composeSecure) {
+ await this._writePart(topPart);
+ this._composeSecure.finishCryptoEncapsulation(false, this._sendReport);
+ } else {
+ await this._writePart(topPart);
+ }
+
+ this._fstream.close();
+ fstream.close();
+
+ return file;
+ }
+
+ /**
+ * Create a top MimePart to represent the full message.
+ *
+ * @returns {MimePart}
+ */
+ _initMimePart() {
+ let { plainPart, htmlPart } = this._gatherMainParts();
+ let embeddedParts = this._gatherEmbeddedParts();
+ let attachmentParts = this._gatherAttachmentParts();
+
+ let relatedPart = htmlPart;
+ if (htmlPart && embeddedParts.length > 0) {
+ relatedPart = new MimeMultiPart("related");
+ relatedPart.addPart(htmlPart);
+ relatedPart.addParts(embeddedParts);
+ }
+ let mainParts = [plainPart, relatedPart].filter(Boolean);
+ let topPart;
+ if (attachmentParts.length > 0) {
+ // Use multipart/mixed as long as there is at least one attachment.
+ topPart = new MimeMultiPart("mixed");
+ if (plainPart && relatedPart) {
+ // Wrap mainParts inside a multipart/alternative MimePart.
+ let alternativePart = new MimeMultiPart("alternative");
+ alternativePart.addParts(mainParts);
+ topPart.addPart(alternativePart);
+ } else {
+ topPart.addParts(mainParts);
+ }
+ topPart.addParts(attachmentParts);
+ } else {
+ if (mainParts.length > 1) {
+ // Mark the topPart as multipart/alternative.
+ topPart = new MimeMultiPart("alternative");
+ } else {
+ topPart = new MimePart();
+ }
+ topPart.addParts(mainParts);
+ }
+
+ topPart.setHeaders(this._gatherMimeHeaders());
+
+ return topPart;
+ }
+
+ /**
+ * Collect top level headers like From/To/Subject into a Map.
+ */
+ _gatherMimeHeaders() {
+ let messageId = this._compFields.messageId;
+ if (
+ !messageId &&
+ (this._compFields.to ||
+ this._compFields.cc ||
+ this._compFields.bcc ||
+ !this._compFields.newsgroups ||
+ this._userIdentity.getBoolAttribute("generate_news_message_id"))
+ ) {
+ // Try to use the domain name of the From header to generate the message ID. We
+ // specifically don't use the nsIMsgIdentity associated with the account, because
+ // the user might have changed the address in the From header to use a different
+ // domain, and we don't want to leak the relationship between the domains.
+ const fromHdr = MailServices.headerParser.parseEncodedHeaderW(
+ this._compFields.from
+ );
+ const fromAddr = fromHdr[0].email;
+
+ // Extract the host from the address, if any, and generate a message ID from it.
+ // If we can't get a host for the message ID, let SMTP populate the header.
+ const atIndex = fromAddr.indexOf("@");
+ if (atIndex >= 0) {
+ messageId = Cc["@mozilla.org/messengercompose/computils;1"]
+ .createInstance(Ci.nsIMsgCompUtils)
+ .msgGenerateMessageId(
+ this._userIdentity,
+ fromAddr.slice(atIndex + 1)
+ );
+ }
+
+ this._compFields.messageId = messageId;
+ }
+ let headers = new Map([
+ ["message-id", messageId],
+ ["date", new Date()],
+ ["mime-version", "1.0"],
+ ]);
+
+ if (Services.prefs.getBoolPref("mailnews.headers.sendUserAgent")) {
+ if (Services.prefs.getBoolPref("mailnews.headers.useMinimalUserAgent")) {
+ headers.set(
+ "user-agent",
+ Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandFullName")
+ );
+ } else {
+ headers.set(
+ "user-agent",
+ Cc["@mozilla.org/network/protocol;1?name=http"].getService(
+ Ci.nsIHttpProtocolHandler
+ ).userAgent
+ );
+ }
+ }
+
+ for (let headerName of [...this._compFields.headerNames]) {
+ let headerContent = this._compFields.getRawHeader(headerName);
+ if (headerContent) {
+ headers.set(headerName, headerContent);
+ }
+ }
+ let isDraft = [
+ Ci.nsIMsgSend.nsMsgQueueForLater,
+ Ci.nsIMsgSend.nsMsgDeliverBackground,
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ Ci.nsIMsgSend.nsMsgSaveAsTemplate,
+ ].includes(this._deliverMode);
+
+ let undisclosedRecipients = MsgUtils.getUndisclosedRecipients(
+ this._compFields,
+ this._deliverMode
+ );
+ if (undisclosedRecipients) {
+ headers.set("to", undisclosedRecipients);
+ }
+
+ if (isDraft) {
+ headers
+ .set(
+ "x-mozilla-draft-info",
+ MsgUtils.getXMozillaDraftInfo(this._compFields)
+ )
+ .set("x-identity-key", this._userIdentity.key)
+ .set("fcc", this._fcc);
+ }
+
+ if (messageId) {
+ // MDN request header requires to have MessageID header presented in the
+ // message in order to coorelate the MDN reports to the original message.
+ headers
+ .set(
+ "disposition-notification-to",
+ MsgUtils.getDispositionNotificationTo(
+ this._compFields,
+ this._deliverMode
+ )
+ )
+ .set(
+ "return-receipt-to",
+ MsgUtils.getReturnReceiptTo(this._compFields, this._deliverMode)
+ );
+ }
+
+ for (let { headerName, headerValue } of MsgUtils.getDefaultCustomHeaders(
+ this._userIdentity
+ )) {
+ headers.set(headerName, headerValue);
+ }
+
+ let rawMftHeader = headers.get("mail-followup-to");
+ // If there's already a Mail-Followup-To header, don't need to do anything.
+ if (!rawMftHeader) {
+ headers.set(
+ "mail-followup-to",
+ MsgUtils.getMailFollowupToHeader(this._compFields, this._userIdentity)
+ );
+ }
+
+ let rawMrtHeader = headers.get("mail-reply-to");
+ // If there's already a Mail-Reply-To header, don't need to do anything.
+ if (!rawMrtHeader) {
+ headers.set(
+ "mail-reply-to",
+ MsgUtils.getMailReplyToHeader(
+ this._compFields,
+ this._userIdentity,
+ rawMrtHeader
+ )
+ );
+ }
+
+ let rawPriority = headers.get("x-priority");
+ if (rawPriority) {
+ headers.set("x-priority", MsgUtils.getXPriority(rawPriority));
+ }
+
+ let rawReferences = headers.get("references");
+ if (rawReferences) {
+ let references = MsgUtils.getReferences(rawReferences);
+ // Don't reset "references" header if references is undefined.
+ if (references) {
+ headers.set("references", references);
+ }
+ headers.set("in-reply-to", MsgUtils.getInReplyTo(rawReferences));
+ }
+ if (
+ rawReferences &&
+ [
+ Ci.nsIMsgCompType.ForwardInline,
+ Ci.nsIMsgCompType.ForwardAsAttachment,
+ ].includes(this._compType)
+ ) {
+ headers.set("x-forwarded-message-id", rawReferences);
+ }
+
+ let rawNewsgroups = headers.get("newsgroups");
+ if (rawNewsgroups) {
+ let { newsgroups, newshost } = MsgUtils.getNewsgroups(
+ this._deliverMode,
+ rawNewsgroups
+ );
+ // Don't reset "newsgroups" header if newsgroups is undefined.
+ if (newsgroups) {
+ headers.set("newsgroups", newsgroups);
+ }
+ headers.set("x-mozilla-news-host", newshost);
+ }
+
+ return headers;
+ }
+
+ /**
+ * Determine if the message should include an HTML part, a plain part or both.
+ *
+ * @returns {{plainPart: MimePart, htmlPart: MimePart}}
+ */
+ _gatherMainParts() {
+ let formatFlowed = Services.prefs.getBoolPref(
+ "mailnews.send_plaintext_flowed"
+ );
+ let formatParam = "";
+ if (formatFlowed) {
+ // Set format=flowed as in RFC 2646 according to the preference.
+ formatParam += "; format=flowed";
+ }
+
+ let htmlPart = null;
+ let plainPart = null;
+ let parts = {};
+
+ if (this._bodyType === "text/html") {
+ htmlPart = new MimePart(
+ this._bodyType,
+ this._compFields.forceMsgEncoding,
+ true
+ );
+ htmlPart.setHeader("content-type", "text/html; charset=UTF-8");
+ htmlPart.bodyText = this._bodyText;
+ } else if (this._bodyType === "text/plain") {
+ plainPart = new MimePart(
+ this._bodyType,
+ this._compFields.forceMsgEncoding,
+ true
+ );
+ plainPart.setHeader(
+ "content-type",
+ `text/plain; charset=UTF-8${formatParam}`
+ );
+ plainPart.bodyText = this._bodyText;
+ parts.plainPart = plainPart;
+ }
+
+ // Assemble a multipart/alternative message.
+ if (
+ (this._compFields.forcePlainText ||
+ this._compFields.useMultipartAlternative) &&
+ plainPart === null &&
+ htmlPart !== null
+ ) {
+ plainPart = new MimePart(
+ "text/plain",
+ this._compFields.forceMsgEncoding,
+ true
+ );
+ plainPart.setHeader(
+ "content-type",
+ `text/plain; charset=UTF-8${formatParam}`
+ );
+ // nsIParserUtils.convertToPlainText expects unicode string.
+ let plainUnicode = MsgUtils.convertToPlainText(
+ new TextDecoder().decode(
+ jsmime.mimeutils.stringToTypedArray(this._bodyText)
+ ),
+ formatFlowed
+ );
+ // MimePart.bodyText should be binary string.
+ plainPart.bodyText = jsmime.mimeutils.typedArrayToString(
+ new TextEncoder().encode(plainUnicode)
+ );
+
+ parts.plainPart = plainPart;
+ }
+
+ // If useMultipartAlternative is true, send multipart/alternative message.
+ // Otherwise, send the plainPart only.
+ if (htmlPart) {
+ if (
+ (plainPart && this._compFields.useMultipartAlternative) ||
+ !plainPart
+ ) {
+ parts.htmlPart = htmlPart;
+ }
+ }
+
+ return parts;
+ }
+
+ /**
+ * Collect local attachments.
+ *
+ * @returns {MimePart[]}
+ */
+ _gatherAttachmentParts() {
+ let attachments = [...this._compFields.attachments];
+ let cloudParts = [];
+ let localParts = [];
+
+ for (let attachment of attachments) {
+ let part;
+ if (attachment.htmlAnnotation) {
+ part = new MimePart();
+ // MimePart.bodyText should be binary string.
+ part.bodyText = jsmime.mimeutils.typedArrayToString(
+ new TextEncoder().encode(attachment.htmlAnnotation)
+ );
+ part.setHeader("content-type", "text/html; charset=utf-8");
+
+ let suffix = /\.html$/i.test(attachment.name) ? "" : ".html";
+ let encodedFilename = MsgUtils.rfc2231ParamFolding(
+ "filename",
+ `${attachment.name}${suffix}`
+ );
+ part.setHeader("content-disposition", `attachment; ${encodedFilename}`);
+ } else {
+ part = new MimePart(null, this._compFields.forceMsgEncoding, false);
+ part.setBodyAttachment(attachment);
+ }
+
+ let cloudPartHeader = MsgUtils.getXMozillaCloudPart(
+ this._deliverMode,
+ attachment
+ );
+ if (cloudPartHeader) {
+ part.setHeader("x-mozilla-cloud-part", cloudPartHeader);
+ }
+
+ localParts.push(part);
+ }
+ // Cloud attachments are handled before local attachments in the C++
+ // implementation. We follow it here so that no need to change tests.
+ return cloudParts.concat(localParts);
+ }
+
+ /**
+ * Collect embedded objects as attachments.
+ *
+ * @returns {MimePart[]}
+ */
+ _gatherEmbeddedParts() {
+ return this._embeddedAttachments.map(attachment => {
+ let part = new MimePart(null, this._compFields.forceMsgEncoding, false);
+ part.setBodyAttachment(attachment, "inline", attachment.contentId);
+ return part;
+ });
+ }
+
+ /**
+ * If crypto encapsulation is required, returns an nsIMsgComposeSecure instance.
+ *
+ * @returns {nsIMsgComposeSecure}
+ */
+ _getComposeSecure() {
+ let secureCompose = this._compFields.composeSecure;
+ if (!secureCompose) {
+ return null;
+ }
+
+ if (
+ this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft &&
+ !this._userIdentity.getBoolAttribute("autoEncryptDrafts")
+ ) {
+ return null;
+ }
+
+ if (
+ !secureCompose.requiresCryptoEncapsulation(
+ this._userIdentity,
+ this._compFields
+ )
+ ) {
+ return null;
+ }
+ return secureCompose;
+ }
+
+ /**
+ * Pass a stream and other params to this._composeSecure to start crypto
+ * encapsulation.
+ */
+ _startCryptoEncapsulation() {
+ let recipients = [
+ this._compFields.to,
+ this._compFields.cc,
+ this._compFields.bcc,
+ this._compFields.newsgroups,
+ ]
+ .filter(Boolean)
+ .join(",");
+
+ this._composeSecure.beginCryptoEncapsulation(
+ this._fstream,
+ recipients,
+ this._compFields,
+ this._userIdentity,
+ this._sendReport,
+ this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft
+ );
+ this._cryptoEncapsulationStarted = true;
+ }
+
+ /**
+ * Recursively write an MimePart and its parts to a this._fstream.
+ *
+ * @param {MimePart} curPart - The MimePart to write out.
+ * @param {number} [depth=0] - Nested level of a part.
+ */
+ async _writePart(curPart, depth = 0) {
+ let bodyString;
+ try {
+ // `getEncodedBodyString()` returns a binary string.
+ bodyString = await curPart.getEncodedBodyString();
+ } catch (e) {
+ if (e.data && /^data:/i.test(e.data.url)) {
+ // Invalid data uri should not prevent sending message.
+ return;
+ }
+ throw e;
+ }
+
+ if (depth == 0 && this._composeSecure) {
+ // Crypto encapsulation will add a new content-type header.
+ curPart.deleteHeader("content-type");
+ if (curPart.parts.length > 1) {
+ // Move child parts one layer deeper so that the message is still well
+ // formed after crypto encapsulation.
+ let newChild = new MimeMultiPart(curPart.subtype);
+ newChild.parts = curPart._parts;
+ curPart.parts = [newChild];
+ }
+ }
+
+ // Write out headers, there could be non-ASCII in the headers
+ // which we need to encode into UTF-8.
+ this._writeString(curPart.getHeaderString());
+
+ // Start crypto encapsulation if needed.
+ if (depth == 0 && this._composeSecure) {
+ this._startCryptoEncapsulation();
+ }
+
+ // Recursively write out parts.
+ if (curPart.parts.length) {
+ // single part message
+ if (curPart.parts.length === 1) {
+ await this._writePart(curPart.parts[0], depth + 1);
+ this._writeBinaryString(bodyString);
+ return;
+ }
+
+ // We can safely use `_writeBinaryString()` for ASCII strings.
+ this._writeBinaryString("\r\n");
+ if (depth == 0) {
+ // Current part is a top part and multipart container.
+ this._writeBinaryString(
+ "This is a multi-part message in MIME format.\r\n"
+ );
+ }
+
+ // multipart message
+ for (let part of curPart.parts) {
+ this._writeBinaryString(`--${curPart.separator}\r\n`);
+ await this._writePart(part, depth + 1);
+ }
+ this._writeBinaryString(`\r\n--${curPart.separator}--\r\n`);
+ if (depth > 1) {
+ // If more separators follow, make sure there is a blank line after
+ // this one.
+ this._writeBinaryString("\r\n");
+ }
+ } else {
+ this._writeBinaryString(`\r\n`);
+ }
+
+ // Ensure there is exactly one blank line after a part and before
+ // the boundary, and exactly one blank line between boundary lines.
+ // This works around bugs in other software that erroneously remove
+ // additional blank lines, thereby causing verification failures of
+ // OpenPGP or S/MIME signatures. For example see bug 1731529.
+
+ // Write out body.
+ this._writeBinaryString(bodyString);
+ }
+
+ /**
+ * Write a binary string to this._fstream.
+ *
+ * @param {BinaryString} str - The binary string to write.
+ */
+ _writeBinaryString(str) {
+ this._cryptoEncapsulationStarted
+ ? this._composeSecure.mimeCryptoWriteBlock(str, str.length)
+ : this._fstream.write(str, str.length);
+ }
+
+ /**
+ * Write a string to this._fstream.
+ *
+ * @param {string} str - The string to write.
+ */
+ _writeString(str) {
+ this._writeBinaryString(
+ jsmime.mimeutils.typedArrayToString(new TextEncoder().encode(str))
+ );
+ }
+}
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];
+ },
+};
diff --git a/comm/mailnews/compose/src/MimePart.jsm b/comm/mailnews/compose/src/MimePart.jsm
new file mode 100644
index 0000000000..a22c0527af
--- /dev/null
+++ b/comm/mailnews/compose/src/MimePart.jsm
@@ -0,0 +1,378 @@
+/* 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 = ["MimePart", "MimeMultiPart"];
+
+let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+let { MimeEncoder } = ChromeUtils.import("resource:///modules/MimeEncoder.jsm");
+let { MsgUtils } = ChromeUtils.import(
+ "resource:///modules/MimeMessageUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+/**
+ * A class to represent a RFC2045 message. MimePart can be nested, each MimePart
+ * can contain a list of MimePart. HTML and plain text are parts as well. Use
+ * class MimeMultiPart for multipart/*, that's why this class doesn't expose an
+ * addPart method
+ */
+class MimePart {
+ /**
+ * @param {string} contentType - Content type of the part, e.g. text/plain.
+ * @param {boolean} forceMsgEncoding - A flag used to determine Content-Transfer-Encoding.
+ * @param {boolean} isMainBody - The part is main part or an attachment part.
+ */
+ constructor(contentType = "", forceMsgEncoding = false, isMainBody = false) {
+ this._charset = "UTF-8";
+ this._contentType = contentType;
+ this._forceMsgEncoding = forceMsgEncoding;
+ this._isMainBody = isMainBody;
+
+ this._headers = new Map();
+ // 8-bit string to avoid converting back and forth.
+ this._bodyText = "";
+ this._bodyAttachment = null;
+ this._contentDisposition = null;
+ this._contentId = null;
+ this._separator = "";
+ this._parts = [];
+ }
+
+ /**
+ * @type {BinaryString} text - The string to use as body.
+ */
+ set bodyText(text) {
+ this._bodyText = text.replaceAll("\r\n", "\n").replaceAll("\n", "\r\n");
+ }
+
+ /**
+ * @type {MimePart[]} - The child parts.
+ */
+ get parts() {
+ return this._parts;
+ }
+
+ /**
+ * @type {MimePart[]} parts - The child parts.
+ */
+ set parts(parts) {
+ this._parts = parts;
+ }
+
+ /**
+ * @type {string} - The separator string.
+ */
+ get separator() {
+ return this._separator;
+ }
+
+ /**
+ * Set a header.
+ *
+ * @param {string} name - The header name, e.g. "content-type".
+ * @param {string} content - The header content, e.g. "text/plain".
+ */
+ setHeader(name, content) {
+ if (!content) {
+ return;
+ }
+ // There is no Content-Type encoder in jsmime yet. If content is not string,
+ // assume it's already a structured header.
+ if (name == "content-type" || typeof content != "string") {
+ // _headers will be passed to jsmime, which requires header content to be
+ // an array.
+ this._headers.set(name, [content]);
+ return;
+ }
+ try {
+ this._headers.set(name, [
+ jsmime.headerparser.parseStructuredHeader(name, content),
+ ]);
+ } catch (e) {
+ this._headers.set(name, [content.trim()]);
+ }
+ }
+
+ /**
+ * Delete a header.
+ *
+ * @param {string} name - The header name to delete, e.g. "content-type".
+ */
+ deleteHeader(name) {
+ this._headers.delete(name);
+ }
+
+ /**
+ * Set headers by an iterable.
+ *
+ * @param {Iterable.<string, string>} entries - The header entries.
+ */
+ setHeaders(entries) {
+ for (let [name, content] of entries) {
+ this.setHeader(name, content);
+ }
+ }
+
+ /**
+ * Set an attachment as body, with optional contentDisposition and contentId.
+ *
+ * @param {nsIMsgAttachment} attachment - The attachment to use as body.
+ * @param {string} [contentDisposition=attachment] - "attachment" or "inline".
+ * @param {string} [contentId] - The url of an embedded object is cid:contentId.
+ */
+ setBodyAttachment(
+ attachment,
+ contentDisposition = "attachment",
+ contentId = null
+ ) {
+ this._bodyAttachment = attachment;
+ this._contentDisposition = contentDisposition;
+ this._contentId = contentId;
+ }
+
+ /**
+ * Add a child part.
+ *
+ * @param {MimePart} part - A MimePart.
+ */
+ addPart(part) {
+ this._parts.push(part);
+ }
+
+ /**
+ * Add child parts.
+ *
+ * @param {MimePart[]} parts - An array of MimePart.
+ */
+ addParts(parts) {
+ this._parts.push(...parts);
+ }
+
+ /**
+ * Pick an encoding according to _bodyText or _bodyAttachment content. Set
+ * content-transfer-encoding header, then return the encoded value.
+ *
+ * @returns {BinaryString}
+ */
+ async getEncodedBodyString() {
+ let bodyString = this._bodyText;
+ // If this is an attachment part, use the attachment content as bodyString.
+ if (this._bodyAttachment) {
+ try {
+ bodyString = await this._fetchAttachment();
+ } catch (e) {
+ MsgUtils.sendLogger.error(
+ `Failed to fetch attachment; name=${this._bodyAttachment.name}, url=${this._bodyAttachment.url}, error=${e}`
+ );
+ throw Components.Exception(
+ "Failed to fetch attachment",
+ MsgUtils.NS_MSG_ERROR_ATTACHING_FILE,
+ e.stack,
+ this._bodyAttachment
+ );
+ }
+ }
+ if (bodyString) {
+ let encoder = new MimeEncoder(
+ this._charset,
+ this._contentType,
+ this._forceMsgEncoding,
+ this._isMainBody,
+ bodyString
+ );
+ encoder.pickEncoding();
+ this.setHeader("content-transfer-encoding", encoder.encoding);
+ bodyString = encoder.encode();
+ } else if (this._isMainBody) {
+ this.setHeader("content-transfer-encoding", "7bit");
+ }
+ return bodyString;
+ }
+
+ /**
+ * Use jsmime to convert _headers to string.
+ *
+ * @returns {string}
+ */
+ getHeaderString() {
+ return jsmime.headeremitter.emitStructuredHeaders(this._headers, {
+ useASCII: true,
+ sanitizeDate: Services.prefs.getBoolPref(
+ "mail.sanitize_date_header",
+ false
+ ),
+ });
+ }
+
+ /**
+ * Fetch the attached message file to get its content.
+ *
+ * @returns {string}
+ */
+ async _fetchMsgAttachment() {
+ let msgService = MailServices.messageServiceFromURI(
+ this._bodyAttachment.url
+ );
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ _data: "",
+ _stream: null,
+ onDataAvailable(request, inputStream, offset, count) {
+ if (!this._stream) {
+ this._stream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ this._stream.init(inputStream);
+ }
+ this._data += this._stream.read(count);
+ },
+ onStartRequest() {},
+ onStopRequest(request, status) {
+ if (Components.isSuccessCode(status)) {
+ resolve(this._data);
+ } else {
+ reject(`Fetch message attachment failed with status=${status}`);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ };
+
+ msgService.streamMessage(
+ this._bodyAttachment.url,
+ streamListener,
+ null, // msgWindow
+ null, // urlListener
+ false, // convertData
+ "" // additionalHeader
+ );
+ });
+ }
+
+ /**
+ * Fetch the attachment file to get its content type and content.
+ *
+ * Previously, we used the Fetch API to download all attachments, but Fetch
+ * doesn't support url with embedded credentials (imap://name@server). As a
+ * result, it's unreliable when having two mail accounts on the same IMAP
+ * server.
+ *
+ * @returns {string}
+ */
+ async _fetchAttachment() {
+ let url = this._bodyAttachment.url;
+ MsgUtils.sendLogger.debug(`Fetching ${url}`);
+
+ let content = "";
+ if (/^[^:]+-message:/i.test(url)) {
+ content = await this._fetchMsgAttachment();
+ if (!content) {
+ // Message content is empty usually means it's (re)moved.
+ throw new Error("Message is gone");
+ }
+ this._contentType = "message/rfc822";
+ } else {
+ let channel = Services.io.newChannelFromURI(
+ Services.io.newURI(url),
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ content = await new Promise((resolve, reject) =>
+ NetUtil.asyncFetch(channel, (stream, status, request) => {
+ if (!Components.isSuccessCode(status)) {
+ reject(`asyncFetch failed with status=${status}`);
+ return;
+ }
+ let data = "";
+ try {
+ data = NetUtil.readInputStreamToString(stream, stream.available());
+ } catch (e) {
+ // stream.available() throws if the file is empty.
+ }
+ resolve(data);
+ })
+ );
+ this._contentType =
+ this._bodyAttachment.contentType || channel.contentType;
+ }
+
+ let parmFolding = Services.prefs.getIntPref(
+ "mail.strictly_mime.parm_folding",
+ 2
+ );
+ // File name can contain non-ASCII chars, encode according to RFC 2231.
+ let encodedName, encodedFileName;
+ if (this._bodyAttachment.name) {
+ encodedName = MsgUtils.rfc2047EncodeParam(this._bodyAttachment.name);
+ encodedFileName = MsgUtils.rfc2231ParamFolding(
+ "filename",
+ this._bodyAttachment.name
+ );
+ }
+ this._charset = this._contentType.startsWith("text/")
+ ? MailStringUtils.detectCharset(content)
+ : "";
+
+ let contentTypeParams = "";
+ if (this._charset) {
+ contentTypeParams += `; charset=${this._charset}`;
+ }
+ if (encodedName && parmFolding != 2) {
+ contentTypeParams += `; name="${encodedName}"`;
+ }
+ this.setHeader("content-type", `${this._contentType}${contentTypeParams}`);
+ if (encodedFileName) {
+ this.setHeader(
+ "content-disposition",
+ `${this._contentDisposition}; ${encodedFileName}`
+ );
+ }
+ if (this._contentId) {
+ this.setHeader("content-id", `<${this._contentId}>`);
+ }
+ if (this._contentType == "text/html") {
+ let contentLocation = MsgUtils.getContentLocation(
+ this._bodyAttachment.url
+ );
+ this.setHeader("content-location", contentLocation);
+ } else if (this._contentType == "application/pgp-keys") {
+ this.setHeader("content-description", "OpenPGP public key");
+ }
+
+ return content;
+ }
+}
+
+/**
+ * A class to represent a multipart/* part inside a RFC2045 message.
+ */
+class MimeMultiPart extends MimePart {
+ /**
+ * @param {string} subtype - The multipart subtype, e.g. "alternative" or "mixed".
+ */
+ constructor(subtype) {
+ super();
+ this.subtype = subtype;
+ this._separator = this._makePartSeparator();
+ this.setHeader(
+ "content-type",
+ `multipart/${subtype}; boundary="${this._separator}"`
+ );
+ }
+
+ /**
+ * Use 12 hyphen characters and 24 random base64 characters as separator.
+ */
+ _makePartSeparator() {
+ return "------------" + MsgUtils.randomString(24);
+ }
+}
diff --git a/comm/mailnews/compose/src/SMTPProtocolHandler.jsm b/comm/mailnews/compose/src/SMTPProtocolHandler.jsm
new file mode 100644
index 0000000000..d139e15784
--- /dev/null
+++ b/comm/mailnews/compose/src/SMTPProtocolHandler.jsm
@@ -0,0 +1,39 @@
+/* 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 = ["SMTPProtocolHandler", "SMTPSProtocolHandler"];
+
+/**
+ * @implements {nsIProtocolHandler}
+ */
+class SMTPProtocolHandler {
+ QueryInterface = ChromeUtils.generateQI(["nsIProtocolHandler"]);
+
+ scheme = "smtp";
+
+ newChannel(aURI, aLoadInfo) {
+ throw Components.Exception(
+ `${this.constructor.name}.newChannel not implemented`,
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ allowPort(port, scheme) {
+ return port == Ci.nsISmtpUrl.DEFAULT_SMTP_PORT;
+ }
+}
+SMTPProtocolHandler.prototype.classID = Components.ID(
+ "{b14c2b67-8680-4c11-8d63-9403c7d4f757}"
+);
+
+class SMTPSProtocolHandler extends SMTPProtocolHandler {
+ scheme = "smtps";
+
+ allowPort(port, scheme) {
+ return port == Ci.nsISmtpUrl.DEFAULT_SMTPS_PORT;
+ }
+}
+SMTPSProtocolHandler.prototype.classID = Components.ID(
+ "{057d0997-9e3a-411e-b4ee-2602f53fe05f}"
+);
diff --git a/comm/mailnews/compose/src/SmtpClient.jsm b/comm/mailnews/compose/src/SmtpClient.jsm
new file mode 100644
index 0000000000..2eb13985e3
--- /dev/null
+++ b/comm/mailnews/compose/src/SmtpClient.jsm
@@ -0,0 +1,1344 @@
+/* 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/.
+ *
+ * Based on https://github.com/emailjs/emailjs-smtp-client
+ *
+ * Copyright (c) 2013 Andris Reinman
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+const EXPORTED_SYMBOLS = ["SmtpClient"];
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+var { SmtpAuthenticator } = ChromeUtils.import(
+ "resource:///modules/MailAuthenticator.jsm"
+);
+var { MsgUtils } = ChromeUtils.import(
+ "resource:///modules/MimeMessageUtils.jsm"
+);
+
+class SmtpClient {
+ /**
+ * The number of RCPT TO commands sent on the connection by this client.
+ * This can count-up over multiple messages.
+ */
+ rcptCount = 0;
+
+ /**
+ * Set true only when doing a retry.
+ */
+ isRetry = false;
+
+ /**
+ * Creates a connection object to a SMTP server and allows to send mail through it.
+ * Call `connect` method to inititate the actual connection, the constructor only
+ * defines the properties but does not actually connect.
+ *
+ * @class
+ *
+ * @param {nsISmtpServer} server - The associated nsISmtpServer instance.
+ */
+ constructor(server) {
+ this.options = {
+ alwaysSTARTTLS:
+ server.socketType == Ci.nsMsgSocketType.trySTARTTLS ||
+ server.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS,
+ requireTLS: server.socketType == Ci.nsMsgSocketType.SSL,
+ };
+
+ this.socket = false; // Downstream TCP socket to the SMTP server, created with TCPSocket
+ this.waitDrain = false; // Keeps track if the downstream socket is currently full and a drain event should be waited for or not
+
+ // Private properties
+
+ this._server = server;
+ this._authenticator = new SmtpAuthenticator(server);
+ this._authenticating = false;
+ // A list of auth methods detected from the EHLO response.
+ this._supportedAuthMethods = [];
+ // A list of auth methods that worth a try.
+ this._possibleAuthMethods = [];
+ // Auth method set by user preference.
+ this._preferredAuthMethods =
+ {
+ [Ci.nsMsgAuthMethod.passwordCleartext]: ["PLAIN", "LOGIN"],
+ [Ci.nsMsgAuthMethod.passwordEncrypted]: ["CRAM-MD5"],
+ [Ci.nsMsgAuthMethod.GSSAPI]: ["GSSAPI"],
+ [Ci.nsMsgAuthMethod.NTLM]: ["NTLM"],
+ [Ci.nsMsgAuthMethod.OAuth2]: ["XOAUTH2"],
+ [Ci.nsMsgAuthMethod.secure]: ["CRAM-MD5", "XOAUTH2"],
+ }[server.authMethod] || [];
+ // The next auth method to try if the current failed.
+ this._nextAuthMethod = null;
+
+ // A list of capabilities detected from the EHLO response.
+ this._capabilities = [];
+
+ this._dataMode = false; // If true, accepts data from the upstream to be passed directly to the downstream socket. Used after the DATA command
+ this._lastDataBytes = ""; // Keep track of the last bytes to see how the terminating dot should be placed
+ this._envelope = null; // Envelope object for tracking who is sending mail to whom
+ this._currentAction = null; // Stores the function that should be run after a response has been received from the server
+
+ this._parseBlock = { data: [], statusCode: null };
+ this._parseRemainder = ""; // If the complete line is not received yet, contains the beginning of it
+
+ this.logger = MsgUtils.smtpLogger;
+
+ // Event placeholders
+ this.onerror = (e, failedSecInfo) => {}; // Will be run when an error occurs. The `onclose` event will fire subsequently.
+ this.ondrain = () => {}; // More data can be buffered in the socket.
+ this.onclose = () => {}; // The connection to the server has been closed
+ this.onidle = () => {}; // The connection is established and idle, you can send mail now
+ this.onready = failedRecipients => {}; // Waiting for mail body, lists addresses that were not accepted as recipients
+ this.ondone = success => {}; // The mail has been sent. Wait for `onidle` next. Indicates if the message was queued by the server.
+ // Callback when this client is ready to be reused.
+ this.onFree = () => {};
+ }
+
+ /**
+ * Initiate a connection to the server
+ */
+ connect() {
+ if (this.socket?.readyState == "open") {
+ this.logger.debug("Reusing a connection");
+ this.onidle();
+ } else {
+ let hostname = this._server.hostname.toLowerCase();
+ let port = this._server.port || (this.options.requireTLS ? 465 : 587);
+ this.logger.debug(`Connecting to smtp://${hostname}:${port}`);
+ this._secureTransport = this.options.requireTLS;
+ this.socket = new TCPSocket(hostname, port, {
+ binaryType: "arraybuffer",
+ useSecureTransport: this._secureTransport,
+ });
+
+ this.socket.onerror = this._onError;
+ this.socket.onopen = this._onOpen;
+ }
+ this._freed = false;
+ }
+
+ /**
+ * Sends QUIT
+ */
+ quit() {
+ this._authenticating = false;
+ this._freed = true;
+ this._sendCommand("QUIT");
+ this._currentAction = this.close;
+ }
+
+ /**
+ * Closes the connection to the server
+ *
+ * @param {boolean} [immediately] - Close the socket without waiting for
+ * unsent data.
+ */
+ close(immediately) {
+ if (this.socket && this.socket.readyState === "open") {
+ if (immediately) {
+ this.logger.debug(
+ `Closing connection to ${this._server.hostname} immediately!`
+ );
+ this.socket.closeImmediately();
+ } else {
+ this.logger.debug(`Closing connection to ${this._server.hostname}...`);
+ this.socket.close();
+ }
+ } else {
+ this.logger.debug(`Connection to ${this._server.hostname} closed`);
+ this._free();
+ }
+ }
+
+ // Mail related methods
+
+ /**
+ * Initiates a new message by submitting envelope data, starting with
+ * `MAIL FROM:` command. Use after `onidle` event
+ *
+ * @param {object} envelope - The envelope object.
+ * @param {string} envelope.from - The from address.
+ * @param {string[]} envelope.to - The to addresses.
+ * @param {number} envelope.size - The file size.
+ * @param {boolean} envelope.requestDSN - Whether to request Delivery Status Notifications.
+ * @param {boolean} envelope.messageId - The message id.
+ */
+ useEnvelope(envelope) {
+ this._envelope = envelope || {};
+ this._envelope.from = [].concat(
+ this._envelope.from || "anonymous@" + this._getHelloArgument()
+ )[0];
+
+ if (!this._capabilities.includes("SMTPUTF8")) {
+ // If server doesn't support SMTPUTF8, check if addresses contain invalid
+ // characters.
+
+ let recipients = this._envelope.to;
+ this._envelope.to = [];
+
+ for (let recipient of recipients) {
+ let lastAt = null;
+ let firstInvalid = null;
+ for (let i = 0; i < recipient.length; i++) {
+ let ch = recipient[i];
+ if (ch == "@") {
+ lastAt = i;
+ } else if ((ch < " " || ch > "~") && ch != "\t") {
+ firstInvalid = i;
+ break;
+ }
+ }
+ if (!recipient || firstInvalid != null) {
+ if (!lastAt) {
+ // Invalid char found in the localpart, throw error until we implement RFC 6532.
+ this._onNsError(MsgUtils.NS_ERROR_ILLEGAL_LOCALPART, recipient);
+ return;
+ }
+ // Invalid char found in the domainpart, convert it to ACE.
+ let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
+ Ci.nsIIDNService
+ );
+ let domain = idnService.convertUTF8toACE(recipient.slice(lastAt + 1));
+ recipient = `${recipient.slice(0, lastAt)}@${domain}`;
+ }
+ this._envelope.to.push(recipient);
+ }
+ }
+
+ // clone the recipients array for latter manipulation
+ this._envelope.rcptQueue = [...new Set(this._envelope.to)];
+ this._envelope.rcptFailed = [];
+ this._envelope.responseQueue = [];
+
+ if (!this._envelope.rcptQueue.length) {
+ this._onNsError(MsgUtils.NS_MSG_NO_RECIPIENTS);
+ return;
+ }
+
+ this._currentAction = this._actionMAIL;
+ let cmd = `MAIL FROM:<${this._envelope.from}>`;
+ if (
+ this._capabilities.includes("8BITMIME") &&
+ !Services.prefs.getBoolPref("mail.strictly_mime", false)
+ ) {
+ cmd += " BODY=8BITMIME";
+ }
+ if (this._capabilities.includes("SMTPUTF8")) {
+ // Should not send SMTPUTF8 if all ascii, see RFC6531.
+ // eslint-disable-next-line no-control-regex
+ let ascii = /^[\x00-\x7F]+$/;
+ if ([envelope.from, ...envelope.to].some(x => !ascii.test(x))) {
+ cmd += " SMTPUTF8";
+ }
+ }
+ if (this._capabilities.includes("SIZE")) {
+ cmd += ` SIZE=${this._envelope.size}`;
+ }
+ if (this._capabilities.includes("DSN") && this._envelope.requestDSN) {
+ let ret = Services.prefs.getBoolPref("mail.dsn.ret_full_on")
+ ? "FULL"
+ : "HDRS";
+ cmd += ` RET=${ret} ENVID=${envelope.messageId}`;
+ }
+ this._sendCommand(cmd);
+ }
+
+ /**
+ * Send ASCII data to the server. Works only in data mode (after `onready` event), ignored
+ * otherwise
+ *
+ * @param {string} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
+ * @returns {boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
+ */
+ send(chunk) {
+ // works only in data mode
+ if (!this._dataMode) {
+ // this line should never be reached but if it does,
+ // act like everything's normal.
+ return true;
+ }
+
+ // TODO: if the chunk is an arraybuffer, use a separate function to send the data
+ return this._sendString(chunk);
+ }
+
+ /**
+ * Indicates that a data stream for the socket is ended. Works only in data
+ * mode (after `onready` event), ignored otherwise. Use it when you are done
+ * with sending the mail. This method does not close the socket. Once the mail
+ * has been queued by the server, `ondone` and `onidle` are emitted.
+ *
+ * @param {Buffer} [chunk] Chunk of data to be sent to the server
+ */
+ end(chunk) {
+ // works only in data mode
+ if (!this._dataMode) {
+ // this line should never be reached but if it does,
+ // act like everything's normal.
+ return true;
+ }
+
+ if (chunk && chunk.length) {
+ this.send(chunk);
+ }
+
+ // redirect output from the server to _actionStream
+ this._currentAction = this._actionStream;
+
+ // indicate that the stream has ended by sending a single dot on its own line
+ // if the client already closed the data with \r\n no need to do it again
+ if (this._lastDataBytes === "\r\n") {
+ this.waitDrain = this._send(new Uint8Array([0x2e, 0x0d, 0x0a]).buffer); // .\r\n
+ } else if (this._lastDataBytes.substr(-1) === "\r") {
+ this.waitDrain = this._send(
+ new Uint8Array([0x0a, 0x2e, 0x0d, 0x0a]).buffer
+ ); // \n.\r\n
+ } else {
+ this.waitDrain = this._send(
+ new Uint8Array([0x0d, 0x0a, 0x2e, 0x0d, 0x0a]).buffer
+ ); // \r\n.\r\n
+ }
+
+ // End data mode.
+ this._dataMode = false;
+
+ return this.waitDrain;
+ }
+
+ // PRIVATE METHODS
+
+ /**
+ * Queue some data from the server for parsing.
+ *
+ * @param {string} chunk Chunk of data received from the server
+ */
+ _parse(chunk) {
+ // Lines should always end with <CR><LF> but you never know, might be only <LF> as well
+ var lines = (this._parseRemainder + (chunk || "")).split(/\r?\n/);
+ this._parseRemainder = lines.pop(); // not sure if the line has completely arrived yet
+
+ for (let i = 0, len = lines.length; i < len; i++) {
+ if (!lines[i].trim()) {
+ // nothing to check, empty line
+ continue;
+ }
+
+ // possible input strings for the regex:
+ // 250-MULTILINE REPLY
+ // 250 LAST LINE OF REPLY
+ // 250 1.2.3 MESSAGE
+
+ const match = lines[i].match(
+ /^(\d{3})([- ])(?:(\d+\.\d+\.\d+)(?: ))?(.*)/
+ );
+
+ if (match) {
+ this._parseBlock.data.push(match[4]);
+
+ if (match[2] === "-") {
+ // this is a multiline reply
+ this._parseBlock.statusCode =
+ this._parseBlock.statusCode || Number(match[1]);
+ } else {
+ const statusCode = Number(match[1]) || 0;
+ const response = {
+ statusCode,
+ data: this._parseBlock.data.join("\n"),
+ // Success means can move to the next step. Though 3xx is not
+ // failure, we don't consider it success here.
+ success: statusCode >= 200 && statusCode < 300,
+ };
+
+ this._onCommand(response);
+ this._parseBlock = {
+ data: [],
+ statusCode: null,
+ };
+ }
+ } else {
+ this._onCommand({
+ success: false,
+ statusCode: this._parseBlock.statusCode || null,
+ data: [lines[i]].join("\n"),
+ });
+ this._parseBlock = {
+ data: [],
+ statusCode: null,
+ };
+ }
+ }
+ }
+
+ // EVENT HANDLERS FOR THE SOCKET
+
+ /**
+ * Connection listener that is run when the connection to the server is opened.
+ * Sets up different event handlers for the opened socket
+ */
+ _onOpen = () => {
+ this.logger.debug("Connected");
+
+ this.socket.ondata = this._onData;
+ this.socket.onclose = this._onClose;
+ this.socket.ondrain = this._onDrain;
+
+ this._currentAction = this._actionGreeting;
+ this.socket.transport.setTimeout(
+ Ci.nsISocketTransport.TIMEOUT_READ_WRITE,
+ Services.prefs.getIntPref("mailnews.tcptimeout")
+ );
+ };
+
+ /**
+ * Data listener for chunks of data emitted by the server
+ *
+ * @param {Event} evt - Event object. See `evt.data` for the chunk received
+ */
+ _onData = async evt => {
+ let stringPayload = new TextDecoder("UTF-8").decode(
+ new Uint8Array(evt.data)
+ );
+ // "S: " to denote that this is data from the Server.
+ this.logger.debug(`S: ${stringPayload}`);
+
+ // Prevent blocking the main thread, otherwise onclose/onerror may not be
+ // called in time. test_smtpPasswordFailure3 is such a case, the server
+ // rejects AUTH PLAIN then closes the connection, the client then sends AUTH
+ // LOGIN. This line guarantees onclose is called before sending AUTH LOGIN.
+ await new Promise(resolve => setTimeout(resolve));
+ this._parse(stringPayload);
+ };
+
+ /**
+ * More data can be buffered in the socket, `waitDrain` is reset to false
+ */
+ _onDrain = () => {
+ this.waitDrain = false;
+ this.ondrain();
+ };
+
+ /**
+ * Error handler. Emits an nsresult value.
+ *
+ * @param {Error|TCPSocketErrorEvent} event - An Error or TCPSocketErrorEvent object.
+ */
+ _onError = async event => {
+ this.logger.error(`${event.name}: a ${event.message} error occurred`);
+ if (this._freed) {
+ // Ignore socket errors if already freed.
+ return;
+ }
+
+ this._free();
+ this.quit();
+
+ let nsError = Cr.NS_ERROR_FAILURE;
+ let secInfo = null;
+ if (TCPSocketErrorEvent.isInstance(event)) {
+ nsError = event.errorCode;
+ secInfo =
+ await event.target.transport?.tlsSocketControl?.asyncGetSecurityInfo();
+ if (secInfo) {
+ this.logger.error(`SecurityError info: ${secInfo.errorCodeString}`);
+ if (secInfo.failedCertChain.length) {
+ let chain = secInfo.failedCertChain.map(c => {
+ return c.commonName + "; serial# " + c.serialNumber;
+ });
+ this.logger.error(`SecurityError cert chain: ${chain.join(" <- ")}`);
+ }
+ this._server.closeCachedConnections();
+ }
+ }
+
+ // Use nsresult to integrate with other parts of sending process, e.g.
+ // MessageSend.jsm will show an error message depending on the nsresult.
+ this.onerror(nsError, "", secInfo);
+ };
+
+ /**
+ * Error handler. Emits an nsresult value.
+ *
+ * @param {nsresult} nsError - A nsresult.
+ * @param {string} errorParam - Param to form the error message.
+ * @param {string} [extra] - Some messages take two arguments to format.
+ * @param {number} [statusCode] - Only needed when checking need to retry.
+ */
+ _onNsError(nsError, errorParam, extra, statusCode) {
+ // First check if handling an error response that might need a retry.
+ if ([this._actionMAIL, this._actionRCPT].includes(this._currentAction)) {
+ if (statusCode >= 400 && statusCode < 500) {
+ // Possibly too many recipients, too many messages, to much data
+ // or too much time has elapsed on this connection.
+ if (!this.isRetry) {
+ // Now seeing error 4xx meaning that the current message can't be
+ // accepted. We close the connection and try again to send on a new
+ // connection using this same client instance. If the retry also
+ // fails on the new connection, we give up and report the error.
+ this.logger.debug("Retry send on new connection.");
+ this.quit();
+ this.isRetry = true; // flag that we will retry on new connection
+ this.close(true);
+ this.connect();
+ return; // return without reporting the error yet
+ }
+ }
+ }
+
+ let errorName = MsgUtils.getErrorStringName(nsError);
+ let errorMessage = "";
+ if (
+ [
+ MsgUtils.NS_ERROR_SMTP_SERVER_ERROR,
+ MsgUtils.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED,
+ MsgUtils.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2,
+ MsgUtils.NS_ERROR_SENDING_FROM_COMMAND,
+ MsgUtils.NS_ERROR_SENDING_RCPT_COMMAND,
+ MsgUtils.NS_ERROR_SENDING_DATA_COMMAND,
+ MsgUtils.NS_ERROR_SENDING_MESSAGE,
+ MsgUtils.NS_ERROR_ILLEGAL_LOCALPART,
+ ].includes(nsError)
+ ) {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+ if (nsError == MsgUtils.NS_ERROR_ILLEGAL_LOCALPART) {
+ errorMessage = bundle
+ .GetStringFromName(errorName)
+ .replace("%s", errorParam);
+ } else {
+ errorMessage = bundle.formatStringFromName(errorName, [
+ errorParam,
+ extra,
+ ]);
+ }
+ }
+ this.onerror(nsError, errorMessage);
+ this.close();
+ }
+
+ /**
+ * Indicates that the socket has been closed
+ */
+ _onClose = () => {
+ this.logger.debug("Socket closed.");
+ this._free();
+ this.rcptCount = 0;
+ if (this._authenticating) {
+ // In some cases, socket is closed for invalid username/password.
+ this._onAuthFailed({ data: "Socket closed." });
+ }
+ };
+
+ /**
+ * This is not a socket data handler but the handler for data emitted by the parser,
+ * so this data is safe to use as it is always complete (server might send partial chunks)
+ *
+ * @param {object} command - Parsed data.
+ */
+ _onCommand(command) {
+ if (command.statusCode < 200 || command.statusCode >= 400) {
+ // @see https://datatracker.ietf.org/doc/html/rfc5321#section-3.8
+ // 421: SMTP service shutting down and closing transmission channel.
+ // When that happens during idle, just close the connection.
+ if (
+ command.statusCode == 421 &&
+ this._currentAction == this._actionIdle
+ ) {
+ this.close(true);
+ return;
+ }
+
+ this.logger.error(
+ `Command failed: ${command.statusCode} ${command.data}; currentAction=${this._currentAction?.name}`
+ );
+ }
+ if (typeof this._currentAction === "function") {
+ this._currentAction(command);
+ }
+ }
+
+ /**
+ * This client has finished the current process and ready to be reused.
+ */
+ _free() {
+ if (!this._freed) {
+ this._freed = true;
+ this.onFree();
+ }
+ }
+
+ /**
+ * Sends a string to the socket.
+ *
+ * @param {string} chunk ASCII string (quoted-printable, base64 etc.) to be sent to the server
+ * @returns {boolean} If true, it is safe to send more data, if false, you *should* wait for the ondrain event before sending more
+ */
+ _sendString(chunk) {
+ // escape dots
+ if (!this.options.disableEscaping) {
+ chunk = chunk.replace(/\n\./g, "\n..");
+ if (
+ (this._lastDataBytes.substr(-1) === "\n" || !this._lastDataBytes) &&
+ chunk.charAt(0) === "."
+ ) {
+ chunk = "." + chunk;
+ }
+ }
+
+ // Keeping eye on the last bytes sent, to see if there is a <CR><LF> sequence
+ // at the end which is needed to end the data stream
+ if (chunk.length > 2) {
+ this._lastDataBytes = chunk.substr(-2);
+ } else if (chunk.length === 1) {
+ this._lastDataBytes = this._lastDataBytes.substr(-1) + chunk;
+ }
+
+ this.logger.debug("Sending " + chunk.length + " bytes of payload");
+
+ // pass the chunk to the socket
+ this.waitDrain = this._send(
+ MailStringUtils.byteStringToUint8Array(chunk).buffer
+ );
+ return this.waitDrain;
+ }
+
+ /**
+ * Send a string command to the server, also append CRLF if needed.
+ *
+ * @param {string} str - String to be sent to the server.
+ * @param {boolean} [suppressLogging=false] - If true and not in dev mode,
+ * do not log the str. For non-release builds output won't be suppressed,
+ * so that debugging auth problems is easier.
+ */
+ _sendCommand(str, suppressLogging = false) {
+ if (this.socket.readyState !== "open") {
+ if (str != "QUIT") {
+ this.logger.warn(
+ `Failed to send "${str}" because socket state is ${this.socket.readyState}`
+ );
+ }
+ return;
+ }
+ // "C: " is used to denote that this is data from the Client.
+ if (suppressLogging && AppConstants.MOZ_UPDATE_CHANNEL != "default") {
+ this.logger.debug(
+ "C: Logging suppressed (it probably contained auth information)"
+ );
+ } else {
+ this.logger.debug(`C: ${str}`);
+ }
+ this.waitDrain = this._send(
+ new TextEncoder().encode(str + (str.substr(-2) !== "\r\n" ? "\r\n" : ""))
+ .buffer
+ );
+ }
+
+ _send(buffer) {
+ return this.socket.send(buffer);
+ }
+
+ /**
+ * Intitiate authentication sequence if needed
+ *
+ * @param {boolean} forceNewPassword - Discard cached password.
+ */
+ async _authenticateUser(forceNewPassword) {
+ if (
+ this._preferredAuthMethods.length == 0 ||
+ this._supportedAuthMethods.length == 0
+ ) {
+ // no need to authenticate, at least no data given
+ this._currentAction = this._actionIdle;
+ this.onidle(); // ready to take orders
+ return;
+ }
+
+ if (!this._nextAuthMethod) {
+ this._onAuthFailed({ data: "No available auth method." });
+ return;
+ }
+
+ this._authenticating = true;
+
+ this._currentAuthMethod = this._nextAuthMethod;
+ this._nextAuthMethod =
+ this._possibleAuthMethods[
+ this._possibleAuthMethods.indexOf(this._currentAuthMethod) + 1
+ ];
+ this.logger.debug(`Current auth method: ${this._currentAuthMethod}`);
+
+ switch (this._currentAuthMethod) {
+ case "LOGIN":
+ // LOGIN is a 3 step authentication process
+ // C: AUTH LOGIN
+ // C: BASE64(USER)
+ // C: BASE64(PASS)
+ this.logger.debug("Authentication via AUTH LOGIN");
+ this._currentAction = this._actionAUTH_LOGIN_USER;
+ this._sendCommand("AUTH LOGIN");
+ return;
+ case "PLAIN":
+ // AUTH PLAIN is a 1 step authentication process
+ // C: AUTH PLAIN BASE64(\0 USER \0 PASS)
+ this.logger.debug("Authentication via AUTH PLAIN");
+ this._currentAction = this._actionAUTHComplete;
+ this._sendCommand(
+ "AUTH PLAIN " + this._authenticator.getPlainToken(),
+ true
+ );
+ return;
+ case "CRAM-MD5":
+ this.logger.debug("Authentication via AUTH CRAM-MD5");
+ this._currentAction = this._actionAUTH_CRAM;
+ this._sendCommand("AUTH CRAM-MD5");
+ return;
+ case "XOAUTH2":
+ // See https://developers.google.com/gmail/xoauth2_protocol#smtp_protocol_exchange
+ this.logger.debug("Authentication via AUTH XOAUTH2");
+ this._currentAction = this._actionAUTH_XOAUTH2;
+ let oauthToken = await this._authenticator.getOAuthToken();
+ this._sendCommand("AUTH XOAUTH2 " + oauthToken, true);
+ return;
+ case "GSSAPI": {
+ this.logger.debug("Authentication via AUTH GSSAPI");
+ this._currentAction = this._actionAUTH_GSSAPI;
+ this._authenticator.initGssapiAuth("smtp");
+ let token;
+ try {
+ token = this._authenticator.getNextGssapiToken("");
+ } catch (e) {
+ this.logger.error(e);
+ this._actionAUTHComplete({ success: false, data: "AUTH GSSAPI" });
+ return;
+ }
+ this._sendCommand(`AUTH GSSAPI ${token}`, true);
+ return;
+ }
+ case "NTLM": {
+ this.logger.debug("Authentication via AUTH NTLM");
+ this._currentAction = this._actionAUTH_NTLM;
+ this._authenticator.initNtlmAuth("smtp");
+ let token;
+ try {
+ token = this._authenticator.getNextNtlmToken("");
+ } catch (e) {
+ this.logger.error(e);
+ this._actionAUTHComplete({ success: false, data: "AUTH NTLM" });
+ return;
+ }
+ this._sendCommand(`AUTH NTLM ${token}`, true);
+ return;
+ }
+ }
+
+ this._onAuthFailed({
+ data: `Unknown authentication method ${this._currentAuthMethod}`,
+ });
+ }
+
+ _onAuthFailed(command) {
+ this.logger.error(`Authentication failed: ${command.data}`);
+ if (!this._freed) {
+ if (this._nextAuthMethod) {
+ // Try the next auth method.
+ this._authenticateUser();
+ return;
+ } else if (!this._currentAuthMethod) {
+ // No auth method was even tried.
+ let err;
+ if (
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordEncrypted &&
+ (this._supportedAuthMethods.includes("PLAIN") ||
+ this._supportedAuthMethods.includes("LOGIN"))
+ ) {
+ // Pref has encrypted password, server claims to support plaintext
+ // password.
+ err = [
+ Ci.nsMsgSocketType.alwaysSTARTTLS,
+ Ci.nsMsgSocketType.SSL,
+ ].includes(this._server.socketType)
+ ? MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL
+ : MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL;
+ } else if (
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext &&
+ this._supportedAuthMethods.includes("CRAM-MD5")
+ ) {
+ // Pref has plaintext password, server claims to support encrypted
+ // password.
+ err = MsgUtils.NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT;
+ } else {
+ err = MsgUtils.NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED;
+ }
+ this._onNsError(err);
+ return;
+ }
+ }
+
+ // Ask user what to do.
+ let action = this._authenticator.promptAuthFailed();
+ if (action == 1) {
+ // Cancel button pressed.
+ this.logger.error(`Authentication failed: ${command.data}`);
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE);
+ return;
+ } else if (action == 2) {
+ // 'New password' button pressed. Forget cached password, new password
+ // will be asked.
+ this._authenticator.forgetPassword();
+ }
+
+ if (this._freed) {
+ // If connection is lost, reconnect.
+ this.connect();
+ return;
+ }
+
+ // Reset _nextAuthMethod to start again.
+ this._nextAuthMethod = this._possibleAuthMethods[0];
+ if (action == 2 || action == 0) {
+ // action = 0 means retry button pressed.
+ this._authenticateUser();
+ }
+ }
+
+ _getHelloArgument() {
+ let helloArgument = this._server.helloArgument;
+ if (helloArgument) {
+ return helloArgument;
+ }
+
+ try {
+ // The address format follows rfc5321#section-4.1.3.
+ let netAddr = this.socket?.transport.getScriptableSelfAddr();
+ let address = netAddr.address;
+ if (netAddr.family === Ci.nsINetAddr.FAMILY_INET6) {
+ return `[IPV6:${address}]`;
+ }
+ return `[${address}]`;
+ } catch (e) {}
+
+ return "[127.0.0.1]";
+ }
+
+ // ACTIONS FOR RESPONSES FROM THE SMTP SERVER
+
+ /**
+ * Initial response from the server, must have a status 220
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionGreeting(command) {
+ if (command.statusCode !== 220) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+
+ if (this.options.lmtp) {
+ this._currentAction = this._actionLHLO;
+ this._sendCommand("LHLO " + this._getHelloArgument());
+ } else {
+ this._currentAction = this._actionEHLO;
+ this._sendCommand("EHLO " + this._getHelloArgument());
+ }
+ }
+
+ /**
+ * Response to LHLO
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionLHLO(command) {
+ if (!command.success) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+
+ // Process as EHLO response
+ this._actionEHLO(command);
+ }
+
+ /**
+ * Response to EHLO. If the response is an error, try HELO instead
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionEHLO(command) {
+ if ([500, 502].includes(command.statusCode)) {
+ // EHLO is not implemented by the server.
+ if (this.options.alwaysSTARTTLS) {
+ // If alwaysSTARTTLS is set by the user, EHLO is required to advertise it.
+ this._onNsError(MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS);
+ return;
+ }
+
+ // Try HELO instead
+ this.logger.warn(
+ "EHLO not successful, trying HELO " + this._getHelloArgument()
+ );
+ this._currentAction = this._actionHELO;
+ this._sendCommand("HELO " + this._getHelloArgument());
+ return;
+ } else if (!command.success) {
+ // 501 Syntax error or some other error.
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+
+ this._supportedAuthMethods = [];
+
+ let lines = command.data.toUpperCase().split("\n");
+ // Skip the first greeting line.
+ for (let line of lines.slice(1)) {
+ if (line.startsWith("AUTH ")) {
+ this._supportedAuthMethods = line.slice(5).split(" ");
+ } else {
+ this._capabilities.push(line.split(" ")[0]);
+ }
+ }
+
+ if (!this._secureTransport && this.options.alwaysSTARTTLS) {
+ // STARTTLS is required by the user. Detect if the server supports it.
+ if (this._capabilities.includes("STARTTLS")) {
+ this._currentAction = this._actionSTARTTLS;
+ this._sendCommand("STARTTLS");
+ return;
+ }
+ // STARTTLS is required but not advertised.
+ this._onNsError(MsgUtils.NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS);
+ return;
+ }
+
+ // If a preferred method is not supported by the server, no need to try it.
+ this._possibleAuthMethods = this._preferredAuthMethods.filter(x =>
+ this._supportedAuthMethods.includes(x)
+ );
+ this.logger.debug(`Possible auth methods: ${this._possibleAuthMethods}`);
+ this._nextAuthMethod = this._possibleAuthMethods[0];
+
+ if (
+ this._capabilities.includes("CLIENTID") &&
+ (this._secureTransport ||
+ // For test purpose.
+ ["localhost", "127.0.0.1", "::1"].includes(this._server.hostname)) &&
+ this._server.clientidEnabled &&
+ this._server.clientid
+ ) {
+ // Client identity extension, still a draft.
+ this._currentAction = this._actionCLIENTID;
+ this._sendCommand("CLIENTID UUID " + this._server.clientid, true);
+ } else {
+ this._authenticateUser();
+ }
+ }
+
+ /**
+ * Handles server response for STARTTLS command. If there's an error
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
+ * succeeds restart the EHLO
+ *
+ * @param {string} command - Message from the server.
+ */
+ _actionSTARTTLS(command) {
+ if (!command.success) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+
+ this.socket.upgradeToSecure();
+ this._secureTransport = true;
+
+ // restart protocol flow
+ this._currentAction = this._actionEHLO;
+ this._sendCommand("EHLO " + this._getHelloArgument());
+ }
+
+ /**
+ * Response to HELO
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionHELO(command) {
+ if (!command.success) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+ this._authenticateUser();
+ }
+
+ /**
+ * Handles server response for CLIENTID command. If successful then will
+ * initiate the authenticateUser process.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionCLIENTID(command) {
+ if (!command.success) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ return;
+ }
+ this._authenticateUser();
+ }
+
+ /**
+ * Returns the saved/cached server password, or show a password dialog. If the
+ * user cancels the dialog, abort sending.
+ *
+ * @returns {string} The server password.
+ */
+ _getPassword() {
+ try {
+ return this._authenticator.getPassword();
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_ABORT) {
+ this.quit();
+ this.onerror(e.result);
+ } else {
+ throw e;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Response to AUTH LOGIN, if successful expects base64 encoded username
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionAUTH_LOGIN_USER(command) {
+ if (command.statusCode !== 334 || command.data !== "VXNlcm5hbWU6") {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data);
+ return;
+ }
+ this.logger.debug("AUTH LOGIN USER");
+ this._currentAction = this._actionAUTH_LOGIN_PASS;
+ this._sendCommand(btoa(this._authenticator.username), true);
+ }
+
+ /**
+ * Process the response to AUTH LOGIN with a username. If successful, expects
+ * a base64-encoded password.
+ *
+ * @param {{statusCode: number, data: string}} command - Parsed command from
+ * the server.
+ */
+ _actionAUTH_LOGIN_PASS(command) {
+ if (
+ command.statusCode !== 334 ||
+ (command.data !== btoa("Password:") && command.data !== btoa("password:"))
+ ) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data);
+ return;
+ }
+ this.logger.debug("AUTH LOGIN PASS");
+ this._currentAction = this._actionAUTHComplete;
+ let password = this._getPassword();
+ if (
+ !Services.prefs.getBoolPref(
+ "mail.smtp_login_pop3_user_pass_auth_is_latin1",
+ true
+ ) ||
+ !/^[\x00-\xFF]+$/.test(password) // eslint-disable-line no-control-regex
+ ) {
+ // Unlike PLAIN auth, the payload of LOGIN auth is not standardized. When
+ // `mail.smtp_login_pop3_user_pass_auth_is_latin1` is true, we apply
+ // base64 encoding directly. Otherwise, we convert it to UTF-8
+ // BinaryString first.
+ password = MailStringUtils.stringToByteString(password);
+ }
+ this._sendCommand(btoa(password), true);
+ }
+
+ /**
+ * Response to AUTH CRAM, if successful expects base64 encoded challenge.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ async _actionAUTH_CRAM(command) {
+ if (command.statusCode !== 334) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data);
+ return;
+ }
+ this._currentAction = this._actionAUTHComplete;
+ this._sendCommand(
+ this._authenticator.getCramMd5Token(this._getPassword(), command.data),
+ true
+ );
+ }
+
+ /**
+ * Response to AUTH XOAUTH2 token, if error occurs send empty response
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionAUTH_XOAUTH2(command) {
+ if (!command.success) {
+ this.logger.warn("Error during AUTH XOAUTH2, sending empty response");
+ this._sendCommand("");
+ this._currentAction = this._actionAUTHComplete;
+ } else {
+ this._actionAUTHComplete(command);
+ }
+ }
+
+ /**
+ * Response to AUTH GSSAPI, if successful expects a base64 encoded challenge.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionAUTH_GSSAPI(command) {
+ // GSSAPI auth can be multiple steps. We exchange tokens with the server
+ // until success or failure.
+ if (command.success) {
+ this._actionAUTHComplete(command);
+ return;
+ }
+ if (command.statusCode !== 334) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_GSSAPI, command.data);
+ return;
+ }
+ let token = this._authenticator.getNextGssapiToken(command.data);
+ this._currentAction = this._actionAUTH_GSSAPI;
+ this._sendCommand(token, true);
+ }
+
+ /**
+ * Response to AUTH NTLM, if successful expects a base64 encoded challenge.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionAUTH_NTLM(command) {
+ // NTLM auth can be multiple steps. We exchange tokens with the server
+ // until success or failure.
+ if (command.success) {
+ this._actionAUTHComplete(command);
+ return;
+ }
+ if (command.statusCode !== 334) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_AUTH_FAILURE, command.data);
+ return;
+ }
+ let token = this._authenticator.getNextNtlmToken(command.data);
+ this._currentAction = this._actionAUTH_NTLM;
+ this._sendCommand(token, true);
+ }
+
+ /**
+ * Checks if authentication succeeded or not. If successfully authenticated
+ * emit `idle` to indicate that an e-mail can be sent using this connection
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionAUTHComplete(command) {
+ this._authenticating = false;
+ if (!command.success) {
+ this._onAuthFailed(command);
+ return;
+ }
+
+ this.logger.debug("Authentication successful.");
+
+ this._currentAction = this._actionIdle;
+ this.onidle(); // ready to take orders
+ }
+
+ /**
+ * Used when the connection is idle, not expecting anything from the server.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionIdle(command) {
+ this._onNsError(MsgUtils.NS_ERROR_SMTP_SERVER_ERROR, command.data);
+ }
+
+ /**
+ * Response to MAIL FROM command. Proceed to defining RCPT TO list if successful
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionMAIL(command) {
+ if (!command.success) {
+ let errorCode = MsgUtils.NS_ERROR_SENDING_FROM_COMMAND; // default code
+ if (command.statusCode == 552) {
+ // Too much mail data indicated by "size" parameter of MAIL FROM.
+ // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.9
+ errorCode = MsgUtils.NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2;
+ }
+ if (command.statusCode == 452 || command.statusCode == 451) {
+ // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.10
+ errorCode = MsgUtils.NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED;
+ }
+ this._onNsError(errorCode, command.data, null, command.statusCode);
+ return;
+ }
+ this.logger.debug(
+ "MAIL FROM successful, proceeding with " +
+ this._envelope.rcptQueue.length +
+ " recipients"
+ );
+ this.logger.debug("Adding recipient...");
+ this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+ this._currentAction = this._actionRCPT;
+ this._sendCommand(
+ `RCPT TO:<${this._envelope.curRecipient}>${this._getRCPTParameters()}`
+ );
+ }
+
+ /**
+ * Prepare the RCPT params, currently only DSN params. If the server supports
+ * DSN and sender requested DSN, append DSN params to each RCPT TO command.
+ */
+ _getRCPTParameters() {
+ if (this._capabilities.includes("DSN") && this._envelope.requestDSN) {
+ let notify = [];
+ if (Services.prefs.getBoolPref("mail.dsn.request_never_on")) {
+ notify.push("NEVER");
+ } else {
+ if (Services.prefs.getBoolPref("mail.dsn.request_on_success_on")) {
+ notify.push("SUCCESS");
+ }
+ if (Services.prefs.getBoolPref("mail.dsn.request_on_failure_on")) {
+ notify.push("FAILURE");
+ }
+ if (Services.prefs.getBoolPref("mail.dsn.request_on_delay_on")) {
+ notify.push("DELAY");
+ }
+ }
+ if (notify.length > 0) {
+ return ` NOTIFY=${notify.join(",")}`;
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Response to a RCPT TO command. If the command is unsuccessful, emit an
+ * error to abort the sending.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionRCPT(command) {
+ if (!command.success) {
+ this._onNsError(
+ MsgUtils.NS_ERROR_SENDING_RCPT_COMMAND,
+ command.data,
+ this._envelope.curRecipient,
+ command.statusCode
+ );
+ return;
+ }
+ this.rcptCount++;
+ this._envelope.responseQueue.push(this._envelope.curRecipient);
+
+ if (this._envelope.rcptQueue.length) {
+ // Send the next recipient.
+ this._envelope.curRecipient = this._envelope.rcptQueue.shift();
+ this._currentAction = this._actionRCPT;
+ this._sendCommand(
+ `RCPT TO:<${this._envelope.curRecipient}>${this._getRCPTParameters()}`
+ );
+ } else {
+ this.logger.debug(
+ `Total RCPTs during this connection: ${this.rcptCount}`
+ );
+ this.logger.debug("RCPT TO done. Proceeding with payload.");
+ this._currentAction = this._actionDATA;
+ this._sendCommand("DATA");
+ }
+ }
+
+ /**
+ * Response to the DATA command. Server is now waiting for a message, so emit `onready`
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionDATA(command) {
+ // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
+ // some servers might use 250 instead
+ if (![250, 354].includes(command.statusCode)) {
+ this._onNsError(MsgUtils.NS_ERROR_SENDING_DATA_COMMAND, command.data);
+ return;
+ }
+
+ this._dataMode = true;
+ this._currentAction = this._actionIdle;
+ this.onready(this._envelope.rcptFailed);
+ }
+
+ /**
+ * Response from the server, once the message stream has ended with <CR><LF>.<CR><LF>
+ * Emits `ondone`.
+ *
+ * @param {object} command Parsed command from the server {statusCode, data}
+ */
+ _actionStream(command) {
+ var rcpt;
+
+ if (this.options.lmtp) {
+ // LMTP returns a response code for *every* successfully set recipient
+ // For every recipient the message might succeed or fail individually
+
+ rcpt = this._envelope.responseQueue.shift();
+ if (!command.success) {
+ this.logger.error("Local delivery to " + rcpt + " failed.");
+ this._envelope.rcptFailed.push(rcpt);
+ } else {
+ this.logger.error("Local delivery to " + rcpt + " succeeded.");
+ }
+
+ if (this._envelope.responseQueue.length) {
+ this._currentAction = this._actionStream;
+ return;
+ }
+
+ this._currentAction = this._actionIdle;
+ this.ondone(0);
+ } else {
+ // For SMTP the message either fails or succeeds, there is no information
+ // about individual recipients
+
+ if (!command.success) {
+ this.logger.error("Message sending failed.");
+ } else {
+ this.logger.debug("Message sent successfully.");
+ this.isRetry = false;
+ }
+
+ this._currentAction = this._actionIdle;
+ if (command.success) {
+ this.ondone(0);
+ } else {
+ this._onNsError(MsgUtils.NS_ERROR_SENDING_MESSAGE, command.data);
+ }
+ }
+
+ this._freed = true;
+ this.onFree();
+ }
+}
diff --git a/comm/mailnews/compose/src/SmtpServer.jsm b/comm/mailnews/compose/src/SmtpServer.jsm
new file mode 100644
index 0000000000..3ce81ff936
--- /dev/null
+++ b/comm/mailnews/compose/src/SmtpServer.jsm
@@ -0,0 +1,519 @@
+/* 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 = ["SmtpServer"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ SmtpClient: "resource:///modules/SmtpClient.jsm",
+});
+
+/**
+ * This class represents a single SMTP server.
+ *
+ * @implements {nsISmtpServer}
+ * @implements {nsIObserver}
+ */
+
+class SmtpServer {
+ QueryInterface = ChromeUtils.generateQI(["nsISmtpServer", "nsIObserver"]);
+
+ constructor() {
+ this._key = "";
+ this._loadPrefs();
+
+ Services.obs.addObserver(this, "passwordmgr-storage-changed");
+ }
+
+ /**
+ * Observe() receives notifications for all accounts, not just this SMTP
+ * server's * account. So we ignore all notifications not intended for this
+ * server. When the state of the password manager changes we need to clear the
+ * this server's password from the cache in case the user just changed or
+ * removed the password or username.
+ * OAuth2 servers often automatically change the password manager's stored
+ * password (the token).
+ */
+ observe(subject, topic, data) {
+ if (topic == "passwordmgr-storage-changed") {
+ // Check that the notification is for this server and user.
+ let otherFullName = "";
+ let otherUsername = "";
+ if (subject instanceof Ci.nsILoginInfo) {
+ // The login info for a server has been removed with aData being
+ // "removeLogin" or "removeAllLogins".
+ otherFullName = subject.origin;
+ otherUsername = subject.username;
+ } else if (subject instanceof Ci.nsIArray) {
+ // Probably a 2 element array containing old and new login info due to
+ // aData being "modifyLogin". E.g., a user has modified the password or
+ // username in the password manager or an OAuth2 token string has
+ // automatically changed. Only need to look at names in first array
+ // element (login info before any modification) since the user might
+ // have changed the username as found in the 2nd elements. (The
+ // hostname can't be modified in the password manager.
+ otherFullName = subject.queryElementAt(0, Ci.nsISupports).origin;
+ otherUsername = subject.queryElementAt(0, Ci.nsISupports).username;
+ }
+ if (otherFullName) {
+ if (
+ otherFullName != "smtp://" + this.hostname ||
+ otherUsername != this.username
+ ) {
+ // Not for this account; keep this account's password.
+ return;
+ }
+ } else if (data != "hostSavingDisabled") {
+ // "hostSavingDisabled" only occurs during test_smtpServer.js and
+ // expects the password to be removed from memory cache. Otherwise, we
+ // don't have enough information to decide to remove the cached
+ // password, so keep it.
+ return;
+ }
+ // Remove the password for this server cached in memory.
+ this.password = "";
+ }
+ }
+
+ get key() {
+ return this._key;
+ }
+
+ set key(key) {
+ this._key = key;
+ this._loadPrefs();
+ }
+
+ get UID() {
+ let uid = this._prefs.getStringPref("uid", "");
+ if (uid) {
+ return uid;
+ }
+ return (this.UID = Services.uuid
+ .generateUUID()
+ .toString()
+ .substring(1, 37));
+ }
+
+ set UID(uid) {
+ if (this._prefs.prefHasUserValue("uid")) {
+ throw new Components.Exception("uid is already set", Cr.NS_ERROR_ABORT);
+ }
+ this._prefs.setStringPref("uid", uid);
+ }
+
+ get description() {
+ return this._prefs.getStringPref("description", "");
+ }
+
+ set description(value) {
+ this._prefs.setStringPref("description", value);
+ }
+
+ get hostname() {
+ return this._prefs.getStringPref("hostname", "");
+ }
+
+ set hostname(value) {
+ if (value.toLowerCase() != this.hostname.toLowerCase()) {
+ // Reset password so that users are prompted for new password for the new
+ // host.
+ this.forgetPassword();
+ }
+ this._prefs.setStringPref("hostname", value);
+ }
+
+ get port() {
+ return this._prefs.getIntPref("port", 0);
+ }
+
+ set port(value) {
+ if (value) {
+ this._prefs.setIntPref("port", value);
+ } else {
+ this._prefs.clearUserPref("port");
+ }
+ }
+
+ get displayname() {
+ return `${this.hostname}` + (this.port ? `:${this.port}` : "");
+ }
+
+ get username() {
+ return this._prefs.getCharPref("username", "");
+ }
+
+ set username(value) {
+ if (value != this.username) {
+ // Reset password so that users are prompted for new password for the new
+ // username.
+ this.forgetPassword();
+ }
+ this._setCharPref("username", value);
+ }
+
+ get clientid() {
+ return this._getCharPrefWithDefault("clientid");
+ }
+
+ set clientid(value) {
+ this._setCharPref("clientid", value);
+ }
+
+ get clientidEnabled() {
+ try {
+ return this._prefs.getBoolPref("clientidEnabled");
+ } catch (e) {
+ return this._defaultPrefs.getBoolPref("clientidEnabled", false);
+ }
+ }
+
+ set clientidEnabled(value) {
+ this._prefs.setBoolPref("clientidEnabled", value);
+ }
+
+ get authMethod() {
+ return this._getIntPrefWithDefault("authMethod", 3);
+ }
+
+ set authMethod(value) {
+ this._prefs.setIntPref("authMethod", value);
+ }
+
+ get socketType() {
+ return this._getIntPrefWithDefault("try_ssl", 0);
+ }
+
+ set socketType(value) {
+ this._prefs.setIntPref("try_ssl", value);
+ }
+
+ get helloArgument() {
+ return this._getCharPrefWithDefault("hello_argument");
+ }
+
+ get serverURI() {
+ return this._getServerURI(true);
+ }
+
+ /**
+ * If pref max_cached_connection is set to less than 1, allow only one
+ * connection and one message to be sent on that connection. Otherwise, allow
+ * up to max_cached_connection (default to 3) with each connection allowed to
+ * send multiple messages.
+ */
+ get maximumConnectionsNumber() {
+ let maxConnections = this._getIntPrefWithDefault(
+ "max_cached_connections",
+ 3
+ );
+ // Always return a value >= 0.
+ return maxConnections > 0 ? maxConnections : 0;
+ }
+
+ set maximumConnectionsNumber(value) {
+ this._prefs.setIntPref("max_cached_connections", value);
+ }
+
+ get password() {
+ if (this._password) {
+ return this._password;
+ }
+ let incomingAccountKey = this._prefs.getCharPref("incomingAccount", "");
+ let incomingServer;
+ if (incomingAccountKey) {
+ incomingServer =
+ MailServices.accounts.getIncomingServer(incomingAccountKey);
+ } else {
+ let useMatchingHostNameServer = Services.prefs.getBoolPref(
+ "mail.smtp.useMatchingHostNameServer"
+ );
+ let useMatchingDomainServer = Services.prefs.getBoolPref(
+ "mail.smtp.useMatchingDomainServer"
+ );
+ if (useMatchingHostNameServer || useMatchingDomainServer) {
+ if (useMatchingHostNameServer) {
+ // Pass in empty type and port=0, to match imap and pop3.
+ incomingServer = MailServices.accounts.findServer(
+ this.username,
+ this.hostname,
+ "",
+ 0
+ );
+ }
+ if (
+ !incomingServer &&
+ useMatchingDomainServer &&
+ this.hostname.includes(".")
+ ) {
+ let newHostname = this.hostname.slice(0, this.hostname.indexOf("."));
+ for (let server of MailServices.accounts.allServers) {
+ if (server.username == this.username) {
+ let serverHostName = server.hostName;
+ if (
+ serverHostName.includes(".") &&
+ serverHostName.slice(0, serverHostName.indexOf(".")) ==
+ newHostname
+ ) {
+ incomingServer = server;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return incomingServer?.password || "";
+ }
+
+ set password(password) {
+ this._password = password;
+ }
+
+ getPasswordWithUI(promptMessage, promptTitle) {
+ let authPrompt;
+ try {
+ // This prompt has a checkbox for saving password.
+ authPrompt = Cc["@mozilla.org/messenger/msgAuthPrompt;1"].getService(
+ Ci.nsIAuthPrompt
+ );
+ } catch (e) {
+ // Often happens in tests. This prompt has no checkbox for saving password.
+ authPrompt = Services.ww.getNewAuthPrompter(null);
+ }
+ let password = this._getPasswordWithoutUI();
+ if (password) {
+ this.password = password;
+ return this.password;
+ }
+ let outUsername = {};
+ let outPassword = {};
+ let ok;
+ if (this.username) {
+ ok = authPrompt.promptPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outPassword
+ );
+ } else {
+ ok = authPrompt.promptUsernameAndPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outUsername,
+ outPassword
+ );
+ }
+ if (ok) {
+ if (outUsername.value) {
+ this.username = outUsername.value;
+ }
+ this.password = outPassword.value;
+ } else {
+ throw Components.Exception("Password dialog canceled", Cr.NS_ERROR_ABORT);
+ }
+ return this.password;
+ }
+
+ forgetPassword() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ Services.logins.removeLogin(login);
+ }
+ }
+ this.password = "";
+ }
+
+ verifyLogon(urlListener, msgWindow) {
+ return MailServices.smtp.verifyLogon(this, urlListener, msgWindow);
+ }
+
+ clearAllValues() {
+ for (let prefName of this._prefs.getChildList("")) {
+ this._prefs.clearUserPref(prefName);
+ }
+ }
+
+ /**
+ * @returns {string}
+ */
+ _getPasswordWithoutUI() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ return login.password;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get server URI in the form of smtp://[user@]hostname.
+ *
+ * @param {boolean} includeUsername - Whether to include the username.
+ * @returns {string}
+ */
+ _getServerURI(includeUsername) {
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ let hostname = this.hostname.includes(":")
+ ? `[${this.hostname}]`
+ : this.hostname;
+ return (
+ "smtp://" +
+ (includeUsername && this.username
+ ? `${encodeURIComponent(this.username)}@`
+ : "") +
+ hostname
+ );
+ }
+
+ /**
+ * Get the associated pref branch and the default SMTP server branch.
+ */
+ _loadPrefs() {
+ this._prefs = Services.prefs.getBranch(`mail.smtpserver.${this._key}.`);
+ this._defaultPrefs = Services.prefs.getBranch("mail.smtpserver.default.");
+ }
+
+ /**
+ * Set or clear a string preference.
+ *
+ * @param {string} name - The preference name.
+ * @param {string} value - The preference value.
+ */
+ _setCharPref(name, value) {
+ if (value) {
+ this._prefs.setCharPref(name, value);
+ } else {
+ this._prefs.clearUserPref(name);
+ }
+ }
+
+ /**
+ * Get the value of a char preference from this or default SMTP server.
+ *
+ * @param {string} name - The preference name.
+ * @param {number} [defaultValue=""] - The default value to return.
+ * @returns {string}
+ */
+ _getCharPrefWithDefault(name, defaultValue = "") {
+ try {
+ return this._prefs.getCharPref(name);
+ } catch (e) {
+ return this._defaultPrefs.getCharPref(name, defaultValue);
+ }
+ }
+
+ /**
+ * Get the value of an integer preference from this or default SMTP server.
+ *
+ * @param {string} name - The preference name.
+ * @param {number} defaultValue - The default value to return.
+ * @returns {number}
+ */
+ _getIntPrefWithDefault(name, defaultValue) {
+ try {
+ return this._prefs.getIntPref(name);
+ } catch (e) {
+ return this._defaultPrefs.getIntPref(name, defaultValue);
+ }
+ }
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ // @type {SmtpClient[]} - An array of connections can be used.
+ _freeConnections = [];
+ // @type {SmtpClient[]} - An array of connections in use.
+ _busyConnections = [];
+ // @type {Function[]} - An array of Promise.resolve functions.
+ _connectionWaitingQueue = [];
+
+ closeCachedConnections() {
+ // Close all connections.
+ for (let client of [...this._freeConnections, ...this._busyConnections]) {
+ client.quit();
+ }
+ // Cancel all waitings in queue.
+ for (let resolve of this._connectionWaitingQueue) {
+ resolve(false);
+ }
+ this._freeConnections = [];
+ this._busyConnections = [];
+ }
+
+ /**
+ * Get an idle connection that can be used.
+ *
+ * @returns {SmtpClient}
+ */
+ async _getNextClient() {
+ // The newest connection is the least likely to have timed out.
+ let client = this._freeConnections.pop();
+ if (client) {
+ this._busyConnections.push(client);
+ return client;
+ }
+ const maxConns = this.maximumConnectionsNumber
+ ? this.maximumConnectionsNumber
+ : 1;
+ if (
+ this._freeConnections.length + this._busyConnections.length <
+ maxConns
+ ) {
+ // Create a new client if the pool is not full.
+ client = new lazy.SmtpClient(this);
+ this._busyConnections.push(client);
+ return client;
+ }
+ // Wait until a connection is available.
+ await new Promise(resolve => this._connectionWaitingQueue.push(resolve));
+ return this._getNextClient();
+ }
+ /**
+ * Do some actions with a connection.
+ *
+ * @param {Function} handler - A callback function to take a SmtpClient
+ * instance, and do some actions.
+ */
+ async withClient(handler) {
+ let client = await this._getNextClient();
+ client.onFree = () => {
+ this._busyConnections = this._busyConnections.filter(c => c != client);
+ // Per RFC, the minimum total number of recipients that MUST be buffered
+ // is 100 recipients.
+ // @see https://datatracker.ietf.org/doc/html/rfc5321#section-4.5.3.1.8
+ // So use a new connection for the next message to avoid running into
+ // recipient limits.
+ // If user has set SMTP pref max_cached_connection to less than 1,
+ // use a new connection for each message.
+ if (this.maximumConnectionsNumber == 0 || client.rcptCount > 99) {
+ // Send QUIT, server will then terminate the connection
+ client.quit();
+ } else {
+ // Keep using this connection
+ this._freeConnections.push(client);
+ // Resolve the first waiting in queue.
+ this._connectionWaitingQueue.shift()?.();
+ }
+ };
+ handler(client);
+ client.connect();
+ }
+}
diff --git a/comm/mailnews/compose/src/SmtpService.jsm b/comm/mailnews/compose/src/SmtpService.jsm
new file mode 100644
index 0000000000..d90983a213
--- /dev/null
+++ b/comm/mailnews/compose/src/SmtpService.jsm
@@ -0,0 +1,350 @@
+/* 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 = ["SmtpService"];
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ SmtpClient: "resource:///modules/SmtpClient.jsm",
+ MsgUtils: "resource:///modules/MimeMessageUtils.jsm",
+});
+
+/**
+ * The SMTP service.
+ *
+ * @implements {nsISmtpService}
+ */
+class SmtpService {
+ QueryInterface = ChromeUtils.generateQI(["nsISmtpService"]);
+
+ constructor() {
+ this._servers = [];
+ this._logger = lazy.MsgUtils.smtpLogger;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ get defaultServer() {
+ let defaultServerKey = Services.prefs.getCharPref(
+ "mail.smtp.defaultserver",
+ ""
+ );
+ if (defaultServerKey) {
+ // Get it from the prefs.
+ return this.getServerByKey(defaultServerKey);
+ }
+
+ // No pref set, so set the first one as default, and return it.
+ if (this.servers.length > 0) {
+ this.defaultServer = this.servers[0];
+ return this.servers[0];
+ }
+ return null;
+ }
+
+ set defaultServer(server) {
+ Services.prefs.setCharPref("mail.smtp.defaultserver", server.key);
+ }
+
+ get servers() {
+ if (!this._servers.length) {
+ // Load SMTP servers from prefs.
+ this._servers = this._getSmtpServerKeys().map(key =>
+ this._keyToServer(key)
+ );
+ }
+ return this._servers;
+ }
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ async sendMailMessage(
+ messageFile,
+ recipients,
+ userIdentity,
+ sender,
+ password,
+ deliveryListener,
+ statusListener,
+ notificationCallbacks,
+ requestDSN,
+ messageId,
+ outURI,
+ outRequest
+ ) {
+ this._logger.debug(`Sending message ${messageId}`);
+ let server = this.getServerByIdentity(userIdentity);
+ if (!server) {
+ // Occurs for at least one unit test, but test does not fail if return
+ // here. This check for "server" can be removed if tests are fixed.
+ console.log(
+ `No server found for identity with email ${userIdentity.email} and ` +
+ `smtpServerKey ${userIdentity.smtpServerKey}`
+ );
+ return;
+ }
+ if (password) {
+ server.password = password;
+ }
+ let runningUrl = this._getRunningUri(server);
+ await server.wrappedJSObject.withClient(client => {
+ deliveryListener?.OnStartRunningUrl(runningUrl, 0);
+ let fresh = true;
+ client.onidle = () => {
+ // onidle can occur multiple times, but we should only init sending
+ // when sending a new message(fresh is true) or when a new connection
+ // replaces the original connection due to error 4xx response
+ // (client.isRetry is true).
+ if (!fresh && !client.isRetry) {
+ return;
+ }
+ // Init when fresh==true OR re-init sending when client.isRetry==true.
+ fresh = false;
+ let from = sender;
+ let to = MailServices.headerParser
+ .parseEncodedHeaderW(decodeURIComponent(recipients))
+ .map(rec => rec.email);
+
+ if (
+ !Services.prefs.getBoolPref(
+ "mail.smtp.useSenderForSmtpMailFrom",
+ false
+ )
+ ) {
+ from = userIdentity.email;
+ }
+ if (!messageId) {
+ messageId = Cc["@mozilla.org/messengercompose/computils;1"]
+ .createInstance(Ci.nsIMsgCompUtils)
+ .msgGenerateMessageId(userIdentity, null);
+ }
+ client.useEnvelope({
+ from: MailServices.headerParser.parseEncodedHeaderW(
+ decodeURIComponent(from)
+ )[0].email,
+ to,
+ size: messageFile.fileSize,
+ requestDSN,
+ messageId,
+ });
+ };
+ let socketOnDrain;
+ client.onready = async () => {
+ let fstream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ // PR_RDONLY
+ fstream.init(messageFile, 0x01, 0, 0);
+
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sstream.init(fstream);
+
+ let sentSize = 0;
+ let totalSize = messageFile.fileSize;
+ let progressListener = statusListener?.QueryInterface(
+ Ci.nsIWebProgressListener
+ );
+
+ while (sstream.available()) {
+ let chunk = sstream.read(65536);
+ let canSendMore = client.send(chunk);
+ if (!canSendMore) {
+ // Socket buffer is full, wait for the ondrain event.
+ await new Promise(resolve => (socketOnDrain = resolve));
+ }
+ // In practice, chunks are buffered by TCPSocket, progress reaches 100%
+ // almost immediately unless message is larger than chunk size.
+ sentSize += chunk.length;
+ progressListener?.onProgressChange(
+ null,
+ null,
+ sentSize,
+ totalSize,
+ sentSize,
+ totalSize
+ );
+ }
+ sstream.close();
+ fstream.close();
+ client.end();
+
+ // Set progress to indeterminate.
+ progressListener?.onProgressChange(null, null, 0, -1, 0, -1);
+ };
+ client.ondrain = () => {
+ // Socket buffer is empty, safe to continue sending.
+ socketOnDrain();
+ };
+ client.ondone = exitCode => {
+ if (!AppConstants.MOZ_SUITE) {
+ Services.telemetry.scalarAdd("tb.mails.sent", 1);
+ }
+ deliveryListener?.OnStopRunningUrl(runningUrl, exitCode);
+ };
+ client.onerror = (nsError, errorMessage, secInfo) => {
+ runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ if (secInfo) {
+ // TODO(emilio): Passing the failed security info as part of the URI is
+ // quite a smell, but monkey see monkey do...
+ runningUrl.failedSecInfo = secInfo;
+ }
+ runningUrl.errorMessage = errorMessage;
+ deliveryListener?.OnStopRunningUrl(runningUrl, nsError);
+ };
+
+ outRequest.value = {
+ cancel() {
+ client.close(true);
+ },
+ };
+ });
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ verifyLogon(server, urlListener, msgWindow) {
+ let client = new lazy.SmtpClient(server);
+ client.connect();
+ let runningUrl = this._getRunningUri(server);
+ client.onerror = (nsError, errorMessage, secInfo) => {
+ runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ if (secInfo) {
+ runningUrl.failedSecInfo = secInfo;
+ }
+ runningUrl.errorMessage = errorMessage;
+ urlListener.OnStopRunningUrl(runningUrl, nsError);
+ };
+ client.onready = () => {
+ urlListener.OnStopRunningUrl(runningUrl, 0);
+ client.close();
+ };
+ return runningUrl;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ getServerByIdentity(userIdentity) {
+ return userIdentity.smtpServerKey
+ ? this.getServerByKey(userIdentity.smtpServerKey)
+ : this.defaultServer;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ getServerByKey(key) {
+ return this.servers.find(s => s.key == key);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ createServer() {
+ let serverKeys = this._getSmtpServerKeys();
+ let i = 1;
+ let key;
+ do {
+ key = `smtp${i++}`;
+ } while (serverKeys.includes(key));
+
+ serverKeys.push(key);
+ this._saveSmtpServerKeys(serverKeys);
+ this._servers = []; // Reset to force repopulation of this.servers.
+ return this.servers.at(-1);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ deleteServer(server) {
+ let serverKeys = this._getSmtpServerKeys().filter(k => k != server.key);
+ this._servers = this.servers.filter(s => s.key != server.key);
+ this._saveSmtpServerKeys(serverKeys);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ findServer(username, hostname) {
+ username = username?.toLowerCase();
+ hostname = hostname?.toLowerCase();
+ return this.servers.find(server => {
+ if (
+ (username && server.username.toLowerCase() != username) ||
+ (hostname && server.hostname.toLowerCase() != hostname)
+ ) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ /**
+ * Get all SMTP server keys from prefs.
+ *
+ * @returns {string[]}
+ */
+ _getSmtpServerKeys() {
+ return Services.prefs
+ .getCharPref("mail.smtpservers", "")
+ .split(",")
+ .filter(Boolean);
+ }
+
+ /**
+ * Save SMTP server keys to prefs.
+ *
+ * @param {string[]} keys - The key list to save.
+ */
+ _saveSmtpServerKeys(keys) {
+ return Services.prefs.setCharPref("mail.smtpservers", keys.join(","));
+ }
+
+ /**
+ * Create an nsISmtpServer from a key.
+ *
+ * @param {string} key - The key for the SmtpServer.
+ * @returns {nsISmtpServer}
+ */
+ _keyToServer(key) {
+ let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance(
+ Ci.nsISmtpServer
+ );
+ // Setting the server key will set up all of its other properties by
+ // reading them from the prefs.
+ server.key = key;
+ return server;
+ }
+
+ /**
+ * Get the server URI in the form of smtp://user@hostname:port.
+ *
+ * @param {nsISmtpServer} server - The SMTP server.
+ * @returns {nsIURI}
+ */
+ _getRunningUri(server) {
+ let spec = server.serverURI + (server.port ? `:${server.port}` : "");
+ return Services.io.newURI(spec);
+ }
+}
diff --git a/comm/mailnews/compose/src/components.conf b/comm/mailnews/compose/src/components.conf
new file mode 100644
index 0000000000..44754289f5
--- /dev/null
+++ b/comm/mailnews/compose/src/components.conf
@@ -0,0 +1,197 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ "cid": "{588595fe-1ada-11d3-a715-0060b0eb39b5}",
+ "contract_ids": [
+ "@mozilla.org/messengercompose;1",
+ "@mozilla.org/commandlinehandler/general-startup;1?type=compose",
+ ],
+ "type": "nsMsgComposeService",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/compose/src/nsMsgComposeService.h"],
+ "categories": {"command-line-handler": "m-compose"},
+ "name": "Compose",
+ "interfaces": ["nsIMsgComposeService"],
+ },
+ {
+ "cid": "{0b63fb80-bbba-11d4-9daa-91b657eb313c}",
+ "contract_ids": [
+ "@mozilla.org/uriloader/content-handler;1?type=application/x-mailto"
+ ],
+ "type": "nsMsgComposeContentHandler",
+ "headers": ["/comm/mailnews/compose/src/nsMsgComposeContentHandler.h"],
+ },
+ {
+ "cid": "{eb5bdaf8-bbc6-11d2-a6ec-0060b0eb39b5}",
+ "contract_ids": ["@mozilla.org/messengercompose/compose;1"],
+ "type": "nsMsgCompose",
+ "headers": ["/comm/mailnews/compose/src/nsMsgCompose.h"],
+ },
+ {
+ "cid": "{cb998a00-c079-11d4-9daa-8df64bab2efc}",
+ "contract_ids": ["@mozilla.org/messengercompose/composeparams;1"],
+ "type": "nsMsgComposeParams",
+ "headers": ["/comm/mailnews/compose/src/nsMsgComposeParams.h"],
+ },
+ {
+ "cid": "{acc72781-2cea-11d5-9daa-bacdeac1eefc}",
+ "contract_ids": ["@mozilla.org/messengercompose/composesendlistener;1"],
+ "type": "nsMsgComposeSendListener",
+ "headers": ["/comm/mailnews/compose/src/nsMsgCompose.h"],
+ },
+ {
+ "cid": "{1e0e7c01-3e4c-11d5-9daa-f88d288130fc}",
+ "contract_ids": ["@mozilla.org/messengercompose/composeprogressparameters;1"],
+ "type": "nsMsgComposeProgressParams",
+ "headers": ["/comm/mailnews/compose/src/nsMsgComposeProgressParams.h"],
+ },
+ {
+ "cid": "{e64b0f51-0d7b-4e2f-8c60-3862ee8c174f}",
+ "contract_ids": ["@mozilla.org/messengercompose/composefields;1"],
+ "type": "nsMsgCompFields",
+ "headers": ["/comm/mailnews/compose/src/nsMsgCompFields.h"],
+ },
+ {
+ "cid": "{27b8d045-8d9f-4fa8-bfb6-8a0f8d09ce89}",
+ "contract_ids": ["@mozilla.org/messengercompose/attachment;1"],
+ "type": "nsMsgAttachment",
+ "headers": ["/comm/mailnews/compose/src/nsMsgAttachment.h"],
+ },
+ {
+ "cid": "{9e16958d-d9e9-4cae-b723-a5bccf104998}",
+ "contract_ids": ["@mozilla.org/messengercompose/attachmentdata;1"],
+ "type": "nsMsgAttachmentData",
+ "headers": ["/comm/mailnews/compose/src/nsMsgAttachmentData.h"],
+ },
+ {
+ "cid": "{ef173501-4e14-42b9-ae1f-7770de235c29}",
+ "contract_ids": ["@mozilla.org/messengercompose/attachedfile;1"],
+ "type": "nsMsgAttachedFile",
+ "headers": ["/comm/mailnews/compose/src/nsMsgAttachedFile.h"],
+ },
+ {
+ "cid": "{e15c83f1-1cf4-11d3-8ef0-00a024a7d144}",
+ "contract_ids": ["@mozilla.org/messengercompose/sendlater;1"],
+ "type": "nsMsgSendLater",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/compose/src/nsMsgSendLater.h"],
+ },
+ {
+ "cid": "{be59dbf0-2812-11d3-80a3-006008128c4e}",
+ "contract_ids": ["@mozilla.org/messengercompose/smtpurl;1"],
+ "type": "nsSmtpUrl",
+ "headers": ["/comm/mailnews/compose/src/nsSmtpUrl.h"],
+ },
+ {
+ "cid": "{05bab5e7-9c7d-11d3-98a3-001083010e9b}",
+ "contract_ids": ["@mozilla.org/messengercompose/mailtourl;1"],
+ "type": "nsMailtoUrl",
+ "headers": ["/comm/mailnews/compose/src/nsSmtpUrl.h"],
+ },
+ {
+ "cid": "{1c7abf0c-21e5-11d3-8ef1-00a024a7d144}",
+ "contract_ids": ["@mozilla.org/messengercompose/quoting;1"],
+ "type": "nsMsgQuote",
+ "headers": ["/comm/mailnews/compose/src/nsMsgQuote.h"],
+ },
+ {
+ "cid": "{683728ac-88df-11d3-989d-001083010e9b}",
+ "contract_ids": ["@mozilla.org/messengercompose/quotinglistener;1"],
+ "type": "nsMsgQuoteListener",
+ "headers": ["/comm/mailnews/compose/src/nsMsgQuote.h"],
+ },
+ {
+ "cid": "{ceb0dca2-5e7d-4204-94d4-2ab925921fae}",
+ "contract_ids": ["@mozilla.org/messengercompose/computils;1"],
+ "type": "nsMsgCompUtils",
+ "headers": ["/comm/mailnews/compose/src/nsMsgCompUtils.h"],
+ },
+ {
+ "cid": "{0874c3b5-317d-11d3-8efb-00a024a7d144}",
+ "contract_ids": ["@mozilla.org/messengercompose/msgcopy;1"],
+ "type": "nsMsgCopy",
+ "headers": ["/comm/mailnews/compose/src/nsMsgCopy.h"],
+ },
+ {
+ "cid": "{e5872045-a87b-4ea0-b366-45ebd7dc89d9}",
+ "contract_ids": ["@mozilla.org/messengercompose/sendreport;1"],
+ "type": "nsMsgSendReport",
+ "headers": ["/comm/mailnews/compose/src/nsMsgSendReport.h"],
+ },
+ {
+ "cid": "{028b9c1e-8d0a-4518-80c2-842e07846eaa}",
+ "contract_ids": ["@mozilla.org/messengercompose/send;1"],
+ "jsm": "resource:///modules/MessageSend.jsm",
+ "constructor": "MessageSend",
+ },
+ {
+ "cid": "{b14c2b67-8680-4c11-8d63-9403c7d4f757}",
+ "contract_ids": ["@mozilla.org/network/protocol;1?name=smtp"],
+ "jsm": "resource:///modules/SMTPProtocolHandler.jsm",
+ "constructor": "SMTPProtocolHandler",
+ "protocol_config": {
+ "scheme": "smtp",
+ "flags": [
+ "URI_NORELATIVE",
+ "URI_DANGEROUS_TO_LOAD",
+ "ALLOWS_PROXY",
+ "URI_NON_PERSISTABLE",
+ "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT",
+ ],
+ "default_port": 25,
+ },
+ },
+ {
+ "cid": "{057d0997-9e3a-411e-b4ee-2602f53fe05f}",
+ "contract_ids": ["@mozilla.org/network/protocol;1?name=smtps"],
+ "jsm": "resource:///modules/SMTPProtocolHandler.jsm",
+ "constructor": "SMTPSProtocolHandler",
+ "protocol_config": {
+ "scheme": "smtps",
+ "flags": [
+ "URI_NORELATIVE",
+ "URI_DANGEROUS_TO_LOAD",
+ "ALLOWS_PROXY",
+ "URI_NON_PERSISTABLE",
+ "URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT",
+ ],
+ "default_port": 465,
+ },
+ },
+ {
+ "cid": "{af314bd9-0b28-4f69-9bea-592ab4dc6811}",
+ "contract_ids": ["@mozilla.org/network/protocol;1?name=mailto"],
+ "jsm": "resource:///modules/MailtoProtocolHandler.jsm",
+ "constructor": "MailtoProtocolHandler",
+ "protocol_config": {
+ "scheme": "mailto",
+ "flags": [
+ "URI_NORELATIVE",
+ "ALLOWS_PROXY",
+ "URI_LOADABLE_BY_ANYONE",
+ "URI_NON_PERSISTABLE",
+ "URI_DOES_NOT_RETURN_DATA",
+ "URI_FORBIDS_COOKIE_ACCESS",
+ ],
+ },
+ },
+ {
+ "cid": "{acda6039-8b17-46c1-a8ed-ad50aa80f412}",
+ "contract_ids": ["@mozilla.org/messengercompose/smtp;1"],
+ "jsm": "resource:///modules/SmtpService.jsm",
+ "constructor": "SmtpService",
+ "name": "Smtp",
+ "interfaces": ["nsISmtpService"],
+ },
+ {
+ "cid": "{3a75f5ea-651e-4696-9813-848c03da8bbd}",
+ "contract_ids": ["@mozilla.org/messenger/smtp/server;1"],
+ "jsm": "resource:///modules/SmtpServer.jsm",
+ "constructor": "SmtpServer",
+ },
+]
diff --git a/comm/mailnews/compose/src/moz.build b/comm/mailnews/compose/src/moz.build
new file mode 100644
index 0000000000..60ff148540
--- /dev/null
+++ b/comm/mailnews/compose/src/moz.build
@@ -0,0 +1,60 @@
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+ "nsComposeStrings.h",
+ "nsMsgAttachmentData.h",
+ "nsMsgCompFields.h",
+ "nsMsgCompose.h",
+]
+
+SOURCES += [
+ "nsComposeStrings.cpp",
+ "nsMsgAttachedFile.cpp",
+ "nsMsgAttachment.cpp",
+ "nsMsgAttachmentData.cpp",
+ "nsMsgCompFields.cpp",
+ "nsMsgCompose.cpp",
+ "nsMsgComposeContentHandler.cpp",
+ "nsMsgComposeParams.cpp",
+ "nsMsgComposeProgressParams.cpp",
+ "nsMsgComposeService.cpp",
+ "nsMsgCompUtils.cpp",
+ "nsMsgCopy.cpp",
+ "nsMsgPrompts.cpp",
+ "nsMsgQuote.cpp",
+ "nsMsgSendLater.cpp",
+ "nsMsgSendReport.cpp",
+ "nsSmtpUrl.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "mail"
+
+# clang-cl rightly complains about switch on nsresult.
+if CONFIG["CC_TYPE"] == "clang-cl":
+ CXXFLAGS += ["-Wno-switch"]
+
+EXTRA_JS_MODULES += [
+ "MailtoProtocolHandler.jsm",
+ "MessageSend.jsm",
+ "MimeEncoder.jsm",
+ "MimeMessage.jsm",
+ "MimeMessageUtils.jsm",
+ "MimePart.jsm",
+ "SmtpClient.jsm",
+ "SMTPProtocolHandler.jsm",
+ "SmtpServer.jsm",
+ "SmtpService.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mailnews/compose/src/nsComposeStrings.cpp b/comm/mailnews/compose/src/nsComposeStrings.cpp
new file mode 100644
index 0000000000..666cd14e07
--- /dev/null
+++ b/comm/mailnews/compose/src/nsComposeStrings.cpp
@@ -0,0 +1,106 @@
+/* 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/. */
+
+#include "nsComposeStrings.h"
+
+const char* errorStringNameForErrorCode(nsresult aCode) {
+#ifdef __GNUC__
+// Temporary workaround until bug 783526 is fixed.
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch (aCode) {
+ case NS_MSG_UNABLE_TO_OPEN_FILE:
+ return "unableToOpenFile";
+ case NS_MSG_UNABLE_TO_OPEN_TMP_FILE:
+ return "unableToOpenTmpFile";
+ case NS_MSG_UNABLE_TO_SAVE_TEMPLATE:
+ return "unableToSaveTemplate";
+ case NS_MSG_UNABLE_TO_SAVE_DRAFT:
+ return "unableToSaveDraft";
+ case NS_MSG_COULDNT_OPEN_FCC_FOLDER:
+ return "couldntOpenFccFolder";
+ case NS_MSG_NO_SENDER:
+ return "noSender";
+ case NS_MSG_NO_RECIPIENTS:
+ return "noRecipients";
+ case NS_MSG_ERROR_WRITING_FILE:
+ return "errorWritingFile";
+ case NS_ERROR_SENDING_FROM_COMMAND:
+ return "errorSendingFromCommand";
+ case NS_ERROR_SENDING_DATA_COMMAND:
+ return "errorSendingDataCommand";
+ case NS_ERROR_SENDING_MESSAGE:
+ return "errorSendingMessage";
+ case NS_ERROR_POST_FAILED:
+ return "postFailed";
+ case NS_ERROR_SMTP_SERVER_ERROR:
+ return "smtpServerError";
+ case NS_MSG_UNABLE_TO_SEND_LATER:
+ return "unableToSendLater";
+ case NS_ERROR_COMMUNICATIONS_ERROR:
+ return "communicationsError";
+ case NS_ERROR_BUT_DONT_SHOW_ALERT:
+ return "dontShowAlert";
+ case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS:
+ return "couldNotGetUsersMailAddress2";
+ case NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY:
+ return "couldNotGetSendersIdentity";
+ case NS_ERROR_MIME_MPART_ATTACHMENT_ERROR:
+ return "mimeMpartAttachmentError";
+ case NS_ERROR_NNTP_NO_CROSS_POSTING:
+ return "nntpNoCrossPosting";
+ case NS_MSG_ERROR_READING_FILE:
+ return "errorReadingFile";
+ case NS_MSG_ERROR_ATTACHING_FILE:
+ return "errorAttachingFile";
+ case NS_ERROR_SMTP_GREETING:
+ return "incorrectSmtpGreeting";
+ case NS_ERROR_SENDING_RCPT_COMMAND:
+ return "errorSendingRcptCommand";
+ case NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS:
+ return "startTlsFailed";
+ case NS_ERROR_SMTP_PASSWORD_UNDEFINED:
+ return "smtpPasswordUndefined";
+ case NS_ERROR_SMTP_SEND_NOT_ALLOWED:
+ return "smtpSendNotAllowed";
+ case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED:
+ return "smtpTooManyRecipients";
+ case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2:
+ return "smtpPermSizeExceeded2";
+ case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER:
+ return "smtpSendFailedUnknownServer";
+ case NS_ERROR_SMTP_SEND_FAILED_REFUSED:
+ return "smtpSendRequestRefused";
+ case NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED:
+ return "smtpSendInterrupted";
+ case NS_ERROR_SMTP_SEND_FAILED_TIMEOUT:
+ return "smtpSendTimeout";
+ case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON:
+ return "smtpSendFailedUnknownReason";
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL:
+ return "smtpHintAuthEncryptToPlainNoSsl";
+ case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL:
+ return "smtpHintAuthEncryptToPlainSsl";
+ case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT:
+ return "smtpHintAuthPlainToEncrypt";
+ case NS_ERROR_SMTP_AUTH_FAILURE:
+ return "smtpAuthFailure";
+ case NS_ERROR_SMTP_AUTH_GSSAPI:
+ return "smtpAuthGssapi";
+ case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED:
+ return "smtpAuthMechNotSupported";
+ case NS_ERROR_ILLEGAL_LOCALPART:
+ return "errorIllegalLocalPart2";
+ case NS_ERROR_CLIENTID:
+ return "smtpClientid";
+ case NS_ERROR_CLIENTID_PERMISSION:
+ return "smtpClientidPermission";
+ default:
+ return "sendFailed";
+ }
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+}
diff --git a/comm/mailnews/compose/src/nsComposeStrings.h b/comm/mailnews/compose/src/nsComposeStrings.h
new file mode 100644
index 0000000000..3d6a24516f
--- /dev/null
+++ b/comm/mailnews/compose/src/nsComposeStrings.h
@@ -0,0 +1,76 @@
+/* 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/. */
+
+// clang-format off
+/**
+ String Ids used by mailnews\compose
+ To Do: Convert the callers to use names instead of ids and then make this file obsolete.
+ */
+
+#ifndef _nsComposeStrings_H__
+#define _nsComposeStrings_H__
+
+#include "msgCore.h"
+
+#define NS_MSG_UNABLE_TO_OPEN_FILE NS_MSG_GENERATE_FAILURE(12500)
+#define NS_MSG_UNABLE_TO_OPEN_TMP_FILE NS_MSG_GENERATE_FAILURE(12501)
+#define NS_MSG_UNABLE_TO_SAVE_TEMPLATE NS_MSG_GENERATE_FAILURE(12502)
+#define NS_MSG_UNABLE_TO_SAVE_DRAFT NS_MSG_GENERATE_FAILURE(12503)
+#define NS_MSG_COULDNT_OPEN_FCC_FOLDER NS_MSG_GENERATE_FAILURE(12506)
+#define NS_MSG_NO_SENDER NS_MSG_GENERATE_FAILURE(12510)
+#define NS_MSG_NO_RECIPIENTS NS_MSG_GENERATE_FAILURE(12511)
+#define NS_MSG_ERROR_WRITING_FILE NS_MSG_GENERATE_FAILURE(12512)
+#define NS_ERROR_SENDING_FROM_COMMAND NS_MSG_GENERATE_FAILURE(12514)
+#define NS_ERROR_SENDING_DATA_COMMAND NS_MSG_GENERATE_FAILURE(12516)
+#define NS_ERROR_SENDING_MESSAGE NS_MSG_GENERATE_FAILURE(12517)
+#define NS_ERROR_POST_FAILED NS_MSG_GENERATE_FAILURE(12518)
+#define NS_ERROR_SMTP_SERVER_ERROR NS_MSG_GENERATE_FAILURE(12524)
+#define NS_MSG_UNABLE_TO_SEND_LATER NS_MSG_GENERATE_FAILURE(12525)
+#define NS_ERROR_COMMUNICATIONS_ERROR NS_MSG_GENERATE_FAILURE(12526)
+#define NS_ERROR_BUT_DONT_SHOW_ALERT NS_MSG_GENERATE_FAILURE(12527)
+#define NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS NS_MSG_GENERATE_FAILURE(12529)
+#define NS_ERROR_COULD_NOT_GET_SENDERS_IDENTITY NS_MSG_GENERATE_FAILURE(12530)
+#define NS_ERROR_MIME_MPART_ATTACHMENT_ERROR NS_MSG_GENERATE_FAILURE(12531)
+
+/* 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one */
+
+// For message sending report
+#define NS_MSG_ERROR_READING_FILE NS_MSG_GENERATE_FAILURE(12563)
+
+#define NS_MSG_ERROR_ATTACHING_FILE NS_MSG_GENERATE_FAILURE(12570)
+
+#define NS_ERROR_SMTP_GREETING NS_MSG_GENERATE_FAILURE(12572)
+
+#define NS_ERROR_SENDING_RCPT_COMMAND NS_MSG_GENERATE_FAILURE(12575)
+
+#define NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS NS_MSG_GENERATE_FAILURE(12582)
+
+#define NS_ERROR_SMTP_PASSWORD_UNDEFINED NS_MSG_GENERATE_FAILURE(12584)
+#define NS_ERROR_SMTP_SEND_NOT_ALLOWED NS_MSG_GENERATE_FAILURE(12585)
+#define NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED NS_MSG_GENERATE_FAILURE(12586)
+#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 NS_MSG_GENERATE_FAILURE(12588)
+
+#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER NS_MSG_GENERATE_FAILURE(12589)
+#define NS_ERROR_SMTP_SEND_FAILED_REFUSED NS_MSG_GENERATE_FAILURE(12590)
+#define NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED NS_MSG_GENERATE_FAILURE(12591)
+#define NS_ERROR_SMTP_SEND_FAILED_TIMEOUT NS_MSG_GENERATE_FAILURE(12592)
+#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON NS_MSG_GENERATE_FAILURE(12593)
+
+#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL NS_MSG_GENERATE_FAILURE(12594)
+#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL NS_MSG_GENERATE_FAILURE(12595)
+#define NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT NS_MSG_GENERATE_FAILURE(12596)
+#define NS_ERROR_SMTP_AUTH_FAILURE NS_MSG_GENERATE_FAILURE(12597)
+#define NS_ERROR_SMTP_AUTH_GSSAPI NS_MSG_GENERATE_FAILURE(12598)
+#define NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12599)
+
+#define NS_ERROR_ILLEGAL_LOCALPART NS_MSG_GENERATE_FAILURE(12601)
+
+#define NS_ERROR_CLIENTID NS_MSG_GENERATE_FAILURE(12610)
+#define NS_ERROR_CLIENTID_PERMISSION NS_MSG_GENERATE_FAILURE(12611)
+
+const char* errorStringNameForErrorCode(nsresult aCode);
+
+#endif /* _nsComposeStrings_H__ */
+
+// clang-format on
diff --git a/comm/mailnews/compose/src/nsMsgAttachedFile.cpp b/comm/mailnews/compose/src/nsMsgAttachedFile.cpp
new file mode 100644
index 0000000000..cb293ee964
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachedFile.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMsgAttachedFile.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAttachedFile, nsIMsgAttachedFile)
+
+nsMsgAttachedFile::nsMsgAttachedFile()
+ : m_size(0),
+ m_unprintableCount(0),
+ m_highbitCount(0),
+ m_ctlCount(0),
+ m_nullCount(0),
+ m_maxLineLength(0) {}
+
+nsMsgAttachedFile::~nsMsgAttachedFile() {}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetOrigUrl(nsIURI** aOrigUrl) {
+ NS_ENSURE_ARG_POINTER(aOrigUrl);
+ NS_IF_ADDREF(*aOrigUrl = m_origUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetOrigUrl(nsIURI* aOrigUrl) {
+ m_origUrl = aOrigUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetTmpFile(nsIFile** aTmpFile) {
+ NS_ENSURE_ARG_POINTER(aTmpFile);
+ NS_IF_ADDREF(*aTmpFile = m_tmpFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetTmpFile(nsIFile* aTmpFile) {
+ m_tmpFile = aTmpFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetType(nsACString& aType) {
+ aType = m_type;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetType(const nsACString& aType) {
+ m_type = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetEncoding(nsACString& aEncoding) {
+ aEncoding = m_encoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetEncoding(const nsACString& aEncoding) {
+ m_encoding = aEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetDescription(nsACString& aDescription) {
+ aDescription = m_description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetDescription(
+ const nsACString& aDescription) {
+ m_description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetCloudPartInfo(nsACString& aCloudPartInfo) {
+ aCloudPartInfo = m_cloudPartInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetCloudPartInfo(
+ const nsACString& aCloudPartInfo) {
+ m_cloudPartInfo = aCloudPartInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetXMacType(nsACString& aXMacType) {
+ aXMacType = m_xMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetXMacType(const nsACString& aXMacType) {
+ m_xMacType = aXMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetXMacCreator(nsACString& aXMacCreator) {
+ aXMacCreator = m_xMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetXMacCreator(
+ const nsACString& aXMacCreator) {
+ m_xMacCreator = aXMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetRealName(nsACString& aRealName) {
+ aRealName = m_realName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetRealName(const nsACString& aRealName) {
+ m_realName = aRealName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetSize(uint32_t* aSize) {
+ NS_ENSURE_ARG_POINTER(aSize);
+ *aSize = m_size;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetSize(uint32_t aSize) {
+ m_size = aSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetUnprintableCount(
+ uint32_t* aUnprintableCount) {
+ NS_ENSURE_ARG_POINTER(aUnprintableCount);
+ *aUnprintableCount = m_unprintableCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetUnprintableCount(
+ uint32_t aUnprintableCount) {
+ m_unprintableCount = aUnprintableCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetHighbitCount(uint32_t* aHighbitCount) {
+ NS_ENSURE_ARG_POINTER(aHighbitCount);
+ *aHighbitCount = m_highbitCount;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachedFile::SetHighbitCount(uint32_t aHighbitCount) {
+ m_highbitCount = aHighbitCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetCtlCount(uint32_t* aCtlCount) {
+ NS_ENSURE_ARG_POINTER(aCtlCount);
+ *aCtlCount = m_ctlCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetCtlCount(uint32_t aCtlCount) {
+ m_ctlCount = aCtlCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetNullCount(uint32_t* aNullCount) {
+ NS_ENSURE_ARG_POINTER(aNullCount);
+ *aNullCount = m_nullCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetNullCount(uint32_t aNullCount) {
+ m_nullCount = aNullCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::GetMaxLineLength(uint32_t* aMaxLineLength) {
+ NS_ENSURE_ARG_POINTER(aMaxLineLength);
+ *aMaxLineLength = m_maxLineLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachedFile::SetMaxLineLength(uint32_t aMaxLineLength) {
+ m_maxLineLength = aMaxLineLength;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgAttachedFile.h b/comm/mailnews/compose/src/nsMsgAttachedFile.h
new file mode 100644
index 0000000000..5f68731927
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachedFile.h
@@ -0,0 +1,13 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgAttachedFile_H_
+#define _nsMsgAttachedFile_H_
+
+#include "nsCOMPtr.h"
+#include "nsIMsgSend.h"
+#include "nsMsgAttachmentData.h"
+
+#endif /* _nsMsgAttachedFile_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgAttachment.cpp b/comm/mailnews/compose/src/nsMsgAttachment.cpp
new file mode 100644
index 0000000000..55edd348ca
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachment.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgAttachment.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsMsgCompUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAttachment, nsIMsgAttachment)
+
+nsMsgAttachment::nsMsgAttachment() {
+ mTemporary = false;
+ mSendViaCloud = false;
+ mSize = -1;
+}
+
+nsMsgAttachment::~nsMsgAttachment() {
+ MOZ_LOG(Compose, mozilla::LogLevel::Debug, ("~nsMsgAttachment()"));
+}
+
+/* attribute wstring name; */
+NS_IMETHODIMP nsMsgAttachment::GetName(nsAString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetName(const nsAString& aName) {
+ mName = aName;
+ return NS_OK;
+}
+
+/* attribute string url; */
+NS_IMETHODIMP nsMsgAttachment::GetUrl(nsACString& aUrl) {
+ aUrl = mUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetUrl(const nsACString& aUrl) {
+ mUrl = aUrl;
+ return NS_OK;
+}
+
+/* attribute string msgUri; */
+NS_IMETHODIMP nsMsgAttachment::GetMsgUri(nsACString& aMsgUri) {
+ aMsgUri = mMsgUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetMsgUri(const nsACString& aMsgUri) {
+ mMsgUri = aMsgUri;
+ return NS_OK;
+}
+
+/* attribute string urlCharset; */
+NS_IMETHODIMP nsMsgAttachment::GetUrlCharset(nsACString& aUrlCharset) {
+ aUrlCharset = mUrlCharset;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetUrlCharset(const nsACString& aUrlCharset) {
+ mUrlCharset = aUrlCharset;
+ return NS_OK;
+}
+
+/* attribute boolean temporary; */
+NS_IMETHODIMP nsMsgAttachment::GetTemporary(bool* aTemporary) {
+ NS_ENSURE_ARG_POINTER(aTemporary);
+
+ *aTemporary = mTemporary;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetTemporary(bool aTemporary) {
+ mTemporary = aTemporary;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetSendViaCloud(bool* aSendViaCloud) {
+ NS_ENSURE_ARG_POINTER(aSendViaCloud);
+
+ *aSendViaCloud = mSendViaCloud;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetSendViaCloud(bool aSendViaCloud) {
+ mSendViaCloud = aSendViaCloud;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetHtmlAnnotation(
+ const nsACString& aAnnotation) {
+ mHtmlAnnotation = aAnnotation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetHtmlAnnotation(nsACString& aAnnotation) {
+ aAnnotation = mHtmlAnnotation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAttachment::SetCloudFileAccountKey(
+ const nsACString& aCloudFileAccountKey) {
+ mCloudFileAccountKey = aCloudFileAccountKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAttachment::GetCloudFileAccountKey(nsACString& aCloudFileAccountKey) {
+ aCloudFileAccountKey = mCloudFileAccountKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetCloudPartHeaderData(
+ nsACString& aCloudPartHeaderData) {
+ aCloudPartHeaderData = mCloudPartHeaderData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetCloudPartHeaderData(
+ const nsACString& aCloudPartHeaderData) {
+ mCloudPartHeaderData = aCloudPartHeaderData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentLocation(
+ nsACString& aContentLocation) {
+ aContentLocation = mContentLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentLocation(
+ const nsACString& aContentLocation) {
+ mContentLocation = aContentLocation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentType(char** aContentType) {
+ NS_ENSURE_ARG_POINTER(aContentType);
+
+ *aContentType = ToNewCString(mContentType);
+ return (*aContentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentType(const char* aContentType) {
+ mContentType = aContentType;
+ // a full content type could also contains parameters but we need to
+ // keep only the content type alone. Therefore we need to cleanup it.
+ int32_t offset = mContentType.FindChar(';');
+ if (offset >= 0) mContentType.SetLength(offset);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentTypeParam(char** aContentTypeParam) {
+ NS_ENSURE_ARG_POINTER(aContentTypeParam);
+
+ *aContentTypeParam = ToNewCString(mContentTypeParam);
+ return (*aContentTypeParam ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentTypeParam(
+ const char* aContentTypeParam) {
+ if (aContentTypeParam)
+ while (*aContentTypeParam == ';' || *aContentTypeParam == ' ')
+ aContentTypeParam++;
+ mContentTypeParam = aContentTypeParam;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::GetContentId(nsACString& aContentId) {
+ aContentId = mContentId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachment::SetContentId(const nsACString& aContentId) {
+ mContentId = aContentId;
+ return NS_OK;
+}
+
+/* attribute string charset; */
+NS_IMETHODIMP nsMsgAttachment::GetCharset(char** aCharset) {
+ NS_ENSURE_ARG_POINTER(aCharset);
+
+ *aCharset = ToNewCString(mCharset);
+ return (*aCharset ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetCharset(const char* aCharset) {
+ mCharset = aCharset;
+ return NS_OK;
+}
+
+/* attribute string macType; */
+NS_IMETHODIMP nsMsgAttachment::GetMacType(char** aMacType) {
+ NS_ENSURE_ARG_POINTER(aMacType);
+
+ *aMacType = ToNewCString(mMacType);
+ return (*aMacType ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetMacType(const char* aMacType) {
+ mMacType = aMacType;
+ return NS_OK;
+}
+
+/* attribute string macCreator; */
+NS_IMETHODIMP nsMsgAttachment::GetMacCreator(char** aMacCreator) {
+ NS_ENSURE_ARG_POINTER(aMacCreator);
+
+ *aMacCreator = ToNewCString(mMacCreator);
+ return (*aMacCreator ? NS_OK : NS_ERROR_OUT_OF_MEMORY);
+}
+NS_IMETHODIMP nsMsgAttachment::SetMacCreator(const char* aMacCreator) {
+ mMacCreator = aMacCreator;
+ return NS_OK;
+}
+
+/* attribute int64_t size; */
+NS_IMETHODIMP nsMsgAttachment::GetSize(int64_t* aSize) {
+ NS_ENSURE_ARG_POINTER(aSize);
+
+ *aSize = mSize;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgAttachment::SetSize(int64_t aSize) {
+ mSize = aSize;
+ return NS_OK;
+}
+
+/* boolean equalsUrl (in nsIMsgAttachment attachment); */
+NS_IMETHODIMP nsMsgAttachment::EqualsUrl(nsIMsgAttachment* attachment,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(attachment);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString url;
+ attachment->GetUrl(url);
+
+ *_retval = mUrl.Equals(url);
+ return NS_OK;
+}
+
+nsresult nsMsgAttachment::DeleteAttachment() {
+ nsresult rv;
+ bool isAFile = false;
+
+ nsCOMPtr<nsIFile> urlFile;
+ rv = NS_GetFileFromURLSpec(mUrl, getter_AddRefs(urlFile));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't nsIFile from URL string");
+ if (NS_SUCCEEDED(rv)) {
+ bool bExists = false;
+ rv = urlFile->Exists(&bExists);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Exists() call failed!");
+ if (NS_SUCCEEDED(rv) && bExists) {
+ rv = urlFile->IsFile(&isAFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "IsFile() call failed!");
+ }
+ }
+
+ // remove it if it's a valid file
+ if (isAFile) rv = urlFile->Remove(false);
+
+ return rv;
+}
diff --git a/comm/mailnews/compose/src/nsMsgAttachment.h b/comm/mailnews/compose/src/nsMsgAttachment.h
new file mode 100644
index 0000000000..d9e0de696e
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachment.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgAttachment_H_
+#define _nsMsgAttachment_H_
+
+#include "nsIMsgAttachment.h"
+#include "nsString.h"
+
+class nsMsgAttachment : public nsIMsgAttachment {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGATTACHMENT
+
+ nsMsgAttachment();
+
+ private:
+ virtual ~nsMsgAttachment();
+ nsresult DeleteAttachment();
+
+ nsString mName;
+ nsCString mUrl;
+ nsCString mMsgUri;
+ nsCString mUrlCharset;
+ bool mTemporary;
+ bool mSendViaCloud;
+ nsCString mCloudFileAccountKey;
+ nsCString mCloudPartHeaderData;
+ nsCString mContentLocation;
+ nsCString mContentType;
+ nsCString mContentTypeParam;
+ nsCString mContentId;
+ nsCString mCharset;
+ nsCString mMacType;
+ nsCString mMacCreator;
+ nsCString mHtmlAnnotation;
+ int64_t mSize;
+};
+
+#endif /* _nsMsgAttachment_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgAttachmentData.cpp b/comm/mailnews/compose/src/nsMsgAttachmentData.cpp
new file mode 100644
index 0000000000..c32b31d0c1
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachmentData.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMsgAttachmentData.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAttachmentData, nsIMsgAttachmentData)
+
+nsMsgAttachmentData::nsMsgAttachmentData()
+ : m_size(0),
+ m_sizeExternalStr("-1"),
+ m_isExternalAttachment(false),
+ m_isExternalLinkAttachment(false),
+ m_isDownloaded(false),
+ m_hasFilename(false),
+ m_displayableInline(false) {}
+
+nsMsgAttachmentData::~nsMsgAttachmentData() {}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetUrl(nsIURI** aUrl) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_IF_ADDREF(*aUrl = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetUrl(nsIURI* aUrl) {
+ m_url = aUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetDesiredType(nsACString& aDesiredType) {
+ aDesiredType = m_desiredType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetDesiredType(
+ const nsACString& aDesiredType) {
+ m_desiredType = aDesiredType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealType(nsACString& aRealType) {
+ aRealType = m_realType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealType(const nsACString& aRealType) {
+ m_realType = aRealType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealEncoding(nsACString& aRealEncoding) {
+ aRealEncoding = m_realEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealEncoding(
+ const nsACString& aRealEncoding) {
+ m_realEncoding = aRealEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetRealName(nsACString& aRealName) {
+ aRealName = m_realName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetRealName(const nsACString& aRealName) {
+ m_realName = aRealName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetDescription(nsACString& aDescription) {
+ aDescription = m_description;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetDescription(
+ const nsACString& aDescription) {
+ m_description = aDescription;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetXMacType(nsACString& aXMacType) {
+ aXMacType = m_xMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetXMacType(const nsACString& aXMacType) {
+ m_xMacType = aXMacType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::GetXMacCreator(nsACString& aXMacCreator) {
+ aXMacCreator = m_xMacCreator;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAttachmentData::SetXMacCreator(
+ const nsACString& aXMacCreator) {
+ m_xMacCreator = aXMacCreator;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgAttachmentData.h b/comm/mailnews/compose/src/nsMsgAttachmentData.h
new file mode 100644
index 0000000000..763a4377e9
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgAttachmentData.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef __MSGATTACHMENTDATA_H__
+#define __MSGATTACHMENTDATA_H__
+
+#include "nsIURL.h"
+#include "nsString.h"
+#include "nsIMsgSend.h"
+
+// Attachment file/URL structures - we're letting libmime use this directly
+class nsMsgAttachmentData final : public nsIMsgAttachmentData {
+ public:
+ NS_DECL_NSIMSGATTACHMENTDATA
+ NS_DECL_ISUPPORTS
+
+ nsMsgAttachmentData();
+ virtual ~nsMsgAttachmentData();
+
+ nsCOMPtr<nsIURI> m_url; // The URL to attach.
+
+ nsCString m_desiredType; // The type to which this document should be
+ // converted. Legal values are NULL, TEXT_PLAIN
+ // and APPLICATION_POSTSCRIPT (which are macros
+ // defined in net.h); other values are ignored.
+
+ nsCString
+ m_realType; // The type of the URL if known, otherwise NULL. For example,
+ // if you were attaching a temp file which was known to
+ // contain HTML data, you would pass in TEXT_HTML as the
+ // real_type, to override whatever type the name of the tmp
+ // file might otherwise indicate.
+
+ nsCString m_realEncoding; // Goes along with real_type
+
+ nsCString
+ m_realName; // The original name of this document, which will eventually
+ // show up in the Content-Disposition header. For example, if
+ // you had copied a document to a tmp file, this would be the
+ // original, human-readable name of the document.
+
+ nsCString m_description; // If you put a string here, it will show up as the
+ // Content-Description header. This can be any
+ // explanatory text; it's not a file name.
+
+ nsCString m_disposition; // The Content-Disposition header (if any). a
+ // nsMsgAttachmentData can very well have
+ // Content-Disposition: inline value, instead of
+ // "attachment".
+ nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any
+
+ // Mac-specific data that should show up as optional parameters
+ // to the content-type header.
+ nsCString m_xMacType;
+ nsCString m_xMacCreator;
+
+ int32_t m_size; // The size of the attachment. May be 0.
+ nsCString
+ m_sizeExternalStr; // The reported size of an external attachment.
+ // Originally set at "-1" to mean an unknown value.
+ bool m_isExternalAttachment; // Flag for determining if the attachment is
+ // external
+ bool m_isExternalLinkAttachment; // Flag for determining if the attachment is
+ // external and an http link.
+ bool m_isDownloaded; // Flag for determining if the attachment has already
+ // been downloaded
+ bool m_hasFilename; // Tells whether the name is provided by us or if it's a
+ // Part 1.2-like attachment
+ bool m_displayableInline; // Tells whether the attachment could be displayed
+ // inline
+};
+
+class nsMsgAttachedFile final : public nsIMsgAttachedFile {
+ public:
+ NS_DECL_NSIMSGATTACHEDFILE
+ NS_DECL_ISUPPORTS
+
+ nsMsgAttachedFile();
+ virtual ~nsMsgAttachedFile();
+
+ nsCOMPtr<nsIURI> m_origUrl; // Where it came from on the network (or even
+ // elsewhere on the local disk.)
+
+ nsCOMPtr<nsIFile> m_tmpFile; // The tmp file in which the (possibly
+ // converted) data now resides.
+
+ nsCString m_type; // The type of the data in file_name (not necessarily the
+ // same as the type of orig_url.)
+
+ nsCString
+ m_encoding; // Likewise, the encoding of the tmp file. This will be set
+ // only if the original document had an encoding already; we
+ // don't do base64 encoding and so forth until it's time to
+ // assemble a full MIME message of all parts.
+
+ nsCString m_description; // For Content-Description header
+ nsCString m_cloudPartInfo; // For X-Mozilla-Cloud-Part header, if any
+ nsCString m_xMacType; // mac-specific info
+ nsCString m_xMacCreator; // mac-specific info
+ nsCString m_realName; // The real name of the file.
+
+ // Some statistics about the data that was written to the file, so that when
+ // it comes time to compose a MIME message, we can make an informed decision
+ // about what Content-Transfer-Encoding would be best for this attachment.
+ // (If it's encoded already, we ignore this information and ship it as-is.)
+ uint32_t m_size;
+ uint32_t m_unprintableCount;
+ uint32_t m_highbitCount;
+ uint32_t m_ctlCount;
+ uint32_t m_nullCount;
+ uint32_t m_maxLineLength;
+};
+
+#undef MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING
+#ifdef MOZ_IS_DESTRUCTIBLE
+# define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \
+ static_assert( \
+ !MOZ_IS_DESTRUCTIBLE(X) || \
+ mozilla::IsSame<X, nsMsgAttachmentData>::value || \
+ mozilla::IsSame<X, nsMsgAttachedFile>::value, \
+ "Reference-counted class " #X \
+ " should not have a public destructor. " \
+ "Try to make this class's destructor non-public. If that is really " \
+ "not possible, you can whitelist this class by providing a " \
+ "HasDangerousPublicDestructor specialization for it.");
+#else
+# define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X)
+#endif
+#endif
diff --git a/comm/mailnews/compose/src/nsMsgCompFields.cpp b/comm/mailnews/compose/src/nsMsgCompFields.cpp
new file mode 100644
index 0000000000..9bfb66a253
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompFields.cpp
@@ -0,0 +1,559 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgCompose.h"
+#include "nsMsgCompFields.h"
+#include "nsMsgI18N.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgUtils.h"
+#include "prmem.h"
+#include "nsIFileChannel.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+
+using namespace mozilla::mailnews;
+
+struct HeaderInfo {
+ /// Header name
+ const char* mName;
+ /// If true, nsMsgCompFields should reflect the raw header value instead of
+ /// the unstructured header value.
+ bool mStructured;
+};
+
+// This is a mapping of the m_headers local set to the actual header name we
+// store on the structured header object.
+static HeaderInfo kHeaders[] = {
+ {"From", true},
+ {"Reply-To", true},
+ {"To", true},
+ {"Cc", true},
+ {"Bcc", true},
+ {nullptr, false}, // FCC
+ {nullptr, false}, // FCC2
+ {"Newsgroups", true},
+ {"Followup-To", true},
+ {"Subject", false},
+ {"Organization", false},
+ {"References", true},
+ {"X-Mozilla-News-Host", false},
+ {"X-Priority", false},
+ {nullptr, false}, // CHARACTER_SET
+ {"Message-Id", true},
+ {"X-Template", true},
+ {nullptr, false}, // DRAFT_ID
+ {nullptr, false}, // TEMPLATE_ID
+ {"Content-Language", true},
+ {nullptr, false} // CREATOR IDENTITY KEY
+};
+
+static_assert(
+ MOZ_ARRAY_LENGTH(kHeaders) == nsMsgCompFields::MSG_MAX_HEADERS,
+ "These two arrays need to be kept in sync or bad things will happen!");
+
+NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields, msgIStructuredHeaders,
+ msgIWritableStructuredHeaders)
+
+nsMsgCompFields::nsMsgCompFields()
+ : mStructuredHeaders(do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID)) {
+ m_body.Truncate();
+
+ m_attachVCard = false;
+ m_forcePlainText = false;
+ m_useMultipartAlternative = false;
+ m_returnReceipt = false;
+ m_receiptHeaderType = nsIMsgMdnGenerator::eDntType;
+ m_DSN = false;
+ m_bodyIsAsciiOnly = false;
+ m_forceMsgEncoding = false;
+ m_needToCheckCharset = true;
+ m_attachmentReminder = false;
+ m_deliveryFormat = nsIMsgCompSendFormat::Unset;
+}
+
+nsMsgCompFields::~nsMsgCompFields() {
+ MOZ_LOG(Compose, mozilla::LogLevel::Debug, ("~nsMsgCompFields()"));
+}
+
+nsresult nsMsgCompFields::SetAsciiHeader(MsgHeaderID header,
+ const char* value) {
+ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS,
+ "Invalid message header index!");
+
+ // If we are storing this on the structured header object, we need to set the
+ // value on that object as well. Note that the value may be null, which we'll
+ // take as an attempt to delete the header.
+ const char* headerName = kHeaders[header].mName;
+ if (headerName) {
+ if (!value || !*value) return mStructuredHeaders->DeleteHeader(headerName);
+
+ return mStructuredHeaders->SetRawHeader(headerName,
+ nsDependentCString(value));
+ }
+
+ // Not on the structurd header object, so save it locally.
+ m_headers[header] = value;
+
+ return NS_OK;
+}
+
+const char* nsMsgCompFields::GetAsciiHeader(MsgHeaderID header) {
+ NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS,
+ "Invalid message header index!");
+
+ const char* headerName = kHeaders[header].mName;
+ if (headerName) {
+ // We may be out of sync with the structured header object. Retrieve the
+ // header value.
+ if (kHeaders[header].mStructured) {
+ mStructuredHeaders->GetRawHeader(headerName, m_headers[header]);
+ } else {
+ nsString value;
+ mStructuredHeaders->GetUnstructuredHeader(headerName, value);
+ CopyUTF16toUTF8(value, m_headers[header]);
+ }
+ }
+
+ return m_headers[header].get();
+}
+
+nsresult nsMsgCompFields::SetUnicodeHeader(MsgHeaderID header,
+ const nsAString& value) {
+ return SetAsciiHeader(header, NS_ConvertUTF16toUTF8(value).get());
+}
+
+nsresult nsMsgCompFields::GetUnicodeHeader(MsgHeaderID header,
+ nsAString& aResult) {
+ CopyUTF8toUTF16(nsDependentCString(GetAsciiHeader(header)), aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFrom(const nsAString& value) {
+ return SetUnicodeHeader(MSG_FROM_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFrom(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_FROM_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReplyTo(const nsAString& value) {
+ return SetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReplyTo(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetTo(const nsAString& value) {
+ return SetUnicodeHeader(MSG_TO_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTo(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetCc(const nsAString& value) {
+ return SetUnicodeHeader(MSG_CC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetCc(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_CC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBcc(const nsAString& value) {
+ return SetUnicodeHeader(MSG_BCC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBcc(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_BCC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFcc(const nsAString& value) {
+ return SetUnicodeHeader(MSG_FCC_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFcc(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_FCC_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFcc2(const nsAString& value) {
+ return SetUnicodeHeader(MSG_FCC2_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFcc2(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_FCC2_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNewsgroups(const nsAString& aValue) {
+ return SetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aValue);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNewsgroups(nsAString& aGroup) {
+ return GetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aGroup);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetFollowupTo(const nsAString& aValue) {
+ return SetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetFollowupTo(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetHasRecipients(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = NS_SUCCEEDED(mime_sanity_check_fields_recipients(
+ GetTo(), GetCc(), GetBcc(), GetNewsgroups()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetCreatorIdentityKey(const char* value) {
+ return SetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetCreatorIdentityKey(char** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = strdup(GetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetSubject(const nsAString& value) {
+ return SetUnicodeHeader(MSG_SUBJECT_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetSubject(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_SUBJECT_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetOrganization(const nsAString& value) {
+ return SetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetOrganization(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReferences(const char* value) {
+ return SetAsciiHeader(MSG_REFERENCES_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReferences(char** _retval) {
+ *_retval = strdup(GetAsciiHeader(MSG_REFERENCES_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNewspostUrl(const char* value) {
+ return SetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNewspostUrl(char** _retval) {
+ *_retval = strdup(GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetPriority(const char* value) {
+ return SetAsciiHeader(MSG_PRIORITY_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetPriority(char** _retval) {
+ *_retval = strdup(GetAsciiHeader(MSG_PRIORITY_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetMessageId(const char* value) {
+ return SetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetMessageId(char** _retval) {
+ *_retval = strdup(GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetTemplateName(const nsAString& value) {
+ return SetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTemplateName(nsAString& _retval) {
+ return GetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, _retval);
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDraftId(const nsACString& value) {
+ return SetAsciiHeader(MSG_DRAFT_ID_HEADER_ID,
+ PromiseFlatCString(value).get());
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDraftId(nsACString& _retval) {
+ _retval.Assign(GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetTemplateId(const nsACString& value) {
+ return SetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID,
+ PromiseFlatCString(value).get());
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTemplateId(nsACString& _retval) {
+ _retval.Assign(GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReturnReceipt(bool value) {
+ m_returnReceipt = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReturnReceipt(bool* _retval) {
+ *_retval = m_returnReceipt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetReceiptHeaderType(int32_t value) {
+ m_receiptHeaderType = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetReceiptHeaderType(int32_t* _retval) {
+ *_retval = m_receiptHeaderType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDSN(bool value) {
+ m_DSN = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDSN(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_DSN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetAttachVCard(bool value) {
+ m_attachVCard = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetAttachVCard(bool* _retval) {
+ *_retval = m_attachVCard;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetAttachmentReminder(bool* _retval) {
+ *_retval = m_attachmentReminder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetAttachmentReminder(bool value) {
+ m_attachmentReminder = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetDeliveryFormat(int32_t value) {
+ switch (value) {
+ case nsIMsgCompSendFormat::Auto:
+ case nsIMsgCompSendFormat::PlainText:
+ case nsIMsgCompSendFormat::HTML:
+ case nsIMsgCompSendFormat::Both:
+ m_deliveryFormat = value;
+ break;
+ case nsIMsgCompSendFormat::Unset:
+ default:
+ m_deliveryFormat = nsIMsgCompSendFormat::Unset;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetDeliveryFormat(int32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_deliveryFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetContentLanguage(const char* value) {
+ return SetAsciiHeader(MSG_CONTENT_LANGUAGE_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetContentLanguage(char** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = strdup(GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID));
+ return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetForcePlainText(bool value) {
+ m_forcePlainText = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetForcePlainText(bool* _retval) {
+ *_retval = m_forcePlainText;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetForceMsgEncoding(bool value) {
+ m_forceMsgEncoding = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetForceMsgEncoding(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_forceMsgEncoding;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetUseMultipartAlternative(bool value) {
+ m_useMultipartAlternative = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetUseMultipartAlternative(bool* _retval) {
+ *_retval = m_useMultipartAlternative;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBodyIsAsciiOnly(bool value) {
+ m_bodyIsAsciiOnly = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBodyIsAsciiOnly(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = m_bodyIsAsciiOnly;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetBody(const nsAString& value) {
+ m_body = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetBody(nsAString& _retval) {
+ _retval = m_body;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetAttachments(
+ nsTArray<RefPtr<nsIMsgAttachment>>& attachments) {
+ attachments = m_attachments.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::AddAttachment(nsIMsgAttachment* attachment) {
+ // Don't add the same attachment twice.
+ for (nsIMsgAttachment* a : m_attachments) {
+ bool sameUrl;
+ a->EqualsUrl(attachment, &sameUrl);
+ if (sameUrl) return NS_OK;
+ }
+ m_attachments.AppendElement(attachment);
+ return NS_OK;
+}
+
+/* void removeAttachment (in nsIMsgAttachment attachment); */
+NS_IMETHODIMP nsMsgCompFields::RemoveAttachment(nsIMsgAttachment* attachment) {
+ for (uint32_t i = 0; i < m_attachments.Length(); i++) {
+ bool sameUrl;
+ m_attachments[i]->EqualsUrl(attachment, &sameUrl);
+ if (sameUrl) {
+ m_attachments.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetOtherHeaders(
+ const nsTArray<nsString>& headers) {
+ m_otherHeaders = headers.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetOtherHeaders(nsTArray<nsString>& headers) {
+ headers = m_otherHeaders.Clone();
+ return NS_OK;
+}
+
+/* void removeAttachments (); */
+NS_IMETHODIMP nsMsgCompFields::RemoveAttachments() {
+ m_attachments.Clear();
+ return NS_OK;
+}
+
+// This method is called during the creation of a new window.
+NS_IMETHODIMP
+nsMsgCompFields::SplitRecipients(const nsAString& aRecipients,
+ bool aEmailAddressOnly,
+ nsTArray<nsString>& aResult) {
+ nsCOMArray<msgIAddressObject> header(EncodedHeaderW(aRecipients));
+ if (aEmailAddressOnly)
+ ExtractEmails(header, aResult);
+ else
+ ExtractDisplayAddresses(header, aResult);
+
+ return NS_OK;
+}
+
+// This method is called during the sending of message from
+// nsMsgCompose::CheckAndPopulateRecipients()
+nsresult nsMsgCompFields::SplitRecipientsEx(const nsAString& recipients,
+ nsTArray<nsMsgRecipient>& aResult) {
+ nsTArray<nsString> names, addresses;
+ ExtractAllAddresses(EncodedHeaderW(recipients), names, addresses);
+
+ uint32_t numAddresses = names.Length();
+ for (uint32_t i = 0; i < numAddresses; ++i) {
+ nsMsgRecipient msgRecipient;
+ msgRecipient.mEmail = addresses[i];
+ msgRecipient.mName = names[i];
+ aResult.AppendElement(msgRecipient);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::ConvertBodyToPlainText() {
+ nsresult rv = NS_OK;
+
+ if (!m_body.IsEmpty()) {
+ if (NS_SUCCEEDED(rv)) {
+ bool flowed, formatted;
+ GetSerialiserFlags(&flowed, &formatted);
+ rv = ConvertBufToPlainText(m_body, flowed, formatted, true);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetComposeSecure(
+ nsIMsgComposeSecure** aComposeSecure) {
+ NS_ENSURE_ARG_POINTER(aComposeSecure);
+ NS_IF_ADDREF(*aComposeSecure = mSecureCompFields);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetComposeSecure(
+ nsIMsgComposeSecure* aComposeSecure) {
+ mSecureCompFields = aComposeSecure;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetNeedToCheckCharset(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_needToCheckCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompFields::SetNeedToCheckCharset(bool aCheck) {
+ m_needToCheckCharset = aCheck;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgCompFields.h b/comm/mailnews/compose/src/nsMsgCompFields.h
new file mode 100644
index 0000000000..312d19192c
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompFields.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _MsgCompFields_H_
+#define _MsgCompFields_H_
+
+#include "nsIMsgCompFields.h"
+#include "msgCore.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIMsgComposeSecure.h"
+
+struct nsMsgRecipient {
+ nsString mName;
+ nsString mEmail;
+ nsCOMPtr<nsIAbCard> mCard;
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+};
+
+/* Note that all the "Get" methods never return NULL (except in case of serious
+ error, like an illegal parameter); rather, they return "" if things were set
+ to NULL. This makes it real handy for the callers. */
+
+class nsMsgCompFields : public nsIMsgCompFields {
+ public:
+ nsMsgCompFields();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->)
+ NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->)
+ NS_DECL_NSIMSGCOMPFIELDS
+
+ typedef enum MsgHeaderID {
+ MSG_FROM_HEADER_ID = 0,
+ MSG_REPLY_TO_HEADER_ID,
+ MSG_TO_HEADER_ID,
+ MSG_CC_HEADER_ID,
+ MSG_BCC_HEADER_ID,
+ MSG_FCC_HEADER_ID,
+ MSG_FCC2_HEADER_ID,
+ MSG_NEWSGROUPS_HEADER_ID,
+ MSG_FOLLOWUP_TO_HEADER_ID,
+ MSG_SUBJECT_HEADER_ID,
+ MSG_ORGANIZATION_HEADER_ID,
+ MSG_REFERENCES_HEADER_ID,
+ MSG_NEWSPOSTURL_HEADER_ID,
+ MSG_PRIORITY_HEADER_ID,
+ MSG_CHARACTER_SET_HEADER_ID,
+ MSG_MESSAGE_ID_HEADER_ID,
+ MSG_X_TEMPLATE_HEADER_ID,
+ MSG_DRAFT_ID_HEADER_ID,
+ MSG_TEMPLATE_ID_HEADER_ID,
+ MSG_CONTENT_LANGUAGE_ID,
+ MSG_CREATOR_IDENTITY_KEY_ID,
+
+ MSG_MAX_HEADERS // Must be the last one.
+ } MsgHeaderID;
+
+ nsresult SetAsciiHeader(MsgHeaderID header, const char* value);
+ const char* GetAsciiHeader(
+ MsgHeaderID header); // just return the address of the internal header
+ // variable, don't dispose it
+
+ nsresult SetUnicodeHeader(MsgHeaderID header, const nsAString& value);
+ nsresult GetUnicodeHeader(MsgHeaderID header, nsAString& _retval);
+
+ /* Convenience routines to get and set header's value...
+
+ IMPORTANT:
+ all routines const char* GetXxx(void) will return a pointer to the header,
+ please don't free it.
+ */
+
+ nsresult SetFrom(const char* value) {
+ return SetAsciiHeader(MSG_FROM_HEADER_ID, value);
+ }
+ const char* GetFrom(void) { return GetAsciiHeader(MSG_FROM_HEADER_ID); }
+
+ nsresult SetReplyTo(const char* value) {
+ return SetAsciiHeader(MSG_REPLY_TO_HEADER_ID, value);
+ }
+ const char* GetReplyTo() { return GetAsciiHeader(MSG_REPLY_TO_HEADER_ID); }
+
+ nsresult SetTo(const char* value) {
+ return SetAsciiHeader(MSG_TO_HEADER_ID, value);
+ }
+ const char* GetTo() { return GetAsciiHeader(MSG_TO_HEADER_ID); }
+
+ nsresult SetCc(const char* value) {
+ return SetAsciiHeader(MSG_CC_HEADER_ID, value);
+ }
+ const char* GetCc() { return GetAsciiHeader(MSG_CC_HEADER_ID); }
+
+ nsresult SetBcc(const char* value) {
+ return SetAsciiHeader(MSG_BCC_HEADER_ID, value);
+ }
+ const char* GetBcc() { return GetAsciiHeader(MSG_BCC_HEADER_ID); }
+
+ nsresult SetFcc(const char* value) {
+ return SetAsciiHeader(MSG_FCC_HEADER_ID, value);
+ }
+ const char* GetFcc() { return GetAsciiHeader(MSG_FCC_HEADER_ID); }
+
+ nsresult SetFcc2(const char* value) {
+ return SetAsciiHeader(MSG_FCC2_HEADER_ID, value);
+ }
+ const char* GetFcc2() { return GetAsciiHeader(MSG_FCC2_HEADER_ID); }
+
+ nsresult SetNewsgroups(const char* aValue) {
+ return SetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID, aValue);
+ }
+ const char* GetNewsgroups() {
+ return GetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID);
+ }
+
+ nsresult SetFollowupTo(const char* aValue) {
+ return SetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue);
+ }
+ const char* GetFollowupTo() {
+ return GetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID);
+ }
+
+ nsresult SetSubject(const char* value) {
+ return SetAsciiHeader(MSG_SUBJECT_HEADER_ID, value);
+ }
+ const char* GetSubject() { return GetAsciiHeader(MSG_SUBJECT_HEADER_ID); }
+
+ nsresult SetOrganization(const char* value) {
+ return SetAsciiHeader(MSG_ORGANIZATION_HEADER_ID, value);
+ }
+ const char* GetOrganization() {
+ return GetAsciiHeader(MSG_ORGANIZATION_HEADER_ID);
+ }
+
+ const char* GetReferences() {
+ return GetAsciiHeader(MSG_REFERENCES_HEADER_ID);
+ }
+
+ const char* GetNewspostUrl() {
+ return GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID);
+ }
+
+ const char* GetPriority() { return GetAsciiHeader(MSG_PRIORITY_HEADER_ID); }
+
+ const char* GetCharacterSet() {
+ return GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID);
+ }
+
+ const char* GetMessageId() {
+ return GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID);
+ }
+
+ nsresult SetTemplateName(const char* value) {
+ return SetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID, value);
+ }
+ const char* GetTemplateName() {
+ return GetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID);
+ }
+
+ const char* GetDraftId() { return GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID); }
+ const char* GetTemplateId() {
+ return GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID);
+ }
+
+ const char* GetContentLanguage() {
+ return GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID);
+ }
+
+ bool GetReturnReceipt() { return m_returnReceipt; }
+ bool GetDSN() { return m_DSN; }
+ bool GetAttachVCard() { return m_attachVCard; }
+ bool GetAttachmentReminder() { return m_attachmentReminder; }
+ int32_t GetDeliveryFormat() { return m_deliveryFormat; }
+ bool GetForcePlainText() { return m_forcePlainText; }
+ bool GetUseMultipartAlternative() { return m_useMultipartAlternative; }
+ bool GetBodyIsAsciiOnly() { return m_bodyIsAsciiOnly; }
+ bool GetForceMsgEncoding() { return m_forceMsgEncoding; }
+
+ nsresult SplitRecipientsEx(const nsAString& recipients,
+ nsTArray<nsMsgRecipient>& aResult);
+
+ protected:
+ virtual ~nsMsgCompFields();
+ nsCString m_headers[MSG_MAX_HEADERS];
+ nsString m_body;
+ nsTArray<RefPtr<nsIMsgAttachment>> m_attachments;
+ nsTArray<nsString> m_otherHeaders;
+ bool m_attachVCard;
+ bool m_attachmentReminder;
+ int32_t m_deliveryFormat;
+ bool m_forcePlainText;
+ bool m_useMultipartAlternative;
+ bool m_returnReceipt;
+ bool m_DSN;
+ bool m_bodyIsAsciiOnly;
+ bool m_forceMsgEncoding;
+ int32_t m_receiptHeaderType; /* receipt header type */
+ nsCString m_DefaultCharacterSet;
+ bool m_needToCheckCharset;
+
+ nsCOMPtr<nsIMsgComposeSecure> mSecureCompFields;
+ nsCOMPtr<msgIWritableStructuredHeaders> mStructuredHeaders;
+};
+
+#endif /* _MsgCompFields_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgCompUtils.cpp b/comm/mailnews/compose/src/nsMsgCompUtils.cpp
new file mode 100644
index 0000000000..50ce86497b
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompUtils.cpp
@@ -0,0 +1,1164 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsCOMPtr.h"
+#include "nsMsgCompUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsStringFwd.h"
+#include "prmem.h"
+#include "nsIStringBundle.h"
+#include "nsIIOService.h"
+#include "nsIHttpProtocolHandler.h"
+#include "nsMailHeaders.h"
+#include "nsMsgI18N.h"
+#include "nsINntpService.h"
+#include "nsMimeTypes.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsMsgPrompts.h"
+#include "nsMsgUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsComposeStrings.h"
+#include "nsIMsgCompUtils.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsCRTGlue.h"
+#include <ctype.h>
+#include "mozilla/dom/Element.h"
+#include "mozilla/EncodingDetector.h"
+#include "mozilla/Components.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/dom/Document.h"
+#include "nsIMIMEInfo.h"
+#include "nsIMsgHeaderParser.h"
+#include "nsIMutableArray.h"
+#include "nsIRandomGenerator.h"
+#include "nsID.h"
+
+void msg_generate_message_id(nsIMsgIdentity* identity,
+ const nsACString& customHost,
+ nsACString& messageID);
+
+NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils)
+
+nsMsgCompUtils::nsMsgCompUtils() {}
+
+nsMsgCompUtils::~nsMsgCompUtils() {}
+
+NS_IMETHODIMP nsMsgCompUtils::MimeMakeSeparator(const char* prefix,
+ char** _retval) {
+ NS_ENSURE_ARG_POINTER(prefix);
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mime_make_separator(prefix);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompUtils::MsgGenerateMessageId(nsIMsgIdentity* identity,
+ const nsACString& host,
+ nsACString& messageID) {
+ // We don't check `host` because it's allowed to be a null pointer (which
+ // means we should ignore it for message ID generation).
+ NS_ENSURE_ARG_POINTER(identity);
+ msg_generate_message_id(identity, host, messageID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nsMsgMIMEGetConformToStandard();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompUtils::DetectCharset(const nsACString& aContent,
+ nsACString& aCharset) {
+ mozilla::UniquePtr<mozilla::EncodingDetector> detector =
+ mozilla::EncodingDetector::Create();
+ mozilla::Span<const uint8_t> src = mozilla::AsBytes(
+ mozilla::Span(ToNewCString(aContent), aContent.Length()));
+ mozilla::Unused << detector->Feed(src, true);
+ auto encoding = detector->Guess(nullptr, true);
+ encoding->Name(aCharset);
+ return NS_OK;
+}
+
+//
+// Create a file for the a unique temp file
+// on the local machine. Caller must free memory
+//
+nsresult nsMsgCreateTempFile(const char* tFileName, nsIFile** tFile) {
+ if ((!tFileName) || (!*tFileName)) tFileName = "nsmail.tmp";
+
+ nsresult rv =
+ GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, tFileName, tFile);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv)) NS_RELEASE(*tFile);
+
+ return rv;
+}
+
+// This is the value a caller will Get if they don't Set first (like MDN
+// sending a return receipt), so init to the default value of the
+// mail.strictly_mime_headers preference.
+static bool mime_headers_use_quoted_printable_p = true;
+
+bool nsMsgMIMEGetConformToStandard(void) {
+ return mime_headers_use_quoted_printable_p;
+}
+
+void nsMsgMIMESetConformToStandard(bool conform_p) {
+ /*
+ * If we are conforming to mime standard no matter what we set
+ * for the headers preference when generating mime headers we should
+ * also conform to the standard. Otherwise, depends the preference
+ * we set. For now, the headers preference is not accessible from UI.
+ */
+ if (conform_p)
+ mime_headers_use_quoted_printable_p = true;
+ else {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ prefs->GetBoolPref("mail.strictly_mime_headers",
+ &mime_headers_use_quoted_printable_p);
+ }
+ }
+}
+
+/**
+ * Checks if the recipient fields have sane values for message send.
+ */
+nsresult mime_sanity_check_fields_recipients(const char* to, const char* cc,
+ const char* bcc,
+ const char* newsgroups) {
+ if (to)
+ while (IS_SPACE(*to)) to++;
+ if (cc)
+ while (IS_SPACE(*cc)) cc++;
+ if (bcc)
+ while (IS_SPACE(*bcc)) bcc++;
+ if (newsgroups)
+ while (IS_SPACE(*newsgroups)) newsgroups++;
+
+ if ((!to || !*to) && (!cc || !*cc) && (!bcc || !*bcc) &&
+ (!newsgroups || !*newsgroups))
+ return NS_MSG_NO_RECIPIENTS;
+
+ return NS_OK;
+}
+
+/**
+ * Checks if the compose fields have sane values for message send.
+ */
+nsresult mime_sanity_check_fields(
+ const char* from, const char* reply_to, const char* to, const char* cc,
+ const char* bcc, const char* fcc, const char* newsgroups,
+ const char* followup_to, const char* /*subject*/,
+ const char* /*references*/, const char* /*organization*/,
+ const char* /*other_random_headers*/) {
+ if (from)
+ while (IS_SPACE(*from)) from++;
+ if (reply_to)
+ while (IS_SPACE(*reply_to)) reply_to++;
+ if (fcc)
+ while (IS_SPACE(*fcc)) fcc++;
+ if (followup_to)
+ while (IS_SPACE(*followup_to)) followup_to++;
+
+ // TODO: sanity check other_random_headers for newline conventions
+ if (!from || !*from) return NS_MSG_NO_SENDER;
+
+ return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups);
+}
+
+// Helper macro for generating the X-Mozilla-Draft-Info header.
+#define APPEND_BOOL(method, param) \
+ do { \
+ bool val = false; \
+ fields->Get##method(&val); \
+ if (val) \
+ draftInfo.AppendLiteral(param "=1"); \
+ else \
+ draftInfo.AppendLiteral(param "=0"); \
+ } while (false)
+
+nsresult mime_generate_headers(nsIMsgCompFields* fields,
+ nsMsgDeliverMode deliver_mode,
+ msgIWritableStructuredHeaders* finalHeaders) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDraft = deliver_mode == nsIMsgSend::nsMsgSaveAsDraft ||
+ deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate ||
+ deliver_mode == nsIMsgSend::nsMsgQueueForLater ||
+ deliver_mode == nsIMsgSend::nsMsgDeliverBackground;
+
+ bool hasDisclosedRecipient = false;
+
+ MOZ_ASSERT(fields, "null fields");
+ NS_ENSURE_ARG_POINTER(fields);
+
+ nsTArray<RefPtr<msgIAddressObject>> from;
+ fields->GetAddressingHeader("From", true, from);
+
+ // Copy all headers from the original compose field.
+ rv = finalHeaders->AddAllHeaders(fields);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMessageId = false;
+ if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) &&
+ hasMessageId) {
+ /* MDN request header requires to have MessageID header presented
+ * in the message in order to
+ * coorelate the MDN reports to the original message. Here will be
+ * the right place
+ */
+
+ bool returnReceipt = false;
+ fields->GetReturnReceipt(&returnReceipt);
+ if (returnReceipt && (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft &&
+ deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate)) {
+ int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType;
+ fields->GetReceiptHeaderType(&receipt_header_type);
+
+ // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ;
+ // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ;
+ // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers .
+ if (receipt_header_type != nsIMsgMdnGenerator::eRrtType)
+ finalHeaders->SetAddressingHeader("Disposition-Notification-To", from);
+ if (receipt_header_type != nsIMsgMdnGenerator::eDntType)
+ finalHeaders->SetAddressingHeader("Return-Receipt-To", from);
+ }
+ }
+
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ int gmtoffset =
+ (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
+
+ // Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ // then figure out what our local GMT offset is, and append it (since
+ // PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ // per RFC 1123 (superseding RFC 822.)
+ char dateString[130];
+ PR_FormatTimeUSEnglish(dateString, sizeof(dateString),
+ "%a, %d %b %Y %H:%M:%S ", &now);
+
+ char* entryPoint = dateString + strlen(dateString);
+ PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString),
+ "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+ finalHeaders->SetRawHeader("Date", nsDependentCString(dateString));
+
+ // X-Mozilla-Draft-Info
+ if (isDraft) {
+ nsAutoCString draftInfo;
+ draftInfo.AppendLiteral("internal/draft; ");
+ APPEND_BOOL(AttachVCard, "vcard");
+ draftInfo.AppendLiteral("; ");
+ bool hasReturnReceipt = false;
+ fields->GetReturnReceipt(&hasReturnReceipt);
+ if (hasReturnReceipt) {
+ // 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
+ int32_t headerType = 0;
+ fields->GetReceiptHeaderType(&headerType);
+ draftInfo.AppendLiteral("receipt=");
+ draftInfo.AppendInt(headerType + 1);
+ } else
+ draftInfo.AppendLiteral("receipt=0");
+ draftInfo.AppendLiteral("; ");
+ APPEND_BOOL(DSN, "DSN");
+ draftInfo.AppendLiteral("; ");
+ draftInfo.AppendLiteral("uuencode=0");
+ draftInfo.AppendLiteral("; ");
+ APPEND_BOOL(AttachmentReminder, "attachmentreminder");
+ draftInfo.AppendLiteral("; ");
+ int32_t deliveryFormat;
+ fields->GetDeliveryFormat(&deliveryFormat);
+ draftInfo.AppendLiteral("deliveryformat=");
+ draftInfo.AppendInt(deliveryFormat);
+
+ finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo);
+ }
+
+ bool sendUserAgent = false;
+ if (prefs) {
+ prefs->GetBoolPref("mailnews.headers.sendUserAgent", &sendUserAgent);
+ }
+ if (sendUserAgent) {
+ bool useMinimalUserAgent = false;
+ if (prefs) {
+ prefs->GetBoolPref("mailnews.headers.useMinimalUserAgent",
+ &useMinimalUserAgent);
+ }
+ if (useMinimalUserAgent) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(
+ "chrome://branding/locale/brand.properties",
+ getter_AddRefs(brandBundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString brandName;
+ brandBundle->GetStringFromName("brandFullName", brandName);
+ if (!brandName.IsEmpty())
+ finalHeaders->SetUnstructuredHeader("User-Agent", brandName);
+ }
+ }
+ } else {
+ nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler =
+ do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv);
+ if (NS_SUCCEEDED(rv) && pHTTPHandler) {
+ nsAutoCString userAgentString;
+ // Ignore error since we're testing the return value.
+ mozilla::Unused << pHTTPHandler->GetUserAgent(userAgentString);
+
+ if (!userAgentString.IsEmpty())
+ finalHeaders->SetUnstructuredHeader(
+ "User-Agent", NS_ConvertUTF8toUTF16(userAgentString));
+ }
+ }
+ }
+
+ finalHeaders->SetUnstructuredHeader("MIME-Version", u"1.0"_ns);
+
+ nsAutoCString newsgroups;
+ finalHeaders->GetRawHeader("Newsgroups", newsgroups);
+ if (!newsgroups.IsEmpty()) {
+ // Since the newsgroup header can contain data in the form of:
+ // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk"
+ // we need to turn that into: "netscape.test,netscape.junk"
+ // (XXX: can it really?)
+ nsCOMPtr<nsINntpService> nntpService =
+ do_GetService("@mozilla.org/messenger/nntpservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newsgroupsHeaderVal;
+ nsCString newshostHeaderVal;
+ rv = nntpService->GenerateNewsHeaderValsForPosting(
+ newsgroups, getter_Copies(newsgroupsHeaderVal),
+ getter_Copies(newshostHeaderVal));
+ NS_ENSURE_SUCCESS(rv, rv);
+ finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal);
+
+ // 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 (deliver_mode != nsIMsgSend::nsMsgDeliverNow &&
+ deliver_mode != 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"
+ finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal);
+ }
+
+ // Newsgroups are a recipient...
+ hasDisclosedRecipient = true;
+ }
+
+ nsTArray<RefPtr<msgIAddressObject>> recipients;
+ finalHeaders->GetAddressingHeader("To", false, recipients);
+ hasDisclosedRecipient |= !recipients.IsEmpty();
+ finalHeaders->GetAddressingHeader("Cc", false, recipients);
+ hasDisclosedRecipient |= !recipients.IsEmpty();
+
+ // If we don't have disclosed recipient (only Bcc), address the message to
+ // undisclosed-recipients to prevent problem with some servers
+
+ // 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 &&
+ (!isDraft || deliver_mode == nsIMsgSend::nsMsgQueueForLater)) {
+ bool bAddUndisclosedRecipients = true;
+ prefs->GetBoolPref("mail.compose.add_undisclosed_recipients",
+ &bAddUndisclosedRecipients);
+ if (bAddUndisclosedRecipients) {
+ bool hasBcc = false;
+ fields->HasHeader("Bcc", &hasBcc);
+ if (hasBcc) {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::components::StringBundle::Service();
+ if (stringService) {
+ nsCOMPtr<nsIStringBundle> composeStringBundle;
+ rv = stringService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/"
+ "composeMsgs.properties",
+ getter_AddRefs(composeStringBundle));
+ if (NS_SUCCEEDED(rv)) {
+ nsString undisclosedRecipients;
+ rv = composeStringBundle->GetStringFromName("undisclosedRecipients",
+ undisclosedRecipients);
+ if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) {
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ mozilla::components::HeaderParser::Service());
+ nsCOMPtr<msgIAddressObject> group;
+ nsTArray<RefPtr<msgIAddressObject>> noRecipients;
+ headerParser->MakeGroupObject(undisclosedRecipients, noRecipients,
+ getter_AddRefs(group));
+ recipients.AppendElement(group);
+ finalHeaders->SetAddressingHeader("To", recipients);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // We don't want to emit a Bcc header to the output. If we are saving this to
+ // Drafts/Sent, this is re-added later in nsMsgSend.cpp.
+ finalHeaders->DeleteHeader("bcc");
+
+ // Skip no or empty priority.
+ nsAutoCString priority;
+ rv = fields->GetRawHeader("X-Priority", priority);
+ if (NS_SUCCEEDED(rv) && !priority.IsEmpty()) {
+ nsMsgPriorityValue priorityValue;
+
+ NS_MsgGetPriorityFromString(priority.get(), priorityValue);
+
+ // Skip default priority.
+ if (priorityValue != nsMsgPriority::Default) {
+ nsAutoCString priorityName;
+ nsAutoCString priorityValueString;
+
+ NS_MsgGetPriorityValueString(priorityValue, priorityValueString);
+ NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName);
+
+ // Output format: [X-Priority: <pValue> (<pName>)]
+ priorityValueString.AppendLiteral(" (");
+ priorityValueString += priorityName;
+ priorityValueString.Append(')');
+ finalHeaders->SetRawHeader("X-Priority", priorityValueString);
+ }
+ }
+
+ nsAutoCString references;
+ finalHeaders->GetRawHeader("References", references);
+ if (!references.IsEmpty()) {
+ // The References header should be kept under 998 characters: if it's too
+ // long, trim out the earliest references to make it smaller.
+ if (references.Length() > 986) {
+ int32_t firstRef = references.FindChar('<');
+ int32_t secondRef = references.FindChar('<', firstRef + 1);
+ if (secondRef > 0) {
+ nsAutoCString newReferences(StringHead(references, secondRef));
+ int32_t bracket = references.FindChar(
+ '<', references.Length() + newReferences.Length() - 986);
+ if (bracket > 0) {
+ newReferences.Append(Substring(references, bracket));
+ finalHeaders->SetRawHeader("References", newReferences);
+ }
+ }
+ }
+ // The In-Reply-To header is the last entry in the references header...
+ int32_t bracket = references.RFind("<");
+ if (bracket >= 0)
+ finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket));
+ }
+
+ return NS_OK;
+}
+
+#undef APPEND_BOOL // X-Mozilla-Draft-Info helper macro
+
+static void GenerateGlobalRandomBytes(unsigned char* buf, int32_t len) {
+ // Attempt to generate bytes from system entropy-based RNG.
+ nsCOMPtr<nsIRandomGenerator> randomGenerator(
+ do_GetService("@mozilla.org/security/random-generator;1"));
+ MOZ_ASSERT(randomGenerator, "nsIRandomGenerator service not retrievable");
+ uint8_t* tempBuffer;
+ nsresult rv = randomGenerator->GenerateRandomBytes(len, &tempBuffer);
+ if (NS_SUCCEEDED(rv)) {
+ memcpy(buf, tempBuffer, len);
+ free(tempBuffer);
+ return;
+ }
+ // nsIRandomGenerator failed -- fall back to low entropy PRNG.
+ static bool firstTime = true;
+ if (firstTime) {
+ // Seed the random-number generator with current time so that
+ // the numbers will be different every time we run.
+ srand((unsigned)PR_Now());
+ firstTime = false;
+ }
+
+ for (int32_t i = 0; i < len; i++) buf[i] = rand() % 256;
+}
+
+char* mime_make_separator(const char* prefix) {
+ unsigned char rand_buf[13];
+ GenerateGlobalRandomBytes(rand_buf, 12);
+
+ return PR_smprintf(
+ "------------%s"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X",
+ prefix, rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], rand_buf[4],
+ rand_buf[5], rand_buf[6], rand_buf[7], rand_buf[8], rand_buf[9],
+ rand_buf[10], rand_buf[11]);
+}
+
+// Tests if the content of a string is a valid host name.
+// In this case, a valid host name is any non-empty string that only contains
+// letters (a-z + A-Z), numbers (0-9) and the characters '-', '_' and '.'.
+static bool isValidHost(const nsCString& host) {
+ if (host.IsEmpty()) {
+ return false;
+ }
+
+ const auto* cur = host.BeginReading();
+ const auto* end = host.EndReading();
+ for (; cur < end; ++cur) {
+ if (!isalpha(*cur) && !isdigit(*cur) && *cur != '-' && *cur != '_' &&
+ *cur != '.') {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Extract the domain name from an address.
+// If none could be found (i.e. the address does not contain an '@' sign, or
+// the value following it is not a valid domain), then nullptr is returned.
+void msg_domain_name_from_address(const nsACString& address, nsACString& host) {
+ auto atIndex = address.FindChar('@');
+
+ if (address.IsEmpty() || atIndex == kNotFound) {
+ return;
+ }
+
+ // Substring() should handle cases where we would go out of bounds (by
+ // preventing the index from exceeding the length of the source string), so we
+ // don't need to handle this here.
+ host = Substring(address, atIndex + 1);
+}
+
+// Generate a value for a Message-Id header using the identity and optional
+// hostname provided.
+void msg_generate_message_id(nsIMsgIdentity* identity,
+ const nsACString& customHost,
+ nsACString& messageID) {
+ nsCString host;
+
+ // Check if the identity forces host name. This is sometimes the case when
+ // using newsgroup.
+ nsCString forcedFQDN;
+ nsresult rv = identity->GetCharAttribute("FQDN", forcedFQDN);
+ if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty()) {
+ host = forcedFQDN;
+ }
+
+ // If no valid host name has been set, try using the value defined by the
+ // caller, if any.
+ if (!isValidHost(host)) {
+ host = customHost;
+ }
+
+ // If no valid host name has been set, try extracting one from the email
+ // address associated with the identity.
+ if (!isValidHost(host)) {
+ nsCString from;
+ rv = identity->GetEmail(from);
+ if (NS_SUCCEEDED(rv) && !from.IsEmpty()) {
+ msg_domain_name_from_address(from, host);
+ }
+ }
+
+ // If we still couldn't find a valid host name to use, we can't generate a
+ // valid message ID, so bail, and let NNTP and SMTP generate them.
+ if (!isValidHost(host)) {
+ return;
+ }
+
+ // Generate 128-bit UUID for the local part of the ID. `nsID` provides us with
+ // cryptographically-secure generation.
+ nsID uuid = nsID::GenerateUUID();
+ char uuidString[NSID_LENGTH];
+ uuid.ToProvidedString(uuidString);
+ // Drop first and last characters (curly braces).
+ uuidString[NSID_LENGTH - 2] = 0;
+
+ messageID.AppendPrintf("<%s@%s>", uuidString + 1, host.get());
+}
+
+// this is to guarantee the folded line will never be greater
+// than 78 = 75 + CRLFLWSP
+#define PR_MAX_FOLDING_LEN 75
+
+/*static */ char* RFC2231ParmFolding(const char* parmName,
+ const char* parmValue) {
+ NS_ENSURE_TRUE(parmName && *parmName && parmValue && *parmValue, nullptr);
+
+ bool needEscape;
+ nsCString dupParm;
+ nsCString charset("UTF-8");
+
+ if (!mozilla::IsAsciiNullTerminated(parmValue)) {
+ needEscape = true;
+ dupParm.Assign(parmValue);
+ MsgEscapeString(dupParm, nsINetUtil::ESCAPE_ALL, dupParm);
+ } else {
+ needEscape = false;
+ dupParm.Adopt(msg_make_filename_qtext(parmValue, true));
+ }
+
+ int32_t parmNameLen = PL_strlen(parmName);
+ int32_t parmValueLen = dupParm.Length();
+
+ parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___"
+
+ char* foldedParm = nullptr;
+
+ if ((parmValueLen + parmNameLen + strlen("UTF-8")) < PR_MAX_FOLDING_LEN) {
+ foldedParm = PL_strdup(parmName);
+ if (needEscape) {
+ NS_MsgSACat(&foldedParm, "*=");
+ NS_MsgSACat(&foldedParm, "UTF-8");
+ NS_MsgSACat(&foldedParm, "''"); // We don't support language.
+ } else
+ NS_MsgSACat(&foldedParm, "=\"");
+ NS_MsgSACat(&foldedParm, dupParm.get());
+ if (!needEscape) NS_MsgSACat(&foldedParm, "\"");
+ } else {
+ int curLineLen = 0;
+ int counter = 0;
+ char digits[32];
+ char* start = dupParm.BeginWriting();
+ char* end = NULL;
+ char tmp = 0;
+
+ while (parmValueLen > 0) {
+ curLineLen = 0;
+ if (counter == 0) {
+ PR_FREEIF(foldedParm)
+ foldedParm = PL_strdup(parmName);
+ } else {
+ NS_MsgSACat(&foldedParm, ";\r\n ");
+ NS_MsgSACat(&foldedParm, parmName);
+ }
+ PR_snprintf(digits, sizeof(digits), "*%d", counter);
+ NS_MsgSACat(&foldedParm, digits);
+ curLineLen += PL_strlen(digits);
+ if (needEscape) {
+ NS_MsgSACat(&foldedParm, "*=");
+ if (counter == 0) {
+ NS_MsgSACat(&foldedParm, "UTF-8");
+ NS_MsgSACat(&foldedParm, "''"); // We don't support language.
+ curLineLen += strlen("UTF-8");
+ }
+ } else {
+ NS_MsgSACat(&foldedParm, "=\"");
+ }
+ counter++;
+ curLineLen += parmNameLen;
+ if (parmValueLen <= PR_MAX_FOLDING_LEN - curLineLen)
+ end = start + parmValueLen;
+ else
+ end = start + (PR_MAX_FOLDING_LEN - curLineLen);
+
+ tmp = 0;
+ if (*end && needEscape) {
+ // 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 && *(end - 1) == '%') {
+ end -= 1;
+ } else if (end - 2 > start && *(end - 2) == '%') {
+ end -= 2;
+ }
+ // *end is now a '%'.
+ // Check if the following UTF-8 octet is a continuation.
+ while (end - 3 > start && (*(end + 1) == '8' || *(end + 1) == '9' ||
+ *(end + 1) == 'A' || *(end + 1) == 'B')) {
+ end -= 3;
+ }
+ tmp = *end;
+ *end = 0;
+ } else {
+ tmp = *end;
+ *end = 0;
+ }
+ NS_MsgSACat(&foldedParm, start);
+ if (!needEscape) NS_MsgSACat(&foldedParm, "\"");
+
+ parmValueLen -= (end - start);
+ if (tmp) *end = tmp;
+ start = end;
+ }
+ }
+
+ return foldedParm;
+}
+
+bool mime_7bit_data_p(const char* string, uint32_t size) {
+ if ((!string) || (!*string)) return true;
+
+ char* ptr = (char*)string;
+ for (uint32_t i = 0; i < size; i++) {
+ if ((unsigned char)ptr[i] > 0x7F) return false;
+ }
+ return true;
+}
+
+// Strips whitespace, and expands newlines into newline-tab for use in
+// mail headers. Returns a new string or 0 (if it would have been empty.)
+// If addr_p is true, the addresses will be parsed and reemitted as
+// rfc822 mailboxes.
+char* mime_fix_header_1(const char* string, bool addr_p, bool news_p) {
+ char* new_string;
+ const char* in;
+ char* out;
+ int32_t i, old_size, new_size;
+
+ if (!string || !*string) return 0;
+
+ if (addr_p) {
+ return strdup(string);
+ }
+
+ old_size = PL_strlen(string);
+ new_size = old_size;
+ for (i = 0; i < old_size; i++)
+ if (string[i] == '\r' || string[i] == '\n') new_size += 2;
+
+ new_string = (char*)PR_Malloc(new_size + 1);
+ if (!new_string) return 0;
+
+ in = string;
+ out = new_string;
+
+ /* strip leading whitespace. */
+ while (IS_SPACE(*in)) in++;
+
+ /* replace CR, LF, or CRLF with CRLF-TAB. */
+ while (*in) {
+ if (*in == '\r' || *in == '\n') {
+ if (*in == '\r' && in[1] == '\n') in++;
+ in++;
+ *out++ = '\r';
+ *out++ = '\n';
+ *out++ = '\t';
+ } else if (news_p && *in == ',') {
+ *out++ = *in++;
+ /* skip over all whitespace after a comma. */
+ while (IS_SPACE(*in)) in++;
+ } else
+ *out++ = *in++;
+ }
+ *out = 0;
+
+ /* strip trailing whitespace. */
+ while (out > in && IS_SPACE(out[-1])) *out-- = 0;
+
+ /* If we ended up throwing it all away, use 0 instead of "". */
+ if (!*new_string) {
+ PR_Free(new_string);
+ new_string = 0;
+ }
+
+ return new_string;
+}
+
+char* mime_fix_header(const char* string) {
+ return mime_fix_header_1(string, false, false);
+}
+
+char* mime_fix_addr_header(const char* string) {
+ return mime_fix_header_1(string, true, false);
+}
+
+char* mime_fix_news_header(const char* string) {
+ return mime_fix_header_1(string, false, true);
+}
+
+bool mime_type_requires_b64_p(const char* type) {
+ if (!type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE))
+ // Unknown types don't necessarily require encoding. (Note that
+ // "unknown" and "application/octet-stream" aren't the same.)
+ return false;
+
+ else if (!PL_strncasecmp(type, "image/", 6) ||
+ !PL_strncasecmp(type, "audio/", 6) ||
+ !PL_strncasecmp(type, "video/", 6) ||
+ !PL_strncasecmp(type, "application/", 12)) {
+ // The following types are application/ or image/ types that are actually
+ // known to contain textual data (meaning line-based, not binary, where
+ // CRLF conversion is desired rather than disastrous.) So, if the type
+ // is any of these, it does not *require* base64, and if we do need to
+ // encode it for other reasons, we'll probably use quoted-printable.
+ // But, if it's not one of these types, then we assume that any subtypes
+ // of the non-"text/" types are binary data, where CRLF conversion would
+ // corrupt it, so we use base64 right off the bat.
+
+ // The reason it's desirable to ship these as text instead of just using
+ // base64 all the time is mainly to preserve the readability of them for
+ // non-MIME users: if I mail a /bin/sh script to someone, it might not
+ // need to be encoded at all, so we should leave it readable if we can.
+
+ // This list of types was derived from the comp.mail.mime FAQ, section
+ // 10.2.2, "List of known unregistered MIME types" on 2-Feb-96.
+ static const char* app_and_image_types_which_are_really_text[] = {
+ "application/mac-binhex40", /* APPLICATION_BINHEX */
+ "application/pgp", /* APPLICATION_PGP */
+ "application/pgp-keys",
+ "application/x-pgp-message", /* APPLICATION_PGP2 */
+ "application/postscript", /* APPLICATION_POSTSCRIPT */
+ "application/x-uuencode", /* APPLICATION_UUENCODE */
+ "application/x-uue", /* APPLICATION_UUENCODE2 */
+ "application/uue", /* APPLICATION_UUENCODE4 */
+ "application/uuencode", /* APPLICATION_UUENCODE3 */
+ "application/sgml",
+ "application/x-csh",
+ "application/javascript",
+ "application/ecmascript",
+ "application/x-javascript",
+ "application/x-latex",
+ "application/x-macbinhex40",
+ "application/x-ns-proxy-autoconfig",
+ "application/x-www-form-urlencoded",
+ "application/x-perl",
+ "application/x-sh",
+ "application/x-shar",
+ "application/x-tcl",
+ "application/x-tex",
+ "application/x-texinfo",
+ "application/x-troff",
+ "application/x-troff-man",
+ "application/x-troff-me",
+ "application/x-troff-ms",
+ "application/x-troff-ms",
+ "application/x-wais-source",
+ "image/x-bitmap",
+ "image/x-pbm",
+ "image/x-pgm",
+ "image/x-portable-anymap",
+ "image/x-portable-bitmap",
+ "image/x-portable-graymap",
+ "image/x-portable-pixmap", /* IMAGE_PPM */
+ "image/x-ppm",
+ "image/x-xbitmap", /* IMAGE_XBM */
+ "image/x-xbm", /* IMAGE_XBM2 */
+ "image/xbm", /* IMAGE_XBM3 */
+ "image/x-xpixmap",
+ "image/x-xpm",
+ 0};
+ const char** s;
+ for (s = app_and_image_types_which_are_really_text; *s; s++)
+ if (!PL_strcasecmp(type, *s)) return false;
+
+ /* All others must be assumed to be binary formats, and need Base64. */
+ return true;
+ }
+
+ else
+ return false;
+}
+
+//
+// Some types should have a "charset=" parameter, and some shouldn't.
+// This is what decides.
+//
+bool mime_type_needs_charset(const char* type) {
+ /* Only text types should have charset. */
+ if (!type || !*type)
+ return false;
+ else if (!PL_strncasecmp(type, "text", 4))
+ return true;
+ else
+ return false;
+}
+
+// Given a string, convert it to 'qtext' (quoted text) for RFC822 header
+// purposes.
+char* msg_make_filename_qtext(const char* srcText, bool stripCRLFs) {
+ /* newString can be at most twice the original string (every char quoted). */
+ char* newString = (char*)PR_Malloc(PL_strlen(srcText) * 2 + 1);
+ if (!newString) return NULL;
+
+ const char* s = srcText;
+ const char* end = srcText + PL_strlen(srcText);
+ char* d = newString;
+
+ while (*s) {
+ // Put backslashes in front of existing backslashes, or double quote
+ // characters.
+ // If stripCRLFs is true, don't write out CRs or LFs. Otherwise,
+ // write out a backslash followed by the CR but not
+ // linear-white-space.
+ // We might already have quoted pair of "\ " or "\\t" skip it.
+ if (*s == '\\' || *s == '"' ||
+ (!stripCRLFs &&
+ (*s == '\r' && (s[1] != '\n' ||
+ (s[1] == '\n' && (s + 2) < end && !IS_SPACE(s[2]))))))
+ *d++ = '\\';
+
+ if (stripCRLFs && *s == '\r' && s[1] == '\n' && (s + 2) < end &&
+ IS_SPACE(s[2])) {
+ s += 3; // skip CRLFLWSP
+ } else {
+ *d++ = *s++;
+ }
+ }
+ *d = 0;
+
+ return newString;
+}
+
+// Utility to create a nsIURI object...
+nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const nsCString& aSpec) {
+ nsresult rv = NS_OK;
+ if (nullptr == aInstancePtrResult) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIIOService> pNetService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED);
+ if (aSpec.Find("://") == kNotFound && !StringBeginsWith(aSpec, "data:"_ns)) {
+ // XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get
+ // fixed
+ nsAutoCString uri("http://"_ns);
+ uri.Append(aSpec);
+ rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult);
+ } else
+ rv = pNetService->NewURI(aSpec, nullptr, nullptr, aInstancePtrResult);
+ return rv;
+}
+
+char* nsMsgGetLocalFileFromURL(const char* url) {
+ char* finalPath;
+ NS_ASSERTION(PL_strncasecmp(url, "file://", 7) == 0, "invalid url");
+ finalPath = (char*)PR_Malloc(strlen(url));
+ if (finalPath == NULL) return NULL;
+ strcpy(finalPath, url + 6 + 1);
+ return finalPath;
+}
+
+char* nsMsgParseURLHost(const char* url) {
+ nsIURI* workURI = nullptr;
+ nsresult rv;
+
+ rv = nsMsgNewURL(&workURI, nsDependentCString(url));
+ if (NS_FAILED(rv) || !workURI) return nullptr;
+
+ nsAutoCString host;
+ rv = workURI->GetHost(host);
+ NS_IF_RELEASE(workURI);
+ if (NS_FAILED(rv)) return nullptr;
+
+ return ToNewCString(host);
+}
+
+char* GenerateFileNameFromURI(nsIURI* aURL) {
+ nsresult rv;
+ nsCString file;
+ nsCString spec;
+ char* returnString;
+ char* cp = nullptr;
+ char* cp1 = nullptr;
+
+ rv = aURL->GetPathQueryRef(file);
+ if (NS_SUCCEEDED(rv) && !file.IsEmpty()) {
+ char* newFile = ToNewCString(file);
+ if (!newFile) return nullptr;
+
+ // strip '/'
+ cp = PL_strrchr(newFile, '/');
+ if (cp)
+ ++cp;
+ else
+ cp = newFile;
+
+ if (*cp) {
+ if ((cp1 = PL_strchr(cp, '/'))) *cp1 = 0;
+ if ((cp1 = PL_strchr(cp, '?'))) *cp1 = 0;
+ if ((cp1 = PL_strchr(cp, '>'))) *cp1 = 0;
+ if (*cp != '\0') {
+ returnString = PL_strdup(cp);
+ PR_FREEIF(newFile);
+ return returnString;
+ }
+ } else
+ return nullptr;
+ }
+
+ cp = nullptr;
+ cp1 = nullptr;
+
+ rv = aURL->GetSpec(spec);
+ if (NS_SUCCEEDED(rv) && !spec.IsEmpty()) {
+ char* newSpec = ToNewCString(spec);
+ if (!newSpec) return nullptr;
+
+ char *cp2 = NULL, *cp3 = NULL;
+
+ // strip '"'
+ cp2 = newSpec;
+ while (*cp2 == '"') cp2++;
+ if ((cp3 = PL_strchr(cp2, '"'))) *cp3 = 0;
+
+ char* hostStr = nsMsgParseURLHost(cp2);
+ if (!hostStr) hostStr = PL_strdup(cp2);
+
+ bool isHTTP = false;
+ if (NS_SUCCEEDED(aURL->SchemeIs("http", &isHTTP)) && isHTTP) {
+ returnString = PR_smprintf("%s.html", hostStr);
+ PR_FREEIF(hostStr);
+ } else
+ returnString = hostStr;
+
+ PR_FREEIF(newSpec);
+ return returnString;
+ }
+
+ return nullptr;
+}
+
+//
+// This routine will generate a content id for use in a mail part.
+// It will take the part number passed in as well as the email
+// address. If the email address is null or invalid, we will simply
+// use netscape.com for the interesting part. The content ID's will
+// look like the following:
+//
+// Content-ID: <part1.36DF1DCE.73B5A330@netscape.com>
+//
+char* mime_gen_content_id(uint32_t aPartNum, const char* aEmailAddress) {
+ int32_t randLen = 5;
+ unsigned char rand_buf1[5];
+ unsigned char rand_buf2[5];
+ const char* domain = nullptr;
+ const char* defaultDomain = "@netscape.com";
+
+ memset(rand_buf1, 0, randLen - 1);
+ memset(rand_buf2, 0, randLen - 1);
+
+ GenerateGlobalRandomBytes(rand_buf1, randLen);
+ GenerateGlobalRandomBytes(rand_buf2, randLen);
+
+ // Find the @domain.com string...
+ if (aEmailAddress && *aEmailAddress)
+ domain = const_cast<const char*>(PL_strchr(aEmailAddress, '@'));
+
+ if (!domain) domain = defaultDomain;
+
+ char* retVal = PR_smprintf(
+ "part%d."
+ "%02X%02X%02X%02X"
+ "."
+ "%02X%02X%02X%02X"
+ "%s",
+ aPartNum, rand_buf1[0], rand_buf1[1], rand_buf1[2], rand_buf1[3],
+ rand_buf2[0], rand_buf2[1], rand_buf2[2], rand_buf2[3], domain);
+
+ return retVal;
+}
+
+void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity,
+ nsCString& uri) {
+ nsresult rv;
+ uri.Truncate();
+
+ // QueueForLater (Outbox)
+ if (aMode == nsIMsgSend::nsMsgQueueForLater ||
+ aMode == nsIMsgSend::nsMsgDeliverBackground) {
+ nsCOMPtr<nsIPrefBranch> prefs(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return;
+ rv = prefs->GetCharPref("mail.default_sendlater_uri", uri);
+ if (NS_FAILED(rv) || uri.IsEmpty())
+ uri.AssignLiteral(ANY_SERVER);
+ else {
+ // check if uri is unescaped, and if so, escape it and reset the pef.
+ if (uri.FindChar(' ') != kNotFound) {
+ uri.ReplaceSubstring(" ", "%20");
+ prefs->SetCharPref("mail.default_sendlater_uri", uri);
+ }
+ }
+ return;
+ }
+
+ if (!identity) return;
+
+ if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ rv = identity->GetDraftFolder(uri);
+ else if (aMode ==
+ nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ rv = identity->GetStationeryFolder(uri);
+ else {
+ bool doFcc = false;
+ rv = identity->GetDoFcc(&doFcc);
+ if (doFcc) rv = identity->GetFccFolder(uri);
+ }
+ return;
+}
+
+/**
+ * Check if we should use format=flowed (RFC 2646) for a mail.
+ * We will use format=flowed unless the preference tells us not to do so.
+ * In this function we set all the serialiser flags.
+ * 'formatted' is always 'true'.
+ */
+void GetSerialiserFlags(bool* flowed, bool* formatted) {
+ *flowed = false;
+ *formatted = true;
+
+ // Set format=flowed as in RFC 2646 according to the preference.
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed);
+ }
+}
+
+already_AddRefed<nsIArray> GetEmbeddedObjects(
+ mozilla::dom::Document* aDocument) {
+ nsCOMPtr<nsIMutableArray> nodes = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ if (NS_WARN_IF(!nodes)) {
+ return nullptr;
+ }
+
+ mozilla::PostContentIterator iter;
+ nsresult rv = iter.Init(aDocument->GetRootElement());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Loop through the content iterator for each content node.
+ while (!iter.IsDone()) {
+ nsINode* node = iter.GetCurrentNode();
+ if (node->IsElement()) {
+ mozilla::dom::Element* element = node->AsElement();
+
+ // See if it's an image or also include all links.
+ // Let mail decide which link to send or not
+ if (element->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::a) ||
+ (element->IsHTMLElement(nsGkAtoms::body) &&
+ element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) {
+ nodes->AppendElement(node);
+ }
+ }
+ iter.Next();
+ }
+
+ return nodes.forget();
+}
diff --git a/comm/mailnews/compose/src/nsMsgCompUtils.h b/comm/mailnews/compose/src/nsMsgCompUtils.h
new file mode 100644
index 0000000000..7197cde459
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompUtils.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgCompUtils_H_
+#define _nsMsgCompUtils_H_
+
+#include "nscore.h"
+#include "mozilla/dom/Document.h"
+#include "nsMsgCompFields.h"
+#include "nsIMsgSend.h"
+#include "nsIMsgCompUtils.h"
+
+class nsIArray;
+class nsIDocument;
+class nsIPrompt;
+
+#define ANY_SERVER "anyfolder://"
+
+// these are msg hdr property names for storing the original
+// msg uri's and disposition(replied/forwarded) when queuing
+// messages to send later.
+#define ORIG_URI_PROPERTY "origURIs"
+#define QUEUED_DISPOSITION_PROPERTY "queuedDisposition"
+
+extern mozilla::LazyLogModule Compose;
+
+class nsMsgCompUtils : public nsIMsgCompUtils {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPUTILS
+
+ nsMsgCompUtils();
+
+ private:
+ virtual ~nsMsgCompUtils();
+};
+
+already_AddRefed<nsIArray> GetEmbeddedObjects(
+ mozilla::dom::Document* aDocument);
+
+PR_BEGIN_EXTERN_C
+
+//
+// Create a file spec or file name using the name passed
+// in as a template
+//
+nsresult nsMsgCreateTempFile(const char* tFileName, nsIFile** tFile);
+char* nsMsgCreateTempFileName(const char* tFileName);
+
+//
+// Various utilities for building parts of MIME encoded
+// messages during message composition
+//
+
+nsresult mime_sanity_check_fields_recipients(const char* to, const char* cc,
+ const char* bcc,
+ const char* newsgroups);
+
+nsresult mime_sanity_check_fields(
+ const char* from, const char* reply_to, const char* to, const char* cc,
+ const char* bcc, const char* fcc, const char* newsgroups,
+ const char* followup_to, const char* /*subject*/,
+ const char* /*references*/, const char* /*organization*/,
+ const char* /*other_random_headers*/);
+
+nsresult mime_generate_headers(nsIMsgCompFields* fields,
+ nsMsgDeliverMode deliver_mode,
+ msgIWritableStructuredHeaders* headers);
+
+char* mime_make_separator(const char* prefix);
+char* mime_gen_content_id(uint32_t aPartNum, const char* aEmailAddress);
+
+bool mime_7bit_data_p(const char* string, uint32_t size);
+
+char* mime_fix_header_1(const char* string, bool addr_p, bool news_p);
+char* mime_fix_header(const char* string);
+char* mime_fix_addr_header(const char* string);
+char* mime_fix_news_header(const char* string);
+
+bool mime_type_requires_b64_p(const char* type);
+bool mime_type_needs_charset(const char* type);
+
+char* msg_make_filename_qtext(const char* srcText, bool stripCRLFs);
+
+char* RFC2231ParmFolding(const char* parmName, const char* parmValue);
+
+//
+// Informational calls...
+//
+void nsMsgMIMESetConformToStandard(bool conform_p);
+bool nsMsgMIMEGetConformToStandard(void);
+
+//
+// network service type calls...
+//
+nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const nsCString& aSpec);
+char* nsMsgGetLocalFileFromURL(const char* url);
+
+char* nsMsgParseURLHost(const char* url);
+
+char* GenerateFileNameFromURI(nsIURI* aURL);
+
+//
+// Folder calls...
+//
+void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity,
+ nsCString& uri);
+
+// Check if we should use format=flowed
+void GetSerialiserFlags(bool* flowed, bool* formatted);
+
+PR_END_EXTERN_C
+
+#endif /* _nsMsgCompUtils_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgCompose.cpp b/comm/mailnews/compose/src/nsMsgCompose.cpp
new file mode 100644
index 0000000000..2630852f5a
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompose.cpp
@@ -0,0 +1,5094 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgCompose.h"
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsISelectionController.h"
+#include "nsMsgI18N.h"
+#include "nsMsgQuote.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIDocumentEncoder.h" // for editor output flags
+#include "nsMsgCompUtils.h"
+#include "nsComposeStrings.h"
+#include "nsIMsgSend.h"
+#include "nsMailHeaders.h"
+#include "nsMsgPrompts.h"
+#include "nsMimeTypes.h"
+#include "nsICharsetConverterManager.h"
+#include "nsTextFormatter.h"
+#include "nsIHTMLEditor.h"
+#include "nsIEditor.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIDocShell.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIMIMEService.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIWindowMediator.h"
+#include "nsIURL.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+#include "nsIMsgComposeService.h"
+#include "nsIMsgComposeProgressParams.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsUnicharUtils.h"
+#include "nsNetUtil.h"
+#include "nsIContentViewer.h"
+#include "nsIMsgMdnGenerator.h"
+#include "plbase64.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgProgress.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgDatabase.h"
+#include "nsStringStream.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsITextToSubURI.h"
+#include "nsIAbManager.h"
+#include "nsCRT.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/Utf8.h"
+#include "nsStreamConverter.h"
+#include "nsIObserverService.h"
+#include "nsIProtocolHandler.h"
+#include "nsContentUtils.h"
+#include "nsStreamUtils.h"
+#include "nsIFileURL.h"
+#include "nsTextNode.h" // from dom/base
+#include "nsIParserUtils.h"
+#include "nsIStringBundle.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::mailnews;
+
+LazyLogModule Compose("Compose");
+
+static nsresult GetReplyHeaderInfo(int32_t* reply_header_type,
+ nsString& reply_header_authorwrote,
+ nsString& reply_header_ondateauthorwrote,
+ nsString& reply_header_authorwroteondate,
+ nsString& reply_header_originalmessage) {
+ nsresult rv;
+ *reply_header_type = 0;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If fetching any of the preferences fails,
+ // we return early with header_type = 0 meaning "no header".
+ rv = NS_GetLocalizedUnicharPreference(
+ prefBranch, "mailnews.reply_header_authorwrotesingle",
+ reply_header_authorwrote);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(
+ prefBranch, "mailnews.reply_header_ondateauthorwrote",
+ reply_header_ondateauthorwrote);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(
+ prefBranch, "mailnews.reply_header_authorwroteondate",
+ reply_header_authorwroteondate);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_GetLocalizedUnicharPreference(prefBranch,
+ "mailnews.reply_header_originalmessage",
+ reply_header_originalmessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefBranch->GetIntPref("mailnews.reply_header_type",
+ reply_header_type);
+}
+
+static void TranslateLineEnding(nsString& data) {
+ char16_t* rPtr; // Read pointer
+ char16_t* wPtr; // Write pointer
+ char16_t* sPtr; // Start data pointer
+ char16_t* ePtr; // End data pointer
+
+ rPtr = wPtr = sPtr = data.BeginWriting();
+ ePtr = rPtr + data.Length();
+
+ while (rPtr < ePtr) {
+ if (*rPtr == nsCRT::CR) {
+ *wPtr = nsCRT::LF;
+ if (rPtr + 1 < ePtr && *(rPtr + 1) == nsCRT::LF) rPtr++;
+ } else
+ *wPtr = *rPtr;
+
+ rPtr++;
+ wPtr++;
+ }
+
+ data.SetLength(wPtr - sPtr);
+}
+
+nsMsgCompose::nsMsgCompose() {
+ mQuotingToFollow = false;
+ mAllowRemoteContent = false;
+ mWhatHolder = 1;
+ m_window = nullptr;
+ m_editor = nullptr;
+ mQuoteStreamListener = nullptr;
+ mAutodetectCharset = false;
+ mDeleteDraft = false;
+ m_compFields =
+ nullptr; // m_compFields will be set during nsMsgCompose::Initialize
+ mType = nsIMsgCompType::New;
+
+ // For TagConvertible
+ // Read and cache pref
+ mConvertStructs = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs);
+
+ m_composeHTML = false;
+
+ mTmpAttachmentsDeleted = false;
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None;
+ mDeliverMode = 0;
+}
+
+nsMsgCompose::~nsMsgCompose() {
+ MOZ_LOG(Compose, LogLevel::Debug, ("~nsMsgCompose()"));
+ if (!m_compFields) {
+ // Uhoh. We're in an uninitialized state. Maybe initialize() failed, or
+ // was never even called.
+ return;
+ }
+ m_window = nullptr;
+ if (!mMsgSend) {
+ // This dtor can be called before mMsgSend->CreateAndSendMessage returns,
+ // tmp attachments are needed to create the message, so don't delete them.
+ DeleteTmpAttachments();
+ }
+}
+
+/* the following macro actually implement addref, release and query interface
+ * for our component. */
+NS_IMPL_ISUPPORTS(nsMsgCompose, nsIMsgCompose, nsIMsgSendListener,
+ nsISupportsWeakReference)
+
+//
+// Once we are here, convert the data which we know to be UTF-8 to UTF-16
+// for insertion into the editor
+//
+nsresult GetChildOffset(nsINode* aChild, nsINode* aParent, int32_t& aOffset) {
+ NS_ASSERTION((aChild && aParent), "bad args");
+
+ if (!aChild || !aParent) return NS_ERROR_NULL_POINTER;
+
+ nsINodeList* childNodes = aParent->ChildNodes();
+ for (uint32_t i = 0; i < childNodes->Length(); i++) {
+ nsINode* childNode = childNodes->Item(i);
+ if (childNode == aChild) {
+ aOffset = i;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+nsresult GetNodeLocation(nsINode* inChild, nsCOMPtr<nsINode>* outParent,
+ int32_t* outOffset) {
+ NS_ASSERTION((outParent && outOffset), "bad args");
+ nsresult result = NS_ERROR_NULL_POINTER;
+ if (inChild && outParent && outOffset) {
+ nsCOMPtr<nsINode> inChild2 = inChild;
+ *outParent = inChild2->GetParentNode();
+ if (*outParent) {
+ result = GetChildOffset(inChild2, *outParent, *outOffset);
+ }
+ }
+
+ return result;
+}
+
+bool nsMsgCompose::IsEmbeddedObjectSafe(const char* originalScheme,
+ const char* originalHost,
+ const char* originalPath,
+ Element* element) {
+ nsresult rv;
+
+ nsAutoString objURL;
+
+ if (!originalScheme || !originalPath) // Having a null host is OK.
+ return false;
+
+ RefPtr<HTMLImageElement> image = HTMLImageElement::FromNode(element);
+ RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(element);
+
+ if (image)
+ image->GetSrc(objURL);
+ else if (anchor)
+ anchor->GetHref(objURL);
+ else
+ return false;
+
+ if (!objURL.IsEmpty()) {
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), objURL);
+ if (NS_SUCCEEDED(rv) && uri) {
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_SUCCEEDED(rv) &&
+ scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator)) {
+ nsAutoCString host;
+ rv = uri->GetAsciiHost(host);
+ // mailbox url don't have a host therefore don't be too strict.
+ if (NS_SUCCEEDED(rv) &&
+ (host.IsEmpty() || originalHost ||
+ host.Equals(originalHost, nsCaseInsensitiveCStringComparator))) {
+ nsAutoCString path;
+ rv = uri->GetPathQueryRef(path);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString orgPath(originalPath);
+ MsgRemoveQueryPart(orgPath);
+ MsgRemoveQueryPart(path);
+ // mailbox: and JS Account URLs have a message number in
+ // the query part of "path query ref". We removed this so
+ // we're not comparing down to the message but down to the folder.
+ // Code in the frontend (in the "error" event listener in
+ // MsgComposeCommands.js that deals with unblocking images) will
+ // prompt if a part of another message is referenced.
+ // A saved message opened for reply or forwarding has a
+ // mailbox: URL.
+ // imap: URLs don't have the message number in the query, so we do
+ // compare it here.
+ // news: URLs use group and key in the query, but it's OK to compare
+ // without them.
+ return path.Equals(orgPath, nsCaseInsensitiveCStringComparator);
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+/* The purpose of this function is to mark any embedded object that wasn't a
+ RFC822 part of the original message as moz-do-not-send. That will prevent us
+ to attach data not specified by the user or not present in the original
+ message.
+*/
+nsresult nsMsgCompose::TagEmbeddedObjects(nsIEditor* aEditor) {
+ nsresult rv = NS_OK;
+ uint32_t count;
+ uint32_t i;
+
+ if (!aEditor) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<Document> document;
+ aEditor->GetDocument(getter_AddRefs(document));
+ if (!document) return NS_ERROR_FAILURE;
+ nsCOMPtr<nsIArray> aNodeList = GetEmbeddedObjects(document);
+ if (!aNodeList) return NS_ERROR_FAILURE;
+
+ if (NS_FAILED(aNodeList->GetLength(&count))) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIURI> originalUrl;
+ nsCString originalScheme;
+ nsCString originalHost;
+ nsCString originalPath;
+
+ // first, convert the rdf original msg uri into a url that represents the
+ // message...
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService));
+ if (NS_SUCCEEDED(rv)) {
+ rv = msgService->GetUrlForUri(mOriginalMsgURI, nullptr,
+ getter_AddRefs(originalUrl));
+ if (NS_SUCCEEDED(rv) && originalUrl) {
+ originalUrl->GetScheme(originalScheme);
+ originalUrl->GetAsciiHost(originalHost);
+ originalUrl->GetPathQueryRef(originalPath);
+ }
+ }
+
+ // Then compare the url of each embedded objects with the original message.
+ // If they a not coming from the original message, they should not be sent
+ // with the message.
+ for (i = 0; i < count; i++) {
+ nsCOMPtr<Element> domElement = do_QueryElementAt(aNodeList, i);
+ if (!domElement) continue;
+ if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(),
+ originalPath.get(), domElement))
+ continue; // Don't need to tag this object, it's safe to send it.
+
+ // The source of this object should not be sent with the message.
+ IgnoredErrorResult rv2;
+ domElement->SetAttribute(u"moz-do-not-send"_ns, u"true"_ns, rv2);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetAllowRemoteContent(bool* aAllowRemoteContent) {
+ NS_ENSURE_ARG_POINTER(aAllowRemoteContent);
+ *aAllowRemoteContent = mAllowRemoteContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::SetAllowRemoteContent(bool aAllowRemoteContent) {
+ mAllowRemoteContent = aAllowRemoteContent;
+ return NS_OK;
+}
+
+void nsMsgCompose::InsertDivWrappedTextAtSelection(const nsAString& aText,
+ const nsAString& classStr) {
+ NS_ASSERTION(m_editor,
+ "InsertDivWrappedTextAtSelection called, but no editor exists");
+ if (!m_editor) return;
+
+ RefPtr<Element> divElem;
+ nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor));
+
+ nsresult rv =
+ htmlEditor->CreateElementWithDefaults(u"div"_ns, getter_AddRefs(divElem));
+
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // We need the document
+ nsCOMPtr<Document> doc;
+ rv = m_editor->GetDocument(getter_AddRefs(doc));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Break up the text by newlines, and then insert text nodes followed
+ // by <br> nodes.
+ int32_t start = 0;
+ int32_t end = aText.Length();
+
+ for (;;) {
+ int32_t delimiter = aText.FindChar('\n', start);
+ if (delimiter == kNotFound) delimiter = end;
+
+ RefPtr<nsTextNode> textNode =
+ doc->CreateTextNode(Substring(aText, start, delimiter - start));
+
+ IgnoredErrorResult rv2;
+ divElem->AppendChild(*textNode, rv2);
+ if (rv2.Failed()) {
+ return;
+ }
+
+ // Now create and insert a BR
+ RefPtr<Element> brElem;
+ rv =
+ htmlEditor->CreateElementWithDefaults(u"br"_ns, getter_AddRefs(brElem));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ divElem->AppendChild(*brElem, rv2);
+ if (rv2.Failed()) {
+ return;
+ }
+
+ if (delimiter == end) break;
+ start = ++delimiter;
+ if (start == end) break;
+ }
+
+ htmlEditor->InsertElementAtSelection(divElem, true);
+ nsCOMPtr<nsINode> parent;
+ int32_t offset;
+
+ rv = GetNodeLocation(divElem, address_of(parent), &offset);
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Selection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+
+ if (selection) selection->CollapseInLimiter(parent, offset + 1);
+ }
+ if (divElem) {
+ RefPtr<Element> divElem2 = divElem;
+ IgnoredErrorResult rv2;
+ divElem2->SetAttribute(u"class"_ns, classStr, rv2);
+ }
+}
+
+/*
+ * The following function replaces <plaintext> tags with <x-plaintext>.
+ * <plaintext> is a funny beast: It leads to everything following it
+ * being displayed verbatim, even a </plaintext> tag is ignored.
+ */
+static void remove_plaintext_tag(nsString& body) {
+ // Replace all <plaintext> and </plaintext> tags.
+ int32_t index = 0;
+ bool replaced = false;
+ while ((index = body.LowerCaseFindASCII("<plaintext", index)) != kNotFound) {
+ body.Insert(u"x-", index + 1);
+ index += 12;
+ replaced = true;
+ }
+ if (replaced) {
+ index = 0;
+ while ((index = body.LowerCaseFindASCII("</plaintext", index)) !=
+ kNotFound) {
+ body.Insert(u"x-", index + 2);
+ index += 13;
+ }
+ }
+}
+
+static void remove_conditional_CSS(const nsAString& in, nsAString& out) {
+ nsCOMPtr<nsIParserUtils> parserUtils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ parserUtils->RemoveConditionalCSS(in, out);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix, nsString& aBuf,
+ nsString& aSignature, bool aQuoted,
+ bool aHTMLEditor) {
+ NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor");
+ NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED);
+
+ // First, get the nsIEditor interface for future use
+ nsCOMPtr<nsINode> nodeInserted;
+
+ TranslateLineEnding(aPrefix);
+ TranslateLineEnding(aBuf);
+ TranslateLineEnding(aSignature);
+
+ m_editor->EnableUndo(false);
+
+ // Ok - now we need to figure out the charset of the aBuf we are going to send
+ // into the editor shell. There are I18N calls to sniff the data and then we
+ // need to call the new routine in the editor that will allow us to send in
+ // the charset
+ //
+
+ // Now, insert it into the editor...
+ RefPtr<HTMLEditor> htmlEditor = m_editor->AsHTMLEditor();
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ m_identity->GetReplyOnTop(&reply_on_top);
+ m_identity->GetSigBottom(&sig_bottom);
+ bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
+ bool isForwarded = (mType == nsIMsgCompType::ForwardInline);
+
+ // When in paragraph mode, don't call InsertLineBreak() since that inserts
+ // a full paragraph instead of just a line break since we switched
+ // the default paragraph separator to "p".
+ bool paragraphMode =
+ mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false);
+
+ if (aQuoted) {
+ if (!aPrefix.IsEmpty()) {
+ if (!aHTMLEditor) aPrefix.AppendLiteral("\n");
+
+ int32_t reply_on_top = 0;
+ m_identity->GetReplyOnTop(&reply_on_top);
+ if (reply_on_top == 1) {
+ // HTML editor eats one line break but not a whole paragraph.
+ if (aHTMLEditor && !paragraphMode) htmlEditor->InsertLineBreak();
+
+ // add one newline if a signature comes before the quote, two otherwise
+ bool includeSignature = true;
+ bool sig_bottom = true;
+ bool attachFile = false;
+ nsString prefSigText;
+
+ m_identity->GetSigOnReply(&includeSignature);
+ m_identity->GetSigBottom(&sig_bottom);
+ m_identity->GetHtmlSigText(prefSigText);
+ nsresult rv = m_identity->GetAttachSignature(&attachFile);
+ if (!paragraphMode || !aHTMLEditor) {
+ if (includeSignature && !sig_bottom &&
+ ((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty()))
+ htmlEditor->InsertLineBreak();
+ else {
+ htmlEditor->InsertLineBreak();
+ htmlEditor->InsertLineBreak();
+ }
+ }
+ }
+
+ InsertDivWrappedTextAtSelection(aPrefix, u"moz-cite-prefix"_ns);
+ }
+
+ if (!aBuf.IsEmpty()) {
+ // This leaves the caret at the right place to insert a bottom signature.
+ if (aHTMLEditor) {
+ nsAutoString body(aBuf);
+ remove_plaintext_tag(body);
+ htmlEditor->InsertAsCitedQuotation(body, mCiteReference, true,
+ getter_AddRefs(nodeInserted));
+ } else {
+ htmlEditor->InsertAsQuotation(aBuf, getter_AddRefs(nodeInserted));
+ }
+ }
+
+ (void)TagEmbeddedObjects(htmlEditor);
+
+ if (!aSignature.IsEmpty()) {
+ // we cannot add it on top earlier, because TagEmbeddedObjects will mark
+ // all images in the signature as "moz-do-not-send"
+ if (sigOnTop) MoveToBeginningOfDocument();
+
+ if (aHTMLEditor) {
+ bool oldAllow;
+ GetAllowRemoteContent(&oldAllow);
+ SetAllowRemoteContent(true);
+ htmlEditor->InsertHTML(aSignature);
+ SetAllowRemoteContent(oldAllow);
+ } else {
+ htmlEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
+ }
+
+ if (sigOnTop) htmlEditor->EndOfDocument();
+ }
+ } else {
+ if (aHTMLEditor) {
+ if (isForwarded &&
+ Substring(aBuf, 0, sizeof(MIME_FORWARD_HTML_PREFIX) - 1)
+ .EqualsLiteral(MIME_FORWARD_HTML_PREFIX)) {
+ // We assign the opening tag inside "<HTML><BODY><BR><BR>" before the
+ // two <br> elements.
+ // This is a bit hacky but we know that the MIME code prepares the
+ // forwarded content like this:
+ // <HTML><BODY><BR><BR> + forwarded header + header table.
+ // Note: We only do this when we prepare the message to be forwarded,
+ // a re-opened saved draft of a forwarded message does not repeat this.
+ nsString divTag;
+ divTag.AssignLiteral("<div class=\"moz-forward-container\">");
+ aBuf.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX) - 1 - 8);
+ }
+ remove_plaintext_tag(aBuf);
+
+ bool stripConditionalCSS = mozilla::Preferences::GetBool(
+ "mail.html_sanitize.drop_conditional_css", true);
+
+ if (stripConditionalCSS) {
+ nsString newBody;
+ remove_conditional_CSS(aBuf, newBody);
+ htmlEditor->RebuildDocumentFromSource(newBody);
+ } else {
+ htmlEditor->RebuildDocumentFromSource(aBuf);
+ }
+
+ // When forwarding a message as inline, or editing as new (which could
+ // contain unsanitized remote content), tag any embedded objects
+ // with moz-do-not-send=true so they don't get attached upon send.
+ if (isForwarded || mType == nsIMsgCompType::EditAsNew)
+ (void)TagEmbeddedObjects(htmlEditor);
+
+ if (!aSignature.IsEmpty()) {
+ if (isForwarded && sigOnTop) {
+ // Use our own function, nsEditor::BeginningOfDocument() would
+ // position into the <div class="moz-forward-container"> we've just
+ // created.
+ MoveToBeginningOfDocument();
+ } else {
+ // Use our own function, nsEditor::EndOfDocument() would position
+ // into the <div class="moz-forward-container"> we've just created.
+ MoveToEndOfDocument();
+ }
+
+ bool oldAllow;
+ GetAllowRemoteContent(&oldAllow);
+ SetAllowRemoteContent(true);
+ htmlEditor->InsertHTML(aSignature);
+ SetAllowRemoteContent(oldAllow);
+
+ if (isForwarded && sigOnTop) htmlEditor->EndOfDocument();
+ } else
+ htmlEditor->EndOfDocument();
+ } else {
+ bool sigOnTopInserted = false;
+ if (isForwarded && sigOnTop && !aSignature.IsEmpty()) {
+ htmlEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
+ htmlEditor->EndOfDocument();
+ sigOnTopInserted = true;
+ }
+
+ if (!aBuf.IsEmpty()) {
+ nsresult rv;
+ RefPtr<Element> divElem;
+ RefPtr<Element> extraBr;
+
+ if (isForwarded) {
+ // Special treatment for forwarded messages: Part 1.
+ // Create a <div> of the required class.
+ rv = htmlEditor->CreateElementWithDefaults(u"div"_ns,
+ getter_AddRefs(divElem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString attributeName;
+ nsAutoString attributeValue;
+ attributeName.AssignLiteral("class");
+ attributeValue.AssignLiteral("moz-forward-container");
+ IgnoredErrorResult rv1;
+ divElem->SetAttribute(attributeName, attributeValue, rv1);
+
+ // We can't insert an empty <div>, so fill it with something.
+ rv = htmlEditor->CreateElementWithDefaults(u"br"_ns,
+ getter_AddRefs(extraBr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult rv2;
+ divElem->AppendChild(*extraBr, rv2);
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+
+ // Insert the non-empty <div> into the DOM.
+ rv = htmlEditor->InsertElementAtSelection(divElem, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Position into the div, so out content goes there.
+ RefPtr<Selection> selection;
+ htmlEditor->GetSelection(getter_AddRefs(selection));
+ rv = selection->CollapseInLimiter(divElem, 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = htmlEditor->InsertTextWithQuotations(aBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isForwarded) {
+ // Special treatment for forwarded messages: Part 2.
+ if (sigOnTopInserted) {
+ // Sadly the M-C editor inserts a <br> between the <div> for the
+ // signature and this <div>, so remove the <br> we don't want.
+ nsCOMPtr<nsINode> brBeforeDiv;
+ nsAutoString tagLocalName;
+ brBeforeDiv = divElem->GetPreviousSibling();
+ if (brBeforeDiv) {
+ tagLocalName = brBeforeDiv->LocalName();
+ if (tagLocalName.EqualsLiteral("br")) {
+ rv = htmlEditor->DeleteNode(brBeforeDiv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // Clean up the <br> we inserted.
+ rv = htmlEditor->DeleteNode(extraBr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Use our own function instead of nsEditor::EndOfDocument() because
+ // we don't want to position at the end of the div we've just created.
+ // It's OK to use, even if we're not forwarding and didn't create a
+ // <div>.
+ rv = MoveToEndOfDocument();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) {
+ htmlEditor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
+ }
+ }
+ }
+
+ if (aBuf.IsEmpty())
+ htmlEditor->BeginningOfDocument();
+ else {
+ switch (reply_on_top) {
+ // This should set the cursor after the body but before the sig
+ case 0: {
+ if (!htmlEditor) {
+ htmlEditor->BeginningOfDocument();
+ break;
+ }
+
+ RefPtr<Selection> selection;
+ nsCOMPtr<nsINode> parent;
+ int32_t offset;
+ nsresult rv;
+
+ // get parent and offset of mailcite
+ rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
+ if (NS_FAILED(rv) || (!parent)) {
+ htmlEditor->BeginningOfDocument();
+ break;
+ }
+
+ // get selection
+ htmlEditor->GetSelection(getter_AddRefs(selection));
+ if (!selection) {
+ htmlEditor->BeginningOfDocument();
+ break;
+ }
+
+ // place selection after mailcite
+ selection->CollapseInLimiter(parent, offset + 1);
+
+ // insert a break at current selection
+ if (!paragraphMode || !aHTMLEditor) htmlEditor->InsertLineBreak();
+
+ // i'm not sure if you need to move the selection back to before the
+ // break. expirement.
+ selection->CollapseInLimiter(parent, offset + 1);
+
+ break;
+ }
+
+ case 2: {
+ nsCOMPtr<nsIEditor> editor(htmlEditor); // Strong reference.
+ editor->SelectAll();
+ break;
+ }
+
+ // This should set the cursor to the top!
+ default: {
+ MoveToBeginningOfDocument();
+ break;
+ }
+ }
+ }
+
+ nsCOMPtr<nsISelectionController> selCon;
+ htmlEditor->GetSelectionController(getter_AddRefs(selCon));
+
+ if (selCon)
+ selCon->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_ANCHOR_REGION, true);
+
+ htmlEditor->EnableUndo(true);
+ SetBodyModified(false);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ nsCOMPtr<nsIMsgComposeService> composeService(
+ do_GetService("@mozilla.org/messengercompose;1"));
+ composeService->TimeStamp(
+ "Finished inserting data into the editor. The window is finally ready!",
+ false);
+#endif
+ return NS_OK;
+}
+
+/**
+ * Check the identity pref to include signature on replies and forwards.
+ */
+bool nsMsgCompose::CheckIncludeSignaturePrefs(nsIMsgIdentity* identity) {
+ bool includeSignature = true;
+ switch (mType) {
+ case nsIMsgCompType::ForwardInline:
+ case nsIMsgCompType::ForwardAsAttachment:
+ identity->GetSigOnForward(&includeSignature);
+ break;
+ case nsIMsgCompType::Reply:
+ case nsIMsgCompType::ReplyAll:
+ case nsIMsgCompType::ReplyToList:
+ case nsIMsgCompType::ReplyToGroup:
+ case nsIMsgCompType::ReplyToSender:
+ case nsIMsgCompType::ReplyToSenderAndGroup:
+ identity->GetSigOnReply(&includeSignature);
+ break;
+ }
+ return includeSignature;
+}
+
+nsresult nsMsgCompose::SetQuotingToFollow(bool aVal) {
+ mQuotingToFollow = aVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetQuotingToFollow(bool* quotingToFollow) {
+ NS_ENSURE_ARG(quotingToFollow);
+ *quotingToFollow = mQuotingToFollow;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::Initialize(nsIMsgComposeParams* aParams,
+ mozIDOMWindowProxy* aWindow, nsIDocShell* aDocShell) {
+ NS_ENSURE_ARG_POINTER(aParams);
+ nsresult rv;
+
+ aParams->GetIdentity(getter_AddRefs(m_identity));
+
+ if (aWindow) {
+ m_window = aWindow;
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIDocShellTreeItem> treeItem = window->GetDocShell();
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
+ if (NS_FAILED(rv)) return rv;
+
+ m_baseWindow = do_QueryInterface(treeOwner);
+ }
+
+ aParams->GetAutodetectCharset(&mAutodetectCharset);
+
+ MSG_ComposeFormat format;
+ aParams->GetFormat(&format);
+
+ MSG_ComposeType type;
+ aParams->GetType(&type);
+
+ nsCString originalMsgURI;
+ aParams->GetOriginalMsgURI(originalMsgURI);
+ aParams->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr));
+
+ nsCOMPtr<nsIMsgCompFields> composeFields;
+ aParams->GetComposeFields(getter_AddRefs(composeFields));
+
+ nsCOMPtr<nsIMsgComposeService> composeService =
+ do_GetService("@mozilla.org/messengercompose;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifndef MOZ_SUITE
+ if (m_composeHTML) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_HTML, 1);
+ } else {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TB_COMPOSE_FORMAT_PLAIN_TEXT, 1);
+ }
+ Telemetry::Accumulate(Telemetry::TB_COMPOSE_TYPE, type);
+#endif
+
+ if (composeFields) {
+ nsAutoCString draftId; // will get set for drafts and templates
+ rv = composeFields->GetDraftId(draftId);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set return receipt flag and type, and if we should attach a vCard
+ // by checking the identity prefs - but don't clobber the values for
+ // drafts and templates as they were set up already by mime when
+ // initializing the message.
+ if (m_identity && draftId.IsEmpty() && type != nsIMsgCompType::Template) {
+ bool requestReturnReceipt = false;
+ rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetReturnReceipt(requestReturnReceipt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t receiptType = nsIMsgMdnGenerator::eDntType;
+ rv = m_identity->GetReceiptHeaderType(&receiptType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetReceiptHeaderType(receiptType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool requestDSN = false;
+ rv = m_identity->GetRequestDSN(&requestDSN);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetDSN(requestDSN);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool attachVCard;
+ rv = m_identity->GetAttachVCard(&attachVCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = composeFields->SetAttachVCard(attachVCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+ aParams->GetSendListener(getter_AddRefs(externalSendListener));
+ if (externalSendListener) AddMsgSendListener(externalSendListener);
+
+ nsString smtpPassword;
+ aParams->GetSmtpPassword(smtpPassword);
+ mSmtpPassword = smtpPassword;
+
+ aParams->GetHtmlToQuote(mHtmlToQuote);
+
+ if (aDocShell) {
+ mDocShell = aDocShell;
+ // register the compose object with the compose service
+ rv = composeService->RegisterComposeDocShell(aDocShell, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return CreateMessage(originalMsgURI, type, composeFields);
+}
+
+NS_IMETHODIMP
+nsMsgCompose::RegisterStateListener(
+ nsIMsgComposeStateListener* aStateListener) {
+ NS_ENSURE_ARG_POINTER(aStateListener);
+ mStateListeners.AppendElement(aStateListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::UnregisterStateListener(
+ nsIMsgComposeStateListener* aStateListener) {
+ NS_ENSURE_ARG_POINTER(aStateListener);
+ return mStateListeners.RemoveElement(aStateListener) ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+// Added to allow easier use of the nsIMsgSendListener
+NS_IMETHODIMP nsMsgCompose::AddMsgSendListener(
+ nsIMsgSendListener* aMsgSendListener) {
+ NS_ENSURE_ARG_POINTER(aMsgSendListener);
+ mExternalSendListeners.AppendElement(aMsgSendListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::RemoveMsgSendListener(
+ nsIMsgSendListener* aMsgSendListener) {
+ NS_ENSURE_ARG_POINTER(aMsgSendListener);
+ return mExternalSendListeners.RemoveElement(aMsgSendListener)
+ ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::SendMsgToServer(MSG_DeliverMode deliverMode,
+ nsIMsgIdentity* identity, const char* accountKey,
+ Promise** aPromise) {
+ nsresult rv = NS_OK;
+
+ // clear saved message id if sending, so we don't send out the same
+ // message-id.
+ if (deliverMode == nsIMsgCompDeliverMode::Now ||
+ deliverMode == nsIMsgCompDeliverMode::Later ||
+ deliverMode == nsIMsgCompDeliverMode::Background)
+ m_compFields->SetMessageId("");
+
+ if (m_compFields && identity) {
+ // Pref values are supposed to be stored as UTF-8, so no conversion
+ nsCString email;
+ nsString fullName;
+ nsString organization;
+
+ identity->GetEmail(email);
+ identity->GetFullName(fullName);
+ identity->GetOrganization(organization);
+
+ const char* pFrom = m_compFields->GetFrom();
+ if (!pFrom || !*pFrom) {
+ nsCString sender;
+ MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), email, sender);
+ m_compFields->SetFrom(sender.IsEmpty() ? email.get() : sender.get());
+ }
+
+ m_compFields->SetOrganization(organization);
+
+ // We need an nsIMsgSend instance to send the message. Allow extensions
+ // to override the default SMTP sender by observing mail-set-sender.
+ mMsgSend = nullptr;
+ mDeliverMode = deliverMode; // save for possible access by observer.
+
+ // Allow extensions to specify an outgoing server.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(observerService);
+
+ // Assemble a string with sending parameters.
+ nsAutoString sendParms;
+
+ // First parameter: account key. This may be null.
+ sendParms.AppendASCII(accountKey && *accountKey ? accountKey : "");
+ sendParms.Append(',');
+
+ // Second parameter: deliverMode.
+ sendParms.AppendInt(deliverMode);
+ sendParms.Append(',');
+
+ // Third parameter: identity (as identity key).
+ nsAutoCString identityKey;
+ identity->GetKey(identityKey);
+ sendParms.AppendASCII(identityKey.get());
+
+ observerService->NotifyObservers(NS_ISUPPORTS_CAST(nsIMsgCompose*, this),
+ "mail-set-sender", sendParms.get());
+
+ if (!mMsgSend)
+ mMsgSend = do_CreateInstance("@mozilla.org/messengercompose/send;1");
+
+ if (mMsgSend) {
+ nsString bodyString;
+ rv = m_compFields->GetBody(bodyString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the listener for the send operation...
+ nsCOMPtr<nsIMsgComposeSendListener> composeSendListener =
+ do_CreateInstance(
+ "@mozilla.org/messengercompose/composesendlistener;1");
+ if (!composeSendListener) return NS_ERROR_OUT_OF_MEMORY;
+
+ // right now, AutoSaveAsDraft is identical to SaveAsDraft as
+ // far as the msg send code is concerned. This way, we don't have
+ // to add an nsMsgDeliverMode for autosaveasdraft, and add cases for
+ // it in the msg send code.
+ if (deliverMode == nsIMsgCompDeliverMode::AutoSaveAsDraft)
+ deliverMode = nsIMsgCompDeliverMode::SaveAsDraft;
+
+ RefPtr<nsIMsgCompose> msgCompose(this);
+ composeSendListener->SetMsgCompose(msgCompose);
+ composeSendListener->SetDeliverMode(deliverMode);
+
+ if (mProgress) {
+ nsCOMPtr<nsIWebProgressListener> progressListener =
+ do_QueryInterface(composeSendListener);
+ mProgress->RegisterListener(progressListener);
+ }
+
+ // If we are composing HTML, then this should be sent as
+ // multipart/related which means we pass the editor into the
+ // backend...if not, just pass nullptr
+ //
+ nsCOMPtr<nsIMsgSendListener> sendListener =
+ do_QueryInterface(composeSendListener);
+ RefPtr<mozilla::dom::Promise> promise;
+ rv = mMsgSend->CreateAndSendMessage(
+ m_composeHTML ? m_editor.get() : nullptr, identity, accountKey,
+ m_compFields, false, false, (nsMsgDeliverMode)deliverMode, nullptr,
+ m_composeHTML ? TEXT_HTML : TEXT_PLAIN, bodyString, m_window,
+ mProgress, sendListener, mSmtpPassword, mOriginalMsgURI, mType,
+ getter_AddRefs(promise));
+ promise.forget(aPromise);
+ } else
+ rv = NS_ERROR_FAILURE;
+ } else
+ rv = NS_ERROR_NOT_INITIALIZED;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode,
+ nsIMsgIdentity* identity,
+ const char* accountKey,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgProgress* progress,
+ Promise** aPromise) {
+ NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED);
+ nsresult rv = NS_OK;
+
+ // Set content type based on which type of compose window we had.
+ nsString contentType = (m_composeHTML) ? u"text/html"_ns : u"text/plain"_ns;
+ nsString msgBody;
+ if (m_editor) {
+ // Reset message body previously stored in the compose fields
+ m_compFields->SetBody(EmptyString());
+
+ uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak |
+ nsIDocumentEncoder::OutputLFLineBreak;
+
+ if (m_composeHTML) {
+ flags |= nsIDocumentEncoder::OutputFormatted |
+ nsIDocumentEncoder::OutputDisallowLineBreaking;
+ } else {
+ bool flowed, formatted;
+ GetSerialiserFlags(&flowed, &formatted);
+ if (flowed) flags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (formatted) flags |= nsIDocumentEncoder::OutputFormatted;
+ flags |= nsIDocumentEncoder::OutputDisallowLineBreaking;
+ // Don't lose NBSP in the plain text encoder.
+ flags |= nsIDocumentEncoder::OutputPersistNBSP;
+ }
+ nsresult rv = m_editor->OutputToString(contentType, flags, msgBody);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ m_compFields->GetBody(msgBody);
+ }
+ if (!msgBody.IsEmpty()) {
+ // Ensure body ends in CRLF to avoid SMTP server timeout when sent.
+ if (!StringEndsWith(msgBody, u"\r\n"_ns)) msgBody.AppendLiteral("\r\n");
+ bool isAsciiOnly = mozilla::IsAsciiNullTerminated(
+ static_cast<const char16_t*>(msgBody.get()));
+
+ if (m_compFields->GetForceMsgEncoding()) {
+ isAsciiOnly = false;
+ }
+
+ m_compFields->SetBodyIsAsciiOnly(isAsciiOnly);
+ m_compFields->SetBody(msgBody);
+ }
+
+ // Let's open the progress dialog
+ if (progress) {
+ mProgress = progress;
+
+ if (deliverMode != nsIMsgCompDeliverMode::AutoSaveAsDraft) {
+ nsAutoString msgSubject;
+ m_compFields->GetSubject(msgSubject);
+
+ bool showProgress = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ prefBranch->GetBoolPref("mailnews.show_send_progress", &showProgress);
+ if (showProgress) {
+ nsCOMPtr<nsIMsgComposeProgressParams> params = do_CreateInstance(
+ "@mozilla.org/messengercompose/composeprogressparameters;1", &rv);
+ if (NS_FAILED(rv) || !params) return NS_ERROR_FAILURE;
+
+ params->SetSubject(msgSubject.get());
+ params->SetDeliveryMode(deliverMode);
+
+ mProgress->OpenProgressDialog(
+ m_window, aMsgWindow,
+ "chrome://messenger/content/messengercompose/sendProgress.xhtml",
+ false, params);
+ }
+ }
+ }
+
+ mProgress->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_START, NS_OK);
+ }
+
+ bool attachVCard = false;
+ m_compFields->GetAttachVCard(&attachVCard);
+
+ if (attachVCard && identity &&
+ (deliverMode == nsIMsgCompDeliverMode::Now ||
+ deliverMode == nsIMsgCompDeliverMode::Later ||
+ deliverMode == nsIMsgCompDeliverMode::Background)) {
+ nsCString escapedVCard;
+ // make sure, if there is no card, this returns an empty string, or
+ // NS_ERROR_FAILURE
+ rv = identity->GetEscapedVCard(escapedVCard);
+
+ if (NS_SUCCEEDED(rv) && !escapedVCard.IsEmpty()) {
+ nsCString vCardUrl;
+ vCardUrl = "data:text/vcard;charset=utf-8;base64,";
+ nsCString unescapedData;
+ MsgUnescapeString(escapedVCard, 0, unescapedData);
+ char* result = PL_Base64Encode(unescapedData.get(), 0, nullptr);
+ vCardUrl += result;
+ PR_Free(result);
+
+ nsCOMPtr<nsIMsgAttachment> attachment =
+ do_CreateInstance("@mozilla.org/messengercompose/attachment;1", &rv);
+ if (NS_SUCCEEDED(rv) && attachment) {
+ // [comment from 4.x]
+ // Send the vCard out with a filename which distinguishes this user.
+ // e.g. jsmith.vcf The main reason to do this is for interop with
+ // Eudora, which saves off the attachments separately from the message
+ // body
+ nsCString userid;
+ (void)identity->GetEmail(userid);
+ int32_t index = userid.FindChar('@');
+ if (index != kNotFound) userid.SetLength(index);
+
+ if (userid.IsEmpty())
+ attachment->SetName(u"vcard.vcf"_ns);
+ else {
+ // Replace any dot with underscore to stop vCards
+ // generating false positives with some heuristic scanners
+ userid.ReplaceChar('.', '_');
+ userid.AppendLiteral(".vcf");
+ attachment->SetName(NS_ConvertASCIItoUTF16(userid));
+ }
+
+ attachment->SetUrl(vCardUrl);
+ m_compFields->AddAttachment(attachment);
+ }
+ }
+ }
+
+ // Save the identity being sent for later use.
+ m_identity = identity;
+
+ RefPtr<mozilla::dom::Promise> promise;
+ rv = SendMsgToServer(deliverMode, identity, accountKey,
+ getter_AddRefs(promise));
+
+ RefPtr<nsMsgCompose> self = this;
+ auto handleFailure = [self = std::move(self), deliverMode](nsresult rv) {
+ self->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, rv);
+ nsCOMPtr<nsIMsgSendReport> sendReport;
+ if (self->mMsgSend)
+ self->mMsgSend->GetSendReport(getter_AddRefs(sendReport));
+ if (sendReport) {
+ nsresult theError;
+ sendReport->DisplayReport(self->m_window, true, true, &theError);
+ } else {
+ // If we come here it's because we got an error before we could initialize
+ // a send report! Let's try our best...
+ switch (deliverMode) {
+ case nsIMsgCompDeliverMode::Later:
+ nsMsgDisplayMessageByName(self->m_window, "unableToSendLater");
+ break;
+ case nsIMsgCompDeliverMode::AutoSaveAsDraft:
+ case nsIMsgCompDeliverMode::SaveAsDraft:
+ nsMsgDisplayMessageByName(self->m_window, "unableToSaveDraft");
+ break;
+ case nsIMsgCompDeliverMode::SaveAsTemplate:
+ nsMsgDisplayMessageByName(self->m_window, "unableToSaveTemplate");
+ break;
+
+ default:
+ nsMsgDisplayMessageByName(self->m_window, "sendFailed");
+ break;
+ }
+ }
+ if (self->mProgress) self->mProgress->CloseProgressDialog(true);
+
+ self->DeleteTmpAttachments();
+ };
+ if (promise) {
+ RefPtr<DomPromiseListener> listener = new DomPromiseListener(
+ [&](JSContext*, JS::Handle<JS::Value>) { DeleteTmpAttachments(); },
+ handleFailure);
+ promise->AppendNativeHandler(listener);
+ promise.forget(aPromise);
+ } else if (NS_FAILED(rv)) {
+ handleFailure(rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(bool* aDeleteDraft) {
+ NS_ENSURE_ARG_POINTER(aDeleteDraft);
+ *aDeleteDraft = mDeleteDraft;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(bool aDeleteDraft) {
+ mDeleteDraft = aDeleteDraft;
+ return NS_OK;
+}
+
+bool nsMsgCompose::IsLastWindow() {
+ nsresult rv;
+ bool more;
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISimpleEnumerator> windowEnumerator;
+ rv = windowMediator->GetEnumerator(nullptr,
+ getter_AddRefs(windowEnumerator));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISupports> isupports;
+
+ if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports))))
+ if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)))
+ return !more;
+ }
+ }
+ return true;
+}
+
+NS_IMETHODIMP nsMsgCompose::CloseWindow(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgComposeService> composeService =
+ do_GetService("@mozilla.org/messengercompose;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // unregister the compose object with the compose service
+ rv = composeService->UnregisterComposeDocShell(mDocShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDocShell = nullptr;
+
+ // ensure that the destructor of nsMsgSend is invoked to remove
+ // temporary files.
+ mMsgSend = nullptr;
+
+ // We are going away for real, we need to do some clean up first
+ if (m_baseWindow) {
+ if (m_editor) {
+ // The editor will be destroyed during the close window.
+ // Set it to null to be sure we won't use it anymore.
+ m_editor = nullptr;
+ }
+ nsCOMPtr<nsIBaseWindow> window = m_baseWindow.forget();
+ rv = window->Destroy();
+ }
+
+ m_window = nullptr;
+ return rv;
+}
+
+nsresult nsMsgCompose::Abort() {
+ if (mMsgSend) mMsgSend->Abort();
+
+ if (mProgress) mProgress->CloseProgressDialog(true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetEditor(nsIEditor** aEditor) {
+ NS_IF_ADDREF(*aEditor = m_editor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetEditor(nsIEditor* aEditor) {
+ m_editor = aEditor;
+ return NS_OK;
+}
+
+// This used to be called BEFORE editor was created
+// (it did the loadURI that triggered editor creation)
+// It is called from JS after editor creation
+// (loadURI is done in JS)
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsMsgCompose::InitEditor(
+ nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow) {
+ NS_ENSURE_ARG_POINTER(aEditor);
+ NS_ENSURE_ARG_POINTER(aContentWindow);
+ nsresult rv;
+
+ m_editor = aEditor;
+
+ aEditor->SetDocumentCharacterSet("UTF-8"_ns);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ nsPIDOMWindowOuter::From(aContentWindow);
+
+ nsIDocShell* docShell = window->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+
+ bool quotingToFollow = false;
+ GetQuotingToFollow(&quotingToFollow);
+ if (quotingToFollow)
+ return BuildQuotedMessageAndSignature();
+ else {
+ NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady,
+ NS_OK);
+ rv = BuildBodyMessageAndSignature();
+ NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady,
+ NS_OK);
+ return rv;
+ }
+}
+
+nsresult nsMsgCompose::GetBodyModified(bool* modified) {
+ nsresult rv;
+
+ if (!modified) return NS_ERROR_NULL_POINTER;
+
+ *modified = true;
+
+ if (m_editor) {
+ rv = m_editor->GetDocumentModified(modified);
+ if (NS_FAILED(rv)) *modified = true;
+ }
+
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+nsMsgCompose::SetBodyModified(bool modified) {
+ nsresult rv = NS_OK;
+
+ if (m_editor) {
+ nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference.
+ if (modified) {
+ int32_t modCount = 0;
+ editor->GetModificationCount(&modCount);
+ if (modCount == 0) editor->IncrementModificationCount(1);
+ } else
+ editor->ResetModificationCount();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetDomWindow(mozIDOMWindowProxy** aDomWindow) {
+ NS_IF_ADDREF(*aDomWindow = m_window);
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields** aCompFields) {
+ NS_IF_ADDREF(*aCompFields = (nsIMsgCompFields*)m_compFields);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetComposeHTML(bool* aComposeHTML) {
+ *aComposeHTML = m_composeHTML;
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::GetWrapLength(int32_t* aWrapLength) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength);
+}
+
+nsresult nsMsgCompose::CreateMessage(const nsACString& originalMsgURI,
+ MSG_ComposeType type,
+ nsIMsgCompFields* compFields) {
+ nsresult rv = NS_OK;
+ mType = type;
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None;
+
+ mDeleteDraft = (type == nsIMsgCompType::Draft);
+ nsAutoCString msgUri(originalMsgURI);
+ bool fileUrl = StringBeginsWith(msgUri, "file:"_ns);
+ int32_t typeIndex = msgUri.Find("type=application/x-message-display");
+ if (typeIndex != kNotFound && typeIndex > 0) {
+ // Strip out type=application/x-message-display because it confuses libmime.
+ msgUri.Cut(typeIndex, sizeof("type=application/x-message-display"));
+ if (fileUrl) // we're dealing with an .eml file msg
+ {
+ // We have now removed the type from the uri. Make sure we don't have
+ // an uri with "&&" now. If we do, remove the second '&'.
+ if (msgUri.CharAt(typeIndex) == '&') msgUri.Cut(typeIndex, 1);
+ // Remove possible trailing '?'.
+ if (msgUri.CharAt(msgUri.Length() - 1) == '?')
+ msgUri.Cut(msgUri.Length() - 1, 1);
+ } else // we're dealing with a message/rfc822 attachment
+ {
+ // nsURLFetcher will check for "realtype=message/rfc822" and will set the
+ // content type to message/rfc822 in the forwarded message.
+ msgUri.AppendLiteral("&realtype=message/rfc822");
+ }
+ }
+
+ if (compFields) {
+ m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields);
+ } else {
+ m_compFields = new nsMsgCompFields();
+ }
+
+ if (m_identity && mType != nsIMsgCompType::Draft) {
+ // Setup reply-to field.
+ nsCString replyTo;
+ m_identity->GetReplyTo(replyTo);
+ if (!replyTo.IsEmpty()) {
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()),
+ replyTo, resultStr);
+ if (!resultStr.IsEmpty()) {
+ replyTo.Append(',');
+ replyTo.Append(resultStr);
+ }
+ m_compFields->SetReplyTo(replyTo.get());
+ }
+
+ // Setup auto-Cc field.
+ bool doCc;
+ m_identity->GetDoCc(&doCc);
+ if (doCc) {
+ nsCString ccList;
+ m_identity->GetDoCcList(ccList);
+
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()),
+ ccList, resultStr);
+ if (!resultStr.IsEmpty()) {
+ ccList.Append(',');
+ ccList.Append(resultStr);
+ }
+ m_compFields->SetCc(ccList.get());
+ }
+
+ // Setup auto-Bcc field.
+ bool doBcc;
+ m_identity->GetDoBcc(&doBcc);
+ if (doBcc) {
+ nsCString bccList;
+ m_identity->GetDoBccList(bccList);
+
+ nsCString resultStr;
+ RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()),
+ bccList, resultStr);
+ if (!resultStr.IsEmpty()) {
+ bccList.Append(',');
+ bccList.Append(resultStr);
+ }
+ m_compFields->SetBcc(bccList.get());
+ }
+ }
+
+ if (mType == nsIMsgCompType::Draft) {
+ nsCString curDraftIdURL;
+ rv = m_compFields->GetDraftId(curDraftIdURL);
+ // Skip if no draft id (probably a new draft msg).
+ if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) {
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(msgDBHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "CreateMessage can't get msg header DB interface pointer.");
+ if (msgDBHdr) {
+ nsCString queuedDisposition;
+ msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY,
+ queuedDisposition);
+ // We need to retrieve the original URI from the database so we can
+ // set the disposition flags correctly if the draft is a reply or
+ // forwarded message.
+ nsCString originalMsgURIfromDB;
+ msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, originalMsgURIfromDB);
+ mOriginalMsgURI = originalMsgURIfromDB;
+ if (!queuedDisposition.IsEmpty()) {
+ if (queuedDisposition.EqualsLiteral("replied"))
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied;
+ else if (queuedDisposition.EqualsLiteral("forward"))
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded;
+ else if (queuedDisposition.EqualsLiteral("redirected"))
+ mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Redirected;
+ }
+ }
+ } else {
+ NS_WARNING("CreateMessage can't get draft id");
+ }
+ }
+
+ // If we don't have an original message URI, nothing else to do...
+ if (msgUri.IsEmpty()) return NS_OK;
+
+ // store the original message URI so we can extract it after we send the
+ // message to properly mark any disposition flags like replied or forwarded on
+ // the message.
+ if (mOriginalMsgURI.IsEmpty()) mOriginalMsgURI = msgUri;
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // "Forward inline" and "Reply with template" processing.
+ // Note the early return at the end of the block.
+ if (type == nsIMsgCompType::ForwardInline ||
+ type == nsIMsgCompType::ReplyWithTemplate) {
+ // We want to treat this message as a reference too
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(msgUri, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+
+ nsAutoCString reference;
+ // When forwarding we only use the original message for "References:" -
+ // recipients don't have the other messages anyway.
+ // For reply with template we want to preserve all the references.
+ if (type == nsIMsgCompType::ReplyWithTemplate) {
+ uint16_t numReferences = 0;
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = 0; i < numReferences; i++) {
+ nsAutoCString ref;
+ msgHdr->GetStringReference(i, ref);
+ if (!ref.IsEmpty()) {
+ reference.Append('<');
+ reference.Append(ref);
+ reference.AppendLiteral("> ");
+ }
+ }
+ reference.Trim(" ", false, true);
+ }
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ reference.Append('<');
+ reference.Append(messageId);
+ reference.Append('>');
+ m_compFields->SetReferences(reference.get());
+
+ if (type == nsIMsgCompType::ForwardInline) {
+ nsString subject;
+ msgHdr->GetMime2DecodedSubject(subject);
+ nsCString fwdPrefix;
+ prefs->GetCharPrefWithDefault("mail.forward_subject_prefix", "Fwd"_ns,
+ 1, fwdPrefix);
+ nsString unicodeFwdPrefix;
+ CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
+ unicodeFwdPrefix.AppendLiteral(": ");
+ subject.Insert(unicodeFwdPrefix, 0);
+ m_compFields->SetSubject(subject);
+ }
+ }
+
+ // Early return for "ForwardInline" and "ReplyWithTemplate" processing.
+ return NS_OK;
+ }
+
+ // All other processing.
+
+ // Note the following:
+ // LoadDraftOrTemplate() is run in nsMsgComposeService::OpenComposeWindow()
+ // for five compose types: ForwardInline, ReplyWithTemplate (both covered
+ // in the code block above) and Draft, Template and Redirect. For these
+ // compose types, the charset is already correct (incl. MIME-applied override)
+ // unless the default charset should be used.
+
+ bool isFirstPass = true;
+ char* uriList = ToNewCString(msgUri);
+ char* uri = uriList;
+ char* nextUri;
+ do {
+ nextUri = strstr(uri, "://");
+ if (nextUri) {
+ // look for next ://, and then back up to previous ','
+ nextUri = strstr(nextUri + 1, "://");
+ if (nextUri) {
+ *nextUri = '\0';
+ char* saveNextUri = nextUri;
+ nextUri = strrchr(uri, ',');
+ if (nextUri) *nextUri = '\0';
+ *saveNextUri = ':';
+ }
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (mOrigMsgHdr)
+ msgHdr = mOrigMsgHdr;
+ else {
+ rv = GetMsgDBHdrFromURI(nsDependentCString(uri), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (msgHdr) {
+ nsString subject;
+ rv = msgHdr->GetMime2DecodedSubject(subject);
+ if (NS_FAILED(rv)) return rv;
+
+ // Check if (was: is present in the subject
+ int32_t wasOffset = subject.RFind(u" (was:"_ns);
+ bool strip = true;
+
+ if (wasOffset >= 0) {
+ // Check the number of references, to check if was: should be stripped
+ // First, assume that it should be stripped; the variable will be set to
+ // false later if stripping should not happen.
+ uint16_t numRef;
+ msgHdr->GetNumReferences(&numRef);
+ if (numRef) {
+ // If there are references, look for the first message in the thread
+ // firstly, get the database via the folder
+ nsCOMPtr<nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ folder->GetMsgDatabase(getter_AddRefs(db));
+
+ if (db) {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(0, reference);
+
+ nsCOMPtr<nsIMsgDBHdr> refHdr;
+ db->GetMsgHdrForMessageID(reference.get(),
+ getter_AddRefs(refHdr));
+
+ if (refHdr) {
+ nsCString refSubject;
+ rv = refHdr->GetSubject(refSubject);
+ if (NS_SUCCEEDED(rv)) {
+ if (refSubject.Find(" (was:") >= 0) strip = false;
+ }
+ }
+ }
+ }
+ } else
+ strip = false;
+ }
+
+ if (strip && wasOffset >= 0) {
+ // Strip off the "(was: old subject)" part
+ subject.Assign(Substring(subject, 0, wasOffset));
+ }
+
+ switch (type) {
+ default:
+ break;
+ case nsIMsgCompType::Draft:
+ case nsIMsgCompType::Template:
+ case nsIMsgCompType::EditTemplate:
+ case nsIMsgCompType::EditAsNew: {
+ // If opening from file, preseve the subject already present, since
+ // we can't get a subject from db there.
+ if (mOriginalMsgURI.Find("&realtype=message/rfc822") != -1) {
+ break;
+ }
+ // Otherwise, set up the subject from db, with possible modifications.
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::HasRe) {
+ subject.InsertLiteral(u"Re: ", 0);
+ }
+ // Set subject from db, where it's already decrypted. The raw
+ // header may be encrypted.
+ m_compFields->SetSubject(subject);
+ break;
+ }
+ case nsIMsgCompType::Reply:
+ case nsIMsgCompType::ReplyAll:
+ case nsIMsgCompType::ReplyToList:
+ case nsIMsgCompType::ReplyToGroup:
+ case nsIMsgCompType::ReplyToSender:
+ case nsIMsgCompType::ReplyToSenderAndGroup: {
+ if (!isFirstPass) // safeguard, just in case...
+ {
+ PR_Free(uriList);
+ return rv;
+ }
+ mQuotingToFollow = true;
+
+ subject.InsertLiteral(u"Re: ", 0);
+ m_compFields->SetSubject(subject);
+
+ // Setup quoting callbacks for later...
+ mWhatHolder = 1;
+ break;
+ }
+ case nsIMsgCompType::ForwardAsAttachment: {
+ // Add the forwarded message in the references, first
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (isFirstPass) {
+ nsAutoCString reference;
+ reference.Append('<');
+ reference.Append(messageId);
+ reference.Append('>');
+ m_compFields->SetReferences(reference.get());
+ } else {
+ nsAutoCString references;
+ m_compFields->GetReferences(getter_Copies(references));
+ references.AppendLiteral(" <");
+ references.Append(messageId);
+ references.Append('>');
+ m_compFields->SetReferences(references.get());
+ }
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::HasRe)
+ subject.InsertLiteral(u"Re: ", 0);
+
+ // Setup quoting callbacks for later...
+ mQuotingToFollow =
+ false; // We don't need to quote the original message.
+ nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(
+ "@mozilla.org/messengercompose/attachment;1", &rv);
+ if (NS_SUCCEEDED(rv) && attachment) {
+ bool addExtension = true;
+ nsString sanitizedSubj;
+ prefs->GetBoolPref("mail.forward_add_extension", &addExtension);
+
+ // copy subject string to sanitizedSubj, use default if empty
+ if (subject.IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> composeBundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/"
+ "composeMsgs.properties",
+ getter_AddRefs(composeBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ composeBundle->GetStringFromName("messageAttachmentSafeName",
+ sanitizedSubj);
+ } else
+ sanitizedSubj.Assign(subject);
+
+ // set the file size
+ uint32_t messageSize;
+ msgHdr->GetMessageSize(&messageSize);
+ attachment->SetSize(messageSize);
+
+ // change all '.' to '_' see bug #271211
+ sanitizedSubj.ReplaceChar(u".", u'_');
+ if (addExtension) sanitizedSubj.AppendLiteral(".eml");
+ attachment->SetName(sanitizedSubj);
+ attachment->SetUrl(nsDependentCString(uri));
+ m_compFields->AddAttachment(attachment);
+ }
+
+ if (isFirstPass) {
+ nsCString fwdPrefix;
+ prefs->GetCharPrefWithDefault("mail.forward_subject_prefix",
+ "Fwd"_ns, 1, fwdPrefix);
+ nsString unicodeFwdPrefix;
+ CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix);
+ unicodeFwdPrefix.AppendLiteral(": ");
+ subject.Insert(unicodeFwdPrefix, 0);
+ m_compFields->SetSubject(subject);
+ }
+ break;
+ }
+ case nsIMsgCompType::Redirect: {
+ // For a redirect, set the Reply-To: header to what was in the
+ // original From: header...
+ nsAutoCString author;
+ msgHdr->GetAuthor(getter_Copies(author));
+ m_compFields->SetSubject(subject);
+ m_compFields->SetReplyTo(author.get());
+
+ // ... and empty out the various recipient headers
+ nsAutoString empty;
+ m_compFields->SetTo(empty);
+ m_compFields->SetCc(empty);
+ m_compFields->SetBcc(empty);
+ m_compFields->SetNewsgroups(empty);
+ m_compFields->SetFollowupTo(empty);
+
+ // Add the redirected message in the references so that threading
+ // will work when the new recipient eventually replies to the
+ // original sender.
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (isFirstPass) {
+ nsAutoCString reference;
+ reference.Append('<');
+ reference.Append(messageId);
+ reference.Append('>');
+ m_compFields->SetReferences(reference.get());
+ } else {
+ nsAutoCString references;
+ m_compFields->GetReferences(getter_Copies(references));
+ references.AppendLiteral(" <");
+ references.Append(messageId);
+ references.Append('>');
+ m_compFields->SetReferences(references.get());
+ }
+ break;
+ }
+ }
+ }
+ isFirstPass = false;
+ uri = nextUri + 1;
+ } while (nextUri);
+ PR_Free(uriList);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_IF_ADDREF(*_retval = mProgress);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_IF_ADDREF(*_retval = mMsgSend);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetMessageSend(nsIMsgSend* aMsgSend) {
+ mMsgSend = aMsgSend;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::ClearMessageSend() {
+ mMsgSend = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference) {
+ mCiteReference = citeReference;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const nsACString& folderURI) {
+ m_folderName = folderURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(nsACString& folderURI) {
+ folderURI = m_folderName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(nsACString& originalMsgURI) {
+ originalMsgURI = mOriginalMsgURI;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUTPUT
+// FROM LIBMIME. THIS IS FOR QUOTING
+////////////////////////////////////////////////////////////////////////////////////
+QuotingOutputStreamListener::~QuotingOutputStreamListener() {}
+
+QuotingOutputStreamListener::QuotingOutputStreamListener(
+ nsIMsgDBHdr* originalMsgHdr, bool quoteHeaders, bool headersOnly,
+ nsIMsgIdentity* identity, nsIMsgQuote* msgQuote, bool quoteOriginal,
+ const nsACString& htmlToQuote) {
+ nsresult rv;
+ mQuoteHeaders = quoteHeaders;
+ mHeadersOnly = headersOnly;
+ mIdentity = identity;
+ mOrigMsgHdr = originalMsgHdr;
+ mUnicodeBufferCharacterLength = 0;
+ mQuoteOriginal = quoteOriginal;
+ mHtmlToQuote = htmlToQuote;
+ mQuote = msgQuote;
+
+ if (!mHeadersOnly || !mHtmlToQuote.IsEmpty()) {
+ // Get header type, locale and strings from pref.
+ int32_t replyHeaderType;
+ nsString replyHeaderAuthorWrote;
+ nsString replyHeaderOnDateAuthorWrote;
+ nsString replyHeaderAuthorWroteOnDate;
+ nsString replyHeaderOriginalmessage;
+ GetReplyHeaderInfo(
+ &replyHeaderType, replyHeaderAuthorWrote, replyHeaderOnDateAuthorWrote,
+ replyHeaderAuthorWroteOnDate, replyHeaderOriginalmessage);
+
+ // For the built message body...
+ if (originalMsgHdr && !quoteHeaders) {
+ // Setup the cite information....
+ nsCString myGetter;
+ if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter)))) {
+ if (!myGetter.IsEmpty()) {
+ nsAutoCString buf;
+ mCiteReference.AssignLiteral("mid:");
+ MsgEscapeURL(myGetter,
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME |
+ nsINetUtil::ESCAPE_URL_FORCED,
+ buf);
+ mCiteReference.Append(NS_ConvertASCIItoUTF16(buf));
+ }
+ }
+
+ bool citingHeader; // Do we have a header needing to cite any info from
+ // original message?
+ bool headerDate; // Do we have a header needing to cite date/time from
+ // original message?
+ switch (replyHeaderType) {
+ case 0: // No reply header at all (actually the "---- original message
+ // ----" string, which is kinda misleading. TODO: Should there
+ // be a "really no header" option?
+ mCitePrefix.Assign(replyHeaderOriginalmessage);
+ citingHeader = false;
+ headerDate = false;
+ break;
+
+ case 2: // Insert both the original author and date in the reply header
+ // (date followed by author)
+ mCitePrefix.Assign(replyHeaderOnDateAuthorWrote);
+ citingHeader = true;
+ headerDate = true;
+ break;
+
+ case 3: // Insert both the original author and date in the reply header
+ // (author followed by date)
+ mCitePrefix.Assign(replyHeaderAuthorWroteOnDate);
+ citingHeader = true;
+ headerDate = true;
+ break;
+
+ case 4: // TODO bug 107884: implement a more featureful user specified
+ // header
+ case 1:
+ default: // Default is to only show the author.
+ mCitePrefix.Assign(replyHeaderAuthorWrote);
+ citingHeader = true;
+ headerDate = false;
+ break;
+ }
+
+ if (citingHeader) {
+ int32_t placeholderIndex = kNotFound;
+
+ if (headerDate) {
+ PRTime originalMsgDate;
+ rv = originalMsgHdr->GetDate(&originalMsgDate);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString citeDatePart;
+ if ((placeholderIndex = mCitePrefix.Find(u"#2")) != kNotFound) {
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date =
+ mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ rv = mozilla::intl::AppDateTimeFormat::Format(
+ style, originalMsgDate, citeDatePart);
+ if (NS_SUCCEEDED(rv))
+ mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);
+ }
+ if ((placeholderIndex = mCitePrefix.Find(u"#3")) != kNotFound) {
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.time =
+ mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ rv = mozilla::intl::AppDateTimeFormat::Format(
+ style, originalMsgDate, citeDatePart);
+ if (NS_SUCCEEDED(rv))
+ mCitePrefix.Replace(placeholderIndex, 2, citeDatePart);
+ }
+ }
+ }
+
+ if ((placeholderIndex = mCitePrefix.Find(u"#1")) != kNotFound) {
+ nsAutoCString author;
+ rv = originalMsgHdr->GetAuthor(getter_Copies(author));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString citeAuthor;
+ ExtractName(EncodedHeader(author), citeAuthor);
+ mCitePrefix.Replace(placeholderIndex, 2, citeAuthor);
+ }
+ }
+ }
+ }
+
+ // This should not happen, but just in case.
+ if (mCitePrefix.IsEmpty()) {
+ mCitePrefix.AppendLiteral("\n\n");
+ mCitePrefix.Append(replyHeaderOriginalmessage);
+ mCitePrefix.AppendLiteral("\n");
+ }
+ }
+}
+
+/**
+ * The formatflowed parameter directs if formatflowed should be used in the
+ * conversion. format=flowed (RFC 2646) is a way to represent flow in a plain
+ * text mail, without disturbing the plain text.
+ */
+nsresult QuotingOutputStreamListener::ConvertToPlainText(bool formatflowed,
+ bool formatted,
+ bool disallowBreaks) {
+ nsresult rv =
+ ConvertBufToPlainText(mMsgBody, formatflowed, formatted, disallowBreaks);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ConvertBufToPlainText(mSignature, formatflowed, formatted,
+ disallowBreaks);
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest* request) {
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+QuotingOutputStreamListener::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ nsresult rv = NS_OK;
+
+ if (!mHtmlToQuote.IsEmpty()) {
+ // If we had a selection in the original message to quote, we can add
+ // it now that we are done ignoring the original body of the message
+ mHeadersOnly = false;
+ rv = AppendToMsgBody(mHtmlToQuote);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
+ NS_ENSURE_TRUE(compose, NS_ERROR_NULL_POINTER);
+
+ MSG_ComposeType type;
+ compose->GetType(&type);
+
+ // Assign cite information if available...
+ if (!mCiteReference.IsEmpty()) compose->SetCiteReference(mCiteReference);
+
+ bool overrideReplyTo =
+ mozilla::Preferences::GetBool("mail.override_list_reply_to", true);
+
+ if (mHeaders &&
+ (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll ||
+ type == nsIMsgCompType::ReplyToList ||
+ type == nsIMsgCompType::ReplyToSender ||
+ type == nsIMsgCompType::ReplyToGroup ||
+ type == nsIMsgCompType::ReplyToSenderAndGroup) &&
+ mQuoteOriginal) {
+ nsCOMPtr<nsIMsgCompFields> compFields;
+ compose->GetCompFields(getter_AddRefs(compFields));
+ if (compFields) {
+ nsAutoString from;
+ nsAutoString to;
+ nsAutoString cc;
+ nsAutoString bcc;
+ nsAutoString replyTo;
+ nsAutoString mailReplyTo;
+ nsAutoString mailFollowupTo;
+ nsAutoString newgroups;
+ nsAutoString followUpTo;
+ nsAutoString messageId;
+ nsAutoString references;
+ nsAutoString listPost;
+
+ nsCString outCString; // Temp helper string.
+
+ bool needToRemoveDup = false;
+ if (!mMimeConverter) {
+ mMimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCString charset("UTF-8");
+
+ mHeaders->ExtractHeader(HEADER_FROM, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, from);
+
+ mHeaders->ExtractHeader(HEADER_TO, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, to);
+
+ mHeaders->ExtractHeader(HEADER_CC, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, cc);
+
+ mHeaders->ExtractHeader(HEADER_BCC, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, bcc);
+
+ mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, mailFollowupTo);
+
+ mHeaders->ExtractHeader(HEADER_REPLY_TO, false, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, replyTo);
+
+ mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, true, outCString);
+ nsMsgI18NConvertRawBytesToUTF16(outCString, charset, mailReplyTo);
+
+ mHeaders->ExtractHeader(HEADER_NEWSGROUPS, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false,
+ true, newgroups);
+
+ mHeaders->ExtractHeader(HEADER_FOLLOWUP_TO, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false,
+ true, followUpTo);
+
+ mHeaders->ExtractHeader(HEADER_MESSAGE_ID, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false,
+ true, messageId);
+
+ mHeaders->ExtractHeader(HEADER_REFERENCES, false, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false,
+ true, references);
+
+ mHeaders->ExtractHeader(HEADER_LIST_POST, true, outCString);
+ if (!outCString.IsEmpty())
+ mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), false,
+ true, listPost);
+ if (!listPost.IsEmpty()) {
+ int32_t startPos = listPost.Find(u"<mailto:");
+ int32_t endPos = listPost.FindChar('>', startPos);
+ // Extract the e-mail address.
+ if (endPos > startPos) {
+ const uint32_t mailtoLen = strlen("<mailto:");
+ listPost = Substring(listPost, startPos + mailtoLen,
+ endPos - (startPos + mailtoLen));
+ }
+ }
+
+ nsCString fromEmailAddress;
+ ExtractEmail(EncodedHeaderW(from), fromEmailAddress);
+
+ nsTArray<nsCString> toEmailAddresses;
+ ExtractEmails(EncodedHeaderW(to), UTF16ArrayAdapter<>(toEmailAddresses));
+
+ nsTArray<nsCString> ccEmailAddresses;
+ ExtractEmails(EncodedHeaderW(cc), UTF16ArrayAdapter<>(ccEmailAddresses));
+
+ nsCOMPtr<nsIPrefBranch> prefs(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool replyToSelfCheckAll = false;
+ prefs->GetBoolPref("mailnews.reply_to_self_check_all_ident",
+ &replyToSelfCheckAll);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ nsCString accountKey;
+ mOrigMsgHdr->GetAccountKey(getter_Copies(accountKey));
+ if (replyToSelfCheckAll) {
+ // Check all available identities if the pref was set.
+ accountManager->GetAllIdentities(identities);
+ } else if (!accountKey.IsEmpty()) {
+ // Check headers to see which account the message came in from
+ // (only works for pop3).
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->GetAccount(accountKey, getter_AddRefs(account));
+ if (account) {
+ rv = account->GetIdentities(identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // Check identities only for the server of the folder that the message
+ // is in.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = mOrigMsgHdr->GetFolder(getter_AddRefs(msgFolder));
+
+ if (NS_SUCCEEDED(rv) && msgFolder) {
+ nsCOMPtr<nsIMsgIncomingServer> nsIMsgIncomingServer;
+ rv = msgFolder->GetServer(getter_AddRefs(nsIMsgIncomingServer));
+
+ if (NS_SUCCEEDED(rv) && nsIMsgIncomingServer) {
+ rv = accountManager->GetIdentitiesForServer(nsIMsgIncomingServer,
+ identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ bool isReplyToSelf = false;
+ nsCOMPtr<nsIMsgIdentity> selfIdentity;
+ if (!identities.IsEmpty()) {
+ nsTArray<nsCString> toEmailAddressesLower(toEmailAddresses.Length());
+ for (auto email : toEmailAddresses) {
+ ToLowerCase(email);
+ toEmailAddressesLower.AppendElement(email);
+ }
+ nsTArray<nsCString> ccEmailAddressesLower(ccEmailAddresses.Length());
+ for (auto email : ccEmailAddresses) {
+ ToLowerCase(email);
+ ccEmailAddressesLower.AppendElement(email);
+ }
+
+ // Go through the identities to see if any of them is the author of
+ // the email.
+ for (auto lookupIdentity : identities) {
+ selfIdentity = lookupIdentity;
+
+ nsCString curIdentityEmail;
+ lookupIdentity->GetEmail(curIdentityEmail);
+
+ // See if it's a reply to own message, but not a reply between
+ // identities.
+ if (curIdentityEmail.Equals(fromEmailAddress,
+ nsCaseInsensitiveCStringComparator)) {
+ isReplyToSelf = true;
+ // For a true reply-to-self, none of your identities are normally in
+ // To or Cc. We need to avoid doing a reply-to-self for people that
+ // have multiple identities set and sometimes *uses* the other
+ // identity and sometimes *mails* the other identity.
+ // E.g. husband+wife or own-email+company-role-mail.
+ for (auto lookupIdentity2 : identities) {
+ nsCString curIdentityEmail2;
+ lookupIdentity2->GetEmail(curIdentityEmail2);
+ ToLowerCase(curIdentityEmail2);
+ if (toEmailAddressesLower.Contains(curIdentityEmail2)) {
+ // However, "From:me To:me" should be treated as
+ // reply-to-self if we have a Bcc. If we don't have a Bcc we
+ // might have the case of a generated mail of the style
+ // "From:me To:me Reply-To:customer". Then we need to to do a
+ // normal reply to the customer.
+ isReplyToSelf = !bcc.IsEmpty(); // true if bcc is set
+ break;
+ } else if (ccEmailAddressesLower.Contains(curIdentityEmail2)) {
+ // If you auto-Cc yourself your email would be in Cc - but we
+ // can't detect why it is in Cc so lets just treat it like a
+ // normal reply.
+ isReplyToSelf = false;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (type == nsIMsgCompType::ReplyToSenderAndGroup ||
+ type == nsIMsgCompType::ReplyToSender ||
+ type == nsIMsgCompType::Reply) {
+ if (isReplyToSelf) {
+ // Cast to concrete class. We *only* what to change m_identity, not
+ // all the things compose->SetIdentity would do.
+ nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get());
+ _compose->m_identity = selfIdentity;
+ compFields->SetFrom(from);
+ compFields->SetTo(to);
+ compFields->SetReplyTo(replyTo);
+ } else if (!mailReplyTo.IsEmpty()) {
+ // handle Mail-Reply-To (http://cr.yp.to/proto/replyto.html)
+ compFields->SetTo(mailReplyTo);
+ needToRemoveDup = true;
+ } else if (!replyTo.IsEmpty()) {
+ // default reply behaviour then
+
+ if (overrideReplyTo && !listPost.IsEmpty() &&
+ replyTo.Find(listPost) != kNotFound) {
+ // Reply-To munging in this list post. Reply to From instead,
+ // as the user can choose Reply List if that's what he wants.
+ compFields->SetTo(from);
+ } else {
+ compFields->SetTo(replyTo);
+ }
+ needToRemoveDup = true;
+ } else {
+ compFields->SetTo(from);
+ }
+ } else if (type == nsIMsgCompType::ReplyAll) {
+ if (isReplyToSelf) {
+ // Cast to concrete class. We *only* what to change m_identity, not
+ // all the things compose->SetIdentity would do.
+ nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get());
+ _compose->m_identity = selfIdentity;
+ compFields->SetFrom(from);
+ compFields->SetTo(to);
+ compFields->SetCc(cc);
+ // In case it's a reply to self, but it's not the actual source of the
+ // sent message, then we won't know the Bcc header. So set it only if
+ // it's not empty. If you have auto-bcc and removed the auto-bcc for
+ // the original mail, you will have to do it manually for this reply
+ // too.
+ if (!bcc.IsEmpty()) compFields->SetBcc(bcc);
+ compFields->SetReplyTo(replyTo);
+ needToRemoveDup = true;
+ } else if (mailFollowupTo.IsEmpty()) {
+ // default reply-all behaviour then
+
+ nsAutoString allTo;
+ if (!replyTo.IsEmpty()) {
+ allTo.Assign(replyTo);
+ needToRemoveDup = true;
+ if (overrideReplyTo && !listPost.IsEmpty() &&
+ replyTo.Find(listPost) != kNotFound) {
+ // Reply-To munging in this list. Add From to recipients, it's the
+ // lesser evil...
+ allTo.AppendLiteral(", ");
+ allTo.Append(from);
+ }
+ } else {
+ allTo.Assign(from);
+ }
+
+ allTo.AppendLiteral(", ");
+ allTo.Append(to);
+ compFields->SetTo(allTo);
+
+ nsAutoString allCc;
+ compFields->GetCc(allCc); // auto-cc
+ if (!allCc.IsEmpty()) allCc.AppendLiteral(", ");
+ allCc.Append(cc);
+ compFields->SetCc(allCc);
+
+ needToRemoveDup = true;
+ } else {
+ // Handle Mail-Followup-To (http://cr.yp.to/proto/replyto.html)
+ compFields->SetTo(mailFollowupTo);
+ needToRemoveDup = true; // To remove possible self from To.
+
+ // If Cc is set a this point it's auto-Ccs, so we'll just keep those.
+ }
+ } else if (type == nsIMsgCompType::ReplyToList) {
+ compFields->SetTo(listPost);
+ }
+
+ if (!newgroups.IsEmpty()) {
+ if ((type != nsIMsgCompType::Reply) &&
+ (type != nsIMsgCompType::ReplyToSender))
+ compFields->SetNewsgroups(newgroups);
+ if (type == nsIMsgCompType::ReplyToGroup)
+ compFields->SetTo(EmptyString());
+ }
+
+ if (!followUpTo.IsEmpty()) {
+ // Handle "followup-to: poster" magic keyword here
+ if (followUpTo.EqualsLiteral("poster")) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ compose->GetDomWindow(getter_AddRefs(domWindow));
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsMsgDisplayMessageByName(domWindow, "followupToSenderMessage");
+
+ if (!replyTo.IsEmpty()) {
+ compFields->SetTo(replyTo);
+ } else {
+ // If reply-to is empty, use the From header to fetch the original
+ // sender's email.
+ compFields->SetTo(from);
+ }
+
+ // Clear the newsgroup: header field, because followup-to: poster
+ // only follows up to the original sender
+ if (!newgroups.IsEmpty()) compFields->SetNewsgroups(EmptyString());
+ } else // Process "followup-to: newsgroup-content" here
+ {
+ if (type != nsIMsgCompType::ReplyToSender)
+ compFields->SetNewsgroups(followUpTo);
+ if (type == nsIMsgCompType::Reply) {
+ compFields->SetTo(EmptyString());
+ }
+ }
+ }
+
+ if (!references.IsEmpty()) references.Append(char16_t(' '));
+ references += messageId;
+ compFields->SetReferences(NS_LossyConvertUTF16toASCII(references).get());
+
+ nsAutoCString resultStr;
+
+ // Cast interface to concrete class that has direct field getters etc.
+ nsMsgCompFields* _compFields =
+ static_cast<nsMsgCompFields*>(compFields.get());
+
+ // Remove duplicate addresses between To && Cc.
+ if (needToRemoveDup) {
+ nsCString addressesToRemoveFromCc;
+ if (mIdentity) {
+ bool removeMyEmailInCc = true;
+ nsCString myEmail;
+ // Get senders address from composeField or from identity,
+ nsAutoCString sender(_compFields->GetFrom());
+ ExtractEmail(EncodedHeader(sender), myEmail);
+ if (myEmail.IsEmpty()) mIdentity->GetEmail(myEmail);
+
+ // Remove my own address from To, unless it's a reply to self.
+ if (!isReplyToSelf) {
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetTo()),
+ myEmail, resultStr);
+ _compFields->SetTo(resultStr.get());
+ }
+ addressesToRemoveFromCc.Assign(_compFields->GetTo());
+
+ // Remove own address from CC unless we want it in there
+ // through the automatic-CC-to-self (see bug 584962). There are
+ // three cases:
+ // - user has no automatic CC
+ // - user has automatic CC but own email is not in it
+ // - user has automatic CC and own email in it
+ // Only in the last case do we want our own email address to stay
+ // in the CC list.
+ bool automaticCc;
+ mIdentity->GetDoCc(&automaticCc);
+ if (automaticCc) {
+ nsCString autoCcList;
+ mIdentity->GetDoCcList(autoCcList);
+ nsTArray<nsCString> autoCcEmailAddresses;
+ ExtractEmails(EncodedHeader(autoCcList),
+ UTF16ArrayAdapter<>(autoCcEmailAddresses));
+ if (autoCcEmailAddresses.Contains(myEmail)) {
+ removeMyEmailInCc = false;
+ }
+ }
+
+ if (removeMyEmailInCc) {
+ addressesToRemoveFromCc.AppendLiteral(", ");
+ addressesToRemoveFromCc.Append(myEmail);
+ }
+ }
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetCc()),
+ addressesToRemoveFromCc, resultStr);
+ _compFields->SetCc(resultStr.get());
+ if (_compFields->GetBcc()) {
+ // Remove addresses already in Cc from Bcc.
+ RemoveDuplicateAddresses(nsDependentCString(_compFields->GetBcc()),
+ nsDependentCString(_compFields->GetCc()),
+ resultStr);
+ if (!resultStr.IsEmpty()) {
+ // Remove addresses already in To from Bcc.
+ RemoveDuplicateAddresses(
+ resultStr, nsDependentCString(_compFields->GetTo()), resultStr);
+ }
+ _compFields->SetBcc(resultStr.get());
+ }
+ }
+ }
+ }
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ nsCOMPtr<nsIMsgComposeService> composeService(
+ do_GetService("@mozilla.org/messengercompose;1"));
+ composeService->TimeStamp(
+ "Done with MIME. Now we're updating the UI elements", false);
+#endif
+
+ if (mQuoteOriginal)
+ compose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ composeService->TimeStamp(
+ "Addressing widget, window title and focus are now set, time to insert "
+ "the body",
+ false);
+#endif
+
+ if (!mHeadersOnly) mMsgBody.AppendLiteral("</html>");
+
+ // Now we have an HTML representation of the quoted message.
+ // If we are in plain text mode, we need to convert this to plain
+ // text before we try to insert it into the editor. If we don't, we
+ // just get lots of HTML text in the message...not good.
+ //
+ // XXX not m_composeHTML? /BenB
+ bool composeHTML = true;
+ compose->GetComposeHTML(&composeHTML);
+ if (!composeHTML) {
+ // Downsampling.
+
+ // In plain text quotes we always allow line breaking to not end up with
+ // long lines. The quote is inserted into a span with style
+ // "white-space: pre;" which isn't be wrapped.
+ // Update: Bug 387687 changed this to "white-space: pre-wrap;".
+ // Note that the body of the plain text message is wrapped since it uses
+ // "white-space: pre-wrap; width: 72ch;".
+ // Look at it in the DOM Inspector to see it.
+ //
+ // If we're using format flowed, we need to pass it so the encoder
+ // can add a space at the end.
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ bool flowed = false;
+ if (pPrefBranch) {
+ pPrefBranch->GetBoolPref("mailnews.send_plaintext_flowed", &flowed);
+ }
+
+ rv = ConvertToPlainText(flowed,
+ true, // formatted
+ false); // allow line breaks
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ compose->ProcessSignature(mIdentity, true, &mSignature);
+
+ nsCOMPtr<nsIEditor> editor;
+ if (NS_SUCCEEDED(compose->GetEditor(getter_AddRefs(editor))) && editor) {
+ if (mQuoteOriginal)
+ compose->ConvertAndLoadComposeWindow(mCitePrefix, mMsgBody, mSignature,
+ true, composeHTML);
+ else
+ InsertToCompose(editor, composeHTML);
+ }
+
+ if (mQuoteOriginal)
+ compose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK);
+ return rv;
+}
+
+NS_IMETHODIMP QuotingOutputStreamListener::OnDataAvailable(
+ nsIRequest* request, nsIInputStream* inStr, uint64_t sourceOffset,
+ uint32_t count) {
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG(inStr);
+
+ if (mHeadersOnly) return rv;
+
+ char* newBuf = (char*)PR_Malloc(count + 1);
+ if (!newBuf) return NS_ERROR_FAILURE;
+
+ uint32_t numWritten = 0;
+ rv = inStr->Read(newBuf, count, &numWritten);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) rv = NS_OK;
+ newBuf[numWritten] = '\0';
+ if (NS_SUCCEEDED(rv) && numWritten > 0) {
+ rv = AppendToMsgBody(nsDependentCString(newBuf, numWritten));
+ }
+
+ PR_FREEIF(newBuf);
+ return rv;
+}
+
+nsresult QuotingOutputStreamListener::AppendToMsgBody(const nsCString& inStr) {
+ nsresult rv = NS_OK;
+ if (!inStr.IsEmpty()) {
+ nsAutoString tmp;
+ rv = UTF_8_ENCODING->DecodeWithoutBOMHandling(inStr, tmp);
+ if (NS_SUCCEEDED(rv)) mMsgBody.Append(tmp);
+ }
+ return rv;
+}
+
+nsresult QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose* obj) {
+ mWeakComposeObj = do_GetWeakReference(obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders* headers) {
+ mHeaders = headers;
+ return NS_OK;
+}
+
+nsresult QuotingOutputStreamListener::InsertToCompose(nsIEditor* aEditor,
+ bool aHTMLEditor) {
+ NS_ENSURE_ARG(aEditor);
+ nsCOMPtr<nsINode> nodeInserted;
+
+ TranslateLineEnding(mMsgBody);
+
+ // Now, insert it into the editor...
+ aEditor->EnableUndo(true);
+
+ nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj);
+ if (!mMsgBody.IsEmpty() && compose) {
+ compose->SetAllowRemoteContent(true);
+ if (!mCitePrefix.IsEmpty()) {
+ if (!aHTMLEditor) mCitePrefix.AppendLiteral("\n");
+ aEditor->InsertText(mCitePrefix);
+ }
+
+ RefPtr<mozilla::HTMLEditor> htmlEditor = aEditor->AsHTMLEditor();
+ if (aHTMLEditor) {
+ nsAutoString body(mMsgBody);
+ remove_plaintext_tag(body);
+ htmlEditor->InsertAsCitedQuotation(body, EmptyString(), true,
+ getter_AddRefs(nodeInserted));
+ } else {
+ htmlEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted));
+ }
+ compose->SetAllowRemoteContent(false);
+ }
+
+ RefPtr<Selection> selection;
+ nsCOMPtr<nsINode> parent;
+ int32_t offset;
+ nsresult rv;
+
+ // get parent and offset of mailcite
+ rv = GetNodeLocation(nodeInserted, address_of(parent), &offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get selection
+ aEditor->GetSelection(getter_AddRefs(selection));
+ if (selection) {
+ // place selection after mailcite
+ selection->CollapseInLimiter(parent, offset + 1);
+ // insert a break at current selection
+ aEditor->InsertLineBreak();
+ selection->CollapseInLimiter(parent, offset + 1);
+ }
+ nsCOMPtr<nsISelectionController> selCon;
+ aEditor->GetSelectionController(getter_AddRefs(selCon));
+
+ if (selCon)
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ selCon->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_ANCHOR_REGION, true);
+
+ return NS_OK;
+}
+
+/**
+ * Returns true if the domain is a match for the given the domain list.
+ * Subdomains are also considered to match.
+ * @param aDomain - the domain name to check
+ * @param aDomainList - a comma separated string of domain names
+ */
+bool IsInDomainList(const nsAString& aDomain, const nsAString& aDomainList) {
+ if (aDomain.IsEmpty() || aDomainList.IsEmpty()) return false;
+
+ // Check plain text domains.
+ int32_t left = 0;
+ int32_t right = 0;
+ while (right != (int32_t)aDomainList.Length()) {
+ right = aDomainList.FindChar(',', left);
+ if (right == kNotFound) right = aDomainList.Length();
+ nsDependentSubstring domain = Substring(aDomainList, left, right);
+
+ if (aDomain.Equals(domain, nsCaseInsensitiveStringComparator)) return true;
+
+ nsAutoString dotDomain;
+ dotDomain.Assign(u'.');
+ dotDomain.Append(domain);
+ if (StringEndsWith(aDomain, dotDomain, nsCaseInsensitiveStringComparator))
+ return true;
+
+ left = right + 1;
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS(QuotingOutputStreamListener,
+ nsIMsgQuotingOutputStreamListener, nsIRequestObserver,
+ nsIStreamListener, nsISupportsWeakReference)
+
+////////////////////////////////////////////////////////////////////////////////////
+// END OF QUOTING LISTENER
+////////////////////////////////////////////////////////////////////////////////////
+
+/* attribute MSG_ComposeType type; */
+NS_IMETHODIMP nsMsgCompose::SetType(MSG_ComposeType aType) {
+ mType = aType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetType(MSG_ComposeType* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::QuoteMessage(const nsACString& msgURI) {
+ nsresult rv;
+ mQuotingToFollow = false;
+
+ // Create a mime parser (nsIStreamConverter)!
+ mQuote = do_CreateInstance("@mozilla.org/messengercompose/quoting;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgHdr));
+
+ // Create the consumer output stream.. this will receive all the HTML from
+ // libmime
+ mQuoteStreamListener =
+ new QuotingOutputStreamListener(msgHdr, false, !mHtmlToQuote.IsEmpty(),
+ m_identity, mQuote, false, mHtmlToQuote);
+
+ mQuoteStreamListener->SetComposeObj(this);
+
+ rv = mQuote->QuoteMessage(msgURI, false, mQuoteStreamListener,
+ mAutodetectCharset, false, msgHdr);
+ return rv;
+}
+
+nsresult nsMsgCompose::QuoteOriginalMessage() // New template
+{
+ nsresult rv;
+
+ mQuotingToFollow = false;
+
+ // Create a mime parser (nsIStreamConverter)!
+ mQuote = do_CreateInstance("@mozilla.org/messengercompose/quoting;1", &rv);
+ if (NS_FAILED(rv) || !mQuote) return NS_ERROR_FAILURE;
+
+ bool bAutoQuote = true;
+ m_identity->GetAutoQuote(&bAutoQuote);
+
+ nsCOMPtr<nsIMsgDBHdr> originalMsgHdr = mOrigMsgHdr;
+ if (!originalMsgHdr) {
+ rv = GetMsgDBHdrFromURI(mOriginalMsgURI, getter_AddRefs(originalMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsAutoCString msgUri(mOriginalMsgURI);
+ bool fileUrl = StringBeginsWith(msgUri, "file:"_ns);
+ if (fileUrl) {
+ msgUri.Replace(0, 5, "mailbox:"_ns);
+ msgUri.AppendLiteral("?number=0");
+ }
+
+ // Create the consumer output stream.. this will receive all the HTML from
+ // libmime
+ mQuoteStreamListener = new QuotingOutputStreamListener(
+ originalMsgHdr, mWhatHolder != 1, !bAutoQuote || !mHtmlToQuote.IsEmpty(),
+ m_identity, mQuote, true, mHtmlToQuote);
+
+ mQuoteStreamListener->SetComposeObj(this);
+
+ rv = mQuote->QuoteMessage(msgUri, mWhatHolder != 1, mQuoteStreamListener,
+ mAutodetectCharset, !bAutoQuote, originalMsgHdr);
+ return rv;
+}
+
+// CleanUpRecipient will remove un-necessary "<>" when a recipient as an address
+// without name
+void nsMsgCompose::CleanUpRecipients(nsString& recipients) {
+ uint16_t i;
+ bool startANewRecipient = true;
+ bool removeBracket = false;
+ nsAutoString newRecipient;
+ char16_t aChar;
+
+ for (i = 0; i < recipients.Length(); i++) {
+ aChar = recipients[i];
+ switch (aChar) {
+ case '<':
+ if (startANewRecipient)
+ removeBracket = true;
+ else
+ newRecipient += aChar;
+ startANewRecipient = false;
+ break;
+
+ case '>':
+ if (removeBracket)
+ removeBracket = false;
+ else
+ newRecipient += aChar;
+ break;
+
+ case ' ':
+ newRecipient += aChar;
+ break;
+
+ case ',':
+ newRecipient += aChar;
+ startANewRecipient = true;
+ removeBracket = false;
+ break;
+
+ default:
+ newRecipient += aChar;
+ startANewRecipient = false;
+ break;
+ }
+ }
+ recipients = newRecipient;
+}
+
+NS_IMETHODIMP nsMsgCompose::RememberQueuedDisposition() {
+ // need to find the msg hdr in the saved folder and then set a property on
+ // the header that we then look at when we actually send the message.
+ nsresult rv;
+ nsAutoCString dispositionSetting;
+
+ if (mType == nsIMsgCompType::Reply || mType == nsIMsgCompType::ReplyAll ||
+ mType == nsIMsgCompType::ReplyToList ||
+ mType == nsIMsgCompType::ReplyToGroup ||
+ mType == nsIMsgCompType::ReplyToSender ||
+ mType == nsIMsgCompType::ReplyToSenderAndGroup) {
+ dispositionSetting.AssignLiteral("replied");
+ } else if (mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline) {
+ dispositionSetting.AssignLiteral("forwarded");
+ } else if (mType == nsIMsgCompType::Redirect) {
+ dispositionSetting.AssignLiteral("redirected");
+ } else if (mType == nsIMsgCompType::Draft) {
+ nsAutoCString curDraftIdURL;
+ rv = m_compFields->GetDraftId(curDraftIdURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!curDraftIdURL.IsEmpty()) {
+ nsCOMPtr<nsIMsgDBHdr> draftHdr;
+ rv = GetMsgDBHdrFromURI(curDraftIdURL, getter_AddRefs(draftHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ draftHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY,
+ dispositionSetting);
+ }
+ }
+
+ nsMsgKey msgKey;
+ if (mMsgSend) {
+ mMsgSend->GetMessageKey(&msgKey);
+ nsCString identityKey;
+
+ m_identity->GetKey(identityKey);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(m_folderName, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = folder->GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t pseudoHdrProp = 0;
+ msgHdr->GetUint32Property("pseudoHdr", &pseudoHdrProp);
+ if (pseudoHdrProp) {
+ // Use SetAttributeOnPendingHdr for IMAP pseudo headers, as those
+ // will get deleted (and properties set using SetStringProperty lost.)
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ rv = folder->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString messageId;
+ mMsgSend->GetMessageId(messageId);
+ msgHdr->SetMessageId(messageId.get());
+ if (!mOriginalMsgURI.IsEmpty()) {
+ msgDB->SetAttributeOnPendingHdr(msgHdr, ORIG_URI_PROPERTY,
+ mOriginalMsgURI.get());
+ if (!dispositionSetting.IsEmpty())
+ msgDB->SetAttributeOnPendingHdr(msgHdr, QUEUED_DISPOSITION_PROPERTY,
+ dispositionSetting.get());
+ }
+ msgDB->SetAttributeOnPendingHdr(msgHdr, HEADER_X_MOZILLA_IDENTITY_KEY,
+ identityKey.get());
+ } else if (msgHdr) {
+ if (!mOriginalMsgURI.IsEmpty()) {
+ msgHdr->SetStringProperty(ORIG_URI_PROPERTY, mOriginalMsgURI);
+ if (!dispositionSetting.IsEmpty())
+ msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY,
+ dispositionSetting);
+ }
+ msgHdr->SetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::ProcessReplyFlags() {
+ nsresult rv;
+ // check to see if we were doing a reply or a forward, if we were, set the
+ // answered field flag on the message folder for this URI.
+ if (mType == nsIMsgCompType::Reply || mType == nsIMsgCompType::ReplyAll ||
+ mType == nsIMsgCompType::ReplyToList ||
+ mType == nsIMsgCompType::ReplyToGroup ||
+ mType == nsIMsgCompType::ReplyToSender ||
+ mType == nsIMsgCompType::ReplyToSenderAndGroup ||
+ mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline ||
+ mType == nsIMsgCompType::Redirect ||
+ mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None) {
+ if (!mOriginalMsgURI.IsEmpty()) {
+ nsCString msgUri(mOriginalMsgURI);
+ char* newStr = msgUri.BeginWriting();
+ char* uri;
+ while (nullptr != (uri = NS_strtok(",", &newStr))) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv =
+ GetMsgDBHdrFromURI(nsDependentCString(uri), getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgHdr) {
+ // get the folder for the message resource
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder) {
+ // If it's a draft with disposition, default to replied, otherwise,
+ // check if it's a forward.
+ nsMsgDispositionState dispositionSetting =
+ nsIMsgFolder::nsMsgDispositionState_Replied;
+ if (mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None)
+ dispositionSetting = mDraftDisposition;
+ else if (mType == nsIMsgCompType::ForwardAsAttachment ||
+ mType == nsIMsgCompType::ForwardInline)
+ dispositionSetting =
+ nsIMsgFolder::nsMsgDispositionState_Forwarded;
+ else if (mType == nsIMsgCompType::Redirect)
+ dispositionSetting =
+ nsIMsgFolder::nsMsgDispositionState_Redirected;
+
+ msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting);
+ if (mType != nsIMsgCompType::ForwardAsAttachment)
+ break; // just safeguard
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgCompose::OnStartSending(const char* aMsgID,
+ uint32_t aMsgSize) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStartSending(aMsgID, aMsgSize);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnProgress(const char* aMsgID, uint32_t aProgress,
+ uint32_t aProgressMax) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnProgress(aMsgID, aProgress, aProgressMax);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnStatus(const char* aMsgID, const char16_t* aMsg) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStatus(aMsgID, aMsg);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnStopSending(const char* aMsgID, nsresult aStatus,
+ const char16_t* aMsg,
+ nsIFile* returnFile) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::OnTransportSecurityError(const char* msgID, nsresult status,
+ nsITransportSecurityInfo* secInfo,
+ nsACString const& location) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnTransportSecurityError(msgID, status, secInfo,
+ location);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnSendNotPerformed(const char* aMsgID,
+ nsresult aStatus) {
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnSendNotPerformed(aMsgID, aStatus);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::OnGetDraftFolderURI(const char* aMsgID,
+ const nsACString& aFolderURI) {
+ m_folderName = aFolderURI;
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener>>::ForwardIterator iter(
+ mExternalSendListeners);
+ nsCOMPtr<nsIMsgSendListener> externalSendListener;
+
+ while (iter.HasMore()) {
+ externalSendListener = iter.GetNext();
+ externalSendListener->OnGetDraftFolderURI(aMsgID, aFolderURI);
+ }
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for both the send operation and the copy
+// operation. We have to create this class to listen for message send completion
+// and deal with failures in both send and copy operations
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ADDREF(nsMsgComposeSendListener)
+NS_IMPL_RELEASE(nsMsgComposeSendListener)
+
+/*
+NS_IMPL_QUERY_INTERFACE(nsMsgComposeSendListener,
+ nsIMsgComposeSendListener,
+ nsIMsgSendListener,
+ nsIMsgCopyServiceListener,
+ nsIWebProgressListener)
+*/
+NS_INTERFACE_MAP_BEGIN(nsMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgComposeSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgSendListener)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgCopyServiceListener)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END
+
+nsMsgComposeSendListener::nsMsgComposeSendListener(void) { mDeliverMode = 0; }
+
+nsMsgComposeSendListener::~nsMsgComposeSendListener(void) {}
+
+NS_IMETHODIMP nsMsgComposeSendListener::SetMsgCompose(nsIMsgCompose* obj) {
+ mWeakComposeObj = do_GetWeakReference(obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::SetDeliverMode(
+ MSG_DeliverMode deliverMode) {
+ mDeliverMode = deliverMode;
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnStartSending(const char* aMsgID,
+ uint32_t aMsgSize) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStartSending(aMsgID, aMsgSize);
+
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnProgress(const char* aMsgID,
+ uint32_t aProgress,
+ uint32_t aProgressMax) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnProgress(aMsgID, aProgress, aProgressMax);
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnStatus(const char* aMsgID,
+ const char16_t* aMsg) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStatus(aMsgID, aMsg);
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnSendNotPerformed(const char* aMsgID,
+ nsresult aStatus) {
+ // since OnSendNotPerformed is called in the case where the user aborts the
+ // operation by closing the compose window, we need not do the stuff required
+ // for closing the windows. However we would need to do the other operations
+ // as below.
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose)
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnSendNotPerformed(aMsgID, aStatus);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeSendListener::OnTransportSecurityError(
+ const char* msgID, nsresult status, nsITransportSecurityInfo* secInfo,
+ nsACString const& location) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnTransportSecurityError(msgID, status, secInfo,
+ location);
+
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnStopSending(const char* aMsgID,
+ nsresult aStatus,
+ const char16_t* aMsg,
+ nsIFile* returnFile) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose) {
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+
+ if (NS_SUCCEEDED(aStatus)) {
+ nsCOMPtr<nsIMsgCompFields> compFields;
+ msgCompose->GetCompFields(getter_AddRefs(compFields));
+
+ // only process the reply flags if we successfully sent the message
+ msgCompose->ProcessReplyFlags();
+
+ // See if there is a composer window
+ bool hasDomWindow = true;
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ rv = msgCompose->GetDomWindow(getter_AddRefs(domWindow));
+ if (NS_FAILED(rv) || !domWindow) hasDomWindow = false;
+
+ // Close the window ONLY if we are not going to do a save operation
+ nsAutoString fieldsFCC;
+ if (NS_SUCCEEDED(compFields->GetFcc(fieldsFCC))) {
+ if (!fieldsFCC.IsEmpty()) {
+ if (fieldsFCC.LowerCaseEqualsLiteral("nocopy://")) {
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK);
+ if (progress) {
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(false);
+ }
+ if (hasDomWindow) msgCompose->CloseWindow();
+ }
+ }
+ } else {
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK);
+ if (progress) {
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(false);
+ }
+ if (hasDomWindow)
+ msgCompose->CloseWindow(); // if we fail on the simple GetFcc call,
+ // close the window to be safe and avoid
+ // windows hanging around to prevent the
+ // app from exiting.
+ }
+
+ // Remove the current draft msg when sending draft is done.
+ bool deleteDraft;
+ msgCompose->GetDeleteDraft(&deleteDraft);
+ if (deleteDraft) RemoveCurrentDraftMessage(msgCompose, false, false);
+ } else {
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+ if (progress) {
+ progress->CloseProgressDialog(true);
+ progress->UnregisterListener(this);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile);
+
+ return rv;
+}
+
+nsresult nsMsgComposeSendListener::OnGetDraftFolderURI(
+ const char* aMsgID, const nsACString& aFolderURI) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendListener> composeSendListener =
+ do_QueryReferent(mWeakComposeObj, &rv);
+ if (NS_SUCCEEDED(rv) && composeSendListener)
+ composeSendListener->OnGetDraftFolderURI(aMsgID, aFolderURI);
+
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnStartCopy() { return NS_OK; }
+
+nsresult nsMsgComposeSendListener::OnProgress(uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::OnStopCopy(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv);
+ if (msgCompose) {
+ if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater ||
+ mDeliverMode == nsIMsgSend::nsMsgDeliverBackground ||
+ mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft) {
+ msgCompose->RememberQueuedDisposition();
+ }
+
+ // Ok, if we are here, we are done with the send/copy operation so
+ // we have to do something with the window....SHOW if failed, Close
+ // if succeeded
+
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+ if (progress) {
+ // Unregister ourself from msg compose progress
+ progress->UnregisterListener(this);
+ progress->CloseProgressDialog(NS_FAILED(aStatus));
+ }
+
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
+
+ if (NS_SUCCEEDED(aStatus)) {
+ // We should only close the window if we are done. Things like templates
+ // and drafts aren't done so their windows should stay open
+ if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft ||
+ mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate) {
+ msgCompose->NotifyStateListeners(
+ nsIMsgComposeNotificationType::SaveInFolderDone, aStatus);
+ // Remove the current draft msg when saving as draft/template is done.
+ msgCompose->SetDeleteDraft(true);
+ RemoveCurrentDraftMessage(
+ msgCompose, true, mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate);
+ } else {
+ // Remove (possible) draft if we're in send later mode
+ if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater ||
+ mDeliverMode == nsIMsgSend::nsMsgDeliverBackground) {
+ msgCompose->SetDeleteDraft(true);
+ RemoveCurrentDraftMessage(msgCompose, true, false);
+ }
+ msgCompose->CloseWindow();
+ }
+ }
+ msgCompose->ClearMessageSend();
+ }
+
+ return rv;
+}
+
+nsresult nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose* compObj,
+ nsIMsgFolder** msgFolder) {
+ nsCString folderUri;
+
+ nsresult rv = compObj->GetSavedFolderURI(folderUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetOrCreateFolder(folderUri, msgFolder);
+}
+
+nsresult nsMsgComposeSendListener::RemoveDraftOrTemplate(nsIMsgCompose* compObj,
+ nsCString msgURI,
+ bool isSaveTemplate) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+ rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgDBHdr));
+ NS_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "RemoveDraftOrTemplate can't get msg header DB interface pointer");
+ if (NS_SUCCEEDED(rv) && msgDBHdr) {
+ do { // Break on failure or removal not needed.
+ // Get the folder for the message resource.
+ rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder));
+ NS_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "RemoveDraftOrTemplate can't get msg folder interface pointer");
+ if (NS_FAILED(rv) || !msgFolder) break;
+
+ // Only do this if it's a drafts or templates folder.
+ uint32_t flags;
+ msgFolder->GetFlags(&flags);
+ if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)))
+ break;
+ // Only delete a template when saving a new one, never delete a template
+ // when sending.
+ if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates)) break;
+
+ // Only remove if the message is actually in the db. It might have only
+ // been in the use cache.
+ nsMsgKey key;
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_FAILED(rv)) break;
+ nsCOMPtr<nsIMsgDatabase> db;
+ msgFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (!db) break;
+ bool containsKey = false;
+ db->ContainsKey(key, &containsKey);
+ if (!containsKey) break;
+
+ // Ready to delete the msg.
+ rv = msgFolder->DeleteMessages({&*msgDBHdr}, nullptr, true, false,
+ nullptr, false /*allowUndo*/);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveDraftOrTemplate can't delete message");
+ } while (false);
+ } else {
+ // If we get here we have the case where the draft folder is on the server
+ // and it's not currently open (in thread pane), so draft msgs are saved to
+ // the server but they're not in our local DB. In this case,
+ // GetMsgDBHdrFromURI() will never find the msg. If the draft folder is a
+ // local one then we'll not get here because the draft msgs are saved to the
+ // local folder and are in local DB. Make sure the msg folder is imap. Even
+ // if we get here due to DB errors (worst case), we should still try to
+ // delete msg on the server because that's where the master copy of the msgs
+ // are stored, if draft folder is on the server. For local case, since DB is
+ // bad we can't do anything with it anyway so it'll be noop in this case.
+ rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ NS_ASSERTION(imapFolder,
+ "The draft folder MUST be an imap folder in order to mark "
+ "the msg delete!");
+ if (NS_SUCCEEDED(rv) && imapFolder) {
+ // Only do this if it's a drafts or templates folder.
+ uint32_t flags;
+ msgFolder->GetFlags(&flags);
+ if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)))
+ return NS_OK;
+ // Only delete a template when saving a new one, never delete a template
+ // when sending.
+ if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates))
+ return NS_OK;
+
+ const char* str = PL_strchr(msgURI.get(), '#');
+ NS_ASSERTION(str, "Failed to get current draft id url");
+ if (str) {
+ nsAutoCString srcStr(str + 1);
+ nsresult err;
+ nsMsgKey messageID = srcStr.ToInteger(&err);
+ if (messageID != nsMsgKey_None) {
+ rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true,
+ {messageID}, nullptr);
+ }
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * Remove the current draft message since a new one will be saved.
+ * When we're coming to save a template, also delete the original template.
+ * This is necessary since auto-save doesn't delete the original template.
+ */
+nsresult nsMsgComposeSendListener::RemoveCurrentDraftMessage(
+ nsIMsgCompose* compObj, bool calledByCopy, bool isSaveTemplate) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgCompFields> compFields = nullptr;
+
+ rv = compObj->GetCompFields(getter_AddRefs(compFields));
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "RemoveCurrentDraftMessage can't get compose fields");
+ if (NS_FAILED(rv) || !compFields) return rv;
+
+ nsCString curDraftIdURL;
+ rv = compFields->GetDraftId(curDraftIdURL);
+
+ // Skip if no draft id (probably a new draft msg).
+ if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) {
+ rv = RemoveDraftOrTemplate(compObj, curDraftIdURL, isSaveTemplate);
+ if (NS_FAILED(rv)) NS_WARNING("Removing current draft failed");
+ } else {
+ NS_WARNING("RemoveCurrentDraftMessage can't get draft id");
+ }
+
+ if (isSaveTemplate) {
+ nsCString templateIdURL;
+ rv = compFields->GetTemplateId(templateIdURL);
+ if (NS_SUCCEEDED(rv) && !templateIdURL.Equals(curDraftIdURL)) {
+ // Above we deleted an auto-saved draft, so here we need to delete
+ // the original template.
+ rv = RemoveDraftOrTemplate(compObj, templateIdURL, isSaveTemplate);
+ if (NS_FAILED(rv)) NS_WARNING("Removing original template failed");
+ }
+ }
+
+ // Now get the new uid so that next save will remove the right msg
+ // regardless whether or not the exiting msg can be deleted.
+ if (calledByCopy) {
+ nsMsgKey newUid = 0;
+ nsCOMPtr<nsIMsgFolder> savedToFolder;
+ nsCOMPtr<nsIMsgSend> msgSend;
+ rv = compObj->GetMessageSend(getter_AddRefs(msgSend));
+ NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null.");
+ if (NS_FAILED(rv) || !msgSend) return rv;
+
+ rv = msgSend->GetMessageKey(&newUid);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure we have a folder interface pointer
+ rv = GetMsgFolder(compObj, getter_AddRefs(savedToFolder));
+
+ // Reset draft (uid) url with the new uid.
+ if (savedToFolder && newUid != nsMsgKey_None) {
+ uint32_t folderFlags;
+ savedToFolder->GetFlags(&folderFlags);
+ if (folderFlags &
+ (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)) {
+ nsCString newDraftIdURL;
+ rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ compFields->SetDraftId(newDraftIdURL);
+ if (isSaveTemplate) compFields->SetTemplateId(newDraftIdURL);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey) {
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSendListener::GetMessageId(nsACString& messageId) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::OnStateChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aStateFlags,
+ nsresult aStatus) {
+ if (aStateFlags == nsIWebProgressListener::STATE_STOP) {
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj);
+ if (msgCompose) {
+ nsCOMPtr<nsIMsgProgress> progress;
+ msgCompose->GetProgress(getter_AddRefs(progress));
+
+ // Time to stop any pending operation...
+ if (progress) {
+ // Unregister ourself from msg compose progress
+ progress->UnregisterListener(this);
+
+ bool bCanceled = false;
+ progress->GetProcessCanceledByUser(&bCanceled);
+ if (bCanceled) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/"
+ "composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString msg;
+ bundle->GetStringFromName("msgCancelling", msg);
+ progress->OnStatusChange(nullptr, nullptr, NS_OK, msg.get());
+ }
+ }
+
+ nsCOMPtr<nsIMsgSend> msgSend;
+ msgCompose->GetMessageSend(getter_AddRefs(msgSend));
+ if (msgSend) msgSend->Abort();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::OnProgressChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress, int32_t aMaxTotalProgress) {
+ /* Ignore this call */
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* location,
+ uint32_t aFlags) {
+ /* Ignore this call */
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ /* Ignore this call */
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t state) {
+ /* Ignore this call */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeSendListener::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ /* Ignore this call */
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::ConvertHTMLToText(nsIFile* aSigFile,
+ nsString& aSigData) {
+ nsAutoString origBuf;
+
+ nsresult rv = LoadDataFromFile(aSigFile, origBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ConvertBufToPlainText(origBuf, false, true, true);
+ aSigData = origBuf;
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::ConvertTextToHTML(nsIFile* aSigFile,
+ nsString& aSigData) {
+ nsresult rv;
+ nsAutoString origBuf;
+
+ rv = LoadDataFromFile(aSigFile, origBuf);
+ if (NS_FAILED(rv)) return rv;
+
+ // Ok, once we are here, we need to escape the data to make sure that
+ // we don't do HTML stuff with plain text sigs.
+ nsCString escapedUTF8;
+ nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(origBuf), escapedUTF8);
+ aSigData.Append(NS_ConvertUTF8toUTF16(escapedUTF8));
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::LoadDataFromFile(nsIFile* file, nsString& sigData,
+ bool aAllowUTF8, bool aAllowUTF16) {
+ bool isDirectory = false;
+ file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ NS_ERROR("file is a directory");
+ return NS_MSG_ERROR_READING_FILE;
+ }
+
+ nsAutoCString data;
+ nsresult rv = nsMsgCompose::SlurpFileToString(file, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* readBuf = data.get();
+ int32_t readSize = data.Length();
+
+ nsAutoCString sigEncoding(nsMsgI18NParseMetaCharset(file));
+ bool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML;
+
+ if (sigEncoding.IsEmpty()) {
+ if (aAllowUTF8 && mozilla::IsUtf8(nsDependentCString(readBuf))) {
+ sigEncoding.AssignLiteral("UTF-8");
+ } else if (sigEncoding.IsEmpty() && aAllowUTF16 && readSize % 2 == 0 &&
+ readSize >= 2 &&
+ ((readBuf[0] == char(0xFE) && readBuf[1] == char(0xFF)) ||
+ (readBuf[0] == char(0xFF) && readBuf[1] == char(0xFE)))) {
+ sigEncoding.AssignLiteral("UTF-16");
+ } else {
+ // Autodetect encoding for plain text files w/o meta charset
+ nsAutoCString textFileCharset;
+ rv = MsgDetectCharsetFromFile(file, textFileCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ sigEncoding.Assign(textFileCharset);
+ }
+ }
+
+ if (NS_FAILED(nsMsgI18NConvertToUnicode(sigEncoding, data, sigData)))
+ CopyASCIItoUTF16(data, sigData);
+
+ // remove sig meta charset to allow user charset override during composition
+ if (removeSigCharset) {
+ nsAutoCString metaCharset("charset=");
+ metaCharset.Append(sigEncoding);
+ int32_t pos = sigData.LowerCaseFindASCII(metaCharset);
+ if (pos != kNotFound) sigData.Cut(pos, metaCharset.Length());
+ }
+ return NS_OK;
+}
+
+/**
+ * If the data contains file URLs, convert them to data URLs instead.
+ * This is intended to be used in for signature files, so that we can make sure
+ * images loaded into the editor are available on send.
+ */
+nsresult nsMsgCompose::ReplaceFileURLs(nsString& aData) {
+ // XXX This code is rather incomplete since it looks for "file://" even
+ // outside tags.
+
+ int32_t offset = 0;
+ while (true) {
+ int32_t fPos = aData.LowerCaseFindASCII("file://", offset);
+ if (fPos == kNotFound) {
+ break; // All done.
+ }
+ bool quoted = false;
+ char16_t q = 'x'; // initialise to anything to keep compilers happy.
+ if (fPos > 0) {
+ q = aData.CharAt(fPos - 1);
+ quoted = (q == '"' || q == '\'');
+ }
+ int32_t end = kNotFound;
+ if (quoted) {
+ end = aData.FindChar(q, fPos);
+ } else {
+ int32_t spacePos = aData.FindChar(' ', fPos);
+ int32_t gtPos = aData.FindChar('>', fPos);
+ if (gtPos != kNotFound && spacePos != kNotFound) {
+ end = (spacePos < gtPos) ? spacePos : gtPos;
+ } else if (gtPos == kNotFound && spacePos != kNotFound) {
+ end = spacePos;
+ } else if (gtPos != kNotFound && spacePos == kNotFound) {
+ end = gtPos;
+ }
+ }
+ if (end == kNotFound) {
+ break;
+ }
+ nsString fileURL;
+ fileURL = Substring(aData, fPos, end - fPos);
+ nsString dataURL;
+ nsresult rv = DataURLForFileURL(fileURL, dataURL);
+ if (NS_SUCCEEDED(rv)) {
+ aData.Replace(fPos, fileURL.Length(), dataURL);
+ offset = fPos + dataURL.Length();
+ } else {
+ // If this one failed, maybe because the file wasn't found,
+ // continue to process the next one.
+ offset = fPos + fileURL.Length();
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::DataURLForFileURL(const nsAString& aFileURL,
+ nsAString& aDataURL) {
+ nsresult rv;
+ nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> fileUri;
+ rv =
+ NS_NewURI(getter_AddRefs(fileUri), NS_ConvertUTF16toUTF8(aFileURL).get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(fileUri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> file;
+ rv = fileUrl->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString type;
+ rv = mime->GetTypeFromFile(file, type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = nsMsgCompose::SlurpFileToString(file, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aDataURL.AssignLiteral("data:");
+ AppendUTF8toUTF16(type, aDataURL);
+
+ nsAutoString filename;
+ rv = file->GetLeafName(filename);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString fn;
+ MsgEscapeURL(
+ NS_ConvertUTF16toUTF8(filename),
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED,
+ fn);
+ if (!fn.IsEmpty()) {
+ aDataURL.AppendLiteral(";filename=");
+ aDataURL.Append(NS_ConvertUTF8toUTF16(fn));
+ }
+ }
+
+ aDataURL.AppendLiteral(";base64,");
+ char* result = PL_Base64Encode(data.get(), data.Length(), nullptr);
+ nsDependentCString base64data(result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AppendUTF8toUTF16(base64data, aDataURL);
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::SlurpFileToString(nsIFile* aFile, nsACString& aString) {
+ aString.Truncate();
+
+ nsCOMPtr<nsIURI> fileURI;
+ nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), aFile);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), fileURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = channel->Open(getter_AddRefs(stream));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = NS_ConsumeStream(stream, UINT32_MAX, aString);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = stream->Close();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::BuildQuotedMessageAndSignature(void) {
+ //
+ // This should never happen...if it does, just bail out...
+ //
+ NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!");
+ if (!m_editor) return NS_ERROR_FAILURE;
+
+ // We will fire off the quote operation and wait for it to
+ // finish before we actually do anything with Ender...
+ return QuoteOriginalMessage();
+}
+
+//
+// This will process the signature file for the user. This method
+// will always append the results to the mMsgBody member variable.
+//
+nsresult nsMsgCompose::ProcessSignature(nsIMsgIdentity* identity, bool aQuoted,
+ nsString* aMsgBody) {
+ nsresult rv = NS_OK;
+
+ // Now, we can get sort of fancy. This is the time we need to check
+ // for all sorts of user defined stuff, like signatures and editor
+ // types and the like!
+ //
+ // user_pref(".....sig_file", "y:\\sig.html");
+ // user_pref(".....attach_signature", true);
+ // user_pref(".....htmlSigText", "unicode sig");
+ //
+ // Note: We will have intelligent signature behavior in that we
+ // look at the signature file first...if the extension is .htm or
+ // .html, we assume its HTML, otherwise, we assume it is plain text
+ //
+ // ...and that's not all! What we will also do now is look and see if
+ // the file is an image file. If it is an image file, then we should
+ // insert the correct HTML into the composer to have it work, but if we
+ // are doing plain text compose, we should insert some sort of message
+ // saying "Image Signature Omitted" or something (not done yet).
+ //
+ // If there's a sig pref, it will only be used if there is no sig file
+ // defined, thus if attach_signature is checked, htmlSigText is ignored (bug
+ // 324495). Plain-text signatures may or may not have a trailing line break
+ // (bug 428040).
+
+ bool attachFile = false;
+ bool useSigFile = false;
+ bool htmlSig = false;
+ bool imageSig = false;
+ nsAutoString sigData;
+ nsAutoString sigOutput;
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ bool suppressSigSep = false;
+
+ nsCOMPtr<nsIFile> sigFile;
+ if (identity) {
+ if (!CheckIncludeSignaturePrefs(identity)) return NS_OK;
+
+ identity->GetReplyOnTop(&reply_on_top);
+ identity->GetSigBottom(&sig_bottom);
+ identity->GetSuppressSigSep(&suppressSigSep);
+
+ rv = identity->GetAttachSignature(&attachFile);
+ if (NS_SUCCEEDED(rv) && attachFile) {
+ rv = identity->GetSignature(getter_AddRefs(sigFile));
+ if (NS_SUCCEEDED(rv) && sigFile) {
+ if (!sigFile->NativePath().IsEmpty()) {
+ bool exists = false;
+ sigFile->Exists(&exists);
+ if (exists) {
+ useSigFile = true; // ok, there's a signature file
+
+ // Now, most importantly, we need to figure out what the content
+ // type is for this signature...if we can't, we assume text
+ nsAutoCString sigContentType;
+ nsresult rv2; // don't want to clobber the other rv
+ nsCOMPtr<nsIMIMEService> mimeFinder(
+ do_GetService(NS_MIMESERVICE_CONTRACTID, &rv2));
+ if (NS_SUCCEEDED(rv2)) {
+ rv2 = mimeFinder->GetTypeFromFile(sigFile, sigContentType);
+ if (NS_SUCCEEDED(rv2)) {
+ if (StringBeginsWith(sigContentType, "image/"_ns,
+ nsCaseInsensitiveCStringComparator))
+ imageSig = true;
+ else if (sigContentType.Equals(
+ TEXT_HTML, nsCaseInsensitiveCStringComparator))
+ htmlSig = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Unless signature to be attached from file, use preference value;
+ // the htmlSigText value is always going to be treated as html if
+ // the htmlSigFormat pref is true, otherwise it is considered text
+ nsAutoString prefSigText;
+ if (identity && !attachFile) identity->GetHtmlSigText(prefSigText);
+ // Now, if they didn't even want to use a signature, we should
+ // just return nicely.
+ //
+ if ((!useSigFile && prefSigText.IsEmpty()) || NS_FAILED(rv)) return NS_OK;
+
+ static const char htmlBreak[] = "<br>";
+ static const char dashes[] = "-- ";
+ static const char htmlsigopen[] = "<div class=\"moz-signature\">";
+ static const char htmlsigclose[] = "</div>"; /* XXX: Due to a bug in
+ 4.x' HTML editor, it will not be able to
+ break this HTML sig, if quoted (for the user to
+ interleave a comment). */
+ static const char _preopen[] = "<pre class=\"moz-signature\" cols=%d>";
+ char* preopen;
+ static const char preclose[] = "</pre>";
+
+ int32_t wrapLength = 72; // setup default value in case GetWrapLength failed
+ GetWrapLength(&wrapLength);
+ preopen = PR_smprintf(_preopen, wrapLength);
+ if (!preopen) return NS_ERROR_OUT_OF_MEMORY;
+
+ bool paragraphMode =
+ mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false);
+
+ if (imageSig) {
+ // We have an image signature. If we're using the in HTML composer, we
+ // should put in the appropriate HTML for inclusion, otherwise, do nothing.
+ if (m_composeHTML) {
+ if (!paragraphMode) sigOutput.AppendLiteral(htmlBreak);
+ sigOutput.AppendLiteral(htmlsigopen);
+ if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) &&
+ (reply_on_top != 1 || sig_bottom || !aQuoted)) {
+ sigOutput.AppendLiteral(dashes);
+ }
+
+ sigOutput.AppendLiteral(htmlBreak);
+ sigOutput.AppendLiteral("<img src='");
+
+ nsCOMPtr<nsIURI> fileURI;
+ nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), sigFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString fileURL;
+ fileURI->GetSpec(fileURL);
+
+ nsString dataURL;
+ rv = DataURLForFileURL(NS_ConvertUTF8toUTF16(fileURL), dataURL);
+ if (NS_SUCCEEDED(rv)) {
+ sigOutput.Append(dataURL);
+ }
+ sigOutput.AppendLiteral("' border=0>");
+ sigOutput.AppendLiteral(htmlsigclose);
+ }
+ } else if (useSigFile) {
+ // is this a text sig with an HTML editor?
+ if ((m_composeHTML) && (!htmlSig)) {
+ ConvertTextToHTML(sigFile, sigData);
+ }
+ // is this a HTML sig with a text window?
+ else if ((!m_composeHTML) && (htmlSig)) {
+ ConvertHTMLToText(sigFile, sigData);
+ } else { // We have a match...
+ LoadDataFromFile(sigFile, sigData); // Get the data!
+ ReplaceFileURLs(sigData);
+ }
+ }
+
+ // if we have a prefSigText, append it to sigData.
+ if (!prefSigText.IsEmpty()) {
+ // set htmlSig if the pref is supposed to contain HTML code, defaults to
+ // false
+ rv = identity->GetHtmlSigFormat(&htmlSig);
+ if (NS_FAILED(rv)) htmlSig = false;
+
+ if (!m_composeHTML) {
+ if (htmlSig) ConvertBufToPlainText(prefSigText, false, true, true);
+ sigData.Append(prefSigText);
+ } else {
+ if (!htmlSig) {
+ nsCString escapedUTF8;
+ nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(prefSigText), escapedUTF8);
+ sigData.Append(NS_ConvertUTF8toUTF16(escapedUTF8));
+ } else {
+ ReplaceFileURLs(prefSigText);
+ sigData.Append(prefSigText);
+ }
+ }
+ }
+
+ // post-processing for plain-text signatures to ensure we end in CR, LF, or
+ // CRLF
+ if (!htmlSig && !m_composeHTML) {
+ int32_t sigLength = sigData.Length();
+ if (sigLength > 0 && !(sigData.CharAt(sigLength - 1) == '\r') &&
+ !(sigData.CharAt(sigLength - 1) == '\n'))
+ sigData.AppendLiteral(CRLF);
+ }
+
+ // Now that sigData holds data...if any, append it to the body in a nice
+ // looking manner
+ if (!sigData.IsEmpty()) {
+ if (m_composeHTML) {
+ if (!paragraphMode) sigOutput.AppendLiteral(htmlBreak);
+
+ if (htmlSig)
+ sigOutput.AppendLiteral(htmlsigopen);
+ else
+ sigOutput.Append(NS_ConvertASCIItoUTF16(preopen));
+ }
+
+ if ((reply_on_top != 1 || sig_bottom || !aQuoted) &&
+ sigData.Find(u"\r-- \r") < 0 && sigData.Find(u"\n-- \n") < 0 &&
+ sigData.Find(u"\n-- \r") < 0) {
+ nsDependentSubstring firstFourChars(sigData, 0, 4);
+
+ if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) &&
+ !(firstFourChars.EqualsLiteral("-- \n") ||
+ firstFourChars.EqualsLiteral("-- \r"))) {
+ sigOutput.AppendLiteral(dashes);
+
+ if (!m_composeHTML || !htmlSig)
+ sigOutput.AppendLiteral(CRLF);
+ else if (m_composeHTML)
+ sigOutput.AppendLiteral(htmlBreak);
+ }
+ }
+
+ // add CRLF before signature for plain-text mode if signature comes before
+ // quote
+ if (!m_composeHTML && reply_on_top == 1 && !sig_bottom && aQuoted)
+ sigOutput.AppendLiteral(CRLF);
+
+ sigOutput.Append(sigData);
+
+ if (m_composeHTML) {
+ if (htmlSig)
+ sigOutput.AppendLiteral(htmlsigclose);
+ else
+ sigOutput.AppendLiteral(preclose);
+ }
+ }
+
+ aMsgBody->Append(sigOutput);
+ PR_Free(preopen);
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::BuildBodyMessageAndSignature() {
+ nsresult rv = NS_OK;
+
+ //
+ // This should never happen...if it does, just bail out...
+ //
+ if (!m_editor) return NS_ERROR_FAILURE;
+
+ //
+ // Now, we have the body so we can just blast it into the
+ // composition editor window.
+ //
+ nsAutoString body;
+ m_compFields->GetBody(body);
+
+ // Some time we want to add a signature and sometime we won't.
+ // Let's figure that out now...
+ bool addSignature;
+ bool isQuoted = false;
+ switch (mType) {
+ case nsIMsgCompType::ForwardInline:
+ addSignature = true;
+ isQuoted = true;
+ break;
+ case nsIMsgCompType::New:
+ case nsIMsgCompType::MailToUrl: /* same as New */
+ case nsIMsgCompType::Reply: /* should not happen! but just in case */
+ case nsIMsgCompType::ReplyAll: /* should not happen! but just in case */
+ case nsIMsgCompType::ReplyToList: /* should not happen! but just in case */
+ case nsIMsgCompType::ForwardAsAttachment: /* should not happen! but just in
+ case */
+ case nsIMsgCompType::NewsPost:
+ case nsIMsgCompType::ReplyToGroup:
+ case nsIMsgCompType::ReplyToSender:
+ case nsIMsgCompType::ReplyToSenderAndGroup:
+ addSignature = true;
+ break;
+
+ case nsIMsgCompType::Draft:
+ case nsIMsgCompType::Template:
+ case nsIMsgCompType::Redirect:
+ case nsIMsgCompType::EditAsNew:
+ addSignature = false;
+ break;
+
+ default:
+ addSignature = false;
+ break;
+ }
+
+ nsAutoString tSignature;
+ if (addSignature) ProcessSignature(m_identity, isQuoted, &tSignature);
+
+ // if type is new, but we have body, this is probably a mapi send, so we need
+ // to replace '\n' with <br> so that the line breaks won't be lost by html. if
+ // mailtourl, do the same.
+ if (m_composeHTML &&
+ (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl))
+ body.ReplaceSubstring(u"\n"_ns, u"<br>"_ns);
+
+ // Restore flowed text wrapping for Drafts/Templates.
+ // Look for unquoted lines - if we have an unquoted line
+ // that ends in a space, join this line with the next one
+ // by removing the end of line char(s).
+ int32_t wrapping_enabled = 0;
+ GetWrapLength(&wrapping_enabled);
+ if (!m_composeHTML && wrapping_enabled) {
+ bool quote = false;
+ for (uint32_t i = 0; i < body.Length(); i++) {
+ if (i == 0 || body[i - 1] == '\n') // newline
+ {
+ if (body[i] == '>') {
+ quote = true;
+ continue;
+ }
+ nsString s(Substring(body, i, 10));
+ if (StringBeginsWith(s, u"-- \r"_ns) ||
+ StringBeginsWith(s, u"-- \n"_ns)) {
+ i += 4;
+ continue;
+ }
+ if (StringBeginsWith(s, u"- -- \r"_ns) ||
+ StringBeginsWith(s, u"- -- \n"_ns)) {
+ i += 6;
+ continue;
+ }
+ }
+ if (body[i] == '\n' && i > 1) {
+ if (quote) {
+ quote = false;
+ continue; // skip quoted lines
+ }
+ uint32_t j = i - 1; // look backward for space
+ if (body[j] == '\r') j--;
+ if (body[j] == ' ') // join this line with next one
+ body.Cut(j + 1, i - j); // remove CRLF
+ }
+ }
+ }
+
+ nsString empty;
+ rv = ConvertAndLoadComposeWindow(empty, body, tSignature, false,
+ m_composeHTML);
+
+ return rv;
+}
+
+nsresult nsMsgCompose::NotifyStateListeners(int32_t aNotificationType,
+ nsresult aResult) {
+ nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener>>::ForwardIterator iter(
+ mStateListeners);
+ nsCOMPtr<nsIMsgComposeStateListener> thisListener;
+
+ while (iter.HasMore()) {
+ thisListener = iter.GetNext();
+
+ switch (aNotificationType) {
+ case nsIMsgComposeNotificationType::ComposeFieldsReady:
+ thisListener->NotifyComposeFieldsReady();
+ break;
+
+ case nsIMsgComposeNotificationType::ComposeProcessDone:
+ thisListener->ComposeProcessDone(aResult);
+ break;
+
+ case nsIMsgComposeNotificationType::SaveInFolderDone:
+ thisListener->SaveInFolderDone(m_folderName.get());
+ break;
+
+ case nsIMsgComposeNotificationType::ComposeBodyReady:
+ thisListener->NotifyComposeBodyReady();
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown notification");
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompose::AttachmentPrettyName(const nsACString& scheme,
+ const char* charset,
+ nsACString& _retval) {
+ nsresult rv;
+
+ if (StringHead(scheme, 5).LowerCaseEqualsLiteral("file:")) {
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetFileFromURLSpec(scheme, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ rv = file->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ CopyUTF16toUTF8(leafName, _retval);
+ return rv;
+ }
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString retUrl;
+ rv = textToSubURI->UnEscapeURIForUI(scheme, retUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(retUrl, _retval);
+ } else {
+ _retval.Assign(scheme);
+ }
+ if (StringHead(scheme, 5).LowerCaseEqualsLiteral("http:")) _retval.Cut(0, 7);
+
+ return NS_OK;
+}
+
+/**
+ * Retrieve address book directories and mailing lists.
+ *
+ * @param aDirUri directory URI
+ * @param allDirectoriesArray retrieved directories and sub-directories
+ * @param allMailListArray retrieved maillists
+ */
+nsresult nsMsgCompose::GetABDirAndMailLists(
+ const nsACString& aDirUri, nsCOMArray<nsIAbDirectory>& aDirArray,
+ nsTArray<nsMsgMailList>& aMailListArray) {
+ static bool collectedAddressbookFound = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService("@mozilla.org/abmanager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDirUri.Equals(kAllDirectoryRoot)) {
+ nsTArray<RefPtr<nsIAbDirectory>> directories;
+ rv = abManager->GetDirectories(directories);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t count = directories.Length();
+ nsCString uri;
+ for (uint32_t i = 0; i < count; i++) {
+ rv = directories[i]->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t pos;
+ if (uri.EqualsLiteral(kPersonalAddressbookUri)) {
+ pos = 0;
+ } else {
+ uint32_t count = aDirArray.Count();
+
+ if (uri.EqualsLiteral(kCollectedAddressbookUri)) {
+ collectedAddressbookFound = true;
+ pos = count;
+ } else {
+ if (collectedAddressbookFound && count > 1) {
+ pos = count - 1;
+ } else {
+ pos = count;
+ }
+ }
+ }
+
+ aDirArray.InsertObjectAt(directories[i], pos);
+ rv = GetABDirAndMailLists(uri, aDirArray, aMailListArray);
+ }
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(aDirUri, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIAbDirectory>> subDirectories;
+ rv = directory->GetChildNodes(subDirectories);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsIAbDirectory* subDirectory : subDirectories) {
+ bool bIsMailList;
+ if (NS_SUCCEEDED(subDirectory->GetIsMailList(&bIsMailList)) &&
+ bIsMailList) {
+ aMailListArray.AppendElement(subDirectory);
+ }
+ }
+ return rv;
+}
+
+/**
+ * Comparator for use with nsTArray::IndexOf to find a recipient.
+ * This comparator will check if an "address" is a mail list or not.
+ */
+struct nsMsgMailListComparator {
+ // A mail list will have one of the formats
+ // 1) "mName <mDescription>" when the list has a description
+ // 2) "mName <mName>" when the list lacks description
+ // A recipient is of the form "mName <mEmail>" - for equality the list
+ // name must be the same. The recipient "email" must match the list name for
+ // case 1, and the list description for case 2.
+ bool Equals(const nsMsgMailList& mailList,
+ const nsMsgRecipient& recipient) const {
+ if (!mailList.mName.Equals(recipient.mName,
+ nsCaseInsensitiveStringComparator))
+ return false;
+ return mailList.mDescription.IsEmpty()
+ ? mailList.mName.Equals(recipient.mEmail,
+ nsCaseInsensitiveStringComparator)
+ : mailList.mDescription.Equals(
+ recipient.mEmail, nsCaseInsensitiveStringComparator);
+ }
+};
+
+/**
+ * Comparator for use with nsTArray::IndexOf to find a recipient.
+ */
+struct nsMsgRecipientComparator {
+ bool Equals(const nsMsgRecipient& recipient,
+ const nsMsgRecipient& recipientToFind) const {
+ if (!recipient.mEmail.Equals(recipientToFind.mEmail,
+ nsCaseInsensitiveStringComparator))
+ return false;
+
+ if (!recipient.mName.Equals(recipientToFind.mName,
+ nsCaseInsensitiveStringComparator))
+ return false;
+
+ return true;
+ }
+};
+
+/**
+ * This function recursively resolves a mailing list and returns individual
+ * email addresses. Nested lists are supported. It maintains an array of
+ * already visited mailing lists to avoid endless recursion.
+ *
+ * @param aMailList the list
+ * @param allDirectoriesArray all directories
+ * @param allMailListArray all maillists
+ * @param mailListProcessed maillists processed (to avoid recursive lists)
+ * @param aListMembers list members
+ */
+nsresult nsMsgCompose::ResolveMailList(
+ nsIAbDirectory* aMailList, nsCOMArray<nsIAbDirectory>& allDirectoriesArray,
+ nsTArray<nsMsgMailList>& allMailListArray,
+ nsTArray<nsMsgMailList>& mailListProcessed,
+ nsTArray<nsMsgRecipient>& aListMembers) {
+ nsresult rv = NS_OK;
+
+ nsTArray<RefPtr<nsIAbCard>> mailListAddresses;
+ rv = aMailList->GetChildCards(mailListAddresses);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIAbCard* existingCard : mailListAddresses) {
+ nsMsgRecipient newRecipient;
+
+ rv = existingCard->GetDisplayName(newRecipient.mName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = existingCard->GetPrimaryEmail(newRecipient.mEmail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newRecipient.mName.IsEmpty() && newRecipient.mEmail.IsEmpty()) {
+ continue;
+ }
+
+ // First check if it's a mailing list.
+ size_t index =
+ allMailListArray.IndexOf(newRecipient, 0, nsMsgMailListComparator());
+ if (index != allMailListArray.NoIndex &&
+ allMailListArray[index].mDirectory) {
+ // Check if maillist processed.
+ if (mailListProcessed.Contains(newRecipient, nsMsgMailListComparator())) {
+ continue;
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory2(allMailListArray[index].mDirectory);
+
+ // Add mailList to mailListProcessed.
+ mailListProcessed.AppendElement(directory2);
+
+ // Resolve mailList members.
+ rv = ResolveMailList(directory2, allDirectoriesArray, allMailListArray,
+ mailListProcessed, aListMembers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ continue;
+ }
+
+ // Check if recipient is in aListMembers.
+ if (aListMembers.Contains(newRecipient, nsMsgRecipientComparator())) {
+ continue;
+ }
+
+ // Now we need to insert the new address into the list of recipients.
+ newRecipient.mCard = existingCard;
+ newRecipient.mDirectory = aMailList;
+
+ aListMembers.AppendElement(newRecipient);
+ }
+
+ return rv;
+}
+
+/**
+ * Lookup the recipients as specified in the compose fields (To, Cc, Bcc)
+ * in the address books and return an array of individual recipients.
+ * Mailing lists are replaced by the cards they contain, nested and recursive
+ * lists are taken care of, recipients contained in multiple lists are only
+ * added once.
+ *
+ * @param recipientsList (out) recipient array
+ */
+nsresult nsMsgCompose::LookupAddressBook(RecipientsArray& recipientsList) {
+ nsresult rv = NS_OK;
+
+ // First, build some arrays with the original recipients.
+
+ nsAutoString originalRecipients[MAX_OF_RECIPIENT_ARRAY];
+ m_compFields->GetTo(originalRecipients[0]);
+ m_compFields->GetCc(originalRecipients[1]);
+ m_compFields->GetBcc(originalRecipients[2]);
+
+ for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) {
+ if (originalRecipients[i].IsEmpty()) continue;
+
+ rv = m_compFields->SplitRecipientsEx(originalRecipients[i],
+ recipientsList[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Then look them up in the Addressbooks
+ bool stillNeedToSearch = true;
+ nsCOMPtr<nsIAbDirectory> abDirectory;
+ nsCOMPtr<nsIAbCard> existingCard;
+ nsTArray<nsMsgMailList> mailListArray;
+ nsTArray<nsMsgMailList> mailListProcessed;
+
+ nsCOMArray<nsIAbDirectory> addrbookDirArray;
+ rv = GetABDirAndMailLists(nsLiteralCString(kAllDirectoryRoot),
+ addrbookDirArray, mailListArray);
+ if (NS_FAILED(rv)) return rv;
+
+ nsString dirPath;
+ uint32_t nbrAddressbook = addrbookDirArray.Count();
+
+ for (uint32_t k = 0; k < nbrAddressbook && stillNeedToSearch; ++k) {
+ // Avoid recursive mailing lists.
+ if (abDirectory && (addrbookDirArray[k] == abDirectory)) {
+ stillNeedToSearch = false;
+ break;
+ }
+
+ abDirectory = addrbookDirArray[k];
+ if (!abDirectory) continue;
+
+ stillNeedToSearch = false;
+ for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; i++) {
+ mailListProcessed.Clear();
+
+ // Note: We check this each time to allow for length changes.
+ for (uint32_t j = 0; j < recipientsList[i].Length(); j++) {
+ nsMsgRecipient& recipient = recipientsList[i][j];
+ if (!recipient.mDirectory) {
+ // First check if it's a mailing list.
+ size_t index =
+ mailListArray.IndexOf(recipient, 0, nsMsgMailListComparator());
+ if (index != mailListArray.NoIndex &&
+ mailListArray[index].mDirectory) {
+ // Check mailList Processed.
+ if (mailListProcessed.Contains(recipient,
+ nsMsgMailListComparator())) {
+ // Remove from recipientsList.
+ recipientsList[i].RemoveElementAt(j--);
+ continue;
+ }
+
+ nsCOMPtr<nsIAbDirectory> directory(mailListArray[index].mDirectory);
+
+ // Add mailList to mailListProcessed.
+ mailListProcessed.AppendElement(directory);
+
+ // Resolve mailList members.
+ nsTArray<nsMsgRecipient> members;
+ rv = ResolveMailList(directory, addrbookDirArray, mailListArray,
+ mailListProcessed, members);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Remove mailList from recipientsList.
+ recipientsList[i].RemoveElementAt(j);
+
+ // Merge members into recipientsList[i].
+ uint32_t pos = 0;
+ for (uint32_t c = 0; c < members.Length(); c++) {
+ nsMsgRecipient& member = members[c];
+ if (!recipientsList[i].Contains(member,
+ nsMsgRecipientComparator())) {
+ recipientsList[i].InsertElementAt(j + pos, member);
+ pos++;
+ }
+ }
+ } else {
+ // Find a card that contains this e-mail address.
+ rv = abDirectory->CardForEmailAddress(
+ NS_ConvertUTF16toUTF8(recipient.mEmail),
+ getter_AddRefs(existingCard));
+ if (NS_SUCCEEDED(rv) && existingCard) {
+ recipient.mCard = existingCard;
+ recipient.mDirectory = abDirectory;
+ } else {
+ stillNeedToSearch = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::ExpandMailingLists() {
+ RecipientsArray recipientsList;
+ nsresult rv = LookupAddressBook(recipientsList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Reset the final headers with the expanded mailing lists.
+ nsAutoString recipientsStr;
+
+ for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) {
+ uint32_t nbrRecipients = recipientsList[i].Length();
+ if (nbrRecipients == 0) continue;
+ recipientsStr.Truncate();
+
+ // Note: We check this each time to allow for length changes.
+ for (uint32_t j = 0; j < recipientsList[i].Length(); ++j) {
+ nsMsgRecipient& recipient = recipientsList[i][j];
+
+ if (!recipientsStr.IsEmpty()) recipientsStr.Append(char16_t(','));
+ nsAutoString address;
+ MakeMimeAddress(recipient.mName, recipient.mEmail, address);
+ recipientsStr.Append(address);
+
+ if (recipient.mCard) {
+ bool readOnly;
+ rv = recipient.mDirectory->GetReadOnly(&readOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Bump the popularity index for this card since we are about to send
+ // e-mail to it.
+ if (!readOnly) {
+ uint32_t popularityIndex = 0;
+ if (NS_FAILED(recipient.mCard->GetPropertyAsUint32(
+ kPopularityIndexProperty, &popularityIndex))) {
+ // TB 2 wrote the popularity value as hex, so if we get here,
+ // then we've probably got a hex value. We'll convert it back
+ // to decimal, as that's the best we can do.
+
+ nsCString hexPopularity;
+ if (NS_SUCCEEDED(recipient.mCard->GetPropertyAsAUTF8String(
+ kPopularityIndexProperty, hexPopularity))) {
+ nsresult errorCode = NS_OK;
+ popularityIndex = hexPopularity.ToInteger(&errorCode, 16);
+ if (NS_FAILED(errorCode))
+ // We failed, just set it to zero.
+ popularityIndex = 0;
+ } else
+ // We couldn't get it as a string either, so just reset to zero.
+ popularityIndex = 0;
+ }
+
+ recipient.mCard->SetPropertyAsUint32(kPopularityIndexProperty,
+ ++popularityIndex);
+ recipient.mDirectory->ModifyCard(recipient.mCard);
+ }
+ }
+ }
+
+ switch (i) {
+ case 0:
+ m_compFields->SetTo(recipientsStr);
+ break;
+ case 1:
+ m_compFields->SetCc(recipientsStr);
+ break;
+ case 2:
+ m_compFields->SetBcc(recipientsStr);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Decides which tags trigger which convertible mode,
+ * i.e. here is the logic for BodyConvertible.
+ * Note: Helper function. Parameters are not checked.
+ */
+void nsMsgCompose::TagConvertible(Element* node, int32_t* _retval) {
+ *_retval = nsIMsgCompConvertible::No;
+
+ nsAutoString element;
+ element = node->NodeName();
+
+ // A style attribute on any element can change layout in any way,
+ // so that is not convertible.
+ nsAutoString attribValue;
+ node->GetAttribute(u"style"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ *_retval = nsIMsgCompConvertible::No;
+ return;
+ }
+
+ // moz-* classes are used internally by the editor and mail composition
+ // (like moz-cite-prefix or moz-signature). Those can be discarded.
+ // But any other ones are unconvertible. Style can be attached to them or any
+ // other context (e.g. in microformats).
+ node->GetAttribute(u"class"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ if (StringBeginsWith(attribValue, u"moz-"_ns,
+ nsCaseInsensitiveStringComparator)) {
+ // We assume that anything with a moz-* class is convertible regardless of
+ // the tag, because we add, for example, class="moz-signature" to HTML
+ // messages and we still want to be able to downgrade them.
+ *_retval = nsIMsgCompConvertible::Plain;
+ } else {
+ *_retval = nsIMsgCompConvertible::No;
+ }
+
+ return;
+ }
+
+ // ID attributes can contain attached style/context or be target of links
+ // so we should preserve them.
+ node->GetAttribute(u"id"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ *_retval = nsIMsgCompConvertible::No;
+ return;
+ }
+
+ // Alignment is not convertible to plaintext; editor currently uses this.
+ node->GetAttribute(u"align"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ *_retval = nsIMsgCompConvertible::No;
+ return;
+ }
+
+ // Title attribute is not convertible to plaintext;
+ // this also preserves any links with titles.
+ node->GetAttribute(u"title"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ *_retval = nsIMsgCompConvertible::No;
+ return;
+ }
+
+ // Treat <font face="monospace"> as converible to plaintext.
+ if (element.LowerCaseEqualsLiteral("font")) {
+ node->GetAttribute(u"size"_ns, attribValue);
+ if (!attribValue.IsEmpty()) {
+ *_retval = nsIMsgCompConvertible::No;
+ return;
+ }
+ node->GetAttribute(u"face"_ns, attribValue);
+ if (attribValue.LowerCaseEqualsLiteral("monospace")) {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+
+ if ( // Considered convertible to plaintext: Some "simple" elements
+ // without non-convertible attributes like style, class, id,
+ // or align (see above).
+ element.LowerCaseEqualsLiteral("br") ||
+ element.LowerCaseEqualsLiteral("p") ||
+ element.LowerCaseEqualsLiteral("tt") ||
+ element.LowerCaseEqualsLiteral("html") ||
+ element.LowerCaseEqualsLiteral("head") ||
+ element.LowerCaseEqualsLiteral("meta") ||
+ element.LowerCaseEqualsLiteral("title")) {
+ *_retval = nsIMsgCompConvertible::Plain;
+ } else if (
+ // element.LowerCaseEqualsLiteral("blockquote") || // see below
+ element.LowerCaseEqualsLiteral("ul") ||
+ element.LowerCaseEqualsLiteral("ol") ||
+ element.LowerCaseEqualsLiteral("li") ||
+ element.LowerCaseEqualsLiteral("dl") ||
+ element.LowerCaseEqualsLiteral("dt") ||
+ element.LowerCaseEqualsLiteral("dd")) {
+ *_retval = nsIMsgCompConvertible::Yes;
+ } else if (
+ // element.LowerCaseEqualsLiteral("a") || // see below
+ element.LowerCaseEqualsLiteral("h1") ||
+ element.LowerCaseEqualsLiteral("h2") ||
+ element.LowerCaseEqualsLiteral("h3") ||
+ element.LowerCaseEqualsLiteral("h4") ||
+ element.LowerCaseEqualsLiteral("h5") ||
+ element.LowerCaseEqualsLiteral("h6") ||
+ element.LowerCaseEqualsLiteral("hr") ||
+ element.LowerCaseEqualsLiteral("pre") ||
+ (mConvertStructs && (element.LowerCaseEqualsLiteral("em") ||
+ element.LowerCaseEqualsLiteral("strong") ||
+ element.LowerCaseEqualsLiteral("code") ||
+ element.LowerCaseEqualsLiteral("b") ||
+ element.LowerCaseEqualsLiteral("i") ||
+ element.LowerCaseEqualsLiteral("u")))) {
+ *_retval = nsIMsgCompConvertible::Altering;
+ } else if (element.LowerCaseEqualsLiteral("body")) {
+ *_retval = nsIMsgCompConvertible::Plain;
+
+ if (node->HasAttribute(u"background"_ns) || // There is a background image
+ node->HasAttribute(
+ u"dir"_ns)) { // dir=rtl attributes should not downconvert
+ *_retval = nsIMsgCompConvertible::No;
+ } else {
+ nsAutoString color;
+ if (node->HasAttribute(u"text"_ns)) {
+ node->GetAttribute(u"text"_ns, color);
+ if (!color.EqualsLiteral("#000000"))
+ *_retval = nsIMsgCompConvertible::Altering;
+ }
+ if (*_retval != nsIMsgCompConvertible::Altering && // small optimization
+ node->HasAttribute(u"bgcolor"_ns)) {
+ node->GetAttribute(u"bgcolor"_ns, color);
+ if (!color.LowerCaseEqualsLiteral("#ffffff"))
+ *_retval = nsIMsgCompConvertible::Altering;
+ }
+ }
+
+ // ignore special color setting for link, vlink and alink at this point.
+ } else if (element.LowerCaseEqualsLiteral("blockquote")) {
+ // Skip <blockquote type="cite">
+ *_retval = nsIMsgCompConvertible::Yes;
+
+ node->GetAttribute(u"type"_ns, attribValue);
+ if (attribValue.LowerCaseEqualsLiteral("cite")) {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ } else if (element.LowerCaseEqualsLiteral("div") ||
+ element.LowerCaseEqualsLiteral("span") ||
+ element.LowerCaseEqualsLiteral("a")) {
+ // Do some special checks for these tags. They are inside this |else if|
+ // for performance reasons.
+
+ // Maybe, it's an <a> element inserted by another recognizer (e.g. 4.x')
+ if (element.LowerCaseEqualsLiteral("a")) {
+ // Ignore anchor tag, if the URI is the same as the text
+ // (as inserted by recognizers).
+ *_retval = nsIMsgCompConvertible::Altering;
+
+ nsAutoString hrefValue;
+ node->GetAttribute(u"href"_ns, hrefValue);
+ nsINodeList* children = node->ChildNodes();
+ if (children->Length() > 0) {
+ nsINode* pItem = children->Item(0);
+ nsAutoString textValue;
+ pItem->GetNodeValue(textValue);
+ if (textValue == hrefValue) *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+
+ // Lastly, test, if it is just a "simple" <div> or <span>
+ else if (element.LowerCaseEqualsLiteral("div") ||
+ element.LowerCaseEqualsLiteral("span")) {
+ *_retval = nsIMsgCompConvertible::Plain;
+ }
+ }
+}
+
+/**
+ * Note: Helper function. Parameters are not checked.
+ */
+NS_IMETHODIMP
+nsMsgCompose::NodeTreeConvertible(Element* node, int32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ int32_t result;
+
+ // Check this node
+ TagConvertible(node, &result);
+
+ // Walk tree recursively to check the children.
+ nsINodeList* children = node->ChildNodes();
+ for (uint32_t i = 0; i < children->Length(); i++) {
+ nsINode* pItem = children->Item(i);
+ // We assume all nodes that are not elements are convertible,
+ // so only test elements.
+ nsCOMPtr<Element> domElement = do_QueryInterface(pItem);
+ if (domElement) {
+ int32_t curresult;
+ NodeTreeConvertible(domElement, &curresult);
+
+ if (curresult > result) result = curresult;
+ }
+ }
+
+ *_retval = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::BodyConvertible(int32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_STATE(m_editor);
+
+ nsCOMPtr<Document> rootDocument;
+ nsresult rv = m_editor->GetDocument(getter_AddRefs(rootDocument));
+ if (NS_FAILED(rv)) return rv;
+ if (!rootDocument) return NS_ERROR_UNEXPECTED;
+
+ // get the top level element, which contains <html>
+ nsCOMPtr<Element> rootElement = rootDocument->GetDocumentElement();
+ if (!rootElement) return NS_ERROR_UNEXPECTED;
+ NodeTreeConvertible(rootElement, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompose::GetIdentity(nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_IF_ADDREF(*aIdentity = m_identity);
+ return NS_OK;
+}
+
+/**
+ * Position above the quote, that is either <blockquote> or
+ * <div class="moz-cite-prefix"> or <div class="moz-forward-container">
+ * in an inline-forwarded message.
+ */
+nsresult nsMsgCompose::MoveToAboveQuote(void) {
+ RefPtr<Element> rootElement;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ nsCOMPtr<nsINode> node;
+ nsAutoString attributeName;
+ nsAutoString attributeValue;
+ nsAutoString tagLocalName;
+ attributeName.AssignLiteral("class");
+
+ RefPtr<nsINode> rootElement2 = rootElement;
+ node = rootElement2->GetFirstChild();
+ while (node) {
+ nsCOMPtr<Element> element = do_QueryInterface(node);
+ if (element) {
+ // First check for <blockquote>. This will most likely not trigger
+ // since well-behaved quotes are preceded by a cite prefix.
+ tagLocalName = node->LocalName();
+ if (tagLocalName.EqualsLiteral("blockquote")) {
+ break;
+ }
+
+ // Get the class value.
+ element->GetAttribute(attributeName, attributeValue);
+
+ // Now check for the cite prefix, so an element with
+ // class="moz-cite-prefix".
+ if (attributeValue.LowerCaseFindASCII("moz-cite-prefix") != kNotFound) {
+ break;
+ }
+
+ // Next check for forwarded content.
+ // The forwarded part is inside an element with
+ // class="moz-forward-container".
+ if (attributeValue.LowerCaseFindASCII("moz-forward-container") !=
+ kNotFound) {
+ break;
+ }
+ }
+
+ node = node->GetNextSibling();
+ if (!node) {
+ // No further siblings found, so we didn't find what we were looking for.
+ rv = NS_OK;
+ break;
+ }
+ }
+
+ // Now position. If no quote was found, we position to the very front.
+ int32_t offset = 0;
+ if (node) {
+ rv = GetChildOffset(node, rootElement2, offset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ RefPtr<Selection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection) rv = selection->CollapseInLimiter(rootElement, offset);
+
+ return rv;
+}
+
+/**
+ * nsEditor::BeginningOfDocument() will position to the beginning of the
+ * document before the first editable element. It will position into a
+ * container. We need to be at the very front.
+ */
+nsresult nsMsgCompose::MoveToBeginningOfDocument(void) {
+ RefPtr<Element> rootElement;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ RefPtr<Selection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection) rv = selection->CollapseInLimiter(rootElement, 0);
+
+ return rv;
+}
+
+/**
+ * M-C's nsEditor::EndOfDocument() will position to the end of the document
+ * but it will position into a container. We really need to position
+ * after the last container so we don't accidentally position into a
+ * <blockquote>. That's why we use our own function.
+ */
+nsresult nsMsgCompose::MoveToEndOfDocument(void) {
+ int32_t offset;
+ RefPtr<Element> rootElement;
+ nsCOMPtr<nsINode> lastNode;
+ nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) {
+ return rv;
+ }
+
+ RefPtr<nsINode> rootElement2 = rootElement;
+ lastNode = rootElement2->GetLastChild();
+ if (!lastNode) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ rv = GetChildOffset(lastNode, rootElement2, offset);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<Selection> selection;
+ m_editor->GetSelection(getter_AddRefs(selection));
+ if (selection) rv = selection->CollapseInLimiter(rootElement, offset + 1);
+
+ return rv;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMsgCompose::SetIdentity(nsIMsgIdentity* aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ m_identity = aIdentity;
+
+ nsresult rv;
+
+ if (!m_editor) return NS_ERROR_FAILURE;
+
+ RefPtr<Element> rootElement;
+ rv = m_editor->GetRootElement(getter_AddRefs(rootElement));
+ if (NS_FAILED(rv) || !rootElement) return rv;
+
+ // First look for the current signature, if we have one
+ nsCOMPtr<nsINode> lastNode;
+ nsCOMPtr<nsINode> node;
+ nsCOMPtr<nsINode> tempNode;
+ nsAutoString tagLocalName;
+
+ RefPtr<nsINode> rootElement2 = rootElement;
+ lastNode = rootElement2->GetLastChild();
+ if (lastNode) {
+ node = lastNode;
+ // In html, the signature is inside an element with
+ // class="moz-signature"
+ bool signatureFound = false;
+ nsAutoString attributeName;
+ attributeName.AssignLiteral("class");
+
+ while (node) {
+ nsCOMPtr<Element> element = do_QueryInterface(node);
+ if (element) {
+ nsAutoString attributeValue;
+
+ element->GetAttribute(attributeName, attributeValue);
+
+ if (attributeValue.LowerCaseFindASCII("moz-signature") != kNotFound) {
+ signatureFound = true;
+ break;
+ }
+ }
+ node = node->GetPreviousSibling();
+ }
+
+ if (signatureFound) {
+ nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference.
+ editor->BeginTransaction();
+ tempNode = node->GetPreviousSibling();
+ rv = editor->DeleteNode(node);
+ if (NS_FAILED(rv)) {
+ editor->EndTransaction();
+ return rv;
+ }
+
+ // Also, remove the <br> right before the signature.
+ if (tempNode) {
+ tagLocalName = tempNode->LocalName();
+ if (tagLocalName.EqualsLiteral("br")) editor->DeleteNode(tempNode);
+ }
+ editor->EndTransaction();
+ }
+ }
+
+ if (!CheckIncludeSignaturePrefs(aIdentity)) return NS_OK;
+
+ // Then add the new one if needed
+ nsAutoString aSignature;
+
+ // No delimiter needed if not a compose window
+ bool isQuoted;
+ switch (mType) {
+ case nsIMsgCompType::New:
+ case nsIMsgCompType::NewsPost:
+ case nsIMsgCompType::MailToUrl:
+ case nsIMsgCompType::ForwardAsAttachment:
+ isQuoted = false;
+ break;
+ default:
+ isQuoted = true;
+ break;
+ }
+
+ ProcessSignature(aIdentity, isQuoted, &aSignature);
+
+ if (!aSignature.IsEmpty()) {
+ TranslateLineEnding(aSignature);
+ nsCOMPtr<nsIEditor> editor(m_editor); // Strong reference.
+
+ editor->BeginTransaction();
+ int32_t reply_on_top = 0;
+ bool sig_bottom = true;
+ aIdentity->GetReplyOnTop(&reply_on_top);
+ aIdentity->GetSigBottom(&sig_bottom);
+ bool sigOnTop = (reply_on_top == 1 && !sig_bottom);
+ if (sigOnTop && isQuoted) {
+ rv = MoveToAboveQuote();
+ } else {
+ // Note: New messages aren't quoted so we always move to the end.
+ rv = MoveToEndOfDocument();
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ if (m_composeHTML) {
+ bool oldAllow;
+ GetAllowRemoteContent(&oldAllow);
+ SetAllowRemoteContent(true);
+ rv = MOZ_KnownLive(editor->AsHTMLEditor())->InsertHTML(aSignature);
+ SetAllowRemoteContent(oldAllow);
+ } else {
+ rv = editor->InsertLineBreak();
+ InsertDivWrappedTextAtSelection(aSignature, u"moz-signature"_ns);
+ }
+ }
+ editor->EndTransaction();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity* identity,
+ char** fallbackCharset,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(identity);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // Kept around for legacy reasons. This method is supposed to check that the
+ // headers can be converted to the appropriate charset, but we don't support
+ // encoding headers to non-UTF-8, so this is now moot.
+ if (fallbackCharset) *fallbackCharset = nullptr;
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompose::GetDeliverMode(MSG_DeliverMode* aDeliverMode) {
+ NS_ENSURE_ARG_POINTER(aDeliverMode);
+ *aDeliverMode = mDeliverMode;
+ return NS_OK;
+}
+
+void nsMsgCompose::DeleteTmpAttachments() {
+ if (mTmpAttachmentsDeleted || m_window) {
+ // Don't delete tmp attachments if compose window is still open, e.g. saving
+ // a draft.
+ return;
+ }
+ mTmpAttachmentsDeleted = true;
+ // Remove temporary attachment files, e.g. key.asc when attaching public key.
+ nsTArray<RefPtr<nsIMsgAttachment>> attachments;
+ m_compFields->GetAttachments(attachments);
+ for (nsIMsgAttachment* attachment : attachments) {
+ bool isTemporary;
+ attachment->GetTemporary(&isTemporary);
+ bool sentViaCloud;
+ attachment->GetSendViaCloud(&sentViaCloud);
+ if (isTemporary && !sentViaCloud) {
+ nsCString url;
+ attachment->GetUrl(url);
+ nsCOMPtr<nsIFile> urlFile;
+ nsresult rv = NS_GetFileFromURLSpec(url, getter_AddRefs(urlFile));
+ if (NS_SUCCEEDED(rv)) {
+ urlFile->Remove(false);
+ }
+ }
+ }
+}
+
+nsMsgMailList::nsMsgMailList(nsIAbDirectory* directory)
+ : mDirectory(directory) {
+ mDirectory->GetDirName(mName);
+ mDirectory->GetDescription(mDescription);
+
+ if (mDescription.IsEmpty()) mDescription = mName;
+
+ mDirectory = directory;
+}
diff --git a/comm/mailnews/compose/src/nsMsgCompose.h b/comm/mailnews/compose/src/nsMsgCompose.h
new file mode 100644
index 0000000000..fe9b9724c8
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCompose.h
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgCompose_H_
+#define _nsMsgCompose_H_
+
+#include "nsIMsgCompose.h"
+#include "nsCOMArray.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
+#include "nsMsgCompFields.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgQuote.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIBaseWindow.h"
+#include "nsIAbDirectory.h"
+#include "nsIWebProgressListener.h"
+#include "nsIMimeConverter.h"
+#include "nsIMsgFolder.h"
+#include "mozIDOMWindow.h"
+#include "mozilla/dom/Element.h"
+
+// Forward declares
+class QuotingOutputStreamListener;
+class nsMsgComposeSendListener;
+class nsIEditor;
+class nsIArray;
+struct nsMsgMailList;
+
+class nsMsgCompose : public nsIMsgCompose, public nsSupportsWeakReference {
+ public:
+ nsMsgCompose();
+
+ /* this macro defines QueryInterface, AddRef and Release for this class */
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /*** nsIMsgCompose pure virtual functions */
+ NS_DECL_NSIMSGCOMPOSE
+
+ /* nsIMsgSendListener interface */
+ NS_DECL_NSIMSGSENDLISTENER
+
+ protected:
+ virtual ~nsMsgCompose();
+
+ // Deal with quoting issues...
+ nsresult QuoteOriginalMessage(); // New template
+ nsresult SetQuotingToFollow(bool aVal);
+ nsresult ConvertHTMLToText(nsIFile* aSigFile, nsString& aSigData);
+ nsresult ConvertTextToHTML(nsIFile* aSigFile, nsString& aSigData);
+ bool IsEmbeddedObjectSafe(const char* originalScheme,
+ const char* originalHost, const char* originalPath,
+ mozilla::dom::Element* element);
+ nsresult TagEmbeddedObjects(nsIEditor* aEditor);
+
+ nsCString mOriginalMsgURI; // used so we can mark message disposition flags
+ // after we send the message
+
+ int32_t mWhatHolder;
+
+ nsresult LoadDataFromFile(nsIFile* file, nsString& sigData,
+ bool aAllowUTF8 = true, bool aAllowUTF16 = true);
+
+ bool CheckIncludeSignaturePrefs(nsIMsgIdentity* identity);
+ // m_folderName to store the value of the saved drafts folder.
+ nsCString m_folderName;
+ MOZ_CAN_RUN_SCRIPT void InsertDivWrappedTextAtSelection(
+ const nsAString& aText, const nsAString& classStr);
+
+ protected:
+ nsresult CreateMessage(const nsACString& originalMsgURI, MSG_ComposeType type,
+ nsIMsgCompFields* compFields);
+ void CleanUpRecipients(nsString& recipients);
+ nsresult GetABDirAndMailLists(const nsACString& aDirUri,
+ nsCOMArray<nsIAbDirectory>& aDirArray,
+ nsTArray<nsMsgMailList>& aMailListArray);
+ nsresult ResolveMailList(nsIAbDirectory* aMailList,
+ nsCOMArray<nsIAbDirectory>& allDirectoriesArray,
+ nsTArray<nsMsgMailList>& allMailListArray,
+ nsTArray<nsMsgMailList>& mailListResolved,
+ nsTArray<nsMsgRecipient>& aListMembers);
+ void TagConvertible(mozilla::dom::Element* node, int32_t* _retval);
+ MOZ_CAN_RUN_SCRIPT nsresult MoveToAboveQuote(void);
+ MOZ_CAN_RUN_SCRIPT nsresult MoveToBeginningOfDocument(void);
+ MOZ_CAN_RUN_SCRIPT nsresult MoveToEndOfDocument(void);
+ nsresult ReplaceFileURLs(nsString& sigData);
+ nsresult DataURLForFileURL(const nsAString& aFileURL, nsAString& aDataURL);
+
+ /**
+ * Given an nsIFile, attempts to read it into aString.
+ *
+ * Note: Use sparingly! This causes main-thread I/O, which causes jank and all
+ * other bad things.
+ */
+ static nsresult SlurpFileToString(nsIFile* aFile, nsACString& aString);
+
+// 3 = To, Cc, Bcc
+#define MAX_OF_RECIPIENT_ARRAY 3
+ typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY];
+ /**
+ * This method parses the compose fields and associates email addresses with
+ * the relevant cards from the address books.
+ */
+ nsresult LookupAddressBook(RecipientsArray& recipientList);
+ bool IsLastWindow();
+
+ // Helper function. Parameters are not checked.
+ bool mConvertStructs; // for TagConvertible
+
+ nsCOMPtr<nsIEditor> m_editor;
+ mozIDOMWindowProxy* m_window;
+ nsCOMPtr<nsIDocShell> mDocShell;
+ nsCOMPtr<nsIBaseWindow> m_baseWindow;
+ RefPtr<nsMsgCompFields> m_compFields;
+ nsCOMPtr<nsIMsgIdentity> m_identity;
+ bool m_composeHTML;
+ RefPtr<QuotingOutputStreamListener> mQuoteStreamListener;
+ nsCOMPtr<nsIOutputStream> mBaseStream;
+
+ nsCOMPtr<nsIMsgSend> mMsgSend; // for composition back end
+ nsCOMPtr<nsIMsgProgress>
+ mProgress; // use by the back end to report progress to the front end
+
+ // Deal with quoting issues...
+ nsString mCiteReference;
+ nsCOMPtr<nsIMsgQuote> mQuote;
+ bool mQuotingToFollow; // Quoting indicator
+ MSG_ComposeType mType; // Message type
+ bool mAutodetectCharset;
+ bool mDeleteDraft;
+ nsMsgDispositionState mDraftDisposition;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+
+ nsString mSmtpPassword;
+ nsCString mHtmlToQuote;
+
+ nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> > mStateListeners;
+ nsTObserverArray<nsCOMPtr<nsIMsgSendListener> > mExternalSendListeners;
+
+ bool mAllowRemoteContent;
+ MSG_DeliverMode mDeliverMode; // nsIMsgCompDeliverMode long.
+
+ friend class QuotingOutputStreamListener;
+ friend class nsMsgComposeSendListener;
+
+ private:
+ void DeleteTmpAttachments();
+ bool mTmpAttachmentsDeleted;
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+// THIS IS THE CLASS THAT IS THE STREAM Listener OF THE HTML OUTPUT
+// FROM LIBMIME. THIS IS FOR QUOTING
+////////////////////////////////////////////////////////////////////////////////////
+class QuotingOutputStreamListener : public nsIMsgQuotingOutputStreamListener,
+ public nsSupportsWeakReference {
+ public:
+ QuotingOutputStreamListener(nsIMsgDBHdr* origMsgHdr, bool quoteHeaders,
+ bool headersOnly, nsIMsgIdentity* identity,
+ nsIMsgQuote* msgQuote, bool quoteOriginal,
+ const nsACString& htmlToQuote);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIMSGQUOTINGOUTPUTSTREAMLISTENER
+
+ nsresult SetComposeObj(nsIMsgCompose* obj);
+ nsresult ConvertToPlainText(bool formatflowed, bool formatted,
+ bool disallowBreaks);
+ MOZ_CAN_RUN_SCRIPT nsresult InsertToCompose(nsIEditor* aEditor,
+ bool aHTMLEditor);
+ nsresult AppendToMsgBody(const nsCString& inStr);
+
+ private:
+ virtual ~QuotingOutputStreamListener();
+ nsWeakPtr mWeakComposeObj;
+ nsString mMsgBody;
+ nsString mCitePrefix;
+ nsString mSignature;
+ bool mQuoteHeaders;
+ bool mHeadersOnly;
+ nsCOMPtr<nsIMsgQuote> mQuote;
+ nsCOMPtr<nsIMimeHeaders> mHeaders;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+ nsString mCiteReference;
+ nsCOMPtr<nsIMimeConverter> mMimeConverter;
+ int32_t mUnicodeBufferCharacterLength;
+ bool mQuoteOriginal;
+ nsCString mHtmlToQuote;
+};
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this
+// class to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class nsMsgComposeSendListener : public nsIMsgComposeSendListener,
+ public nsIMsgSendListener,
+ public nsIMsgCopyServiceListener,
+ public nsIWebProgressListener {
+ public:
+ nsMsgComposeSendListener(void);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIMsgComposeSendListener interface
+ NS_DECL_NSIMSGCOMPOSESENDLISTENER
+
+ // nsIMsgSendListener interface
+ NS_DECL_NSIMSGSENDLISTENER
+
+ // nsIMsgCopyServiceListener interface
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ // nsIWebProgressListener interface
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ nsresult RemoveDraftOrTemplate(nsIMsgCompose* compObj, nsCString msgURI,
+ bool isSaveTemplate);
+ nsresult RemoveCurrentDraftMessage(nsIMsgCompose* compObj, bool calledByCopy,
+ bool isSaveTemplate);
+ nsresult GetMsgFolder(nsIMsgCompose* compObj, nsIMsgFolder** msgFolder);
+
+ private:
+ virtual ~nsMsgComposeSendListener();
+ nsWeakPtr mWeakComposeObj;
+ MSG_DeliverMode mDeliverMode;
+};
+
+/******************************************************************************
+ * nsMsgMailList
+ ******************************************************************************/
+struct nsMsgMailList {
+ explicit nsMsgMailList(nsIAbDirectory* directory);
+
+ nsString mName;
+ nsString mDescription;
+ nsCOMPtr<nsIAbDirectory> mDirectory;
+};
+
+#endif /* _nsMsgCompose_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp b/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp
new file mode 100644
index 0000000000..7683d04d89
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeContentHandler.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgComposeContentHandler.h"
+#include "nsMsgComposeService.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+#include "plstr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+
+#define NS_MSGCOMPOSESERVICE_CID \
+ { /* 588595FE-1ADA-11d3-A715-0060B0EB39B5 */ \
+ 0x588595fe, 0x1ada, 0x11d3, { \
+ 0xa7, 0x15, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5 \
+ } \
+ }
+static NS_DEFINE_CID(kMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID);
+
+nsMsgComposeContentHandler::nsMsgComposeContentHandler() {}
+
+// The following macro actually implement addref, release and query interface
+// for our component.
+NS_IMPL_ISUPPORTS(nsMsgComposeContentHandler, nsIContentHandler)
+
+nsMsgComposeContentHandler::~nsMsgComposeContentHandler() {}
+
+// Try to get an appropriate nsIMsgIdentity by going through the window, getting
+// the document's URI, then the corresponding nsIMsgDBHdr. Then find the server
+// associated with that header and get the first identity for it.
+nsresult nsMsgComposeContentHandler::GetBestIdentity(
+ nsIInterfaceRequestor* aWindowContext, nsIMsgIdentity** aIdentity) {
+ nsresult rv;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext);
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(domWindow);
+
+ nsAutoString documentURIString;
+ rv = window->GetDoc()->GetDocumentURI(documentURIString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> documentURI;
+ rv = NS_NewURI(getter_AddRefs(documentURI), documentURIString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgURI = do_QueryInterface(documentURI);
+ if (!msgURI) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgURI->GetMessageHeader(getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIMsgDBHdrs from .eml messages have a null folder, so bail out if that's
+ // the case.
+ if (!folder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->GetFirstIdentityForServer(server, aIdentity);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeContentHandler::HandleContent(
+ const char* aContentType, nsIInterfaceRequestor* aWindowContext,
+ nsIRequest* request) {
+ nsresult rv = NS_OK;
+ if (!request) return NS_ERROR_NULL_POINTER;
+
+ // First of all, get the content type and make sure it is a content type we
+ // know how to handle!
+ if (PL_strcasecmp(aContentType, "application/x-mailto") == 0) {
+ nsCOMPtr<nsIMsgIdentity> identity;
+
+ if (aWindowContext)
+ GetBestIdentity(aWindowContext, getter_AddRefs(identity));
+
+ nsCOMPtr<nsIURI> aUri;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ rv = aChannel->GetURI(getter_AddRefs(aUri));
+ if (aUri) {
+ nsCOMPtr<nsIMsgComposeService> composeService =
+ do_GetService(kMsgComposeServiceCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = composeService->OpenComposeWindowWithURI(nullptr, aUri, identity);
+ }
+ } else {
+ // The content-type was not application/x-mailto...
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/compose/src/nsMsgComposeContentHandler.h b/comm/mailnews/compose/src/nsMsgComposeContentHandler.h
new file mode 100644
index 0000000000..158edfa09b
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeContentHandler.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIContentHandler.h"
+#include "nsIMsgIdentity.h"
+
+class nsMsgComposeContentHandler : public nsIContentHandler {
+ public:
+ nsMsgComposeContentHandler();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTHANDLER
+ private:
+ virtual ~nsMsgComposeContentHandler();
+ nsresult GetBestIdentity(nsIInterfaceRequestor* aWindowContext,
+ nsIMsgIdentity** identity);
+};
diff --git a/comm/mailnews/compose/src/nsMsgComposeParams.cpp b/comm/mailnews/compose/src/nsMsgComposeParams.cpp
new file mode 100644
index 0000000000..653ed11f46
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeParams.cpp
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgComposeParams.h"
+
+nsMsgComposeParams::nsMsgComposeParams()
+ : mType(nsIMsgCompType::New),
+ mFormat(nsIMsgCompFormat::Default),
+ mBodyIsLink(false),
+ mAutodetectCharset(false) {}
+
+/* the following macro actually implement addref, release and query interface
+ * for our component. */
+NS_IMPL_ISUPPORTS(nsMsgComposeParams, nsIMsgComposeParams)
+
+nsMsgComposeParams::~nsMsgComposeParams() {}
+
+/* attribute MSG_ComposeType type; */
+NS_IMETHODIMP nsMsgComposeParams::GetType(MSG_ComposeType* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aType = mType;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetType(MSG_ComposeType aType) {
+ mType = aType;
+ return NS_OK;
+}
+
+/* attribute MSG_ComposeFormat format; */
+NS_IMETHODIMP nsMsgComposeParams::GetFormat(MSG_ComposeFormat* aFormat) {
+ NS_ENSURE_ARG_POINTER(aFormat);
+
+ *aFormat = mFormat;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetFormat(MSG_ComposeFormat aFormat) {
+ mFormat = aFormat;
+ return NS_OK;
+}
+
+/* attribute string originalMsgURI; */
+NS_IMETHODIMP nsMsgComposeParams::GetOriginalMsgURI(
+ nsACString& aOriginalMsgURI) {
+ aOriginalMsgURI = mOriginalMsgUri;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetOriginalMsgURI(
+ const nsACString& aOriginalMsgURI) {
+ mOriginalMsgUri = aOriginalMsgURI;
+ return NS_OK;
+}
+
+/* attribute nsIMsgIdentity identity; */
+NS_IMETHODIMP nsMsgComposeParams::GetIdentity(nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_IF_ADDREF(*aIdentity = mIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::SetIdentity(nsIMsgIdentity* aIdentity) {
+ mIdentity = aIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::SetOrigMsgHdr(nsIMsgDBHdr* aMsgHdr) {
+ mOrigMsgHdr = aMsgHdr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::GetOrigMsgHdr(nsIMsgDBHdr** aMsgHdr) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeParams::GetAutodetectCharset(
+ bool* aAutodetectCharset) {
+ NS_ENSURE_ARG_POINTER(aAutodetectCharset);
+ *aAutodetectCharset = mAutodetectCharset;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetAutodetectCharset(
+ bool aAutodetectCharset) {
+ mAutodetectCharset = aAutodetectCharset;
+ return NS_OK;
+}
+
+/* attribute ACString htmlToQuote; */
+NS_IMETHODIMP nsMsgComposeParams::GetHtmlToQuote(nsACString& aHtmlToQuote) {
+ aHtmlToQuote = mHtmlToQuote;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetHtmlToQuote(
+ const nsACString& aHtmlToQuote) {
+ mHtmlToQuote = aHtmlToQuote;
+ return NS_OK;
+}
+
+/* attribute nsIMsgCompFields composeFields; */
+NS_IMETHODIMP nsMsgComposeParams::GetComposeFields(
+ nsIMsgCompFields** aComposeFields) {
+ NS_ENSURE_ARG_POINTER(aComposeFields);
+
+ if (mComposeFields) {
+ NS_ADDREF(*aComposeFields = mComposeFields);
+ } else
+ *aComposeFields = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetComposeFields(
+ nsIMsgCompFields* aComposeFields) {
+ mComposeFields = aComposeFields;
+ return NS_OK;
+}
+
+/* attribute boolean bodyIsLink; */
+NS_IMETHODIMP nsMsgComposeParams::GetBodyIsLink(bool* aBodyIsLink) {
+ NS_ENSURE_ARG_POINTER(aBodyIsLink);
+
+ *aBodyIsLink = mBodyIsLink;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetBodyIsLink(bool aBodyIsLink) {
+ mBodyIsLink = aBodyIsLink;
+ return NS_OK;
+}
+
+/* attribute nsIMsgSendLisneter sendListener; */
+NS_IMETHODIMP nsMsgComposeParams::GetSendListener(
+ nsIMsgSendListener** aSendListener) {
+ NS_ENSURE_ARG_POINTER(aSendListener);
+
+ if (mSendListener) {
+ NS_ADDREF(*aSendListener = mSendListener);
+ } else
+ *aSendListener = nullptr;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetSendListener(
+ nsIMsgSendListener* aSendListener) {
+ mSendListener = aSendListener;
+ return NS_OK;
+}
+
+/* attribute string smtpPassword; */
+NS_IMETHODIMP nsMsgComposeParams::GetSmtpPassword(nsAString& aSmtpPassword) {
+ aSmtpPassword = mSMTPPassword;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeParams::SetSmtpPassword(
+ const nsAString& aSmtpPassword) {
+ mSMTPPassword = aSmtpPassword;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgComposeParams.h b/comm/mailnews/compose/src/nsMsgComposeParams.h
new file mode 100644
index 0000000000..b447304e30
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeParams.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIMsgComposeParams.h"
+#include "nsString.h"
+#include "nsIMsgHdr.h"
+#include "nsCOMPtr.h"
+class nsMsgComposeParams : public nsIMsgComposeParams {
+ public:
+ nsMsgComposeParams();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSEPARAMS
+
+ private:
+ virtual ~nsMsgComposeParams();
+ MSG_ComposeType mType;
+ MSG_ComposeFormat mFormat;
+ nsCString mOriginalMsgUri;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCOMPtr<nsIMsgCompFields> mComposeFields;
+ bool mBodyIsLink;
+ nsCOMPtr<nsIMsgSendListener> mSendListener;
+ nsString mSMTPPassword;
+ nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr;
+ bool mAutodetectCharset;
+ nsCString mHtmlToQuote;
+};
diff --git a/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp b/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp
new file mode 100644
index 0000000000..d54355ab8f
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeProgressParams.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgComposeProgressParams.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgComposeProgressParams, nsIMsgComposeProgressParams)
+
+nsMsgComposeProgressParams::nsMsgComposeProgressParams()
+ : m_deliveryMode(nsIMsgCompDeliverMode::Now) {}
+
+nsMsgComposeProgressParams::~nsMsgComposeProgressParams() {}
+
+/* attribute wstring subject; */
+NS_IMETHODIMP nsMsgComposeProgressParams::GetSubject(char16_t** aSubject) {
+ NS_ENSURE_ARG(aSubject);
+
+ *aSubject = ToNewUnicode(m_subject);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeProgressParams::SetSubject(const char16_t* aSubject) {
+ m_subject = aSubject;
+ return NS_OK;
+}
+
+/* attribute MSG_DeliverMode deliveryMode; */
+NS_IMETHODIMP nsMsgComposeProgressParams::GetDeliveryMode(
+ MSG_DeliverMode* aDeliveryMode) {
+ NS_ENSURE_ARG(aDeliveryMode);
+
+ *aDeliveryMode = m_deliveryMode;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgComposeProgressParams::SetDeliveryMode(
+ MSG_DeliverMode aDeliveryMode) {
+ m_deliveryMode = aDeliveryMode;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgComposeProgressParams.h b/comm/mailnews/compose/src/nsMsgComposeProgressParams.h
new file mode 100644
index 0000000000..141d870195
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeProgressParams.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIMsgComposeProgressParams.h"
+
+class nsMsgComposeProgressParams : public nsIMsgComposeProgressParams {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSEPROGRESSPARAMS
+
+ nsMsgComposeProgressParams();
+
+ private:
+ virtual ~nsMsgComposeProgressParams();
+ nsString m_subject;
+ MSG_DeliverMode m_deliveryMode;
+};
diff --git a/comm/mailnews/compose/src/nsMsgComposeService.cpp b/comm/mailnews/compose/src/nsMsgComposeService.cpp
new file mode 100644
index 0000000000..be12fbbef4
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeService.cpp
@@ -0,0 +1,1411 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgComposeService.h"
+#include "nsIMsgSend.h"
+#include "nsIServiceManager.h"
+#include "nsIObserverService.h"
+#include "nsIMsgIdentity.h"
+#include "nsISmtpUrl.h"
+#include "nsIURI.h"
+#include "nsMsgI18N.h"
+#include "nsIMsgComposeParams.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIWindowWatcher.h"
+#include "mozIDOMWindow.h"
+#include "nsIContentViewer.h"
+#include "nsIMsgWindow.h"
+#include "nsIDocShell.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/dom/Document.h"
+#include "nsIAppWindow.h"
+#include "nsIWindowMediator.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIBaseWindow.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIStreamConverter.h"
+#include "nsToolkitCompsCID.h"
+#include "nsNetUtil.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMsgDatabase.h"
+#include "nsIDocumentEncoder.h"
+#include "nsContentCID.h"
+#include "mozilla/dom/Selection.h"
+#include "nsUTF8Utils.h"
+#include "mozilla/intl/LineBreaker.h"
+#include "mimemoz2.h"
+#include "nsIURIMutator.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "nsFrameLoader.h"
+#include "nsSmtpUrl.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/NullPrincipal.h"
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+# include "mozilla/Logging.h"
+# include "nsIMsgHdr.h"
+# include "nsIMsgMessageService.h"
+# include "nsMsgUtils.h"
+#endif
+
+#include "nsICommandLine.h"
+#include "nsIAppStartup.h"
+#include "nsMsgUtils.h"
+#include "nsIPrincipal.h"
+#include "nsIMutableArray.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#ifdef XP_WIN
+# include <windows.h>
+# include <shellapi.h>
+# include "nsIWidget.h"
+#endif
+
+#define DEFAULT_CHROME \
+ "chrome://messenger/content/messengercompose/messengercompose.xhtml"_ns
+
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION "mailnews.reply_quoting_selection"
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD \
+ "mailnews.reply_quoting_selection.multi_word"
+#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF \
+ "mailnews.reply_quoting_selection.only_if_chars"
+
+#define MAIL_ROOT_PREF "mail."
+#define MAILNEWS_ROOT_PREF "mailnews."
+#define HTMLDOMAINUPDATE_VERSION_PREF_NAME "global_html_domains.version"
+#define HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME "global_html_domains"
+#define USER_CURRENT_HTMLDOMAINLIST_PREF_NAME "html_domains"
+#define USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME "plaintext_domains"
+#define DOMAIN_DELIMITER ','
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+static mozilla::LazyLogModule MsgComposeLogModule("MsgCompose");
+
+static uint32_t GetMessageSizeFromURI(const nsACString& originalMsgURI) {
+ uint32_t msgSize = 0;
+
+ if (!originalMsgURI.IsEmpty()) {
+ nsCOMPtr<nsIMsgDBHdr> originalMsgHdr;
+ GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr));
+ if (originalMsgHdr) originalMsgHdr->GetMessageSize(&msgSize);
+ }
+
+ return msgSize;
+}
+#endif
+
+nsMsgComposeService::nsMsgComposeService() {
+ // Defaulting the value of mLogComposePerformance to FALSE to prevent logging.
+ mLogComposePerformance = false;
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ mStartTime = PR_IntervalNow();
+ mPreviousTime = mStartTime;
+#endif
+}
+
+NS_IMPL_ISUPPORTS(nsMsgComposeService, nsIMsgComposeService,
+ ICOMMANDLINEHANDLER, nsISupportsWeakReference)
+
+nsMsgComposeService::~nsMsgComposeService() { mOpenComposeWindows.Clear(); }
+
+nsresult nsMsgComposeService::Init() {
+ nsresult rv = NS_OK;
+
+ Reset();
+
+ AddGlobalHtmlDomains();
+ // Since the compose service should only be initialized once, we can
+ // be pretty sure there aren't any existing compose windows open.
+ MsgCleanupTempFiles("nsmail", "tmp");
+ MsgCleanupTempFiles("nscopy", "tmp");
+ MsgCleanupTempFiles("nsemail", "eml");
+ MsgCleanupTempFiles("nsemail", "tmp");
+ MsgCleanupTempFiles("nsqmail", "tmp");
+ return rv;
+}
+
+void nsMsgComposeService::Reset() {
+ mOpenComposeWindows.Clear();
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs)
+ prefs->GetBoolPref("mailnews.logComposePerformance",
+ &mLogComposePerformance);
+}
+
+// Function to open a message compose window and pass an nsIMsgComposeParams
+// parameter to it.
+NS_IMETHODIMP
+nsMsgComposeService::OpenComposeWindowWithParams(const char* chrome,
+ nsIMsgComposeParams* params) {
+ NS_ENSURE_ARG_POINTER(params);
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ if (mLogComposePerformance) {
+ TimeStamp("Start opening the window", true);
+ }
+#endif
+
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(params);
+
+ // Use default identity if no identity has been specified
+ nsCOMPtr<nsIMsgIdentity> identity;
+ params->GetIdentity(getter_AddRefs(identity));
+ if (!identity) {
+ GetDefaultIdentity(getter_AddRefs(identity));
+ params->SetIdentity(identity);
+ }
+
+ // Create a new window.
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (!wwatch) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsISupportsInterfacePointer> msgParamsWrapper =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgParamsWrapper->SetData(params);
+ msgParamsWrapper->SetDataIID(&NS_GET_IID(nsIMsgComposeParams));
+
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ nsAutoCString chromeURL;
+ if (chrome && *chrome) {
+ chromeURL = nsDependentCString(chrome);
+ } else {
+ chromeURL = DEFAULT_CHROME;
+ }
+ rv = wwatch->OpenWindow(0, chromeURL, "_blank"_ns,
+ "all,chrome,dialog=no,status,toolbar"_ns,
+ msgParamsWrapper, getter_AddRefs(newWindow));
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::DetermineComposeHTML(nsIMsgIdentity* aIdentity,
+ MSG_ComposeFormat aFormat,
+ bool* aComposeHTML) {
+ NS_ENSURE_ARG_POINTER(aComposeHTML);
+
+ *aComposeHTML = true;
+ switch (aFormat) {
+ case nsIMsgCompFormat::HTML:
+ *aComposeHTML = true;
+ break;
+ case nsIMsgCompFormat::PlainText:
+ *aComposeHTML = false;
+ break;
+
+ default:
+ nsCOMPtr<nsIMsgIdentity> identity = aIdentity;
+ if (!identity) GetDefaultIdentity(getter_AddRefs(identity));
+
+ if (identity) {
+ identity->GetComposeHtml(aComposeHTML);
+ if (aFormat == nsIMsgCompFormat::OppositeOfDefault)
+ *aComposeHTML = !*aComposeHTML;
+ } else {
+ // default identity not found. Use the mail.html_compose pref to
+ // determine message compose type (HTML or PlainText).
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ nsresult rv;
+ bool useHTMLCompose;
+ rv = prefs->GetBoolPref(MAIL_ROOT_PREF "html_compose",
+ &useHTMLCompose);
+ if (NS_SUCCEEDED(rv)) *aComposeHTML = useHTMLCompose;
+ }
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION nsresult
+nsMsgComposeService::GetOrigWindowSelection(MSG_ComposeType type,
+ mozilla::dom::Selection* selection,
+ nsACString& aSelHTML) {
+ nsresult rv;
+
+ // Good hygiene
+ aSelHTML.Truncate();
+
+ // Get the pref to see if we even should do reply quoting selection
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool replyQuotingSelection;
+ rv = prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION,
+ &replyQuotingSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!replyQuotingSelection) return NS_ERROR_ABORT;
+
+ bool requireMultipleWords = true;
+ nsAutoCString charsOnlyIf;
+ prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD,
+ &requireMultipleWords);
+ prefs->GetCharPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF,
+ charsOnlyIf);
+ if (requireMultipleWords || !charsOnlyIf.IsEmpty()) {
+ nsAutoString selPlain;
+ selection->Stringify(selPlain);
+
+ // If "mailnews.reply_quoting_selection.multi_word" is on, then there must
+ // be at least two words selected in order to quote just the selected text
+ if (requireMultipleWords) {
+ if (selPlain.IsEmpty()) return NS_ERROR_ABORT;
+
+ if (NS_SUCCEEDED(rv)) {
+ const uint32_t length = selPlain.Length();
+ const char16_t* unicodeStr = selPlain.get();
+ int32_t endWordPos =
+ mozilla::intl::LineBreaker::Next(unicodeStr, length, 0);
+
+ // If there's not even one word, then there's not multiple words
+ if (endWordPos == NS_LINEBREAKER_NEED_MORE_TEXT) return NS_ERROR_ABORT;
+
+ // If after the first word is only space, then there's not multiple
+ // words
+ const char16_t* end;
+ for (end = unicodeStr + endWordPos; mozilla::intl::NS_IsSpace(*end);
+ end++)
+ ;
+ if (!*end) return NS_ERROR_ABORT;
+ }
+ }
+
+ if (!charsOnlyIf.IsEmpty()) {
+ if (selPlain.FindCharInSet(NS_ConvertUTF8toUTF16(charsOnlyIf)) ==
+ kNotFound) {
+ return NS_ERROR_ABORT;
+ }
+ }
+ }
+
+ nsAutoString selHTML;
+ IgnoredErrorResult rv2;
+ selection->ToStringWithFormat(u"text/html"_ns,
+ nsIDocumentEncoder::SkipInvisibleContent, 0,
+ selHTML, rv2);
+ if (rv2.Failed()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now remove <span class="moz-txt-citetags">&gt; </span>.
+ nsAutoCString html(NS_ConvertUTF16toUTF8(selHTML).get());
+ int32_t spanInd = html.Find("<span class=\"moz-txt-citetags\">");
+ while (spanInd != kNotFound) {
+ nsAutoCString right0(Substring(html, spanInd));
+ int32_t endInd = right0.Find("</span>");
+ if (endInd == kNotFound) break; // oops, where is the closing tag gone?
+ nsAutoCString right1(Substring(html, spanInd + endInd + 7));
+ html.SetLength(spanInd);
+ html.Append(right1);
+ spanInd = html.Find("<span class=\"moz-txt-citetags\">");
+ }
+
+ aSelHTML.Assign(html);
+
+ return rv;
+}
+
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION NS_IMETHODIMP
+nsMsgComposeService::OpenComposeWindow(
+ const nsACString& msgComposeWindowURL, nsIMsgDBHdr* origMsgHdr,
+ const nsACString& originalMsgURI, MSG_ComposeType type,
+ MSG_ComposeFormat format, nsIMsgIdentity* aIdentity, const nsACString& from,
+ nsIMsgWindow* aMsgWindow, mozilla::dom::Selection* selection,
+ bool autodetectCharset) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIdentity> identity = aIdentity;
+ if (!identity) GetDefaultIdentity(getter_AddRefs(identity));
+
+ /* Actually, the only way to implement forward inline is to simulate a
+ template message. Maybe one day when we will have more time we can change
+ that
+ */
+ if (type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft ||
+ type == nsIMsgCompType::EditTemplate ||
+ type == nsIMsgCompType::Template ||
+ type == nsIMsgCompType::ReplyWithTemplate ||
+ type == nsIMsgCompType::Redirect || type == nsIMsgCompType::EditAsNew) {
+ nsAutoCString uriToOpen(originalMsgURI);
+ char sep = (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&';
+
+ // The compose type that gets transmitted to a compose window open in mime
+ // is communicated using url query parameters here.
+ if (type == nsIMsgCompType::Redirect) {
+ uriToOpen += sep;
+ uriToOpen.AppendLiteral("redirect=true");
+ } else if (type == nsIMsgCompType::EditAsNew) {
+ uriToOpen += sep;
+ uriToOpen.AppendLiteral("editasnew=true");
+ } else if (type == nsIMsgCompType::EditTemplate) {
+ uriToOpen += sep;
+ uriToOpen.AppendLiteral("edittempl=true");
+ }
+
+ return LoadDraftOrTemplate(
+ uriToOpen,
+ type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft
+ ? nsMimeOutput::nsMimeMessageDraftOrTemplate
+ : nsMimeOutput::nsMimeMessageEditorTemplate,
+ identity, originalMsgURI, origMsgHdr,
+ type == nsIMsgCompType::ForwardInline,
+ format == nsIMsgCompFormat::OppositeOfDefault, aMsgWindow,
+ autodetectCharset);
+ }
+
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(
+ do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv));
+ if (NS_SUCCEEDED(rv) && pMsgComposeParams) {
+ nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance(
+ "@mozilla.org/messengercompose/composefields;1", &rv));
+ if (NS_SUCCEEDED(rv) && pMsgCompFields) {
+ pMsgComposeParams->SetType(type);
+ pMsgComposeParams->SetFormat(format);
+ pMsgComposeParams->SetIdentity(identity);
+ pMsgComposeParams->SetAutodetectCharset(autodetectCharset);
+
+ // When doing a reply (except with a template) see if there's a selection
+ // that we should quote
+ if (selection &&
+ (type == nsIMsgCompType::Reply || type == nsIMsgCompType::ReplyAll ||
+ type == nsIMsgCompType::ReplyToSender ||
+ type == nsIMsgCompType::ReplyToGroup ||
+ type == nsIMsgCompType::ReplyToSenderAndGroup ||
+ type == nsIMsgCompType::ReplyToList)) {
+ nsAutoCString selHTML;
+ if (NS_SUCCEEDED(GetOrigWindowSelection(type, selection, selHTML))) {
+ nsCOMPtr<nsINode> node = selection->GetFocusNode();
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+ IgnoredErrorResult er;
+
+ if ((node->LocalName().IsEmpty() ||
+ node->LocalName().EqualsLiteral("pre")) &&
+ node->OwnerDoc()->QuerySelector(
+ "body > div:first-of-type.moz-text-plain"_ns, er)) {
+ // Treat the quote as <pre> for selections in moz-text-plain bodies.
+ // If focusNode.localName isn't empty, we had e.g. body selected
+ // and should not add <pre>.
+ pMsgComposeParams->SetHtmlToQuote("<pre>"_ns + selHTML +
+ "</pre>"_ns);
+ } else {
+ pMsgComposeParams->SetHtmlToQuote(selHTML);
+ }
+ }
+ }
+
+ if (!originalMsgURI.IsEmpty()) {
+ if (type == nsIMsgCompType::NewsPost) {
+ nsAutoCString newsURI(originalMsgURI);
+ nsAutoCString group;
+ nsAutoCString host;
+
+ int32_t slashpos = newsURI.RFindChar('/');
+ if (slashpos > 0) {
+ // uri is "[s]news://host[:port]/group"
+ host = StringHead(newsURI, slashpos);
+ group = Substring(newsURI, slashpos + 1);
+
+ } else
+ group = originalMsgURI;
+
+ nsAutoCString unescapedName;
+ MsgUnescapeString(group,
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME |
+ nsINetUtil::ESCAPE_URL_FORCED,
+ unescapedName);
+ pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(unescapedName));
+ pMsgCompFields->SetNewspostUrl(host.get());
+ } else {
+ pMsgComposeParams->SetOriginalMsgURI(originalMsgURI);
+ pMsgComposeParams->SetOrigMsgHdr(origMsgHdr);
+ pMsgCompFields->SetFrom(NS_ConvertUTF8toUTF16(from));
+ }
+ }
+
+ pMsgComposeParams->SetComposeFields(pMsgCompFields);
+
+ if (mLogComposePerformance) {
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ // ducarroz, properly fix this in the case of new message (not a reply)
+ if (type != nsIMsgCompType::NewsPost) {
+ char buff[256];
+ sprintf(buff, "Start opening the window, message size = %d",
+ GetMessageSizeFromURI(originalMsgURI));
+ TimeStamp(buff, true);
+ }
+#endif
+ } // end if(mLogComposePerformance)
+
+ rv = OpenComposeWindowWithParams(
+ PromiseFlatCString(msgComposeWindowURL).get(), pMsgComposeParams);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeService::GetParamsForMailto(
+ nsIURI* aURI, nsIMsgComposeParams** aParams) {
+ nsresult rv = NS_OK;
+ if (aURI) {
+ nsCString spec;
+ aURI->GetSpec(spec);
+
+ nsCOMPtr<nsIURI> url;
+ rv = nsMailtoUrl::NewMailtoURI(spec, nullptr, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMailtoUrl> aMailtoUrl = do_QueryInterface(url, &rv);
+
+ if (NS_SUCCEEDED(rv)) {
+ MSG_ComposeFormat requestedComposeFormat = nsIMsgCompFormat::Default;
+ nsCString toPart;
+ nsCString ccPart;
+ nsCString bccPart;
+ nsCString subjectPart;
+ nsCString bodyPart;
+ nsCString newsgroup;
+ nsCString refPart;
+ nsCString HTMLBodyPart;
+
+ aMailtoUrl->GetMessageContents(toPart, ccPart, bccPart, subjectPart,
+ bodyPart, HTMLBodyPart, refPart, newsgroup,
+ &requestedComposeFormat);
+
+ nsAutoString sanitizedBody;
+
+ bool composeHTMLFormat;
+ DetermineComposeHTML(NULL, requestedComposeFormat, &composeHTMLFormat);
+
+ // If there was an 'html-body' param, finding it will have requested
+ // HTML format in GetMessageContents, so we try to use it first. If it's
+ // empty, but we are composing in HTML because of the user's prefs, the
+ // 'body' param needs to be escaped, since it's supposed to be plain
+ // text, but it then doesn't need to sanitized.
+ nsString rawBody;
+ if (HTMLBodyPart.IsEmpty()) {
+ if (composeHTMLFormat) {
+ nsCString escaped;
+ nsAppendEscapedHTML(bodyPart, escaped);
+ CopyUTF8toUTF16(escaped, sanitizedBody);
+ } else
+ CopyUTF8toUTF16(bodyPart, rawBody);
+ } else
+ CopyUTF8toUTF16(HTMLBodyPart, rawBody);
+
+ if (!rawBody.IsEmpty() && composeHTMLFormat) {
+ // For security reason, we must sanitize the message body before
+ // accepting any html...
+
+ rv = HTMLSanitize(rawBody, sanitizedBody); // from mimemoz2.h
+
+ if (NS_FAILED(rv)) {
+ // Something went horribly wrong with parsing for html format
+ // in the body. Set composeHTMLFormat to false so we show the
+ // plain text mail compose.
+ composeHTMLFormat = false;
+ }
+ }
+
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(do_CreateInstance(
+ "@mozilla.org/messengercompose/composeparams;1", &rv));
+ if (NS_SUCCEEDED(rv) && pMsgComposeParams) {
+ pMsgComposeParams->SetType(nsIMsgCompType::MailToUrl);
+ pMsgComposeParams->SetFormat(composeHTMLFormat
+ ? nsIMsgCompFormat::HTML
+ : nsIMsgCompFormat::PlainText);
+
+ nsCOMPtr<nsIMsgCompFields> pMsgCompFields(do_CreateInstance(
+ "@mozilla.org/messengercompose/composefields;1", &rv));
+ if (pMsgCompFields) {
+ // ugghh more conversion work!!!!
+ pMsgCompFields->SetTo(NS_ConvertUTF8toUTF16(toPart));
+ pMsgCompFields->SetCc(NS_ConvertUTF8toUTF16(ccPart));
+ pMsgCompFields->SetBcc(NS_ConvertUTF8toUTF16(bccPart));
+ pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(newsgroup));
+ pMsgCompFields->SetReferences(refPart.get());
+ pMsgCompFields->SetSubject(NS_ConvertUTF8toUTF16(subjectPart));
+ pMsgCompFields->SetBody(composeHTMLFormat ? sanitizedBody : rawBody);
+ pMsgComposeParams->SetComposeFields(pMsgCompFields);
+
+ NS_ADDREF(*aParams = pMsgComposeParams);
+ return NS_OK;
+ }
+ } // if we created msg compose params....
+ } // if we had a mailto url
+ } // if we had a url...
+
+ // if we got here we must have encountered an error
+ *aParams = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgComposeService::OpenComposeWindowWithURI(
+ const char* aMsgComposeWindowURL, nsIURI* aURI, nsIMsgIdentity* identity) {
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams;
+ nsresult rv = GetParamsForMailto(aURI, getter_AddRefs(pMsgComposeParams));
+ if (NS_SUCCEEDED(rv)) {
+ pMsgComposeParams->SetIdentity(identity);
+ rv = OpenComposeWindowWithParams(aMsgComposeWindowURL, pMsgComposeParams);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgComposeService::InitCompose(nsIMsgComposeParams* aParams,
+ mozIDOMWindowProxy* aWindow,
+ nsIDocShell* aDocShell,
+ nsIMsgCompose** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgCompose> msgCompose =
+ do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = msgCompose->Initialize(aParams, aWindow, aDocShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = msgCompose);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetDefaultIdentity(nsIMsgIdentity** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return defaultAccount ? defaultAccount->GetDefaultIdentity(_retval) : NS_OK;
+}
+
+/* readonly attribute boolean logComposePerformance; */
+NS_IMETHODIMP nsMsgComposeService::GetLogComposePerformance(
+ bool* aLogComposePerformance) {
+ *aLogComposePerformance = mLogComposePerformance;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeService::TimeStamp(const char* label,
+ bool resetTime) {
+ if (!mLogComposePerformance) return NS_OK;
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+
+ PRIntervalTime now;
+
+ if (resetTime) {
+ MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info,
+ ("\n[process]: [totalTime][deltaTime]\n--------------------\n"));
+
+ mStartTime = PR_IntervalNow();
+ mPreviousTime = mStartTime;
+ now = mStartTime;
+ } else
+ now = PR_IntervalNow();
+
+ PRIntervalTime totalTime = PR_IntervalToMilliseconds(now - mStartTime);
+ PRIntervalTime deltaTime = PR_IntervalToMilliseconds(now - mPreviousTime);
+
+ MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info,
+ ("[%3.2f][%3.2f] - %s\n", ((double)totalTime / 1000.0) + 0.005,
+ ((double)deltaTime / 1000.0) + 0.005, label));
+
+ mPreviousTime = now;
+#endif
+ return NS_OK;
+}
+
+class nsMsgTemplateReplyHelper final : public nsIStreamListener,
+ public nsIUrlListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+
+ nsMsgTemplateReplyHelper();
+
+ nsCOMPtr<nsIMsgDBHdr> mHdrToReplyTo;
+ nsCOMPtr<nsIMsgDBHdr> mTemplateHdr;
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+ nsCString mTemplateBody;
+ bool mInMsgBody;
+ char mLastBlockChars[3];
+
+ private:
+ ~nsMsgTemplateReplyHelper();
+};
+
+NS_IMPL_ISUPPORTS(nsMsgTemplateReplyHelper, nsIStreamListener,
+ nsIRequestObserver, nsIUrlListener)
+
+nsMsgTemplateReplyHelper::nsMsgTemplateReplyHelper() {
+ mInMsgBody = false;
+ memset(mLastBlockChars, 0, sizeof(mLastBlockChars));
+}
+
+nsMsgTemplateReplyHelper::~nsMsgTemplateReplyHelper() {}
+
+NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStartRunningUrl(nsIURI* aUrl) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ NS_ENSURE_SUCCESS(aExitCode, aExitCode);
+ nsresult rv;
+ nsCOMPtr<nsPIDOMWindowOuter> parentWindow;
+ if (mMsgWindow) {
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = mMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ parentWindow = do_GetInterface(docShell);
+ NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE);
+ }
+
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(
+ do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv));
+ if (NS_FAILED(rv) || (!pMsgComposeParams)) return rv;
+ nsCOMPtr<nsIMsgCompFields> compFields =
+ do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv);
+
+ nsCString replyTo;
+ mHdrToReplyTo->GetStringProperty("replyTo", replyTo);
+ if (replyTo.IsEmpty()) mHdrToReplyTo->GetAuthor(getter_Copies(replyTo));
+ compFields->SetTo(NS_ConvertUTF8toUTF16(replyTo));
+
+ nsString body;
+ nsString templateSubject, replySubject;
+
+ mHdrToReplyTo->GetMime2DecodedSubject(replySubject);
+ mTemplateHdr->GetMime2DecodedSubject(templateSubject);
+ nsString subject(u"Auto: "_ns); // RFC 3834 3.1.5.
+ subject.Append(templateSubject);
+ if (!replySubject.IsEmpty()) {
+ subject.AppendLiteral(u" (was: ");
+ subject.Append(replySubject);
+ subject.Append(u')');
+ }
+
+ compFields->SetSubject(subject);
+ compFields->SetRawHeader("Auto-Submitted", "auto-replied"_ns);
+
+ nsCString charset;
+ rv = mTemplateHdr->GetCharset(getter_Copies(charset));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsMsgI18NConvertToUnicode(charset, mTemplateBody, body);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "couldn't convert templ body to unicode");
+ compFields->SetBody(body);
+
+ nsCString msgUri;
+ nsCOMPtr<nsIMsgFolder> folder;
+ mHdrToReplyTo->GetFolder(getter_AddRefs(folder));
+ folder->GetUriForMsg(mHdrToReplyTo, msgUri);
+ // populate the compose params
+ pMsgComposeParams->SetType(nsIMsgCompType::ReplyWithTemplate);
+ pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default);
+ pMsgComposeParams->SetIdentity(mIdentity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ pMsgComposeParams->SetOriginalMsgURI(msgUri);
+
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose(
+ do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion
+ * response **/
+
+ rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<mozilla::dom::Promise> promise;
+ return pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, mIdentity, nullptr,
+ nullptr, nullptr, getter_AddRefs(promise));
+}
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnStartRequest(nsIRequest* request) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnStopRequest(nsIRequest* request, nsresult status) {
+ if (NS_SUCCEEDED(status)) {
+ // now we've got the message body in mTemplateBody -
+ // need to set body in compose params and send the reply.
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgTemplateReplyHelper::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStream,
+ uint64_t srcOffset, uint32_t count) {
+ nsresult rv = NS_OK;
+
+ char readBuf[1024];
+
+ uint64_t available;
+ uint32_t readCount;
+ uint32_t maxReadCount = sizeof(readBuf) - 1;
+
+ rv = inStream->Available(&available);
+ while (NS_SUCCEEDED(rv) && available > 0) {
+ uint32_t bodyOffset = 0, readOffset = 0;
+ if (!mInMsgBody && mLastBlockChars[0]) {
+ memcpy(readBuf, mLastBlockChars, 3);
+ readOffset = 3;
+ maxReadCount -= 3;
+ }
+ if (maxReadCount > available) maxReadCount = (uint32_t)available;
+ memset(readBuf, 0, sizeof(readBuf));
+ rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount);
+ available -= readCount;
+ readCount += readOffset;
+ // we're mainly interested in the msg body, so we need to
+ // find the header/body delimiter of a blank line. A blank line
+ // looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF>
+ if (!mInMsgBody) {
+ for (uint32_t charIndex = 0; charIndex < readCount && !bodyOffset;
+ charIndex++) {
+ if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n') {
+ if (charIndex + 1 < readCount) {
+ if (readBuf[charIndex] == readBuf[charIndex + 1]) {
+ // got header+body separator
+ bodyOffset = charIndex + 2;
+ break;
+ } else if ((charIndex + 3 < readCount) &&
+ !strncmp(readBuf + charIndex, "\r\n\r\n", 4)) {
+ bodyOffset = charIndex + 4;
+ break;
+ }
+ }
+ }
+ }
+ mInMsgBody = bodyOffset != 0;
+ if (!mInMsgBody && readCount > 3) // still in msg hdrs
+ strncpy(mLastBlockChars, readBuf + readCount - 3, 3);
+ }
+ mTemplateBody.Append(readBuf + bodyOffset);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeService::ReplyWithTemplate(
+ nsIMsgDBHdr* aMsgHdr, const nsACString& templateUri,
+ nsIMsgWindow* aMsgWindow, nsIMsgIncomingServer* aServer) {
+ // To reply with template, we need the message body of the template.
+ // I think we're going to need to stream the template message to ourselves,
+ // and construct the body, and call setBody on the compFields.
+ nsresult rv;
+ const nsPromiseFlatCString& templateUriFlat = PromiseFlatCString(templateUri);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = account->GetIdentities(identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString recipients;
+ aMsgHdr->GetRecipients(getter_Copies(recipients));
+
+ nsAutoCString ccList;
+ aMsgHdr->GetCcList(getter_Copies(ccList));
+
+ // Go through the identities to see to whom this was addressed.
+ // In case we get no match, this is likely a list/bulk/bcc/spam mail and we
+ // shouldn't reply. RFC 3834 2.
+ nsCOMPtr<nsIMsgIdentity> identity; // identity to reply from
+ for (auto anIdentity : identities) {
+ nsAutoCString identityEmail;
+ anIdentity->GetEmail(identityEmail);
+
+ if (FindInReadable(identityEmail, recipients,
+ nsCaseInsensitiveCStringComparator) ||
+ FindInReadable(identityEmail, ccList,
+ nsCaseInsensitiveCStringComparator)) {
+ identity = anIdentity;
+ break;
+ }
+ }
+ if (!identity) // Found no match -> don't reply.
+ return NS_ERROR_ABORT;
+
+ RefPtr<nsMsgTemplateReplyHelper> helper = new nsMsgTemplateReplyHelper;
+
+ helper->mHdrToReplyTo = aMsgHdr;
+ helper->mMsgWindow = aMsgWindow;
+ helper->mIdentity = identity;
+
+ nsAutoCString replyTo;
+ aMsgHdr->GetStringProperty("replyTo", replyTo);
+ if (replyTo.IsEmpty()) aMsgHdr->GetAuthor(getter_Copies(replyTo));
+ if (replyTo.IsEmpty()) return NS_ERROR_FAILURE; // nowhere to send the reply
+
+ nsCOMPtr<nsIMsgFolder> templateFolder;
+ nsCOMPtr<nsIMsgDatabase> templateDB;
+ nsCString templateMsgHdrUri;
+ const char* query = PL_strstr(templateUriFlat.get(), "?messageId=");
+ if (!query) return NS_ERROR_FAILURE;
+
+ nsAutoCString folderUri(Substring(templateUriFlat.get(), query));
+ rv = GetExistingFolder(folderUri, getter_AddRefs(templateFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = templateFolder->GetMsgDatabase(getter_AddRefs(templateDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* subject = PL_strstr(templateUriFlat.get(), "&subject=");
+ if (subject) {
+ const char* subjectEnd = subject + strlen(subject);
+ nsAutoCString messageId(Substring(query + 11, subject));
+ nsAutoCString subjectString(Substring(subject + 9, subjectEnd));
+ templateDB->GetMsgHdrForMessageID(messageId.get(),
+ getter_AddRefs(helper->mTemplateHdr));
+ if (helper->mTemplateHdr)
+ templateFolder->GetUriForMsg(helper->mTemplateHdr, templateMsgHdrUri);
+ // to use the subject, we'd need to expose a method to find a message by
+ // subject, or painfully iterate through messages...We'll try to make the
+ // message-id not change when saving a template first.
+ }
+ if (templateMsgHdrUri.IsEmpty()) {
+ // ### probably want to return a specific error and
+ // have the calling code disable the filter.
+ NS_ASSERTION(false, "failed to get msg hdr");
+ return NS_ERROR_FAILURE;
+ }
+ // we need to convert the template uri, which is of the form
+ // <folder uri>?messageId=<messageId>&subject=<subject>
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(templateMsgHdrUri, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> listenerSupports;
+ helper->QueryInterface(NS_GET_IID(nsISupports),
+ getter_AddRefs(listenerSupports));
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = msgService->StreamMessage(
+ templateMsgHdrUri, listenerSupports, aMsgWindow, helper,
+ false, // convert data
+ EmptyCString(), false, getter_AddRefs(dummyNull));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ if (!folder) return NS_ERROR_NULL_POINTER;
+
+ // We're sending a new message. Conceptually it's a reply though, so mark the
+ // original message as replied.
+ return folder->AddMessageDispositionState(
+ aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Replied);
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::ForwardMessage(const nsAString& forwardTo,
+ nsIMsgDBHdr* aMsgHdr,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgIncomingServer* aServer,
+ uint32_t aForwardType) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ nsresult rv;
+ if (aForwardType == nsIMsgComposeService::kForwardAsDefault) {
+ int32_t forwardPref = 0;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetIntPref("mail.forward_message_mode", &forwardPref);
+ // 0=default as attachment 2=forward as inline with attachments,
+ // (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla)
+ aForwardType = forwardPref == 0 ? nsIMsgComposeService::kForwardAsAttachment
+ : nsIMsgComposeService::kForwardInline;
+ }
+ nsCString msgUri;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER);
+
+ folder->GetUriForMsg(aMsgHdr, msgUri);
+
+ nsAutoCString uriToOpen(msgUri);
+
+ // get the MsgIdentity for the above key using AccountManager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCOMPtr<nsIMsgIdentity> identity;
+
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = account->GetDefaultIdentity(getter_AddRefs(identity));
+ // Use default identity if no identity has been found on this account
+ if (NS_FAILED(rv) || !identity) {
+ rv = GetDefaultIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aForwardType == nsIMsgComposeService::kForwardInline)
+ return RunMessageThroughMimeDraft(
+ uriToOpen, nsMimeOutput::nsMimeMessageDraftOrTemplate, identity,
+ uriToOpen, aMsgHdr, true, forwardTo, false, aMsgWindow, false);
+
+ nsCOMPtr<mozIDOMWindowProxy> parentWindow;
+ if (aMsgWindow) {
+ nsCOMPtr<nsIDocShell> docShell;
+ rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+ parentWindow = do_GetInterface(docShell);
+ NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE);
+ }
+ // create the compose params object
+ nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams(
+ do_CreateInstance("@mozilla.org/messengercompose/composeparams;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgCompFields> compFields =
+ do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv);
+
+ compFields->SetTo(forwardTo);
+ // populate the compose params
+ pMsgComposeParams->SetType(nsIMsgCompType::ForwardAsAttachment);
+ pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default);
+ pMsgComposeParams->SetIdentity(identity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ pMsgComposeParams->SetOriginalMsgURI(uriToOpen);
+ // create the nsIMsgCompose object to send the object
+ nsCOMPtr<nsIMsgCompose> pMsgCompose(
+ do_CreateInstance("@mozilla.org/messengercompose/compose;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /** initialize nsIMsgCompose, Send the message, wait for send completion
+ * response **/
+ rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Promise> promise;
+ rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr,
+ nullptr, nullptr, getter_AddRefs(promise));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsMsgCompose::ProcessReplyFlags usually takes care of marking messages
+ // as forwarded. ProcessReplyFlags is normally called from
+ // nsMsgComposeSendListener::OnStopSending but for this case the msgCompose
+ // object is not set so ProcessReplyFlags won't get called.
+ // Therefore, let's just mark it here instead.
+ return folder->AddMessageDispositionState(
+ aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded);
+}
+
+nsresult nsMsgComposeService::AddGlobalHtmlDomains() {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefs->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
+ rv = prefs->GetDefaultBranch(MAILNEWS_ROOT_PREF,
+ getter_AddRefs(defaultsPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /**
+ * Check to see if we need to add any global domains.
+ * If so, make sure the following prefs are added to mailnews.js
+ *
+ * 1. pref("mailnews.global_html_domains.version", version number);
+ * This pref registers the current version in the user prefs file. A default
+ * value is stored in mailnews file. Depending the changes we plan to make we
+ * can move the default version number. Comparing version number from user's
+ * prefs file and the default one from mailnews.js, we can effect ppropriate
+ * changes.
+ *
+ * 2. pref("mailnews.global_html_domains", <comma separated domain list>);
+ * This pref contains the list of html domains that ISP can add to make that
+ * user's contain all of these under the HTML domains in the
+ * Mail&NewsGrpus|Send Format under global preferences.
+ */
+ int32_t htmlDomainListCurrentVersion, htmlDomainListDefaultVersion;
+ rv = prefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME,
+ &htmlDomainListCurrentVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultsPrefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME,
+ &htmlDomainListDefaultVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update the list as needed
+ if (htmlDomainListCurrentVersion <= htmlDomainListDefaultVersion) {
+ // Get list of global domains need to be added
+ nsCString globalHtmlDomainList;
+ rv = prefBranch->GetCharPref(HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME,
+ globalHtmlDomainList);
+
+ if (NS_SUCCEEDED(rv) && !globalHtmlDomainList.IsEmpty()) {
+ nsTArray<nsCString> domainArray;
+
+ // Get user's current HTML domain set for send format
+ nsCString currentHtmlDomainList;
+ rv = prefBranch->GetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME,
+ currentHtmlDomainList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newHtmlDomainList(currentHtmlDomainList);
+ // Get the current html domain list into new list var
+ ParseString(currentHtmlDomainList, DOMAIN_DELIMITER, domainArray);
+
+ // Get user's current Plaintext domain set for send format
+ nsCString currentPlaintextDomainList;
+ rv = prefBranch->GetCharPref(USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME,
+ currentPlaintextDomainList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current plaintext domain list into new list var
+ ParseString(currentPlaintextDomainList, DOMAIN_DELIMITER, domainArray);
+
+ size_t i = domainArray.Length();
+ if (i > 0) {
+ // Append each domain in the preconfigured html domain list
+ globalHtmlDomainList.StripWhitespace();
+ ParseString(globalHtmlDomainList, DOMAIN_DELIMITER, domainArray);
+
+ // Now add each domain that does not already appear in
+ // the user's current html or plaintext domain lists
+ for (; i < domainArray.Length(); i++) {
+ if (domainArray.IndexOf(domainArray[i]) == i) {
+ if (!newHtmlDomainList.IsEmpty())
+ newHtmlDomainList += DOMAIN_DELIMITER;
+ newHtmlDomainList += domainArray[i];
+ }
+ }
+ } else {
+ // User has no domains listed either in html or plain text category.
+ // Assign the global list to be the user's current html domain list
+ newHtmlDomainList = globalHtmlDomainList;
+ }
+
+ // Set user's html domain pref with the updated list
+ rv = prefBranch->SetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME,
+ newHtmlDomainList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increase the version to avoid running the update code unless needed
+ // (based on default version)
+ rv = prefBranch->SetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME,
+ htmlDomainListCurrentVersion + 1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::RegisterComposeDocShell(nsIDocShell* aDocShell,
+ nsIMsgCompose* aComposeObject) {
+ NS_ENSURE_ARG_POINTER(aDocShell);
+ NS_ENSURE_ARG_POINTER(aComposeObject);
+
+ nsresult rv;
+
+ // add the msg compose / dom window mapping to our hash table
+ nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsWeakPtr weakMsgComposePtr = do_GetWeakReference(aComposeObject);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOpenComposeWindows.InsertOrUpdate(weakDocShell, weakMsgComposePtr);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::UnregisterComposeDocShell(nsIDocShell* aDocShell) {
+ NS_ENSURE_ARG_POINTER(aDocShell);
+
+ nsresult rv;
+ nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mOpenComposeWindows.Remove(weakDocShell);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetMsgComposeForDocShell(nsIDocShell* aDocShell,
+ nsIMsgCompose** aComposeObject) {
+ NS_ENSURE_ARG_POINTER(aDocShell);
+ NS_ENSURE_ARG_POINTER(aComposeObject);
+
+ if (!mOpenComposeWindows.Count()) return NS_ERROR_FAILURE;
+
+ // get the weak reference for our dom window
+ nsresult rv;
+ nsWeakPtr weakDocShell = do_GetWeakReference(aDocShell, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsWeakPtr weakMsgComposePtr;
+
+ if (!mOpenComposeWindows.Get(weakDocShell, getter_AddRefs(weakMsgComposePtr)))
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(weakMsgComposePtr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*aComposeObject = msgCompose);
+ return rv;
+}
+
+/**
+ * LoadDraftOrTemplate
+ * Helper routine used to run msgURI through libmime in order to fetch the
+ * contents for a draft or template.
+ */
+nsresult nsMsgComposeService::LoadDraftOrTemplate(
+ const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI,
+ nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, bool overrideComposeFormat,
+ nsIMsgWindow* aMsgWindow, bool autodetectCharset) {
+ return RunMessageThroughMimeDraft(
+ aMsgURI, aOutType, aIdentity, aOriginalMsgURI, aOrigMsgHdr,
+ aForwardInline, EmptyString(), overrideComposeFormat, aMsgWindow,
+ autodetectCharset);
+}
+
+/**
+ * Run the aMsgURI message through libmime. We set various attributes of the
+ * nsIMimeStreamConverter so mimedrft.cpp will know what to do with the message
+ * when its done streaming. Usually that will be opening a compose window
+ * with the contents of the message, but if forwardTo is non-empty, mimedrft.cpp
+ * will forward the contents directly.
+ *
+ * @param aMsgURI URI to stream, which is the msgUri + any extra terms, e.g.,
+ * "redirect=true".
+ * @param aOutType nsMimeOutput::nsMimeMessageDraftOrTemplate or
+ * nsMimeOutput::nsMimeMessageEditorTemplate
+ * @param aIdentity identity to use for the new message
+ * @param aOriginalMsgURI msgURI w/o any extra terms
+ * @param aOrigMsgHdr nsIMsgDBHdr corresponding to aOriginalMsgURI
+ * @param aForwardInline true if doing a forward inline
+ * @param aForwardTo e-mail address to forward msg to. This is used for
+ * forward inline message filter actions.
+ * @param aOverrideComposeFormat True if the user had shift key down when
+ doing a command that opens the compose window,
+ * which means we switch the compose window used
+ * from the default.
+ * @param aMsgWindow msgWindow to pass into LoadMessage.
+ */
+nsresult nsMsgComposeService::RunMessageThroughMimeDraft(
+ const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI,
+ nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, const nsAString& aForwardTo,
+ bool aOverrideComposeFormat, nsIMsgWindow* aMsgWindow,
+ bool autodetectCharset) {
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsresult rv =
+ GetMessageServiceFromURI(aMsgURI, getter_AddRefs(messageService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create a mime parser (nsIMimeStreamConverter)to do the conversion.
+ nsCOMPtr<nsIMimeStreamConverter> mimeConverter = do_CreateInstance(
+ "@mozilla.org/streamconv;1?from=message/rfc822&to=application/xhtml+xml",
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeConverter->SetMimeOutputType(
+ aOutType); // Set the type of output for libmime
+ mimeConverter->SetForwardInline(aForwardInline);
+ if (!aForwardTo.IsEmpty()) {
+ mimeConverter->SetForwardInlineFilter(true);
+ mimeConverter->SetForwardToAddress(aForwardTo);
+ }
+ mimeConverter->SetOverrideComposeFormat(aOverrideComposeFormat);
+ mimeConverter->SetIdentity(aIdentity);
+ mimeConverter->SetOriginalMsgURI(aOriginalMsgURI);
+ mimeConverter->SetOrigMsgHdr(aOrigMsgHdr);
+
+ nsCOMPtr<nsIURI> url;
+ bool fileUrl = StringBeginsWith(aMsgURI, "file:"_ns);
+ nsCString mailboxUri(aMsgURI);
+ if (fileUrl) {
+ // We loaded a .eml file from a file: url. Construct equivalent mailbox url.
+ mailboxUri.Replace(0, 5, "mailbox:"_ns);
+ mailboxUri.AppendLiteral("&number=0");
+ // Need this to prevent nsMsgCompose::TagEmbeddedObjects from setting
+ // inline images as moz-do-not-send.
+ mimeConverter->SetOriginalMsgURI(mailboxUri);
+ }
+ if (fileUrl || PromiseFlatCString(aMsgURI).Find(
+ "&type=application/x-message-display") >= 0)
+ rv = NS_NewURI(getter_AddRefs(url), mailboxUri);
+ else
+ rv = messageService->GetUrlForUri(aMsgURI, aMsgWindow, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(url);
+ if (!mailnewsurl) {
+ NS_WARNING(
+ "Trying to run a message through MIME which doesn't have a "
+ "nsIMsgMailNewsUrl?");
+ return NS_ERROR_UNEXPECTED;
+ }
+ // SetSpecInternal must not fail, or else the URL won't have a base URL and
+ // we'll crash later.
+ rv = mailnewsurl->SetSpecInternal(mailboxUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we are forwarding a message and that message used a charset override
+ // then forward that as auto-detect flag, too.
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(url));
+ if (i18nUrl) (void)i18nUrl->SetAutodetectCharset(autodetectCharset);
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewInputStreamChannel(
+ getter_AddRefs(channel), url, nullptr, nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed.");
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamConverter> converter = do_QueryInterface(mimeConverter);
+ rv = converter->AsyncConvertData(nullptr, nullptr, nullptr, channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, just plug the two together and get the hell out of the way!
+ nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(mimeConverter);
+ return messageService->LoadMessage(aMsgURI, streamListener, aMsgWindow,
+ nullptr, autodetectCharset);
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::Handle(nsICommandLine* aCmdLine) {
+ NS_ENSURE_ARG_POINTER(aCmdLine);
+
+ nsresult rv;
+ int32_t found, end, count;
+ nsAutoString uristr;
+ bool composeShouldHandle = true;
+
+ rv = aCmdLine->FindFlag(u"compose"_ns, false, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifndef MOZ_SUITE
+ // MAC OS X passes in -url mailto:mscott@mozilla.org into the command line
+ // instead of -compose.
+ if (found == -1) {
+ rv = aCmdLine->FindFlag(u"url"_ns, false, &found);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't want to consume the argument for -url unless we're sure it is a
+ // mailto url and we'll figure that out shortly.
+ composeShouldHandle = false;
+ }
+#endif
+
+ if (found == -1) return NS_OK;
+
+ end = found;
+
+ rv = aCmdLine->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count > found + 1) {
+ aCmdLine->GetArgument(found + 1, uristr);
+ if (StringBeginsWith(uristr, u"mailto:"_ns) ||
+ StringBeginsWith(uristr, u"preselectid="_ns) ||
+ StringBeginsWith(uristr, u"to="_ns) ||
+ StringBeginsWith(uristr, u"cc="_ns) ||
+ StringBeginsWith(uristr, u"bcc="_ns) ||
+ StringBeginsWith(uristr, u"newsgroups="_ns) ||
+ StringBeginsWith(uristr, u"subject="_ns) ||
+ StringBeginsWith(uristr, u"format="_ns) ||
+ StringBeginsWith(uristr, u"body="_ns) ||
+ StringBeginsWith(uristr, u"attachment="_ns) ||
+ StringBeginsWith(uristr, u"message="_ns) ||
+ StringBeginsWith(uristr, u"from="_ns)) {
+ composeShouldHandle = true; // the -url argument looks like mailto
+ end++;
+ // mailto: URIs are frequently passed with spaces in them. They should be
+ // escaped with %20, but we hack around broken clients. See bug 231032.
+ while (end + 1 < count) {
+ nsAutoString curarg;
+ aCmdLine->GetArgument(end + 1, curarg);
+ if (curarg.First() == '-') break;
+
+ uristr.Append(' ');
+ uristr.Append(curarg);
+ ++end;
+ }
+ } else {
+ uristr.Truncate();
+ }
+ }
+ if (composeShouldHandle) {
+ aCmdLine->RemoveArguments(found, end);
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsISupportsString> arg(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
+ if (arg) arg->SetData(uristr);
+
+ nsCOMPtr<nsIMutableArray> params(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ params->AppendElement(arg);
+ params->AppendElement(aCmdLine);
+
+ nsCOMPtr<mozIDOMWindowProxy> opened;
+ wwatch->OpenWindow(nullptr, DEFAULT_CHROME, "_blank"_ns,
+ "chrome,dialog=no,all"_ns, params,
+ getter_AddRefs(opened));
+
+ aCmdLine->SetPreventDefault(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeService::GetHelpInfo(nsACString& aResult) {
+ // clang-format off
+ aResult.AssignLiteral(
+ " -compose [ <options> ] Compose a mail or news message. Options are specified\n"
+ " as string \"option='value,...',option=value,...\" and\n"
+ " include: from, to, cc, bcc, newsgroups, subject, body,\n"
+ " message (file), attachment (file), format (html | text).\n"
+ " Example: \"to=john@example.com,subject='Dinner tonight?'\"\n");
+ return NS_OK;
+ // clang-format on
+}
diff --git a/comm/mailnews/compose/src/nsMsgComposeService.h b/comm/mailnews/compose/src/nsMsgComposeService.h
new file mode 100644
index 0000000000..97911fb71f
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgComposeService.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#define MSGCOMP_TRACE_PERFORMANCE 1
+
+#include "nsIMsgComposeService.h"
+#include "nsCOMPtr.h"
+#include "mozIDOMWindow.h"
+#include "nsIAppWindow.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIWeakReference.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsInterfaceHashtable.h"
+
+#include "nsICommandLineHandler.h"
+#define ICOMMANDLINEHANDLER nsICommandLineHandler
+
+class nsMsgComposeService : public nsIMsgComposeService,
+ public ICOMMANDLINEHANDLER,
+ public nsSupportsWeakReference {
+ public:
+ nsMsgComposeService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSESERVICE
+ NS_DECL_NSICOMMANDLINEHANDLER
+
+ nsresult Init();
+ void Reset();
+ void DeleteCachedWindows();
+ nsresult AddGlobalHtmlDomains();
+
+ private:
+ virtual ~nsMsgComposeService();
+ bool mLogComposePerformance;
+
+ nsresult LoadDraftOrTemplate(
+ const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI,
+ nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, bool overrideComposeFormat,
+ nsIMsgWindow* aMsgWindow, bool autodetectCharset);
+
+ nsresult RunMessageThroughMimeDraft(
+ const nsACString& aMsgURI, nsMimeOutputType aOutType,
+ nsIMsgIdentity* aIdentity, const nsACString& aOriginalMsgURI,
+ nsIMsgDBHdr* aOrigMsgHdr, bool aForwardInline, const nsAString& forwardTo,
+ bool overrideComposeFormat, nsIMsgWindow* aMsgWindow,
+ bool autodetectCharset);
+
+ // hash table mapping dom windows to nsIMsgCompose objects
+ nsInterfaceHashtable<nsISupportsHashKey, nsIWeakReference>
+ mOpenComposeWindows;
+
+ // When doing a reply and the settings are enabled, get the HTML of the
+ // selected text in the original message window so that it can be quoted
+ // instead of the entire message.
+ nsresult GetOrigWindowSelection(MSG_ComposeType type,
+ mozilla::dom::Selection* selection,
+ nsACString& aSelHTML);
+
+#ifdef MSGCOMP_TRACE_PERFORMANCE
+ PRIntervalTime mStartTime;
+ PRIntervalTime mPreviousTime;
+#endif
+};
diff --git a/comm/mailnews/compose/src/nsMsgCopy.cpp b/comm/mailnews/compose/src/nsMsgCopy.cpp
new file mode 100644
index 0000000000..0cccc108f9
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCopy.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMsgCopy.h"
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nscore.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/RefPtr.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsISupports.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsMsgCompUtils.h"
+#include "prcmon.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgProgress.h"
+#include "nsComposeStrings.h"
+#include "prmem.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIURIMutator.h"
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the copy operation. We have to create this
+// class to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ISUPPORTS(CopyListener, nsIMsgCopyServiceListener)
+
+CopyListener::CopyListener(void) { mCopyInProgress = false; }
+
+CopyListener::~CopyListener(void) {}
+
+nsresult CopyListener::OnStartCopy() {
+#ifdef NS_DEBUG
+ printf("CopyListener::OnStartCopy()\n");
+#endif
+
+ if (mComposeAndSend) mComposeAndSend->NotifyListenerOnStartCopy();
+ return NS_OK;
+}
+
+nsresult CopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
+#ifdef NS_DEBUG
+ printf("CopyListener::OnProgress() %d of %d\n", aProgress, aProgressMax);
+#endif
+
+ if (mComposeAndSend)
+ mComposeAndSend->NotifyListenerOnProgressCopy(aProgress, aProgressMax);
+
+ return NS_OK;
+}
+
+nsresult CopyListener::SetMessageKey(nsMsgKey aMessageKey) {
+ if (mComposeAndSend) mComposeAndSend->SetMessageKey(aMessageKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CopyListener::GetMessageId(nsACString& aMessageId) {
+ if (mComposeAndSend) mComposeAndSend->GetMessageId(aMessageId);
+ return NS_OK;
+}
+
+nsresult CopyListener::OnStopCopy(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+#ifdef NS_DEBUG
+ printf("CopyListener: SUCCESSFUL ON THE COPY OPERATION!\n");
+#endif
+ } else {
+#ifdef NS_DEBUG
+ printf("CopyListener: COPY OPERATION FAILED!\n");
+#endif
+ }
+
+ if (mCopyInProgress) {
+ PR_CEnterMonitor(this);
+ PR_CNotifyAll(this);
+ mCopyInProgress = false;
+ PR_CExitMonitor(this);
+ }
+ if (mComposeAndSend) mComposeAndSend->NotifyListenerOnStopCopy(aStatus);
+
+ return NS_OK;
+}
+
+nsresult CopyListener::SetMsgComposeAndSendObject(nsIMsgSend* obj) {
+ if (obj) mComposeAndSend = obj;
+
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// END END END END END END END END END END END END END END END
+// This is the listener class for the copy operation. We have to create this
+// class to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgCopy, nsIMsgCopy, nsIUrlListener)
+
+nsMsgCopy::nsMsgCopy() {
+ mFile = nullptr;
+ mMode = nsIMsgSend::nsMsgDeliverNow;
+ mSavePref = nullptr;
+ mIsDraft = false;
+ mMsgFlags = nsMsgMessageFlags::Read;
+}
+
+nsMsgCopy::~nsMsgCopy() { PR_Free(mSavePref); }
+
+NS_IMETHODIMP
+nsMsgCopy::StartCopyOperation(nsIMsgIdentity* aUserIdentity, nsIFile* aFile,
+ nsMsgDeliverMode aMode, nsIMsgSend* aMsgSendObj,
+ const nsACString& aSavePref,
+ nsIMsgDBHdr* aMsgToReplace) {
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ bool isDraft = false;
+ uint32_t msgFlags = nsMsgMessageFlags::Read;
+ bool waitForUrl = false;
+ nsresult rv;
+
+ if (!aMsgSendObj) return NS_ERROR_INVALID_ARG;
+
+ // Store away the server location...
+ if (!aSavePref.IsEmpty()) mSavePref = ToNewCString(aSavePref);
+
+ //
+ // Vars for implementation...
+ //
+
+ // QueueForLater (Outbox)
+ if (aMode == nsIMsgSend::nsMsgQueueForLater ||
+ aMode == nsIMsgSend::nsMsgDeliverBackground) {
+ rv = GetUnsentMessagesFolder(aUserIdentity, getter_AddRefs(dstFolder),
+ &waitForUrl);
+ isDraft = false;
+ // Do not mark outgoing messages as read.
+ msgFlags = 0;
+ if (!dstFolder || NS_FAILED(rv)) {
+ return NS_MSG_UNABLE_TO_SEND_LATER;
+ }
+ } else if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ {
+ rv = GetDraftsFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ isDraft = true;
+ // Do not mark drafts as read.
+ msgFlags = 0;
+ if (!dstFolder || NS_FAILED(rv)) return NS_MSG_UNABLE_TO_SAVE_DRAFT;
+ } else if (aMode ==
+ nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ {
+ rv = GetTemplatesFolder(aUserIdentity, getter_AddRefs(dstFolder),
+ &waitForUrl);
+ // Mark saved templates as read.
+ isDraft = false;
+ msgFlags = nsMsgMessageFlags::Read;
+ if (!dstFolder || NS_FAILED(rv)) return NS_MSG_UNABLE_TO_SAVE_TEMPLATE;
+ } else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent
+ {
+ rv = GetSentFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl);
+ // Mark send messages as read.
+ isDraft = false;
+ msgFlags = nsMsgMessageFlags::Read;
+ if (!dstFolder || NS_FAILED(rv)) return NS_MSG_COULDNT_OPEN_FCC_FOLDER;
+ }
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+
+ if (aMsgSendObj) {
+ nsCOMPtr<nsIMsgProgress> progress;
+ aMsgSendObj->GetProgress(getter_AddRefs(progress));
+ if (progress) progress->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ mMode = aMode;
+ mFile = aFile;
+ mDstFolder = dstFolder;
+ mMsgToReplace = aMsgToReplace;
+ mIsDraft = isDraft;
+ mMsgSendObj = aMsgSendObj;
+ mMsgFlags = msgFlags;
+ if (!waitForUrl) {
+ // cache info needed for DoCopy and call DoCopy when OnStopUrl is called.
+ rv = DoCopy(aFile, dstFolder, aMsgToReplace, isDraft, msgFlags, msgWindow,
+ aMsgSendObj);
+ // N.B. "this" may be deleted when this call returns.
+ }
+ return rv;
+}
+
+nsresult nsMsgCopy::DoCopy(nsIFile* aDiskFile, nsIMsgFolder* dstFolder,
+ nsIMsgDBHdr* aMsgToReplace, bool aIsDraft,
+ uint32_t aMsgFlags, nsIMsgWindow* msgWindow,
+ nsIMsgSend* aMsgSendObj) {
+ nsresult rv = NS_OK;
+
+ // Check sanity
+ if ((!aDiskFile) || (!dstFolder)) return NS_ERROR_INVALID_ARG;
+
+ // Call copyservice with dstFolder, disk file, and txnManager
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<CopyListener> copyListener = new CopyListener();
+ if (!copyListener) return NS_ERROR_OUT_OF_MEMORY;
+
+ copyListener->SetMsgComposeAndSendObject(aMsgSendObj);
+ nsCOMPtr<nsIThread> thread;
+
+ if (aIsDraft) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(dstFolder);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+ bool shutdownInProgress = false;
+ rv = accountManager->GetShutdownInProgress(&shutdownInProgress);
+
+ if (NS_SUCCEEDED(rv) && shutdownInProgress && imapFolder) {
+ // set the following only when we were in the middle of shutdown
+ // process
+ copyListener->mCopyInProgress = true;
+ thread = do_GetCurrentThread();
+ }
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyService->CopyFileMessage(aDiskFile, dstFolder, aMsgToReplace,
+ aIsDraft, aMsgFlags, EmptyCString(),
+ copyListener, msgWindow);
+ // copyListener->mCopyInProgress can only be set when we are in the
+ // middle of the shutdown process
+ while (copyListener->mCopyInProgress) {
+ PR_CEnterMonitor(copyListener);
+ PR_CWait(copyListener, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(copyListener);
+ if (thread) NS_ProcessPendingEvents(thread);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopy::GetDstFolder(nsIMsgFolder** aDstFolder) {
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+ NS_IF_ADDREF(*aDstFolder = mDstFolder);
+ return NS_OK;
+}
+
+// nsIUrlListener methods
+NS_IMETHODIMP
+nsMsgCopy::OnStartRunningUrl(nsIURI* aUrl) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgCopy::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ nsresult rv = aExitCode;
+ if (NS_SUCCEEDED(aExitCode)) {
+ rv = DoCopy(mFile, mDstFolder, mMsgToReplace, mIsDraft, mMsgFlags, nullptr,
+ mMsgSendObj);
+ }
+ return rv;
+}
+
+nsresult nsMsgCopy::GetUnsentMessagesFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** folder,
+ bool* waitForUrl) {
+ nsresult ret = LocateMessageFolder(
+ userIdentity, nsIMsgSend::nsMsgQueueForLater, mSavePref, folder);
+ if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Queue);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult nsMsgCopy::GetDraftsFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** folder, bool* waitForUrl) {
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsDraft,
+ mSavePref, folder);
+ if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Drafts);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult nsMsgCopy::GetTemplatesFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** folder,
+ bool* waitForUrl) {
+ nsresult ret = LocateMessageFolder(
+ userIdentity, nsIMsgSend::nsMsgSaveAsTemplate, mSavePref, folder);
+ if (*folder) (*folder)->SetFlag(nsMsgFolderFlags::Templates);
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult nsMsgCopy::GetSentFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** folder, bool* waitForUrl) {
+ nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgDeliverNow,
+ mSavePref, folder);
+ if (*folder) {
+ // If mSavePref is the same as the identity's fcc folder, set the sent flag.
+ nsCString identityFccUri;
+ userIdentity->GetFccFolder(identityFccUri);
+ if (identityFccUri.Equals(mSavePref))
+ (*folder)->SetFlag(nsMsgFolderFlags::SentMail);
+ }
+ CreateIfMissing(folder, waitForUrl);
+ return ret;
+}
+
+nsresult nsMsgCopy::CreateIfMissing(nsIMsgFolder** folder, bool* waitForUrl) {
+ nsresult rv = NS_OK;
+ if (folder && *folder) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ (*folder)->GetParent(getter_AddRefs(parent));
+ if (!parent) {
+ nsCOMPtr<nsIFile> folderPath;
+ // for local folders, path is to the berkeley mailbox.
+ // for imap folders, path needs to have .msf appended to the name
+ (*folder)->GetFilePath(getter_AddRefs(folderPath));
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = (*folder)->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isAsyncFolder;
+ rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we can't get the path from the folder, then try to create the
+ // storage. for imap, it doesn't matter if the .msf file exists - it still
+ // might not exist on the server, so we should try to create it
+ bool exists = false;
+ if (!isAsyncFolder && folderPath) folderPath->Exists(&exists);
+ if (!exists) {
+ (*folder)->CreateStorageIfMissing(this);
+ if (isAsyncFolder) *waitForUrl = true;
+
+ rv = NS_OK;
+ }
+ }
+ }
+ return rv;
+}
+////////////////////////////////////////////////////////////////////////////////////
+// Utility Functions for MsgFolders
+////////////////////////////////////////////////////////////////////////////////////
+nsresult LocateMessageFolder(nsIMsgIdentity* userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char* aFolderURI, nsIMsgFolder** msgFolder) {
+ nsresult rv = NS_OK;
+
+ if (!msgFolder) return NS_ERROR_NULL_POINTER;
+ *msgFolder = nullptr;
+
+ if (!aFolderURI || !*aFolderURI) return NS_ERROR_INVALID_ARG;
+
+ // as long as it doesn't start with anyfolder://
+ if (PL_strncasecmp(ANY_SERVER, aFolderURI, strlen(aFolderURI)) != 0) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(nsDependentCString(aFolderURI),
+ getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Don't check validity of folder - caller will handle creating it.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // make sure that folder hierarchy is built so that legitimate parent-child
+ // relationship is established.
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetMsgFolderFromURI(folder, nsDependentCString(aFolderURI),
+ msgFolder);
+ } else {
+ if (!userIdentity) return NS_ERROR_INVALID_ARG;
+
+ // get the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If any folder will do, go look for one.
+ nsTArray<RefPtr<nsIMsgIncomingServer>> servers;
+ rv = accountManager->GetServersForIdentity(userIdentity, servers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ok, we have to look through the servers and try to find the server that
+ // has a valid folder of the type that interests us...
+ for (auto inServer : servers) {
+ // Now that we have the server...we need to get the named message folder
+
+ // If aFolderURI is passed in, then the user has chosen a specific
+ // mail folder to save the message, but if it is null, just find the
+ // first one and make that work. The folder is specified as a URI, like
+ // the following:
+ //
+ // mailbox://nobody@Local Folders/Sent
+ // imap://rhp@nsmail-2/Drafts
+ // newsgroup://news.mozilla.org/netscape.test
+ //
+ nsCString serverURI;
+ rv = inServer->GetServerURI(serverURI);
+ if (NS_FAILED(rv) || serverURI.IsEmpty()) continue;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = inServer->GetRootFolder(getter_AddRefs(rootFolder));
+
+ if (NS_FAILED(rv) || (!rootFolder)) continue;
+
+ // use the defaults by getting the folder by flags
+ if (aFolderType == nsIMsgSend::nsMsgQueueForLater ||
+ aFolderType == nsIMsgSend::nsMsgDeliverBackground) {
+ // QueueForLater (Outbox)
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Queue, msgFolder);
+ } else if (aFolderType ==
+ nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts, msgFolder);
+ } else if (aFolderType ==
+ nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates)
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Templates, msgFolder);
+ } else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent
+ {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail, msgFolder);
+ }
+
+ if (*msgFolder) {
+ return NS_OK;
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+//
+// Figure out if a folder is local or not and return a boolean to
+// say so.
+//
+nsresult MessageFolderIsLocal(nsIMsgIdentity* userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char* aFolderURI, bool* aResult) {
+ nsresult rv;
+
+ if (!aFolderURI) return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(nsDependentCString(aFolderURI))
+ .Finalize(url);
+ if (NS_FAILED(rv)) return rv;
+
+ /* mailbox:/ means its local (on disk) */
+ rv = url->SchemeIs("mailbox", aResult);
+ if (NS_FAILED(rv)) return rv;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgCopy.h b/comm/mailnews/compose/src/nsMsgCopy.h
new file mode 100644
index 0000000000..4fa50bd8ac
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgCopy.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgCopy_H_
+#define _nsMsgCopy_H_
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsIFile.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgCopy.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgCopyService.h"
+
+// Forward declarations...
+class nsMsgCopy;
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the copy operation. We have to create this
+// class to listen for message copy completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class CopyListener : public nsIMsgCopyServiceListener {
+ public:
+ CopyListener(void);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD OnStartCopy() override;
+
+ NS_IMETHOD OnProgress(uint32_t aProgress, uint32_t aProgressMax) override;
+
+ NS_IMETHOD SetMessageKey(nsMsgKey aMessageKey) override;
+
+ NS_IMETHOD GetMessageId(nsACString& aMessageId) override;
+
+ NS_IMETHOD OnStopCopy(nsresult aStatus) override;
+
+ NS_IMETHOD SetMsgComposeAndSendObject(nsIMsgSend* obj);
+
+ bool mCopyInProgress;
+
+ private:
+ virtual ~CopyListener();
+ nsCOMPtr<nsIMsgSend> mComposeAndSend;
+};
+
+//
+// This is a class that deals with processing remote attachments. It implements
+// an nsIStreamListener interface to deal with incoming data
+//
+class nsMsgCopy : public nsIMsgCopy, public nsIUrlListener {
+ public:
+ nsMsgCopy();
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOPY
+ NS_DECL_NSIURLLISTENER
+
+ //////////////////////////////////////////////////////////////////////
+ // Object methods...
+ //////////////////////////////////////////////////////////////////////
+ //
+ nsresult DoCopy(nsIFile* aDiskFile, nsIMsgFolder* dstFolder,
+ nsIMsgDBHdr* aMsgToReplace, bool aIsDraft, uint32_t aMsgFlags,
+ nsIMsgWindow* msgWindow, nsIMsgSend* aMsgSendObj);
+
+ nsresult GetUnsentMessagesFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** msgFolder, bool* waitForUrl);
+ nsresult GetDraftsFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** msgFolder, bool* waitForUrl);
+ nsresult GetTemplatesFolder(nsIMsgIdentity* userIdentity,
+ nsIMsgFolder** msgFolder, bool* waitForUrl);
+ nsresult GetSentFolder(nsIMsgIdentity* userIdentity, nsIMsgFolder** msgFolder,
+ bool* waitForUrl);
+ nsresult CreateIfMissing(nsIMsgFolder** folder, bool* waitForUrl);
+
+ //
+ // Vars for implementation...
+ //
+ nsIFile* mFile; // the file we are sending...
+ nsMsgDeliverMode mMode;
+ nsCOMPtr<nsIMsgFolder> mDstFolder;
+ nsCOMPtr<nsIMsgDBHdr> mMsgToReplace;
+ bool mIsDraft;
+ uint32_t mMsgFlags;
+ nsCOMPtr<nsIMsgSend> mMsgSendObj;
+ char* mSavePref;
+
+ private:
+ virtual ~nsMsgCopy();
+};
+
+// Useful function for the back end...
+nsresult LocateMessageFolder(nsIMsgIdentity* userIdentity,
+ nsMsgDeliverMode aFolderType, const char* aSaveURI,
+ nsIMsgFolder** msgFolder);
+
+nsresult MessageFolderIsLocal(nsIMsgIdentity* userIdentity,
+ nsMsgDeliverMode aFolderType,
+ const char* aSaveURI, bool* aResult);
+
+#endif /* _nsMsgCopy_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgPrompts.cpp b/comm/mailnews/compose/src/nsMsgPrompts.cpp
new file mode 100644
index 0000000000..ff0f133285
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgPrompts.cpp
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMsgPrompts.h"
+
+#include "nsMsgCopy.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsComposeStrings.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Components.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+
+nsresult nsMsgGetMessageByName(const char* aName, nsString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return bundle->GetStringFromName(aName, aResult);
+}
+
+static nsresult nsMsgBuildMessageByName(const char* aName, nsIFile* aFile,
+ nsString& aResult) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString path;
+ aFile->GetPath(path);
+
+ AutoTArray<nsString, 1> params = {path};
+ return bundle->FormatStringFromName(aName, params, aResult);
+}
+
+nsresult nsMsgBuildMessageWithFile(nsIFile* aFile, nsString& aResult) {
+ return nsMsgBuildMessageByName("unableToOpenFile", aFile, aResult);
+}
+
+nsresult nsMsgBuildMessageWithTmpFile(nsIFile* aFile, nsString& aResult) {
+ return nsMsgBuildMessageByName("unableToOpenTmpFile", aFile, aResult);
+}
+
+nsresult nsMsgDisplayMessageByName(mozIDOMWindowProxy* window,
+ const char* aName,
+ const char16_t* windowTitle) {
+ nsString msg;
+ nsMsgGetMessageByName(aName, msg);
+ return nsMsgDisplayMessageByString(window, msg.get(), windowTitle);
+}
+
+nsresult nsMsgDisplayMessageByString(mozIDOMWindowProxy* window,
+ const char16_t* msg,
+ const char16_t* windowTitle) {
+ NS_ENSURE_ARG_POINTER(msg);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> dlgService(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dlgService->Alert(window, windowTitle, msg);
+}
+
+nsresult nsMsgAskBooleanQuestionByString(mozIDOMWindowProxy* window,
+ const char16_t* msg, bool* answer,
+ const char16_t* windowTitle) {
+ NS_ENSURE_TRUE(msg && *msg, NS_ERROR_INVALID_ARG);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> dlgService(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dlgService->Confirm(window, windowTitle, msg, answer);
+}
diff --git a/comm/mailnews/compose/src/nsMsgPrompts.h b/comm/mailnews/compose/src/nsMsgPrompts.h
new file mode 100644
index 0000000000..300b6ffee2
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgPrompts.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgPrompts_H_
+#define _nsMsgPrompts_H_
+
+#include "nscore.h"
+#include "nsError.h"
+#include "nsString.h"
+
+class mozIDOMWindowProxy;
+
+nsresult nsMsgGetMessageByName(const char* aName, nsString& aResult);
+nsresult nsMsgBuildMessageWithFile(nsIFile* aFile, nsString& aResult);
+nsresult nsMsgBuildMessageWithTmpFile(nsIFile* aFile, nsString& aResult);
+nsresult nsMsgDisplayMessageByName(mozIDOMWindowProxy* window,
+ const char* aName,
+ const char16_t* windowTitle = nullptr);
+nsresult nsMsgDisplayMessageByString(mozIDOMWindowProxy* window,
+ const char16_t* msg,
+ const char16_t* windowTitle = nullptr);
+nsresult nsMsgAskBooleanQuestionByString(mozIDOMWindowProxy* window,
+ const char16_t* msg, bool* answer,
+ const char16_t* windowTitle = nullptr);
+
+#endif /* _nsMsgPrompts_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgQuote.cpp b/comm/mailnews/compose/src/nsMsgQuote.cpp
new file mode 100644
index 0000000000..75efbf67ca
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgQuote.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIURL.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamListener.h"
+#include "nsIStreamConverter.h"
+#include "nsIStreamConverterService.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsMimeTypes.h"
+#include "nsICharsetConverterManager.h"
+#include "prprf.h"
+#include "nsMsgQuote.h"
+#include "nsMsgCompUtils.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsNetUtil.h"
+#include "nsMsgCompose.h"
+#include "nsMsgMailNewsUrl.h"
+#include "mozilla/Components.h"
+#include "nsContentUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgQuoteListener, nsIMsgQuoteListener,
+ nsIMimeStreamConverterListener)
+
+nsMsgQuoteListener::nsMsgQuoteListener() {}
+
+nsMsgQuoteListener::~nsMsgQuoteListener() {}
+
+NS_IMETHODIMP nsMsgQuoteListener::SetMsgQuote(nsIMsgQuote* msgQuote) {
+ mMsgQuote = do_GetWeakReference(msgQuote);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuoteListener::GetMsgQuote(nsIMsgQuote** aMsgQuote) {
+ nsresult rv = NS_OK;
+ if (aMsgQuote) {
+ nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote);
+ msgQuote.forget(aMsgQuote);
+ } else
+ rv = NS_ERROR_NULL_POINTER;
+
+ return rv;
+}
+
+nsresult nsMsgQuoteListener::OnHeadersReady(nsIMimeHeaders* headers) {
+ nsCOMPtr<nsIMsgQuotingOutputStreamListener> quotingOutputStreamListener;
+ nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote);
+
+ if (msgQuote)
+ msgQuote->GetStreamListener(getter_AddRefs(quotingOutputStreamListener));
+
+ if (quotingOutputStreamListener)
+ quotingOutputStreamListener->SetMimeHeaders(headers);
+ return NS_OK;
+}
+
+//
+// Implementation...
+//
+nsMsgQuote::nsMsgQuote() {
+ mQuoteHeaders = false;
+ mQuoteListener = nullptr;
+}
+
+nsMsgQuote::~nsMsgQuote() {}
+
+NS_IMPL_ISUPPORTS(nsMsgQuote, nsIMsgQuote, nsISupportsWeakReference)
+
+NS_IMETHODIMP nsMsgQuote::GetStreamListener(
+ nsIMsgQuotingOutputStreamListener** aStreamListener) {
+ if (!aStreamListener) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsCOMPtr<nsIMsgQuotingOutputStreamListener> streamListener =
+ do_QueryReferent(mStreamListener);
+ if (!streamListener) {
+ return NS_ERROR_FAILURE;
+ }
+ NS_IF_ADDREF(*aStreamListener = streamListener);
+ return NS_OK;
+}
+
+nsresult nsMsgQuote::QuoteMessage(
+ const nsACString& msgURI, bool quoteHeaders,
+ nsIMsgQuotingOutputStreamListener* aQuoteMsgStreamListener,
+ bool aAutodetectCharset, bool headersOnly, nsIMsgDBHdr* aMsgHdr) {
+ nsresult rv;
+
+ mQuoteHeaders = quoteHeaders;
+ mStreamListener = do_GetWeakReference(aQuoteMsgStreamListener);
+
+ nsAutoCString msgUri(msgURI);
+ bool fileUrl = StringBeginsWith(msgUri, "file:"_ns);
+ bool forwardedMessage = msgUri.Find("&realtype=message/rfc822") >= 0;
+ nsCOMPtr<nsIURI> newURI;
+ if (fileUrl) {
+ msgUri.Replace(0, 5, "mailbox:"_ns);
+ msgUri.AppendLiteral("?number=0");
+ rv = NS_NewURI(getter_AddRefs(newURI), msgUri);
+ } else if (forwardedMessage)
+ rv = NS_NewURI(getter_AddRefs(newURI), msgURI);
+ else {
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(msgURI, getter_AddRefs(msgService));
+ if (NS_FAILED(rv)) return rv;
+ rv = msgService->GetUrlForUri(msgURI, nullptr, getter_AddRefs(newURI));
+ }
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString queryPart;
+ rv = newURI->GetQuery(queryPart);
+ if (!queryPart.IsEmpty()) queryPart.Append('&');
+
+ if (headersOnly) /* We don't need to quote the message body but we still need
+ to extract the headers */
+ queryPart.AppendLiteral("header=only");
+ else if (quoteHeaders)
+ queryPart.AppendLiteral("header=quote");
+ else
+ queryPart.AppendLiteral("header=quotebody");
+ rv = NS_MutateURI(newURI).SetQuery(queryPart).Finalize(newURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we were told to auto-detect the charset, pass that on.
+ if (aAutodetectCharset) {
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(newURI));
+ if (i18nUrl) i18nUrl->SetAutodetectCharset(true);
+ }
+
+ mQuoteListener =
+ do_CreateInstance("@mozilla.org/messengercompose/quotinglistener;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+ mQuoteListener->SetMsgQuote(this);
+
+ // funky magic go get the isupports for this class which inherits from
+ // multiple interfaces.
+ nsISupports* supports;
+ QueryInterface(NS_GET_IID(nsISupports), (void**)&supports);
+ nsCOMPtr<nsISupports> quoteSupport = supports;
+ NS_IF_RELEASE(supports);
+
+ // now we want to create a necko channel for this url and we want to open it
+ mQuoteChannel = nullptr;
+ nsCOMPtr<nsIIOService> netService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED);
+ rv = netService->NewChannelFromURI(
+ newURI, nullptr, nsContentUtils::GetSystemPrincipal(), nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mQuoteChannel));
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIStreamConverterService> streamConverterService =
+ do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ nsCOMPtr<nsIMsgQuotingOutputStreamListener> streamListener =
+ do_QueryReferent(mStreamListener);
+ rv = streamConverterService->AsyncConvertData(
+ "message/rfc822", "application/xhtml+xml", streamListener, quoteSupport,
+ getter_AddRefs(convertedListener));
+ if (NS_FAILED(rv)) return rv;
+
+ // now try to open the channel passing in our display consumer as the
+ // listener
+ rv = mQuoteChannel->AsyncOpen(convertedListener);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgQuote::GetQuoteListener(nsIMimeStreamConverterListener** aQuoteListener) {
+ if (!aQuoteListener || !mQuoteListener) return NS_ERROR_NULL_POINTER;
+ NS_ADDREF(*aQuoteListener = mQuoteListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuote::GetQuoteChannel(nsIChannel** aQuoteChannel) {
+ if (!aQuoteChannel || !mQuoteChannel) return NS_ERROR_NULL_POINTER;
+ NS_ADDREF(*aQuoteChannel = mQuoteChannel);
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgQuote.h b/comm/mailnews/compose/src/nsMsgQuote.h
new file mode 100644
index 0000000000..27525a6dc1
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgQuote.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+#ifndef __nsMsgQuote_h__
+#define __nsMsgQuote_h__
+
+#include "nsIMsgQuote.h"
+#include "nsIMsgMessageService.h"
+#include "nsIStreamListener.h"
+#include "nsIMimeStreamConverter.h"
+#include "nsIChannel.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.h"
+
+class nsMsgQuote;
+
+class nsMsgQuoteListener : public nsIMsgQuoteListener {
+ public:
+ nsMsgQuoteListener();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIMimeStreamConverterListener support
+ NS_DECL_NSIMIMESTREAMCONVERTERLISTENER
+ NS_DECL_NSIMSGQUOTELISTENER
+
+ private:
+ virtual ~nsMsgQuoteListener();
+ nsWeakPtr mMsgQuote;
+};
+
+class nsMsgQuote : public nsIMsgQuote, public nsSupportsWeakReference {
+ public:
+ nsMsgQuote();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGQUOTE
+
+ private:
+ virtual ~nsMsgQuote();
+ //
+ // Implementation data...
+ //
+ nsWeakPtr mStreamListener;
+ bool mQuoteHeaders;
+ nsCOMPtr<nsIMsgQuoteListener> mQuoteListener;
+ nsCOMPtr<nsIChannel> mQuoteChannel;
+};
+
+#endif /* __nsMsgQuote_h__ */
diff --git a/comm/mailnews/compose/src/nsMsgSendLater.cpp b/comm/mailnews/compose/src/nsMsgSendLater.cpp
new file mode 100644
index 0000000000..8e3b8b0a5d
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgSendLater.cpp
@@ -0,0 +1,1406 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+#include "nsMsgSendLater.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgCopy.h"
+#include "nsIMsgSend.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgCompUtils.h"
+#include "nsMsgUtils.h"
+#include "nsMailHeaders.h"
+#include "nsMsgPrompts.h"
+#include "nsISmtpUrl.h"
+#include "nsIChannel.h"
+#include "nsNetUtil.h"
+#include "prlog.h"
+#include "prmem.h"
+#include "nsIMimeConverter.h"
+#include "nsComposeStrings.h"
+#include "nsIObserverService.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgDatabase.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgWindow.h"
+#include "nsMsgMessageFlags.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/Services.h"
+
+// Consts for checking and sending mail in milliseconds
+
+// 1 second from mail into the unsent messages folder to initially trying to
+// send it.
+const uint32_t kInitialMessageSendTime = 1000;
+
+NS_IMPL_ISUPPORTS(nsMsgSendLater, nsIMsgSendLater, nsIFolderListener,
+ nsIRequestObserver, nsIStreamListener, nsIObserver,
+ nsIUrlListener, nsIMsgShutdownTask)
+
+nsMsgSendLater::nsMsgSendLater() {
+ mSendingMessages = false;
+ mTimerSet = false;
+ mTotalSentSuccessfully = 0;
+ mTotalSendCount = 0;
+ mLeftoverBuffer = nullptr;
+
+ m_to = nullptr;
+ m_bcc = nullptr;
+ m_fcc = nullptr;
+ m_newsgroups = nullptr;
+ m_newshost = nullptr;
+ m_headers = nullptr;
+ m_flags = 0;
+ m_headersFP = 0;
+ m_inhead = true;
+ m_headersPosition = 0;
+
+ m_bytesRead = 0;
+ m_position = 0;
+ m_flagsPosition = 0;
+ m_headersSize = 0;
+
+ mIdentityKey = nullptr;
+ mAccountKey = nullptr;
+
+ mUserInitiated = false;
+}
+
+nsMsgSendLater::~nsMsgSendLater() {
+ PR_Free(m_to);
+ PR_Free(m_fcc);
+ PR_Free(m_bcc);
+ PR_Free(m_newsgroups);
+ PR_Free(m_newshost);
+ PR_Free(m_headers);
+ PR_Free(mLeftoverBuffer);
+ PR_Free(mIdentityKey);
+ PR_Free(mAccountKey);
+}
+
+nsresult nsMsgSendLater::Init() {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sendInBackground;
+ rv = prefs->GetBoolPref("mailnews.sendInBackground", &sendInBackground);
+ // If we're not sending in the background, don't do anything else
+ if (NS_FAILED(rv) || !sendInBackground) return NS_OK;
+
+ // We need to know when we're shutting down.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ rv = observerService->AddObserver(this, "xpcom-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "quit-application", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->AddObserver(this, "msg-shutdown", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Subscribe to the unsent messages folder
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder));
+ // There doesn't have to be a nsMsgQueueForLater flagged folder.
+ if (NS_FAILED(rv) || !folder) return NS_OK;
+
+ rv = folder->AddFolderListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX may want to send messages X seconds after startup if there are any.
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (aSubject == mTimer && !strcmp(aTopic, "timer-callback")) {
+ if (mTimer)
+ mTimer->Cancel();
+ else
+ NS_ERROR("mTimer was null in nsMsgSendLater::Observe");
+
+ mTimerSet = false;
+ // If we've already started a send since the timer fired, don't start
+ // another
+ if (!mSendingMessages) InternalSendMessages(false, nullptr);
+ } else if (!strcmp(aTopic, "quit-application")) {
+ // If the timer is set, cancel it - we're quitting, the shutdown service
+ // interfaces will sort out sending etc.
+ if (mTimer) mTimer->Cancel();
+
+ mTimerSet = false;
+ } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ // We're shutting down. Unsubscribe from the unsentFolder notifications
+ // they aren't any use to us now, we don't want to start sending more
+ // messages.
+ nsresult rv;
+ if (mMessageFolder) {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ if (folder) {
+ rv = folder->RemoveFolderListener(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folder->ForceDBClosed();
+ }
+ }
+
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ rv = observerService->RemoveObserver(this, "xpcom-shutdown");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, "quit-application");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = observerService->RemoveObserver(this, "msg-shutdown");
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::SetStatusFeedback(nsIMsgStatusFeedback* aFeedback) {
+ mFeedback = aFeedback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetStatusFeedback(nsIMsgStatusFeedback** aFeedback) {
+ NS_ENSURE_ARG_POINTER(aFeedback);
+ NS_IF_ADDREF(*aFeedback = mFeedback);
+ return NS_OK;
+}
+
+// Stream is done...drive on!
+NS_IMETHODIMP
+nsMsgSendLater::OnStopRequest(nsIRequest* request, nsresult status) {
+ nsresult rv;
+
+ // First, this shouldn't happen, but if
+ // it does, flush the buffer and move on.
+ if (mLeftoverBuffer) {
+ DeliverQueuedLine(mLeftoverBuffer, PL_strlen(mLeftoverBuffer));
+ }
+
+ if (mOutFile) mOutFile->Close();
+
+ // See if we succeeded on reading the message from the message store?
+ //
+ if (NS_SUCCEEDED(status)) {
+ // Message is done...send it!
+ rv = CompleteMailFileSend();
+
+#ifdef NS_DEBUG
+ printf("nsMsgSendLater: Success on getting message...\n");
+#endif
+
+ // If the send operation failed..try the next one...
+ if (NS_FAILED(rv)) {
+ rv = StartNextMailFileSend(rv);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+ } else {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (!channel) return NS_ERROR_FAILURE;
+
+ // extract the prompt object to use for the alert from the url....
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(uri));
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (msgUrl) msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow) msgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ }
+
+ nsMsgDisplayMessageByName(domWindow, "errorQueuedDeliveryFailed");
+
+ // Getting the data failed, but we will still keep trying to send the
+ // rest...
+ rv = StartNextMailFileSend(status);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+
+ return rv;
+}
+
+char* FindEOL(char* inBuf, char* buf_end) {
+ char* buf = inBuf;
+ char* findLoc = nullptr;
+
+ while (buf <= buf_end)
+ if (*buf == 0)
+ return buf;
+ else if ((*buf == '\n') || (*buf == '\r')) {
+ findLoc = buf;
+ break;
+ } else
+ ++buf;
+
+ if (!findLoc)
+ return nullptr;
+ else if ((findLoc + 1) > buf_end)
+ return buf;
+
+ if ((*findLoc == '\n' && *(findLoc + 1) == '\r') ||
+ (*findLoc == '\r' && *(findLoc + 1) == '\n'))
+ findLoc++; // possibly a pair.
+ return findLoc;
+}
+
+nsresult nsMsgSendLater::RebufferLeftovers(char* startBuf, uint32_t aLen) {
+ PR_FREEIF(mLeftoverBuffer);
+ mLeftoverBuffer = (char*)PR_Malloc(aLen + 1);
+ if (!mLeftoverBuffer) return NS_ERROR_OUT_OF_MEMORY;
+
+ memcpy(mLeftoverBuffer, startBuf, aLen);
+ mLeftoverBuffer[aLen] = '\0';
+ return NS_OK;
+}
+
+nsresult nsMsgSendLater::BuildNewBuffer(const char* aBuf, uint32_t aCount,
+ uint32_t* totalBufSize) {
+ // Only build a buffer when there are leftovers...
+ if (!mLeftoverBuffer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t leftoverSize = PL_strlen(mLeftoverBuffer);
+ char* newBuffer = (char*)PR_Realloc(mLeftoverBuffer, aCount + leftoverSize);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ mLeftoverBuffer = newBuffer;
+
+ memcpy(mLeftoverBuffer + leftoverSize, aBuf, aCount);
+ *totalBufSize = aCount + leftoverSize;
+ return NS_OK;
+}
+
+// Got data?
+NS_IMETHODIMP
+nsMsgSendLater::OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ NS_ENSURE_ARG_POINTER(inStr);
+
+ // This is a little bit tricky since we have to chop random
+ // buffers into lines and deliver the lines...plus keeping the
+ // leftovers for next time...some fun, eh?
+ //
+ nsresult rv = NS_OK;
+ char* startBuf;
+ char* endBuf;
+ char* lineEnd;
+ char* newbuf = nullptr;
+ uint32_t size;
+
+ uint32_t aCount = count;
+ char* aBuf = (char*)PR_Malloc(aCount + 1);
+
+ inStr->Read(aBuf, count, &aCount);
+
+ // First, create a new work buffer that will
+ if (NS_FAILED(BuildNewBuffer(aBuf, aCount, &size))) // no leftovers...
+ {
+ startBuf = (char*)aBuf;
+ endBuf = (char*)(aBuf + aCount - 1);
+ } else // yum, leftovers...new buffer created...sitting in mLeftoverBuffer
+ {
+ newbuf = mLeftoverBuffer;
+ startBuf = newbuf;
+ endBuf = startBuf + size - 1;
+ mLeftoverBuffer = nullptr; // null out this
+ }
+
+ while (startBuf <= endBuf) {
+ lineEnd = FindEOL(startBuf, endBuf);
+ if (!lineEnd) {
+ rv = RebufferLeftovers(startBuf, (endBuf - startBuf) + 1);
+ break;
+ }
+
+ rv = DeliverQueuedLine(startBuf, (lineEnd - startBuf) + 1);
+ if (NS_FAILED(rv)) break;
+
+ startBuf = lineEnd + 1;
+ }
+
+ PR_Free(newbuf);
+ PR_Free(aBuf);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStartRunningUrl(nsIURI* url) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStopRunningUrl(nsIURI* url, nsresult aExitCode) {
+ if (NS_SUCCEEDED(aExitCode)) InternalSendMessages(mUserInitiated, mIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnStartRequest(nsIRequest* request) { return NS_OK; }
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this
+// class to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+NS_IMPL_ISUPPORTS(SendOperationListener, nsIMsgSendListener,
+ nsIMsgCopyServiceListener)
+
+SendOperationListener::SendOperationListener(nsMsgSendLater* aSendLater)
+ : mSendLater(aSendLater) {}
+
+SendOperationListener::~SendOperationListener(void) {}
+
+NS_IMETHODIMP
+SendOperationListener::OnGetDraftFolderURI(const char* aMsgID,
+ const nsACString& aFolderURI) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStartSending(const char* aMsgID, uint32_t aMsgSize) {
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnStartSending()\n");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnProgress(const char* aMsgID, uint32_t aProgress,
+ uint32_t aProgressMax) {
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnProgress()\n");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStatus(const char* aMsgID, const char16_t* aMsg) {
+#ifdef NS_DEBUG
+ printf("SendOperationListener::OnStatus()\n");
+#endif
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnSendNotPerformed(const char* aMsgID,
+ nsresult aStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStopSending(const char* aMsgID, nsresult aStatus,
+ const char16_t* aMsg,
+ nsIFile* returnFile) {
+ if (mSendLater && !mSendLater->OnSendStepFinished(aStatus))
+ mSendLater = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnTransportSecurityError(
+ const char* msgID, nsresult status, nsITransportSecurityInfo* secInfo,
+ nsACString const& location) {
+ return NS_OK;
+}
+
+// nsIMsgCopyServiceListener
+
+NS_IMETHODIMP
+SendOperationListener::OnStartCopy(void) { return NS_OK; }
+
+NS_IMETHODIMP
+SendOperationListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendOperationListener::SetMessageKey(nsMsgKey aKey) {
+ MOZ_ASSERT_UNREACHABLE("SendOperationListener::SetMessageKey()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SendOperationListener::GetMessageId(nsACString& messageId) {
+ MOZ_ASSERT_UNREACHABLE("SendOperationListener::GetMessageId()");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SendOperationListener::OnStopCopy(nsresult aStatus) {
+ if (mSendLater) {
+ mSendLater->OnCopyStepFinished(aStatus);
+ mSendLater = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgSendLater::CompleteMailFileSend() {
+ // get the identity from the key
+ // if no key, or we fail to find the identity
+ // use the default identity on the default account
+ nsCOMPtr<nsIMsgIdentity> identity;
+ nsresult rv = GetIdentityFromKey(mIdentityKey, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!identity) return NS_ERROR_UNEXPECTED;
+
+ // If for some reason the tmp file didn't get created, we've failed here
+ bool created;
+ mTempFile->Exists(&created);
+ if (!created) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgCompFields> compFields =
+ do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSend> pMsgSend =
+ do_CreateInstance("@mozilla.org/messengercompose/send;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Since we have already parsed all of the headers, we are simply going to
+ // set the composition fields and move on.
+ nsCString author;
+ mMessage->GetAuthor(getter_Copies(author));
+
+ nsMsgCompFields* fields = (nsMsgCompFields*)compFields.get();
+
+ fields->SetFrom(author.get());
+
+ if (m_to) {
+ fields->SetTo(m_to);
+ }
+
+ if (m_bcc) {
+ fields->SetBcc(m_bcc);
+ }
+
+ if (m_fcc) {
+ fields->SetFcc(m_fcc);
+ }
+
+ if (m_newsgroups) fields->SetNewsgroups(m_newsgroups);
+
+#if 0
+ // needs cleanup. Is this needed?
+ if (m_newshost)
+ fields->SetNewspostUrl(m_newshost);
+#endif
+
+ // Create the listener for the send operation...
+ RefPtr<SendOperationListener> sendListener = new SendOperationListener(this);
+
+ RefPtr<mozilla::dom::Promise> promise;
+ rv = pMsgSend->SendMessageFile(
+ identity, mAccountKey,
+ compFields, // nsIMsgCompFields *fields,
+ mTempFile, // nsIFile *sendFile,
+ true, // bool deleteSendFileOnCompletion,
+ false, // bool digest_p,
+ nsIMsgSend::nsMsgSendUnsent, // nsMsgDeliverMode mode,
+ nullptr, // nsIMsgDBHdr *msgToReplace,
+ sendListener, mFeedback, nullptr, getter_AddRefs(promise));
+ return rv;
+}
+
+nsresult nsMsgSendLater::StartNextMailFileSend(nsresult prevStatus) {
+ if (mTotalSendCount >= (uint32_t)mMessagesToSend.Count()) {
+ // Notify that this message has finished being sent.
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100,
+ 100);
+
+ // EndSendMessages resets everything for us
+ EndSendMessages(prevStatus, nullptr, mTotalSendCount,
+ mTotalSentSuccessfully);
+
+ // XXX Should we be releasing references so that we don't hold onto items
+ // unnecessarily.
+ return NS_OK;
+ }
+
+ // If we've already sent a message, and are sending more, send out a progress
+ // update with 100% for both send and copy as we must have finished by now.
+ if (mTotalSendCount > 0) {
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100,
+ 100);
+ }
+
+ mMessage = mMessagesToSend[mTotalSendCount++];
+
+ if (!mMessageFolder) return NS_ERROR_UNEXPECTED;
+
+ nsCString messageURI;
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folder->GetUriForMsg(mMessage, messageURI);
+
+ rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService));
+ if (NS_FAILED(rv) && !messageService) return NS_ERROR_FACTORY_NOT_LOADED;
+
+ nsCString identityKey;
+ rv = mMessage->GetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = GetIdentityFromKey(identityKey.get(), getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!identity) return NS_ERROR_UNEXPECTED;
+
+ // Notify that we're just about to start sending this message
+ NotifyListenersOnMessageStartSending(mTotalSendCount, mMessagesToSend.Count(),
+ identity);
+
+ // Setup what we need to parse the data stream correctly
+ m_inhead = true;
+ m_headersFP = 0;
+ m_headersPosition = 0;
+ m_bytesRead = 0;
+ m_position = 0;
+ m_flagsPosition = 0;
+ m_headersSize = 0;
+ PR_FREEIF(mLeftoverBuffer);
+
+ // Now, get our stream listener interface and plug it into the LoadMessage
+ // operation
+ rv = messageService->LoadMessage(messageURI,
+ static_cast<nsIStreamListener*>(this),
+ nullptr, nullptr, false);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetUnsentMessagesFolder(nsIMsgIdentity* aIdentity,
+ nsIMsgFolder** aFolder) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder);
+ if (!folder) {
+ nsCString uri;
+ GetFolderURIFromUserPrefs(nsIMsgSend::nsMsgQueueForLater, aIdentity, uri);
+ rv = LocateMessageFolder(aIdentity, nsIMsgSend::nsMsgQueueForLater,
+ uri.get(), getter_AddRefs(folder));
+ mMessageFolder = do_GetWeakReference(folder);
+ if (!mMessageFolder) return NS_ERROR_FAILURE;
+ }
+ if (folder) folder.forget(aFolder);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::HasUnsentMessages(nsIMsgIdentity* aIdentity, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgAccount>> accounts;
+ rv = accountManager->GetAccounts(accounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (accounts.IsEmpty())
+ return NS_OK; // no account set up -> no unsent messages
+
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ if (!mMessageFolder) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder));
+ // There doesn't have to be a nsMsgQueueForLater flagged folder.
+ if (NS_FAILED(rv) || !folder) return NS_OK;
+ }
+ rv = ReparseDBIfNeeded(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalMessages;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetTotalMessages(false, &totalMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aResult = totalMessages > 0;
+ return NS_OK;
+}
+
+//
+// To really finalize this capability, we need to have the ability to get
+// the message from the mail store in a stream for processing. The flow
+// would be something like this:
+//
+// foreach (message in Outbox folder)
+// get stream of Nth message
+// if (not done with headers)
+// Tack on to current buffer of headers
+// when done with headers
+// BuildHeaders()
+// Write Headers to Temp File
+// after done with headers
+// write rest of message body to temp file
+//
+// when done with the message
+// do send operation
+//
+// when send is complete
+// Copy from Outbox to FCC folder
+// Delete from Outbox folder
+//
+//
+NS_IMETHODIMP
+nsMsgSendLater::SendUnsentMessages(nsIMsgIdentity* aIdentity) {
+ return InternalSendMessages(true, aIdentity);
+}
+
+// Returns NS_OK if the db is OK, an error otherwise, e.g., we had to reparse.
+nsresult nsMsgSendLater::ReparseDBIfNeeded(nsIUrlListener* aListener) {
+ // This will kick off a reparse, if needed. So the next time we check if
+ // there are unsent messages, the db will be up to date.
+ nsCOMPtr<nsIMsgDatabase> unsentDB;
+ nsresult rv;
+ nsCOMPtr<nsIMsgLocalMailFolder> locFolder(
+ do_QueryReferent(mMessageFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return locFolder->GetDatabaseWithReparse(aListener, nullptr,
+ getter_AddRefs(unsentDB));
+}
+
+nsresult nsMsgSendLater::InternalSendMessages(bool aUserInitiated,
+ nsIMsgIdentity* aIdentity) {
+ if (WeAreOffline()) return NS_MSG_ERROR_OFFLINE;
+
+ // Protect against being called whilst we're already sending.
+ if (mSendingMessages) {
+ NS_ERROR("nsMsgSendLater is already sending messages");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // XXX This code should be set up for multiple unsent folders, however we
+ // don't support that at the moment, so for now just assume one folder.
+ if (!mMessageFolder) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(folder));
+ if (NS_FAILED(rv) || !folder) return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIMsgDatabase> unsentDB;
+ // Remember these in case we need to reparse the db.
+ mUserInitiated = aUserInitiated;
+ mIdentity = aIdentity;
+ rv = ReparseDBIfNeeded(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mIdentity = nullptr; // don't hold onto the identity since we're a service.
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgEnumerator> enumerator;
+ rv = folder->GetMessages(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Build mMessagesToSend array.
+ bool hasMoreElements = false;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+ hasMoreElements) {
+ nsCOMPtr<nsIMsgDBHdr> messageHeader;
+ rv = enumerator->GetNext(getter_AddRefs(messageHeader));
+ if (NS_SUCCEEDED(rv)) {
+ if (aUserInitiated) {
+ // If the user initiated the send, add all messages
+ mMessagesToSend.AppendObject(messageHeader);
+ } else {
+ // Else just send those that are NOT marked as Queued.
+ uint32_t flags;
+ rv = messageHeader->GetFlags(&flags);
+ if (NS_SUCCEEDED(rv) && !(flags & nsMsgMessageFlags::Queued))
+ mMessagesToSend.AppendObject(messageHeader);
+ }
+ }
+ }
+
+ // We're now sending messages so its time to signal that and reset our counts.
+ mSendingMessages = true;
+ mTotalSentSuccessfully = 0;
+ mTotalSendCount = 0;
+
+ // Notify the listeners that we are starting a send.
+ NotifyListenersOnStartSending(mMessagesToSend.Count());
+
+ return StartNextMailFileSend(NS_OK);
+}
+
+nsresult nsMsgSendLater::SetOrigMsgDisposition() {
+ if (!mMessage) return NS_ERROR_NULL_POINTER;
+
+ // We're finished sending a queued message. We need to look at mMessage
+ // and see if we need to set replied/forwarded
+ // flags for the original message that this message might be a reply to
+ // or forward of.
+ nsCString originalMsgURIs;
+ nsCString queuedDisposition;
+ mMessage->GetStringProperty(ORIG_URI_PROPERTY, originalMsgURIs);
+ mMessage->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, queuedDisposition);
+ if (!queuedDisposition.IsEmpty()) {
+ nsTArray<nsCString> uriArray;
+ ParseString(originalMsgURIs, ',', uriArray);
+ for (uint32_t i = 0; i < uriArray.Length(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgDBHdrFromURI(uriArray[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgHdr) {
+ // get the folder for the message resource
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder) {
+ nsMsgDispositionState dispositionSetting =
+ nsIMsgFolder::nsMsgDispositionState_None;
+ if (queuedDisposition.EqualsLiteral("replied"))
+ dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied;
+ else if (queuedDisposition.EqualsLiteral("forwarded"))
+ dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded;
+ else if (queuedDisposition.EqualsLiteral("redirected"))
+ dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Redirected;
+
+ msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgSendLater::DeleteCurrentMessage() {
+ if (!mMessage) {
+ NS_ERROR("nsMsgSendLater: Attempt to delete an already deleted message");
+ return NS_OK;
+ }
+
+ // Get the composition fields interface
+ if (!mMessageFolder) return NS_ERROR_UNEXPECTED;
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->DeleteMessages({&*mMessage}, nullptr, true, false, nullptr,
+ false /*allowUndo*/);
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ // Null out the message so we don't try and delete it again.
+ mMessage = nullptr;
+
+ return NS_OK;
+}
+
+//
+// This function parses the headers, and also deletes from the header block
+// any headers which should not be delivered in mail, regardless of whether
+// they were present in the queue file. Such headers include: BCC, FCC,
+// Sender, X-Mozilla-Status, X-Mozilla-News-Host, and Content-Length.
+// (Content-Length is for the disk file only, and must not be allowed to
+// escape onto the network, since it depends on the local linebreak
+// representation. Arguably, we could allow Lines to escape, but it's not
+// required by NNTP.)
+//
+nsresult nsMsgSendLater::BuildHeaders() {
+ char* buf = m_headers;
+ char* buf_end = buf + m_headersFP;
+
+ PR_FREEIF(m_to);
+ PR_FREEIF(m_bcc);
+ PR_FREEIF(m_newsgroups);
+ PR_FREEIF(m_newshost);
+ PR_FREEIF(m_fcc);
+ PR_FREEIF(mIdentityKey);
+ PR_FREEIF(mAccountKey);
+ m_flags = 0;
+
+ while (buf < buf_end) {
+ bool prune_p = false;
+ bool do_flags_p = false;
+ char* colon = PL_strchr(buf, ':');
+ char* end;
+ char* value = 0;
+ char** header = 0;
+ char* header_start = buf;
+
+ if (!colon) break;
+
+ end = colon;
+ while (end > buf && (*end == ' ' || *end == '\t')) end--;
+
+ switch (buf[0]) {
+ case 'B':
+ case 'b':
+ if (!PL_strncasecmp("BCC", buf, end - buf)) {
+ header = &m_bcc;
+ }
+ break;
+ case 'C':
+ case 'c':
+ if (!PL_strncasecmp("CC", buf, end - buf))
+ header = &m_to;
+ else if (!PL_strncasecmp(HEADER_CONTENT_LENGTH, buf, end - buf))
+ prune_p = true;
+ break;
+ case 'F':
+ case 'f':
+ if (!PL_strncasecmp("FCC", buf, end - buf)) {
+ header = &m_fcc;
+ prune_p = true;
+ }
+ break;
+ case 'L':
+ case 'l':
+ if (!PL_strncasecmp("Lines", buf, end - buf)) prune_p = true;
+ break;
+ case 'N':
+ case 'n':
+ if (!PL_strncasecmp("Newsgroups", buf, end - buf))
+ header = &m_newsgroups;
+ break;
+ case 'S':
+ case 's':
+ if (!PL_strncasecmp("Sender", buf, end - buf)) prune_p = true;
+ break;
+ case 'T':
+ case 't':
+ if (!PL_strncasecmp("To", buf, end - buf)) header = &m_to;
+ break;
+ case 'X':
+ case 'x': {
+ if (buf + strlen(HEADER_X_MOZILLA_STATUS2) == end &&
+ !PL_strncasecmp(HEADER_X_MOZILLA_STATUS2, buf, end - buf))
+ prune_p = true;
+ else if (buf + strlen(HEADER_X_MOZILLA_STATUS) == end &&
+ !PL_strncasecmp(HEADER_X_MOZILLA_STATUS, buf, end - buf))
+ prune_p = do_flags_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_DRAFT_INFO, buf, end - buf))
+ prune_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf))
+ prune_p = true;
+ else if (!PL_strncasecmp(HEADER_X_MOZILLA_NEWSHOST, buf, end - buf)) {
+ prune_p = true;
+ header = &m_newshost;
+ } else if (!PL_strncasecmp(HEADER_X_MOZILLA_IDENTITY_KEY, buf,
+ end - buf)) {
+ prune_p = true;
+ header = &mIdentityKey;
+ } else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf,
+ end - buf)) {
+ prune_p = true;
+ header = &mAccountKey;
+ }
+ break;
+ }
+ }
+
+ buf = colon + 1;
+ while (*buf == ' ' || *buf == '\t') buf++;
+
+ value = buf;
+
+ SEARCH_NEWLINE:
+ while (*buf != 0 && *buf != '\r' && *buf != '\n') buf++;
+
+ if (buf + 1 >= buf_end)
+ ;
+ // If "\r\n " or "\r\n\t" is next, that doesn't terminate the header.
+ else if (buf + 2 < buf_end && (buf[0] == '\r' && buf[1] == '\n') &&
+ (buf[2] == ' ' || buf[2] == '\t')) {
+ buf += 3;
+ goto SEARCH_NEWLINE;
+ }
+ // If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate
+ // the header either.
+ else if ((buf[0] == '\r' || buf[0] == '\n') &&
+ (buf[1] == ' ' || buf[1] == '\t')) {
+ buf += 2;
+ goto SEARCH_NEWLINE;
+ }
+
+ if (header) {
+ int L = buf - value;
+ if (*header) {
+ char* newh = (char*)PR_Realloc((*header), PL_strlen(*header) + L + 10);
+ if (!newh) return NS_ERROR_OUT_OF_MEMORY;
+ *header = newh;
+ newh = (*header) + PL_strlen(*header);
+ *newh++ = ',';
+ *newh++ = ' ';
+ memcpy(newh, value, L);
+ newh[L] = 0;
+ } else {
+ *header = (char*)PR_Malloc(L + 1);
+ if (!*header) return NS_ERROR_OUT_OF_MEMORY;
+ memcpy((*header), value, L);
+ (*header)[L] = 0;
+ }
+ } else if (do_flags_p) {
+ char* s = value;
+ PR_ASSERT(*s != ' ' && *s != '\t');
+ NS_ASSERTION(MsgIsHex(s, 4), "Expected 4 hex digits for flags.");
+ m_flags = MsgUnhex(s, 4);
+ }
+
+ if (*buf == '\r' || *buf == '\n') {
+ if (*buf == '\r' && buf[1] == '\n') buf++;
+ buf++;
+ }
+
+ if (prune_p) {
+ char* to = header_start;
+ char* from = buf;
+ while (from < buf_end) *to++ = *from++;
+ buf = header_start;
+ buf_end = to;
+ m_headersFP = buf_end - m_headers;
+ }
+ }
+
+ m_headers[m_headersFP++] = '\r';
+ m_headers[m_headersFP++] = '\n';
+
+ // Now we have parsed out all of the headers we need and we
+ // can proceed.
+ return NS_OK;
+}
+
+nsresult DoGrowBuffer(int32_t desired_size, int32_t element_size,
+ int32_t quantum, char** buffer, int32_t* size) {
+ if (*size <= desired_size) {
+ char* new_buf;
+ int32_t increment = desired_size - *size;
+ if (increment < quantum) // always grow by a minimum of N bytes
+ increment = quantum;
+
+ new_buf =
+ (*buffer ? (char*)PR_Realloc(*buffer, (*size + increment) *
+ (element_size / sizeof(char)))
+ : (char*)PR_Malloc((*size + increment) *
+ (element_size / sizeof(char))));
+ if (!new_buf) return NS_ERROR_OUT_OF_MEMORY;
+ *buffer = new_buf;
+ *size += increment;
+ }
+ return NS_OK;
+}
+
+#define do_grow_headers(desired_size) \
+ (((desired_size) >= m_headersSize) \
+ ? DoGrowBuffer((desired_size), sizeof(char), 1024, &m_headers, \
+ &m_headersSize) \
+ : NS_OK)
+
+nsresult nsMsgSendLater::DeliverQueuedLine(const char* line, int32_t length) {
+ int32_t flength = length;
+
+ m_bytesRead += length;
+
+ // convert existing newline to CRLF
+ // Don't need this because the calling routine is taking care of it.
+ // if (length > 0 && (line[length-1] == '\r' ||
+ // (line[length-1] == '\n' && (length < 2 || line[length-2] != '\r'))))
+ // {
+ // line[length-1] = '\r';
+ // line[length++] = '\n';
+ // }
+ //
+ //
+ // We are going to check if we are looking at a "From - " line. If so,
+ // then just eat it and return NS_OK
+ //
+ if (!PL_strncasecmp(line, "From - ", 7)) return NS_OK;
+
+ if (m_inhead) {
+ if (m_headersPosition == 0) {
+ // This line is the first line in a header block.
+ // Remember its position.
+ m_headersPosition = m_position;
+
+ // Also, since we're now processing the headers, clear out the
+ // slots which we will parse data into, so that the values that
+ // were used the last time around do not persist.
+
+ // We must do that here, and not in the previous clause of this
+ // `else' (the "I've just seen a `From ' line clause") because
+ // that clause happens before delivery of the previous message is
+ // complete, whereas this clause happens after the previous msg
+ // has been delivered. If we did this up there, then only the
+ // last message in the folder would ever be able to be both
+ // mailed and posted (or fcc'ed.)
+ PR_FREEIF(m_to);
+ PR_FREEIF(m_bcc);
+ PR_FREEIF(m_newsgroups);
+ PR_FREEIF(m_newshost);
+ PR_FREEIF(m_fcc);
+ PR_FREEIF(mIdentityKey);
+ }
+
+ if (line[0] == '\r' || line[0] == '\n' || line[0] == 0) {
+ // End of headers. Now parse them; open the temp file;
+ // and write the appropriate subset of the headers out.
+ m_inhead = false;
+
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile),
+ mTempFile, -1, 00600);
+ if (NS_FAILED(rv)) return NS_MSG_ERROR_WRITING_FILE;
+
+ nsresult status = BuildHeaders();
+ if (NS_FAILED(status)) return status;
+
+ uint32_t n;
+ rv = mOutFile->Write(m_headers, m_headersFP, &n);
+ if (NS_FAILED(rv) || n != (uint32_t)m_headersFP)
+ return NS_MSG_ERROR_WRITING_FILE;
+ } else {
+ // Otherwise, this line belongs to a header. So append it to the
+ // header data.
+
+ if (!PL_strncasecmp(line, HEADER_X_MOZILLA_STATUS,
+ PL_strlen(HEADER_X_MOZILLA_STATUS)))
+ // Notice the position of the flags.
+ m_flagsPosition = m_position;
+ else if (m_headersFP == 0)
+ m_flagsPosition = 0;
+
+ nsresult status = do_grow_headers(length + m_headersFP + 10);
+ if (NS_FAILED(status)) return status;
+
+ memcpy(m_headers + m_headersFP, line, length);
+ m_headersFP += length;
+ }
+ } else {
+ // This is a body line. Write it to the file.
+ PR_ASSERT(mOutFile);
+ if (mOutFile) {
+ uint32_t wrote;
+ nsresult rv = mOutFile->Write(line, length, &wrote);
+ if (NS_FAILED(rv) || wrote < (uint32_t)length)
+ return NS_MSG_ERROR_WRITING_FILE;
+ }
+ }
+
+ m_position += flength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::AddListener(nsIMsgSendLaterListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListenerArray.AppendElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::RemoveListener(nsIMsgSendLaterListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ return mListenerArray.RemoveElement(aListener) ? NS_OK : NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetSendingMessages(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSendingMessages;
+ return NS_OK;
+}
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener>>::ForwardIterator iter( \
+ mListenerArray); \
+ nsCOMPtr<nsIMsgSendLaterListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+void nsMsgSendLater::NotifyListenersOnStartSending(
+ uint32_t aTotalMessageCount) {
+ NOTIFY_LISTENERS(OnStartSending, (aTotalMessageCount));
+}
+
+void nsMsgSendLater::NotifyListenersOnMessageStartSending(
+ uint32_t aCurrentMessage, uint32_t aTotalMessage,
+ nsIMsgIdentity* aIdentity) {
+ NOTIFY_LISTENERS(OnMessageStartSending,
+ (aCurrentMessage, aTotalMessage, mMessage, aIdentity));
+}
+
+void nsMsgSendLater::NotifyListenersOnProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ uint32_t aSendPercent,
+ uint32_t aCopyPercent) {
+ NOTIFY_LISTENERS(OnMessageSendProgress, (aCurrentMessage, aTotalMessage,
+ aSendPercent, aCopyPercent));
+}
+
+void nsMsgSendLater::NotifyListenersOnMessageSendError(uint32_t aCurrentMessage,
+ nsresult aStatus,
+ const char16_t* aMsg) {
+ NOTIFY_LISTENERS(OnMessageSendError,
+ (aCurrentMessage, mMessage, aStatus, aMsg));
+}
+
+/**
+ * This function is called to end sending of messages, it resets the send later
+ * system and notifies the relevant parties that we have finished.
+ */
+void nsMsgSendLater::EndSendMessages(nsresult aStatus, const char16_t* aMsg,
+ uint32_t aTotalTried,
+ uint32_t aSuccessful) {
+ // Catch-all, we may have had an issue sending, so we may not be calling
+ // StartNextMailFileSend to fully finish the sending. Therefore set
+ // mSendingMessages to false here so that we don't think we're still trying
+ // to send messages
+ mSendingMessages = false;
+
+ // Clear out our array of messages.
+ mMessagesToSend.Clear();
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mMessageFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, );
+ // We don't need to keep hold of the database now we've finished sending.
+ (void)folder->SetMsgDatabase(nullptr);
+
+ // or temp file or output stream
+ mTempFile = nullptr;
+ mOutFile = nullptr;
+
+ NOTIFY_LISTENERS(OnStopSending, (aStatus, aMsg, aTotalTried, aSuccessful));
+
+ // If we've got a shutdown listener, notify it that we've finished.
+ if (mShutdownListener) {
+ mShutdownListener->OnStopRunningUrl(nullptr, NS_OK);
+ mShutdownListener = nullptr;
+ }
+}
+
+/**
+ * Called when the send part of sending a message is finished. This will set up
+ * for the next step or "end" depending on the status.
+ *
+ * @param aStatus The success or fail result of the send step.
+ * @return True if the copy process will continue, false otherwise.
+ */
+bool nsMsgSendLater::OnSendStepFinished(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ SetOrigMsgDisposition();
+ DeleteCurrentMessage();
+
+ // Send finished, so that is now 100%, copy to proceed...
+ NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 0);
+
+ ++mTotalSentSuccessfully;
+ return true;
+ } else {
+ // XXX we don't currently get a message string from the send service.
+ NotifyListenersOnMessageSendError(mTotalSendCount, aStatus, nullptr);
+ nsresult rv = StartNextMailFileSend(aStatus);
+ // if this is the last message we're sending, we should report
+ // the status failure.
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+ }
+ return false;
+}
+
+/**
+ * Called when the copy part of sending a message is finished. This will send
+ * the next message or handle failure as appropriate.
+ *
+ * @param aStatus The success or fail result of the copy step.
+ */
+void nsMsgSendLater::OnCopyStepFinished(nsresult aStatus) {
+ // Regardless of the success of the copy we will still keep trying
+ // to send the rest...
+ nsresult rv = StartNextMailFileSend(aStatus);
+ if (NS_FAILED(rv))
+ EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully);
+}
+
+// XXX todo
+// maybe this should just live in the account manager?
+nsresult nsMsgSendLater::GetIdentityFromKey(const char* aKey,
+ nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aKey) {
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ if (NS_SUCCEEDED(accountManager->GetAllIdentities(identities))) {
+ for (auto lookupIdentity : identities) {
+ nsCString key;
+ lookupIdentity->GetKey(key);
+ if (key.Equals(aKey)) {
+ lookupIdentity.forget(aIdentity);
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ // If no aKey, or we failed to find the identity from the key
+ // use the identity from the default account.
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (defaultAccount)
+ rv = defaultAccount->GetDefaultIdentity(aIdentity);
+ else
+ *aIdentity = nullptr;
+
+ return rv;
+}
+
+nsresult nsMsgSendLater::StartTimer() {
+ // No need to trigger if timer is already set
+ if (mTimerSet) return NS_OK;
+
+ // XXX only trigger for non-queued headers
+
+ // Items from this function return NS_OK because the callee won't care about
+ // the result anyway.
+ nsresult rv;
+ if (!mTimer) {
+ mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ }
+
+ rv = mTimer->Init(static_cast<nsIObserver*>(this), kInitialMessageSendTime,
+ nsITimer::TYPE_ONE_SHOT);
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ mTimerSet = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderAdded(nsIMsgFolder* /*parent*/,
+ nsIMsgFolder* /*child*/) {
+ return StartTimer();
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnMessageAdded(nsIMsgFolder* /*parent*/, nsIMsgDBHdr* /*msg*/) {
+ return StartTimer();
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderRemoved(nsIMsgFolder* /*parent*/,
+ nsIMsgFolder* /*child*/) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnMessageRemoved(nsIMsgFolder* /*parent*/,
+ nsIMsgDBHdr* /*msg*/) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderPropertyChanged(nsIMsgFolder* aFolder,
+ const nsACString& aProperty,
+ const nsACString& aOldValue,
+ const nsACString& aNewValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderIntPropertyChanged(nsIMsgFolder* aFolder,
+ const nsACString& aProperty,
+ int64_t aOldValue,
+ int64_t aNewValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderBoolPropertyChanged(nsIMsgFolder* aFolder,
+ const nsACString& aProperty,
+ bool aOldValue, bool aNewValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderUnicharPropertyChanged(nsIMsgFolder* aFolder,
+ const nsACString& aProperty,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderPropertyFlagChanged(nsIMsgDBHdr* aMsg,
+ const nsACString& aProperty,
+ uint32_t aOldValue,
+ uint32_t aNewValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::OnFolderEvent(nsIMsgFolder* aFolder, const nsACString& aEvent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetNeedsToRunTask(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSendingMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::DoShutdownTask(nsIUrlListener* aListener, nsIMsgWindow* aWindow,
+ bool* aResult) {
+ if (mTimer) mTimer->Cancel();
+ // If we're already sending messages, nothing to do, but save the shutdown
+ // listener until we've finished.
+ if (mSendingMessages) {
+ mShutdownListener = aListener;
+ return NS_OK;
+ }
+ // Else we have pending messages, we need to throw up a dialog to find out
+ // if to send them or not.
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgSendLater::GetCurrentTaskName(nsAString& aResult) {
+ // XXX Bug 440794 will localize this, left as non-localized whilst we decide
+ // on the actual strings and try out the UI.
+ aResult = u"Sending Messages"_ns;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgSendLater.h b/comm/mailnews/compose/src/nsMsgSendLater.h
new file mode 100644
index 0000000000..fcd8c53e38
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgSendLater.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef _nsMsgSendLater_H_
+#define _nsMsgSendLater_H_
+
+#include "nsCOMArray.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgSendListener.h"
+#include "nsIMsgSendLaterListener.h"
+#include "nsIMsgSendLater.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsTObserverArray.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgShutdown.h"
+#include "nsIWeakReferenceUtils.h"
+
+////////////////////////////////////////////////////////////////////////////////////
+// This is the listener class for the send operation. We have to create this
+// class to listen for message send completion and eventually notify the caller
+////////////////////////////////////////////////////////////////////////////////////
+class nsMsgSendLater;
+
+class SendOperationListener : public nsIMsgSendListener,
+ public nsIMsgCopyServiceListener {
+ public:
+ explicit SendOperationListener(nsMsgSendLater* aSendLater);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ private:
+ virtual ~SendOperationListener();
+ RefPtr<nsMsgSendLater> mSendLater;
+};
+
+class nsMsgSendLater : public nsIMsgSendLater,
+ public nsIFolderListener,
+ public nsIObserver,
+ public nsIUrlListener,
+ public nsIMsgShutdownTask
+
+{
+ public:
+ nsMsgSendLater();
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDLATER
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSHUTDOWNTASK
+
+ // Methods needed for implementing interface...
+ nsresult StartNextMailFileSend(nsresult prevStatus);
+ nsresult CompleteMailFileSend();
+
+ nsresult DeleteCurrentMessage();
+ nsresult SetOrigMsgDisposition();
+ // Necessary for creating a valid list of recipients
+ nsresult BuildHeaders();
+ nsresult DeliverQueuedLine(const char* line, int32_t length);
+ nsresult RebufferLeftovers(char* startBuf, uint32_t aLen);
+ nsresult BuildNewBuffer(const char* aBuf, uint32_t aCount,
+ uint32_t* totalBufSize);
+
+ // methods for listener array processing...
+ void NotifyListenersOnStartSending(uint32_t aTotalMessageCount);
+ void NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage,
+ nsIMsgIdentity* aIdentity);
+ void NotifyListenersOnProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessage, uint32_t aSendPercent,
+ uint32_t aCopyPercent);
+ void NotifyListenersOnMessageSendError(uint32_t aCurrentMessage,
+ nsresult aStatus,
+ const char16_t* aMsg);
+ void EndSendMessages(nsresult aStatus, const char16_t* aMsg,
+ uint32_t aTotalTried, uint32_t aSuccessful);
+
+ bool OnSendStepFinished(nsresult aStatus);
+ void OnCopyStepFinished(nsresult aStatus);
+
+ private:
+ // counters and things for enumeration
+ uint32_t mTotalSentSuccessfully;
+ uint32_t mTotalSendCount;
+ nsCOMArray<nsIMsgDBHdr> mMessagesToSend;
+ nsWeakPtr mMessageFolder;
+ nsCOMPtr<nsIMsgStatusFeedback> mFeedback;
+
+ virtual ~nsMsgSendLater();
+ nsresult GetIdentityFromKey(const char* aKey, nsIMsgIdentity** aIdentity);
+ nsresult ReparseDBIfNeeded(nsIUrlListener* aListener);
+ nsresult InternalSendMessages(bool aUserInitiated, nsIMsgIdentity* aIdentity);
+ nsresult StartTimer();
+
+ nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> > mListenerArray;
+ nsCOMPtr<nsIMsgDBHdr> mMessage;
+ nsCOMPtr<nsITimer> mTimer;
+ bool mTimerSet;
+ nsCOMPtr<nsIUrlListener> mShutdownListener;
+
+ //
+ // File output stuff...
+ //
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIOutputStream> mOutFile;
+
+ // For building headers and stream parsing...
+ char* m_to;
+ char* m_bcc;
+ char* m_fcc;
+ char* m_newsgroups;
+ char* m_newshost;
+ char* m_headers;
+ int32_t m_flags;
+ int32_t m_headersFP;
+ bool m_inhead;
+ int32_t m_headersPosition;
+ int32_t m_bytesRead;
+ int32_t m_position;
+ int32_t m_flagsPosition;
+ int32_t m_headersSize;
+ char* mLeftoverBuffer;
+ char* mIdentityKey;
+ char* mAccountKey;
+
+ bool mSendingMessages;
+ bool mUserInitiated;
+ nsCOMPtr<nsIMsgIdentity> mIdentity;
+};
+
+#endif /* _nsMsgSendLater_H_ */
diff --git a/comm/mailnews/compose/src/nsMsgSendReport.cpp b/comm/mailnews/compose/src/nsMsgSendReport.cpp
new file mode 100644
index 0000000000..5cab724810
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgSendReport.cpp
@@ -0,0 +1,385 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMsgSendReport.h"
+
+#include "msgCore.h"
+#include "nsIMsgCompose.h"
+#include "nsMsgPrompts.h"
+#include "nsError.h"
+#include "nsComposeStrings.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Components.h"
+
+NS_IMPL_ISUPPORTS(nsMsgProcessReport, nsIMsgProcessReport)
+
+nsMsgProcessReport::nsMsgProcessReport() { Reset(); }
+
+nsMsgProcessReport::~nsMsgProcessReport() {}
+
+/* attribute boolean proceeded; */
+NS_IMETHODIMP nsMsgProcessReport::GetProceeded(bool* aProceeded) {
+ NS_ENSURE_ARG_POINTER(aProceeded);
+ *aProceeded = mProceeded;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetProceeded(bool aProceeded) {
+ mProceeded = aProceeded;
+ return NS_OK;
+}
+
+/* attribute nsresult error; */
+NS_IMETHODIMP nsMsgProcessReport::GetError(nsresult* aError) {
+ NS_ENSURE_ARG_POINTER(aError);
+ *aError = mError;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetError(nsresult aError) {
+ mError = aError;
+ return NS_OK;
+}
+
+/* attribute wstring message; */
+NS_IMETHODIMP nsMsgProcessReport::GetMessage(char16_t** aMessage) {
+ NS_ENSURE_ARG_POINTER(aMessage);
+ *aMessage = ToNewUnicode(mMessage);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProcessReport::SetMessage(const char16_t* aMessage) {
+ mMessage = aMessage;
+ return NS_OK;
+}
+
+/* void Reset (); */
+NS_IMETHODIMP nsMsgProcessReport::Reset() {
+ mProceeded = false;
+ mError = NS_OK;
+ mMessage.Truncate();
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgSendReport, nsIMsgSendReport)
+
+nsMsgSendReport::nsMsgSendReport() {
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i++)
+ mProcessReport[i] = new nsMsgProcessReport();
+
+ Reset();
+}
+
+nsMsgSendReport::~nsMsgSendReport() {
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i++) mProcessReport[i] = nullptr;
+}
+
+/* attribute long currentProcess; */
+NS_IMETHODIMP nsMsgSendReport::GetCurrentProcess(int32_t* aCurrentProcess) {
+ NS_ENSURE_ARG_POINTER(aCurrentProcess);
+ *aCurrentProcess = mCurrentProcess;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgSendReport::SetCurrentProcess(int32_t aCurrentProcess) {
+ if (aCurrentProcess < 0 || aCurrentProcess > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ mCurrentProcess = aCurrentProcess;
+ if (mProcessReport[mCurrentProcess])
+ mProcessReport[mCurrentProcess]->SetProceeded(true);
+
+ return NS_OK;
+}
+
+/* attribute long deliveryMode; */
+NS_IMETHODIMP nsMsgSendReport::GetDeliveryMode(int32_t* aDeliveryMode) {
+ NS_ENSURE_ARG_POINTER(aDeliveryMode);
+ *aDeliveryMode = mDeliveryMode;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgSendReport::SetDeliveryMode(int32_t aDeliveryMode) {
+ mDeliveryMode = aDeliveryMode;
+ return NS_OK;
+}
+
+/* void Reset (); */
+NS_IMETHODIMP nsMsgSendReport::Reset() {
+ uint32_t i;
+ for (i = 0; i <= SEND_LAST_PROCESS; i++)
+ if (mProcessReport[i]) mProcessReport[i]->Reset();
+
+ mCurrentProcess = 0;
+ mDeliveryMode = 0;
+ mAlreadyDisplayReport = false;
+
+ return NS_OK;
+}
+
+/* void setProceeded (in long process, in boolean proceeded); */
+NS_IMETHODIMP nsMsgSendReport::SetProceeded(int32_t process, bool proceeded) {
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current) process = mCurrentProcess;
+
+ if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED;
+
+ return mProcessReport[process]->SetProceeded(proceeded);
+}
+
+/* void setError (in long process, in nsresult error, in boolean
+ * overwriteError); */
+NS_IMETHODIMP nsMsgSendReport::SetError(int32_t process, nsresult newError,
+ bool overwriteError) {
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current) {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult currError = NS_OK;
+ mProcessReport[process]->GetError(&currError);
+ if (overwriteError || NS_SUCCEEDED(currError))
+ return mProcessReport[process]->SetError(newError);
+ else
+ return NS_OK;
+}
+
+/* void setMessage (in long process, in wstring message, in boolean
+ * overwriteMessage); */
+NS_IMETHODIMP nsMsgSendReport::SetMessage(int32_t process,
+ const char16_t* message,
+ bool overwriteMessage) {
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current) {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ if (!mProcessReport[process]) return NS_ERROR_NOT_INITIALIZED;
+
+ nsString currMessage;
+ mProcessReport[process]->GetMessage(getter_Copies(currMessage));
+ if (overwriteMessage || currMessage.IsEmpty())
+ return mProcessReport[process]->SetMessage(message);
+ else
+ return NS_OK;
+}
+
+/* nsIMsgProcessReport getProcessReport (in long process); */
+NS_IMETHODIMP nsMsgSendReport::GetProcessReport(int32_t process,
+ nsIMsgProcessReport** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ if (process < process_Current || process > SEND_LAST_PROCESS)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ if (process == process_Current) {
+ if (mCurrentProcess == process_Current)
+ // We don't know what we're currently trying to do
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ process = mCurrentProcess;
+ }
+
+ NS_IF_ADDREF(*_retval = mProcessReport[process]);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSendReport::DisplayReport(mozIDOMWindowProxy* window,
+ bool showErrorOnly,
+ bool dontShowReportTwice,
+ nsresult* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ NS_ENSURE_TRUE(mCurrentProcess >= 0 && mCurrentProcess <= SEND_LAST_PROCESS,
+ NS_ERROR_NOT_INITIALIZED);
+
+ nsresult currError = NS_OK;
+ mProcessReport[mCurrentProcess]->GetError(&currError);
+ *_retval = currError;
+
+ if (dontShowReportTwice && mAlreadyDisplayReport) return NS_OK;
+
+ if (showErrorOnly && NS_SUCCEEDED(currError)) return NS_OK;
+
+ nsString currMessage;
+ mProcessReport[mCurrentProcess]->GetMessage(getter_Copies(currMessage));
+
+ nsresult rv; // don't step on currError.
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties",
+ getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) {
+ // TODO need to display a generic hardcoded message
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+ }
+
+ nsString dialogTitle;
+ nsString dialogMessage;
+
+ if (NS_SUCCEEDED(currError)) {
+ // TODO display a success error message
+ return NS_OK;
+ }
+
+ // Do we have an explanation of the error? if no, try to build one...
+ if (currMessage.IsEmpty()) {
+#ifdef __GNUC__
+// Temporary workaround until bug 783526 is fixed.
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wswitch"
+#endif
+ switch (currError) {
+ case NS_BINDING_ABORTED:
+ case NS_MSG_UNABLE_TO_SEND_LATER:
+ case NS_MSG_UNABLE_TO_SAVE_DRAFT:
+ case NS_MSG_UNABLE_TO_SAVE_TEMPLATE:
+ // Ignore, don't need to repeat ourself.
+ break;
+ default:
+ const char* errorString = errorStringNameForErrorCode(currError);
+ nsMsgGetMessageByName(errorString, currMessage);
+ break;
+ }
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+ }
+
+ if (mDeliveryMode == nsIMsgCompDeliverMode::Now ||
+ mDeliveryMode == nsIMsgCompDeliverMode::SendUnsent) {
+ // SMTP is taking care of it's own error message and will return
+ // NS_ERROR_BUT_DONT_SHOW_ALERT as error code. In that case, we must not
+ // show an alert ourself.
+ if (currError == NS_ERROR_BUT_DONT_SHOW_ALERT) {
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+ }
+
+ bundle->GetStringFromName("sendMessageErrorTitle", dialogTitle);
+
+ const char* preStrName = "sendFailed";
+ bool askToGoBackToCompose = false;
+ switch (mCurrentProcess) {
+ case process_BuildMessage:
+ preStrName = "sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_NNTP:
+ preStrName = "sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_SMTP:
+ bool nntpProceeded;
+ mProcessReport[process_NNTP]->GetProceeded(&nntpProceeded);
+ if (nntpProceeded)
+ preStrName = "sendFailedButNntpOk";
+ else
+ preStrName = "sendFailed";
+ askToGoBackToCompose = false;
+ break;
+ case process_Copy:
+ preStrName = "failedCopyOperation";
+ askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now);
+ break;
+ case process_FCC:
+ preStrName = "failedCopyOperation";
+ askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now);
+ break;
+ }
+ bundle->GetStringFromName(preStrName, dialogMessage);
+
+ // Do we already have an error message?
+ if (!askToGoBackToCompose && currMessage.IsEmpty()) {
+ // we don't have an error description but we can put a generic explanation
+ bundle->GetStringFromName("genericFailureExplanation", currMessage);
+ }
+
+ if (!currMessage.IsEmpty()) {
+ // Don't need to repeat ourself!
+ if (!currMessage.Equals(dialogMessage)) {
+ if (!dialogMessage.IsEmpty()) dialogMessage.Append(char16_t('\n'));
+ dialogMessage.Append(currMessage);
+ }
+ }
+
+ if (askToGoBackToCompose) {
+ bool oopsGiveMeBackTheComposeWindow = true;
+ nsString text1;
+ bundle->GetStringFromName("returnToComposeWindowQuestion", text1);
+ if (!dialogMessage.IsEmpty()) dialogMessage.AppendLiteral("\n");
+ dialogMessage.Append(text1);
+ nsMsgAskBooleanQuestionByString(window, dialogMessage.get(),
+ &oopsGiveMeBackTheComposeWindow,
+ dialogTitle.get());
+ if (!oopsGiveMeBackTheComposeWindow) *_retval = NS_OK;
+ } else
+ nsMsgDisplayMessageByString(window, dialogMessage.get(),
+ dialogTitle.get());
+ } else {
+ const char* title;
+ const char* messageName;
+
+ switch (mDeliveryMode) {
+ case nsIMsgCompDeliverMode::Later:
+ title = "sendLaterErrorTitle";
+ messageName = "unableToSendLater";
+ break;
+
+ case nsIMsgCompDeliverMode::AutoSaveAsDraft:
+ case nsIMsgCompDeliverMode::SaveAsDraft:
+ title = "saveDraftErrorTitle";
+ messageName = "unableToSaveDraft";
+ break;
+
+ case nsIMsgCompDeliverMode::SaveAsTemplate:
+ title = "saveTemplateErrorTitle";
+ messageName = "unableToSaveTemplate";
+ break;
+
+ default:
+ /* This should never happen! */
+ title = "sendMessageErrorTitle";
+ messageName = "sendFailed";
+ break;
+ }
+
+ bundle->GetStringFromName(title, dialogTitle);
+ bundle->GetStringFromName(messageName, dialogMessage);
+
+ // Do we have an error message...
+ if (currMessage.IsEmpty()) {
+ // we don't have an error description but we can put a generic explanation
+ bundle->GetStringFromName("genericFailureExplanation", currMessage);
+ }
+
+ if (!currMessage.IsEmpty()) {
+ if (!dialogMessage.IsEmpty()) dialogMessage.Append(char16_t('\n'));
+ dialogMessage.Append(currMessage);
+ }
+ nsMsgDisplayMessageByString(window, dialogMessage.get(), dialogTitle.get());
+ }
+
+ mAlreadyDisplayReport = true;
+ return NS_OK;
+}
diff --git a/comm/mailnews/compose/src/nsMsgSendReport.h b/comm/mailnews/compose/src/nsMsgSendReport.h
new file mode 100644
index 0000000000..0fcc2f1c9f
--- /dev/null
+++ b/comm/mailnews/compose/src/nsMsgSendReport.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef __nsMsgSendReport_h__
+#define __nsMsgSendReport_h__
+
+#include "nsIMsgSendReport.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsMsgProcessReport : public nsIMsgProcessReport {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPROCESSREPORT
+
+ nsMsgProcessReport();
+
+ private:
+ virtual ~nsMsgProcessReport();
+ bool mProceeded;
+ nsresult mError;
+ nsString mMessage;
+};
+
+class nsMsgSendReport : public nsIMsgSendReport {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSENDREPORT
+
+ nsMsgSendReport();
+
+ protected:
+ virtual ~nsMsgSendReport();
+
+ private:
+#define SEND_LAST_PROCESS process_FCC
+ nsCOMPtr<nsIMsgProcessReport> mProcessReport[SEND_LAST_PROCESS + 1];
+ int32_t mDeliveryMode;
+ int32_t mCurrentProcess;
+ bool mAlreadyDisplayReport;
+};
+
+#endif
diff --git a/comm/mailnews/compose/src/nsSmtpUrl.cpp b/comm/mailnews/compose/src/nsSmtpUrl.cpp
new file mode 100644
index 0000000000..b9ac451650
--- /dev/null
+++ b/comm/mailnews/compose/src/nsSmtpUrl.cpp
@@ -0,0 +1,755 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "msgCore.h"
+
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsSmtpUrl.h"
+#include "nsString.h"
+#include "nsMsgUtils.h"
+#include "nsIMimeConverter.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsCRT.h"
+#include "mozilla/Encoding.h"
+
+/////////////////////////////////////////////////////////////////////////////////////
+// mailto url definition
+/////////////////////////////////////////////////////////////////////////////////////
+nsMailtoUrl::nsMailtoUrl() { mFormat = nsIMsgCompFormat::Default; }
+
+nsMailtoUrl::~nsMailtoUrl() {}
+
+NS_IMPL_ISUPPORTS(nsMailtoUrl, nsIMailtoUrl, nsIURI)
+
+static void UnescapeAndConvert(nsIMimeConverter* mimeConverter,
+ const nsACString& escaped, nsACString& out) {
+ NS_ASSERTION(mimeConverter, "Set mimeConverter before calling!");
+ // If the string is empty, do absolutely nothing.
+ if (escaped.IsEmpty()) return;
+
+ MsgUnescapeString(escaped, 0, out);
+ nsAutoCString decodedString;
+ nsresult rv = mimeConverter->DecodeMimeHeaderToUTF8(out, "UTF_8", false, true,
+ decodedString);
+ if (NS_SUCCEEDED(rv) && !decodedString.IsEmpty()) out = decodedString;
+}
+
+nsresult nsMailtoUrl::ParseMailtoUrl(char* searchPart) {
+ char* rest = searchPart;
+ nsCString escapedInReplyToPart;
+ nsCString escapedToPart;
+ nsCString escapedCcPart;
+ nsCString escapedSubjectPart;
+ nsCString escapedNewsgroupPart;
+ nsCString escapedNewsHostPart;
+ nsCString escapedReferencePart;
+ nsCString escapedBodyPart;
+ nsCString escapedBccPart;
+ nsCString escapedFollowUpToPart;
+ nsCString escapedFromPart;
+ nsCString escapedHtmlPart;
+ nsCString escapedOrganizationPart;
+ nsCString escapedReplyToPart;
+ nsCString escapedPriorityPart;
+
+ // okay, first, free up all of our old search part state.....
+ CleanupMailtoState();
+ // m_toPart has the escaped address from before the query string, copy it
+ // over so we can add on any additional to= addresses and unescape them all.
+ escapedToPart = m_toPart;
+
+ if (rest && *rest == '?') {
+ /* start past the '?' */
+ rest++;
+ }
+
+ if (rest) {
+ char* token = NS_strtok("&", &rest);
+ while (token && *token) {
+ char* value = 0;
+ char* eq = PL_strchr(token, '=');
+ if (eq) {
+ value = eq + 1;
+ *eq = 0;
+ }
+
+ nsCString decodedName;
+ MsgUnescapeString(nsDependentCString(token), 0, decodedName);
+
+ if (decodedName.IsEmpty()) break;
+
+ switch (NS_ToUpper(decodedName.First())) {
+ /* DO NOT support attachment= in mailto urls. This poses a security
+ fire hole!!! case 'A': if (!PL_strcasecmp (token, "attachment"))
+ m_attachmentPart = value;
+ break;
+ */
+ case 'B':
+ if (decodedName.LowerCaseEqualsLiteral("bcc")) {
+ if (!escapedBccPart.IsEmpty()) {
+ escapedBccPart += ", ";
+ escapedBccPart += value;
+ } else
+ escapedBccPart = value;
+ } else if (decodedName.LowerCaseEqualsLiteral("body")) {
+ if (!escapedBodyPart.IsEmpty()) {
+ escapedBodyPart += "\n";
+ escapedBodyPart += value;
+ } else
+ escapedBodyPart = value;
+ }
+ break;
+ case 'C':
+ if (decodedName.LowerCaseEqualsLiteral("cc")) {
+ if (!escapedCcPart.IsEmpty()) {
+ escapedCcPart += ", ";
+ escapedCcPart += value;
+ } else
+ escapedCcPart = value;
+ }
+ break;
+ case 'F':
+ if (decodedName.LowerCaseEqualsLiteral("followup-to"))
+ escapedFollowUpToPart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("from"))
+ escapedFromPart = value;
+ break;
+ case 'H':
+ if (decodedName.LowerCaseEqualsLiteral("html-part") ||
+ decodedName.LowerCaseEqualsLiteral("html-body")) {
+ // escapedHtmlPart holds the body for both html-part and html-body.
+ escapedHtmlPart = value;
+ mFormat = nsIMsgCompFormat::HTML;
+ }
+ break;
+ case 'I':
+ if (decodedName.LowerCaseEqualsLiteral("in-reply-to"))
+ escapedInReplyToPart = value;
+ break;
+
+ case 'N':
+ if (decodedName.LowerCaseEqualsLiteral("newsgroups"))
+ escapedNewsgroupPart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("newshost"))
+ escapedNewsHostPart = value;
+ break;
+ case 'O':
+ if (decodedName.LowerCaseEqualsLiteral("organization"))
+ escapedOrganizationPart = value;
+ break;
+ case 'R':
+ if (decodedName.LowerCaseEqualsLiteral("references"))
+ escapedReferencePart = value;
+ else if (decodedName.LowerCaseEqualsLiteral("reply-to"))
+ escapedReplyToPart = value;
+ break;
+ case 'S':
+ if (decodedName.LowerCaseEqualsLiteral("subject"))
+ escapedSubjectPart = value;
+ break;
+ case 'P':
+ if (decodedName.LowerCaseEqualsLiteral("priority"))
+ escapedPriorityPart = PL_strdup(value);
+ break;
+ case 'T':
+ if (decodedName.LowerCaseEqualsLiteral("to")) {
+ if (!escapedToPart.IsEmpty()) {
+ escapedToPart += ", ";
+ escapedToPart += value;
+ } else
+ escapedToPart = value;
+ }
+ break;
+ default:
+ break;
+ } // end of switch statement...
+
+ if (eq) *eq = '='; /* put it back */
+ token = NS_strtok("&", &rest);
+ } // while we still have part of the url to parse...
+ } // if rest && *rest
+
+ nsresult rv;
+ // Get a global converter
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now unescape everything, and mime-decode the things that can be encoded.
+ UnescapeAndConvert(mimeConverter, escapedToPart, m_toPart);
+ UnescapeAndConvert(mimeConverter, escapedCcPart, m_ccPart);
+ UnescapeAndConvert(mimeConverter, escapedBccPart, m_bccPart);
+ UnescapeAndConvert(mimeConverter, escapedSubjectPart, m_subjectPart);
+ UnescapeAndConvert(mimeConverter, escapedNewsgroupPart, m_newsgroupPart);
+ UnescapeAndConvert(mimeConverter, escapedReferencePart, m_referencePart);
+ if (!escapedBodyPart.IsEmpty())
+ MsgUnescapeString(escapedBodyPart, 0, m_bodyPart);
+ if (!escapedHtmlPart.IsEmpty())
+ MsgUnescapeString(escapedHtmlPart, 0, m_htmlPart);
+ UnescapeAndConvert(mimeConverter, escapedNewsHostPart, m_newsHostPart);
+ UnescapeAndConvert(mimeConverter, escapedFollowUpToPart, m_followUpToPart);
+ UnescapeAndConvert(mimeConverter, escapedFromPart, m_fromPart);
+ UnescapeAndConvert(mimeConverter, escapedOrganizationPart,
+ m_organizationPart);
+ UnescapeAndConvert(mimeConverter, escapedReplyToPart, m_replyToPart);
+ UnescapeAndConvert(mimeConverter, escapedPriorityPart, m_priorityPart);
+
+ nsCString inReplyToPart; // Not a member like the others...
+ UnescapeAndConvert(mimeConverter, escapedInReplyToPart, inReplyToPart);
+
+ if (!inReplyToPart.IsEmpty()) {
+ // Ensure that References and In-Reply-To are consistent... The last
+ // reference will be used as In-Reply-To header.
+ if (m_referencePart.IsEmpty()) {
+ // If References is not set, set it to be the In-Reply-To.
+ m_referencePart = inReplyToPart;
+ } else {
+ // References is set. Add the In-Reply-To as last header unless it's
+ // set as last reference already.
+ int32_t lastRefStart = m_referencePart.RFindChar('<');
+ nsAutoCString lastReference;
+ if (lastRefStart != -1)
+ lastReference = StringTail(m_referencePart, lastRefStart);
+ else
+ lastReference = m_referencePart;
+
+ if (lastReference != inReplyToPart) {
+ m_referencePart += " ";
+ m_referencePart += inReplyToPart;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMailtoUrl::SetSpecInternal(const nsACString& aSpec) {
+ nsresult rv = NS_MutateURI(NS_SIMPLEURIMUTATOR_CONTRACTID)
+ .SetSpec(aSpec)
+ .Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+nsresult nsMailtoUrl::CleanupMailtoState() {
+ m_ccPart = "";
+ m_subjectPart = "";
+ m_newsgroupPart = "";
+ m_newsHostPart = "";
+ m_referencePart = "";
+ m_bodyPart = "";
+ m_bccPart = "";
+ m_followUpToPart = "";
+ m_fromPart = "";
+ m_htmlPart = "";
+ m_organizationPart = "";
+ m_replyToPart = "";
+ m_priorityPart = "";
+ return NS_OK;
+}
+
+nsresult nsMailtoUrl::ParseUrl() {
+ // we can get the path from the simple url.....
+ nsCString escapedPath;
+ m_baseURL->GetPathQueryRef(escapedPath);
+
+ int32_t startOfSearchPart = escapedPath.FindChar('?');
+ if (startOfSearchPart >= 0) {
+ // now parse out the search field...
+ nsAutoCString searchPart(Substring(escapedPath, startOfSearchPart));
+
+ if (!searchPart.IsEmpty()) {
+ // now we need to strip off the search part from the
+ // to part....
+ escapedPath.SetLength(startOfSearchPart);
+ MsgUnescapeString(escapedPath, 0, m_toPart);
+ ParseMailtoUrl(searchPart.BeginWriting());
+ }
+ } else if (!escapedPath.IsEmpty()) {
+ MsgUnescapeString(escapedPath, 0, m_toPart);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetMessageContents(nsACString& aToPart, nsACString& aCcPart,
+ nsACString& aBccPart, nsACString& aSubjectPart,
+ nsACString& aBodyPart, nsACString& aHtmlPart,
+ nsACString& aReferencePart,
+ nsACString& aNewsgroupPart,
+ MSG_ComposeFormat* aFormat) {
+ NS_ENSURE_ARG_POINTER(aFormat);
+
+ aToPart = m_toPart;
+ aCcPart = m_ccPart;
+ aBccPart = m_bccPart;
+ aSubjectPart = m_subjectPart;
+ aBodyPart = m_bodyPart;
+ aHtmlPart = m_htmlPart;
+ aReferencePart = m_referencePart;
+ aNewsgroupPart = m_newsgroupPart;
+ *aFormat = mFormat;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFromPart(nsACString& aResult) {
+ aResult = m_fromPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFollowUpToPart(nsACString& aResult) {
+ aResult = m_followUpToPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetOrganizationPart(nsACString& aResult) {
+ aResult = m_organizationPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetReplyToPart(nsACString& aResult) {
+ aResult = m_replyToPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetPriorityPart(nsACString& aResult) {
+ aResult = m_priorityPart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetNewsHostPart(nsACString& aResult) {
+ aResult = m_newsHostPart;
+ return NS_OK;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMailtoUrl::GetSpec(nsACString& aSpec) {
+ return m_baseURL->GetSpec(aSpec);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPrePath(nsACString& aPrePath) {
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetScheme(nsACString& aScheme) {
+ return m_baseURL->GetScheme(aScheme);
+}
+
+nsresult nsMailtoUrl::SetScheme(const nsACString& aScheme) {
+ nsresult rv = NS_MutateURI(m_baseURL).SetScheme(aScheme).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetUserPass(nsACString& aUserPass) {
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+nsresult nsMailtoUrl::SetUserPass(const nsACString& aUserPass) {
+ nsresult rv =
+ NS_MutateURI(m_baseURL).SetUserPass(aUserPass).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetUsername(nsACString& aUsername) {
+ return m_baseURL->GetUsername(aUsername);
+}
+
+nsresult nsMailtoUrl::SetUsername(const nsACString& aUsername) {
+ nsresult rv =
+ NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPassword(nsACString& aPassword) {
+ return m_baseURL->GetPassword(aPassword);
+}
+
+nsresult nsMailtoUrl::SetPassword(const nsACString& aPassword) {
+ nsresult rv =
+ NS_MutateURI(m_baseURL).SetPassword(aPassword).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetHostPort(nsACString& aHostPort) {
+ return m_baseURL->GetHost(aHostPort);
+}
+
+nsresult nsMailtoUrl::SetHostPort(const nsACString& aHostPort) {
+ nsresult rv =
+ NS_MutateURI(m_baseURL).SetHostPort(aHostPort).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetHost(nsACString& aHost) {
+ return m_baseURL->GetHost(aHost);
+}
+
+nsresult nsMailtoUrl::SetHost(const nsACString& aHost) {
+ nsresult rv = NS_MutateURI(m_baseURL).SetHost(aHost).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPort(int32_t* aPort) {
+ return m_baseURL->GetPort(aPort);
+}
+
+nsresult nsMailtoUrl::SetPort(int32_t aPort) {
+ nsresult rv = NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetPathQueryRef(nsACString& aPath) {
+ return m_baseURL->GetPathQueryRef(aPath);
+}
+
+nsresult nsMailtoUrl::SetPathQueryRef(const nsACString& aPath) {
+ nsresult rv =
+ NS_MutateURI(m_baseURL).SetPathQueryRef(aPath).Finalize(m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ParseUrl();
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiHost(nsACString& aHostA) {
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiHostPort(nsACString& aHostPortA) {
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::GetAsciiSpec(nsACString& aSpecA) {
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsMailtoUrl::SchemeIs(const char* aScheme, bool* _retval) {
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+NS_IMETHODIMP nsMailtoUrl::Equals(nsIURI* other, bool* _retval) {
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other) return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+nsresult nsMailtoUrl::Clone(nsIURI** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ RefPtr<nsMailtoUrl> clone = new nsMailtoUrl();
+
+ NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = NS_MutateURI(m_baseURL).Finalize(clone->m_baseURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ clone->ParseUrl();
+ clone.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailtoUrl::Resolve(const nsACString& relativePath,
+ nsACString& result) {
+ return m_baseURL->Resolve(relativePath, result);
+}
+
+nsresult nsMailtoUrl::SetRef(const nsACString& aRef) {
+ return NS_MutateURI(m_baseURL).SetRef(aRef).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetRef(nsACString& result) { return m_baseURL->GetRef(result); }
+
+NS_IMETHODIMP nsMailtoUrl::EqualsExceptRef(nsIURI* other, bool* result) {
+ // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its
+ // Equals method. The other nsMailtoUrl will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other) return other->EqualsExceptRef(m_baseURL, result);
+
+ return m_baseURL->EqualsExceptRef(other, result);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetSpecIgnoringRef(nsACString& result) {
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return m_baseURL->GetDisplaySpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetDisplayHostPort(nsACString& aUnicodeHostPort) {
+ return m_baseURL->GetDisplayHostPort(aUnicodeHostPort);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetDisplayHost(nsACString& aUnicodeHost) {
+ return m_baseURL->GetDisplayHost(aUnicodeHost);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetDisplayPrePath(nsACString& aPrePath) {
+ return m_baseURL->GetDisplayPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetHasRef(bool* result) { return m_baseURL->GetHasRef(result); }
+
+NS_IMETHODIMP
+nsMailtoUrl::GetFilePath(nsACString& aFilePath) {
+ return m_baseURL->GetFilePath(aFilePath);
+}
+
+nsresult nsMailtoUrl::SetFilePath(const nsACString& aFilePath) {
+ return NS_MutateURI(m_baseURL).SetFilePath(aFilePath).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP
+nsMailtoUrl::GetQuery(nsACString& aQuery) {
+ return m_baseURL->GetQuery(aQuery);
+}
+
+nsresult nsMailtoUrl::SetQuery(const nsACString& aQuery) {
+ return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL);
+}
+
+nsresult nsMailtoUrl::SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding) {
+ return NS_MutateURI(m_baseURL)
+ .SetQueryWithEncoding(aQuery, aEncoding)
+ .Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP_(void)
+nsMailtoUrl::Serialize(mozilla::ipc::URIParams& aParams) {
+ m_baseURL->Serialize(aParams);
+}
+
+NS_IMPL_ISUPPORTS(nsMailtoUrl::Mutator, nsIURISetters, nsIURIMutator)
+
+NS_IMETHODIMP
+nsMailtoUrl::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsMailtoUrl::Mutator> mutator = new nsMailtoUrl::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
+
+nsresult nsMailtoUrl::NewMailtoURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> mailtoUrl;
+ rv = NS_MutateURI(new Mutator()).SetSpec(aSpec).Finalize(mailtoUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mailtoUrl.forget(_retval);
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// smtp url definition
+/////////////////////////////////////////////////////////////////////////////////////
+
+nsSmtpUrl::nsSmtpUrl() : nsMsgMailNewsUrl() {
+ // nsISmtpUrl specific state...
+
+ m_isPostMessage = true;
+ m_requestDSN = false;
+ m_verifyLogon = false;
+}
+
+nsSmtpUrl::~nsSmtpUrl() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsSmtpUrl, nsMsgMailNewsUrl, nsISmtpUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsISmtpUrl specific support
+
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsSmtpUrl::SetRecipients(const char* aRecipientsList) {
+ NS_ENSURE_ARG(aRecipientsList);
+ MsgUnescapeString(nsDependentCString(aRecipientsList), 0, m_toPart);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetRecipients(char** aRecipientsList) {
+ NS_ENSURE_ARG_POINTER(aRecipientsList);
+ if (aRecipientsList) *aRecipientsList = ToNewCString(m_toPart);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetSender(const char* aSender) {
+ NS_ENSURE_ARG(aSender);
+ MsgUnescapeString(nsDependentCString(aSender), 0, m_fromPart);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetSender(char** aSender) {
+ NS_ENSURE_ARG_POINTER(aSender);
+ if (aSender) *aSender = ToNewCString(m_fromPart);
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsSmtpUrl, PostMessage, bool, m_isPostMessage)
+
+NS_IMPL_GETSET(nsSmtpUrl, VerifyLogon, bool, m_verifyLogon)
+
+// the message can be stored in a file....allow accessors for getting and
+// setting the file name to post...
+NS_IMETHODIMP nsSmtpUrl::SetPostMessageFile(nsIFile* aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ m_fileName = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSmtpUrl::GetPostMessageFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (m_fileName) {
+ // Clone the file so nsLocalFile stat caching doesn't make the caller get
+ // the wrong file size.
+ m_fileName->Clone(aFile);
+ return *aFile ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMPL_GETSET(nsSmtpUrl, RequestDSN, bool, m_requestDSN)
+
+NS_IMETHODIMP
+nsSmtpUrl::SetDsnEnvid(const nsACString& aDsnEnvid) {
+ m_dsnEnvid = aDsnEnvid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetDsnEnvid(nsACString& aDsnEnvid) {
+ aDsnEnvid = m_dsnEnvid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetSenderIdentity(nsIMsgIdentity** aSenderIdentity) {
+ NS_ENSURE_ARG_POINTER(aSenderIdentity);
+ NS_ADDREF(*aSenderIdentity = m_senderIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetSenderIdentity(nsIMsgIdentity* aSenderIdentity) {
+ NS_ENSURE_ARG_POINTER(aSenderIdentity);
+ m_senderIdentity = aSenderIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetPrompt(nsIPrompt* aNetPrompt) {
+ NS_ENSURE_ARG_POINTER(aNetPrompt);
+ m_netPrompt = aNetPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetPrompt(nsIPrompt** aNetPrompt) {
+ NS_ENSURE_ARG_POINTER(aNetPrompt);
+ NS_ENSURE_TRUE(m_netPrompt, NS_ERROR_NULL_POINTER);
+ NS_ADDREF(*aNetPrompt = m_netPrompt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetAuthPrompt(nsIAuthPrompt* aNetAuthPrompt) {
+ NS_ENSURE_ARG_POINTER(aNetAuthPrompt);
+ m_netAuthPrompt = aNetAuthPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetAuthPrompt(nsIAuthPrompt** aNetAuthPrompt) {
+ NS_ENSURE_ARG_POINTER(aNetAuthPrompt);
+ NS_ENSURE_TRUE(m_netAuthPrompt, NS_ERROR_NULL_POINTER);
+ NS_ADDREF(*aNetAuthPrompt = m_netAuthPrompt);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ m_callbacks = aCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ NS_ENSURE_ARG_POINTER(aCallbacks);
+ NS_ENSURE_TRUE(m_callbacks, NS_ERROR_NULL_POINTER);
+ NS_ADDREF(*aCallbacks = m_callbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::SetSmtpServer(nsISmtpServer* aSmtpServer) {
+ NS_ENSURE_ARG_POINTER(aSmtpServer);
+ m_smtpServer = aSmtpServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSmtpUrl::GetSmtpServer(nsISmtpServer** aSmtpServer) {
+ NS_ENSURE_ARG_POINTER(aSmtpServer);
+ NS_ENSURE_TRUE(m_smtpServer, NS_ERROR_NULL_POINTER);
+ NS_ADDREF(*aSmtpServer = m_smtpServer);
+ return NS_OK;
+}
+
+nsresult nsSmtpUrl::NewSmtpURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = 0;
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> aSmtpUri = new nsSmtpUrl();
+ if (aBaseURI) {
+ nsAutoCString newSpec;
+ rv = aBaseURI->Resolve(aSpec, newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aSmtpUri->SetSpecInternal(newSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ rv = aSmtpUri->SetSpecInternal(aSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ aSmtpUri.forget(_retval);
+
+ return rv;
+}
diff --git a/comm/mailnews/compose/src/nsSmtpUrl.h b/comm/mailnews/compose/src/nsSmtpUrl.h
new file mode 100644
index 0000000000..bd205f119c
--- /dev/null
+++ b/comm/mailnews/compose/src/nsSmtpUrl.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef nsSmtpUrl_h__
+#define nsSmtpUrl_h__
+
+#include "nsISmtpUrl.h"
+#include "nsIURI.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgIdentity.h"
+#include "nsCOMPtr.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsISmtpServer.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIURIMutator.h"
+
+class nsMailtoUrl : public nsIMailtoUrl, public nsIURI {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURI
+ NS_DECL_NSIMAILTOURL
+
+ nsMailtoUrl();
+ static nsresult NewMailtoURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval);
+
+ protected:
+ virtual nsresult Clone(nsIURI** _retval);
+ virtual nsresult SetSpecInternal(const nsACString& aSpec);
+ virtual nsresult SetScheme(const nsACString& aScheme);
+ virtual nsresult SetUserPass(const nsACString& aUserPass);
+ virtual nsresult SetUsername(const nsACString& aUsername);
+ virtual nsresult SetPassword(const nsACString& aPassword);
+ virtual nsresult SetHostPort(const nsACString& aHostPort);
+ virtual nsresult SetHost(const nsACString& aHost);
+ virtual nsresult SetPort(int32_t aPort);
+ virtual nsresult SetPathQueryRef(const nsACString& aPath);
+ virtual nsresult SetRef(const nsACString& aRef);
+ virtual nsresult SetFilePath(const nsACString& aFilePath);
+ virtual nsresult SetQuery(const nsACString& aQuery);
+ virtual nsresult SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding);
+
+ public:
+ class Mutator : public nsIURIMutator, public BaseURIMutator<nsMailtoUrl> {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) NS_ADDREF(*aMutator = this);
+ return InitFromSpec(aSpec);
+ }
+
+ explicit Mutator() {}
+
+ private:
+ virtual ~Mutator() {}
+
+ friend class nsMailtoUrl;
+ };
+ friend BaseURIMutator<nsMailtoUrl>;
+
+ protected:
+ virtual ~nsMailtoUrl();
+ nsresult ParseUrl();
+ nsresult CleanupMailtoState();
+ nsresult ParseMailtoUrl(char* searchPart);
+
+ nsCOMPtr<nsIURI> m_baseURL;
+
+ // data retrieved from parsing the url: (Note the url could be a post from
+ // file or it could be in the url)
+ nsCString m_toPart;
+ nsCString m_ccPart;
+ nsCString m_subjectPart;
+ nsCString m_newsgroupPart;
+ nsCString m_newsHostPart;
+ nsCString m_referencePart;
+ nsCString m_bodyPart;
+ nsCString m_bccPart;
+ nsCString m_followUpToPart;
+ nsCString m_fromPart;
+ nsCString m_htmlPart;
+ nsCString m_organizationPart;
+ nsCString m_replyToPart;
+ nsCString m_priorityPart;
+
+ MSG_ComposeFormat mFormat;
+};
+
+class nsSmtpUrl : public nsISmtpUrl, public nsMsgMailNewsUrl {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // From nsISmtpUrl
+ NS_DECL_NSISMTPURL
+
+ // nsSmtpUrl
+ nsSmtpUrl();
+ static nsresult NewSmtpURI(const nsACString& aSpec, nsIURI* aBaseURI,
+ nsIURI** _retval);
+
+ protected:
+ virtual ~nsSmtpUrl();
+
+ // data retrieved from parsing the url: (Note the url could be a post from
+ // file or it could be in the url)
+ nsCString m_toPart;
+ nsCString m_fromPart;
+
+ bool m_isPostMessage;
+ bool m_requestDSN;
+ nsCString m_dsnEnvid;
+ bool m_verifyLogon;
+
+ // Smtp specific event sinks
+ nsCOMPtr<nsIFile> m_fileName;
+ nsCOMPtr<nsIMsgIdentity> m_senderIdentity;
+ nsCOMPtr<nsIPrompt> m_netPrompt;
+ nsCOMPtr<nsIAuthPrompt> m_netAuthPrompt;
+ nsCOMPtr<nsIInterfaceRequestor> m_callbacks;
+ nsCOMPtr<nsISmtpServer> m_smtpServer;
+
+ // it is possible to encode the message to parse in the form of a url.
+ // This function is used to decompose the search and path part into the bare
+ // message components (to, fcc, bcc, etc.)
+ nsresult ParseMessageToPost(char* searchPart);
+};
+
+#endif // nsSmtpUrl_h__